Merge branch 'develop' of github.com:sleuthkit/autopsy into 2133_substring_as_case_insensitive_regex

This commit is contained in:
esaunders 2018-09-18 09:55:26 -04:00
commit 52f8ad0cb7
130 changed files with 7574 additions and 3759 deletions

View File

@ -103,6 +103,11 @@
<get src="https://drive.google.com/uc?id=1xv3Lz9m2QLq35ofDfHQNe9aHVtVzHUsj" dest="${test-input}/c2ds2_v1.vhd" skipexisting="true"/>
<get src="https://drive.google.com/uc?id=1sg-znklB9yJAWq8i1cF2W-QLOM4FjZyv" dest="${test-input}/c3ds1_v1.vhd" skipexisting="true"/>
<get src="https://drive.google.com/uc?id=1qXyaSlm3hMhv0jl6JkZEficknKjYNOlt" dest="${test-input}/c3ds2_v1.vhd" skipexisting="true"/>
<get src="https://drive.google.com/uc?id=1gW-SvduRwwoHmOheQypaRZ3ig-bezRre" dest="${test-input}/CommonFilesAttrs_img4_v1.vhd" skipexisting="true"/>
<get src="https://drive.google.com/uc?id=14vx83MZcd5PJadOesQz7tja-4a-Hz7eD" dest="${test-input}/CommonFilesAttrs_img3_v1.vhd" skipexisting="true"/>
<get src="https://drive.google.com/uc?id=1ZIYca4CWxyDcfF5Vf2uWkXeI83mR8L9B" dest="${test-input}/CommonFilesAttrs_img2_v1.vhd" skipexisting="true"/>
<get src="https://drive.google.com/uc?id=1RvLNhuKzqHURqeVml_o4lpv6jPCJJ9sv" dest="${test-input}/CommonFilesAttrs_img1_v1.vhd" skipexisting="true"/>
</target>
<target name="get-deps" depends="init-ivy,getTSKJars,get-thirdparty-dependencies,get-InternalPythonModules, download-binlist,getTestDataFiles">

View File

@ -7,12 +7,8 @@
</configurations>
<dependencies >
<dependency conf="core->default" org="com.github.jgraph" name="jgraphx" rev="v3.8.0"/>
<dependency conf="core->default" org="org.apache.activemq" name="activemq-all" rev="5.11.1"/>
<dependency conf="core->default" org="org.apache.curator" name="curator-client" rev="2.8.0"/>
<dependency conf="core->default" org="org.apache.curator" name="curator-framework" rev="2.8.0"/>
@ -27,6 +23,7 @@
<dependency conf="core->default" org="com.adobe.xmp" name="xmpcore" rev="5.1.2"/>
<dependency conf="core->default" org="org.apache.zookeeper" name="zookeeper" rev="3.4.6"/>
<dependency conf="core->default" org="org.apache.commons" name="commons-dbcp2" rev="2.1.1"/>
<dependency conf="core->default" org="org.apache.commons" name="commons-pool2" rev="2.4.2"/>
<dependency org="com.monitorjbl" name="xlsx-streamer" rev="1.2.1"/>
@ -34,5 +31,7 @@
<dependency conf="core->default" org="org.jsoup" name="jsoup" rev="1.10.3"/>
<dependency conf="core->default" org="com.googlecode.plist" name="dd-plist" rev="1.20"/>
<dependency conf="core->default" org="commons-validator" name="commons-validator" rev="1.6"/>
</dependencies>
</ivy-module>

View File

@ -40,6 +40,7 @@ file.reference.xmpcore-5.1.3.jar=release/modules/ext/xmpcore-5.1.3.jar
file.reference.xz-1.6.jar=release/modules/ext/xz-1.6.jar
file.reference.zookeeper-3.4.6.jar=release/modules/ext/zookeeper-3.4.6.jar
file.reference.SparseBitSet-1.1.jar=release/modules/ext/SparseBitSet-1.1.jar
file.reference.commons-validator-1.6.jar=release/modules/ext/commons-validator-1.6.jar
javac.source=1.8
javac.compilerargs=-Xlint -Xlint:-serial
license.file=../LICENSE-2.0.txt

View File

@ -329,6 +329,7 @@
<package>org.sleuthkit.autopsy.guiutils</package>
<package>org.sleuthkit.autopsy.healthmonitor</package>
<package>org.sleuthkit.autopsy.ingest</package>
<package>org.sleuthkit.autopsy.ingest.events</package>
<package>org.sleuthkit.autopsy.keywordsearchservice</package>
<package>org.sleuthkit.autopsy.menuactions</package>
<package>org.sleuthkit.autopsy.modules.encryptiondetection</package>
@ -356,6 +357,10 @@
<runtime-relative-path>ext/cxf-rt-transports-http-3.0.16.jar</runtime-relative-path>
<binary-origin>release/modules/ext/cxf-rt-transports-http-3.0.16.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/commons-validator-1.6.jar</runtime-relative-path>
<binary-origin>release/modules/ext/commons-validator-1.6.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/curator-framework-2.8.0.jar</runtime-relative-path>
<binary-origin>release/modules/ext/curator-framework-2.8.0.jar</binary-origin>

View File

@ -71,12 +71,13 @@ import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceEvent;
import org.sleuthkit.autopsy.casemodule.events.AddingDataSourceFailedEvent;
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent;
import org.sleuthkit.autopsy.casemodule.events.CommentChangedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.casemodule.events.DataSourceAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ReportAddedEvent;
import org.sleuthkit.autopsy.casemodule.services.Services;
import org.sleuthkit.autopsy.commonfilesearch.CommonFilesSearchAction;
import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeSearchAction;
import org.sleuthkit.autopsy.communications.OpenCommVisualizationToolAction;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService;
import org.sleuthkit.autopsy.coordinationservice.CoordinationService.CategoryNode;
@ -372,7 +373,12 @@ public class Case {
* old value of the PropertyChangeEvent is the display name of the tag
* definition that has changed.
*/
TAG_DEFINITION_CHANGED;
TAG_DEFINITION_CHANGED,
/**
* An item in the central repository has had its comment modified. The
* old value is null, the new value is string for current comment.
*/
CR_COMMENT_CHANGED;
};
@ -1087,7 +1093,7 @@ public class Case {
CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true);
CallableSystemAction.get(OpenTimelineAction.class).setEnabled(true);
CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(true);
CallableSystemAction.get(CommonFilesSearchAction.class).setEnabled(true);
CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(true);
CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
/*
@ -1141,7 +1147,7 @@ public class Case {
CallableSystemAction.get(OpenTimelineAction.class).setEnabled(false);
CallableSystemAction.get(OpenCommVisualizationToolAction.class).setEnabled(false);
CallableSystemAction.get(OpenOutputFolderAction.class).setEnabled(false);
CallableSystemAction.get(CommonFilesSearchAction.class).setEnabled(false);
CallableSystemAction.get(CommonAttributeSearchAction.class).setEnabled(false);
/*
* Clear the notifications in the notfier component in the lower
@ -1533,6 +1539,22 @@ public class Case {
eventPublisher.publish(new AutopsyEvent(Events.TAG_DEFINITION_CHANGED.toString(), changedTagName, null));
}
/**
* Notifies case event subscribers that a central repository comment has been changed.
*
* This should not be called from the event dispatch thread (EDT)
*
* @param contentId the objectId for the Content which has had its central repo comment changed
* @param newComment the new value of the comment
*/
public void notifyCentralRepoCommentChanged(long contentId, String newComment) {
try {
eventPublisher.publish(new CommentChangedEvent(contentId, newComment));
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Unable to send notifcation regarding comment change due to no current case being open", ex);
}
}
/**
* Notifies case event subscribers that an artifact tag has been added.
*

View File

@ -197,7 +197,7 @@ public class LocalFilesDSProcessor implements DataSourceProcessor, AutoIngestDat
command.add("-f");
command.add("files");
command.add("-t");
File l01Dir = new File(Case.getCurrentCaseThrows().getModuleDirectory(), L01_EXTRACTION_DIR); //WJS-TODO change to getOpenCase() when that method exists
File l01Dir = new File(Case.getCurrentCaseThrows().getModuleDirectory(), L01_EXTRACTION_DIR);
if (!l01Dir.exists()) {
l01Dir.mkdirs();
}

View File

@ -0,0 +1,60 @@
/*
* 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.casemodule.events;
import java.io.Serializable;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.events.AutopsyEvent;
/**
* Event published when a central repsoitory comment is changed
*/
public class CommentChangedEvent extends AutopsyEvent implements Serializable {
private static final long serialVersionUID = 1L;
private final long contentID;
private final String caseName; //included so we can eventually use this event between cases
/**
* Constructs a CommentChangedEvent which is published when a central
* repository comment is changed.
*
* @param contentId the objectId of the Content which has had its central repository comment changed
* @param newComment the new value of the comment
* @throws org.sleuthkit.autopsy.casemodule.NoCurrentCaseException if there is no current case open when this event is sent
*/
public CommentChangedEvent(long contentId, String newComment) throws NoCurrentCaseException {
super(Case.Events.CR_COMMENT_CHANGED.toString(), null, newComment);
contentID = contentId;
//get the caseName as well so that this event could be used for notifications between cases without method signature change
caseName = Case.getCurrentCaseThrows().getName();
}
/**
* Get the object id of the content which this event is associated with.
*
* @return the objectId of the content this event is associated with
*/
public long getContentID() {
return contentID;
}
}

View File

@ -24,6 +24,8 @@ import javax.swing.AbstractAction;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
@ -35,38 +37,35 @@ import org.sleuthkit.datamodel.AbstractFile;
* An AbstractAction to manage adding and modifying a Central Repository file
* instance comment.
*/
@Messages({"AddEditCentralRepoCommentAction.menuItemText.addEditCentralRepoComment=Add/Edit Central Repository Comment"})
public final class AddEditCentralRepoCommentAction extends AbstractAction {
private static final Logger logger = Logger.getLogger(AddEditCentralRepoCommentAction.class.getName());
private static final long serialVersionUID = 1L;
private boolean addToDatabase;
private CorrelationAttributeInstance correlationAttributeInstance;
private String comment;
/**
* Constructor to create an instance given a CorrelationAttribute.
*
* @param correlationAttribute The correlation attribute to modify.
*/
public AddEditCentralRepoCommentAction(CorrelationAttributeInstance correlationAttribute) {
super(Bundle.AddEditCentralRepoCommentAction_menuItemText_addEditCentralRepoComment());
this.correlationAttributeInstance = correlationAttribute;
}
private final Long fileId;
/**
* Constructor to create an instance given an AbstractFile.
*
* @param file The file from which a correlation attribute to modify is
* derived.
*
*/
public AddEditCentralRepoCommentAction(AbstractFile file) {
super(Bundle.AddEditCentralRepoCommentAction_menuItemText_addEditCentralRepoComment());
fileId = file.getId();
correlationAttributeInstance = EamArtifactUtil.getInstanceFromContent(file);
if (correlationAttributeInstance == null) {
addToDatabase = true;
correlationAttributeInstance = EamArtifactUtil.makeInstanceFromContent(file);
}
}
/**
@ -100,6 +99,11 @@ public final class AddEditCentralRepoCommentAction extends AbstractAction {
}
comment = centralRepoCommentDialog.getComment();
try {
Case.getCurrentCaseThrows().notifyCentralRepoCommentChanged(fileId, comment);
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Case not open after changing central repository comment", ex);
}
} catch (EamDbException ex) {
logger.log(Level.SEVERE, "Error adding comment", ex);
NotifyDescriptor notifyDescriptor = new NotifyDescriptor.Message(

View File

@ -3,7 +3,6 @@ DataContentViewerOtherCases.showCaseDetailsMenuItem.text=Show Case Details
DataContentViewerOtherCases.table.toolTip.text=Click column name to sort. Right-click on the table for more options.
DataContentViewerOtherCases.exportToCSVMenuItem.text=Export Selected Rows to CSV
DataContentViewerOtherCases.showCommonalityMenuItem.text=Show Frequency
DataContentViewerOtherCases.addCommentMenuItem.text=Add/Edit Central Repository Comment
DataContentViewerOtherCases.earliestCaseDate.text=Earliest Case Date
DataContentViewerOtherCases.earliestCaseLabel.toolTipText=
DataContentViewerOtherCases.earliestCaseLabel.text=Central Repository Starting Date:

View File

@ -39,13 +39,6 @@
</Property>
</Properties>
</MenuItem>
<MenuItem class="javax.swing.JMenuItem" name="addCommentMenuItem">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/centralrepository/contentviewer/Bundle.properties" key="DataContentViewerOtherCases.addCommentMenuItem.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</MenuItem>
</SubComponents>
</Container>
<Component class="javax.swing.JFileChooser" name="CSVFileChooser">
@ -80,7 +73,7 @@
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="otherCasesPanel" pref="59" max="32767" attributes="0"/>
<Component id="otherCasesPanel" pref="58" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
@ -106,7 +99,7 @@
<EmptySpace min="0" pref="483" max="32767" attributes="0"/>
<Group type="103" rootIndex="1" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="tableContainerPanel" pref="59" max="32767" attributes="0"/>
<Component id="tableContainerPanel" pref="58" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
</Group>
</Group>

View File

@ -56,7 +56,7 @@ import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.AddEditCentralRepoCommentAction;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil;
@ -79,14 +79,14 @@ import org.sleuthkit.datamodel.TskData;
* View correlation results from other cases
*/
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
@ServiceProvider(service = DataContentViewer.class, position = 8)
@ServiceProvider(service = DataContentViewer.class, position = 9)
@Messages({"DataContentViewerOtherCases.title=Other Occurrences",
"DataContentViewerOtherCases.toolTip=Displays instances of the selected file/artifact from other occurrences.",})
public class DataContentViewerOtherCases extends JPanel implements DataContentViewer {
private static final long serialVersionUID = -1L;
private static final Logger logger = Logger.getLogger(DataContentViewerOtherCases.class.getName());
private static final Logger LOGGER = Logger.getLogger(DataContentViewerOtherCases.class.getName());
private static final int DEFAULT_MIN_CELL_WIDTH = 15;
private static final int CELL_TEXT_WIDTH_PADDING = 5;
@ -123,23 +123,10 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
try {
saveToCSV();
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS
LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS
}
} else if (jmi.equals(showCommonalityMenuItem)) {
showCommonalityDetails();
} else if (jmi.equals(addCommentMenuItem)) {
try {
OtherOccurrenceNodeInstanceData selectedNode = (OtherOccurrenceNodeInstanceData) tableModel.getRow(otherCasesTable.getSelectedRow());
AddEditCentralRepoCommentAction action = new AddEditCentralRepoCommentAction(selectedNode.getCorrelationAttribute());
action.actionPerformed(null);
String currentComment = action.getComment();
if (currentComment != null) {
selectedNode.updateComment(action.getComment());
otherCasesTable.repaint();
}
} catch (EamDbException ex) {
logger.log(Level.SEVERE, "Error performing Add/Edit Central Repository Comment action", ex);
}
}
}
};
@ -148,7 +135,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
selectAllMenuItem.addActionListener(actList);
showCaseDetailsMenuItem.addActionListener(actList);
showCommonalityMenuItem.addActionListener(actList);
addCommentMenuItem.addActionListener(actList);
// Set background of every nth row as light grey.
TableCellRenderer renderer = new DataContentViewerOtherCasesTableCellRenderer();
@ -179,17 +165,21 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
try {
EamDb dbManager = EamDb.getInstance();
for (CorrelationAttributeInstance eamArtifact : correlationAttributes) {
try {
percentage = dbManager.getFrequencyPercentage(eamArtifact);
msg.append(Bundle.DataContentViewerOtherCases_correlatedArtifacts_byType(percentage,
eamArtifact.getCorrelationType().getDisplayName(),
eamArtifact.getCorrelationValue()));
} catch (CorrelationAttributeNormalizationException ex) {
LOGGER.log(Level.WARNING, String.format("Error getting commonality details for artifact with ID: %s.", eamArtifact.getID()), ex);
}
}
JOptionPane.showConfirmDialog(showCommonalityMenuItem,
msg.toString(),
Bundle.DataContentViewerOtherCases_correlatedArtifacts_title(),
DEFAULT_OPTION, PLAIN_MESSAGE);
} catch (EamDbException ex) {
logger.log(Level.SEVERE, "Error getting commonality details.", ex);
LOGGER.log(Level.SEVERE, "Error getting commonality details.", ex);
JOptionPane.showConfirmDialog(showCommonalityMenuItem,
Bundle.DataContentViewerOtherCases_correlatedArtifacts_failed(),
Bundle.DataContentViewerOtherCases_correlatedArtifacts_title(),
@ -242,7 +232,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
DEFAULT_OPTION, PLAIN_MESSAGE);
}
} catch (EamDbException ex) {
logger.log(Level.SEVERE, "Error loading case details", ex);
LOGGER.log(Level.SEVERE, "Error loading case details", ex);
JOptionPane.showConfirmDialog(showCaseDetailsMenuItem,
Bundle.DataContentViewerOtherCases_caseDetailsDialog_noDetails(),
caseDisplayName,
@ -305,7 +295,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
}
} catch (IOException ex) {
logger.log(Level.SEVERE, "Error writing selected rows to CSV.", ex);
LOGGER.log(Level.SEVERE, "Error writing selected rows to CSV.", ex);
}
}
@ -397,7 +387,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
try {
content = nodeBbArtifact.getSleuthkitCase().getContentById(nodeBbArtifact.getObjectID());
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error retrieving blackboard artifact", ex); // NON-NLS
LOGGER.log(Level.SEVERE, "Error retrieving blackboard artifact", ex); // NON-NLS
return null;
}
@ -438,6 +428,7 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
for (CorrelationAttributeInstance.Type aType : artifactTypes) {
if (aType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) {
CorrelationCase corCase = EamDb.getInstance().getCase(Case.getCurrentCase());
try {
ret.add(new CorrelationAttributeInstance(
md5,
aType,
@ -446,25 +437,38 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
file.getParentPath() + file.getName(),
"",
file.getKnown()));
} catch (CorrelationAttributeNormalizationException ex) {
LOGGER.log(Level.INFO, String.format("Unable to check create CorrelationAttribtueInstance for value %s and type %s.", md5, aType.toString()), ex);
}
break;
}
}
}
} catch (EamDbException | TskCoreException ex) {
logger.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS
LOGGER.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS
}
} else {
try {
// If EamDb not enabled, get the Files default correlation type to allow Other Occurances to be enabled.
if (this.file != null) {
String md5 = this.file.getMd5Hash();
if (md5 != null && !md5.isEmpty()) {
ret.add(new CorrelationAttributeInstance(CorrelationAttributeInstance.getDefaultCorrelationTypes().get(0), md5));
}
}
try {
final CorrelationAttributeInstance.Type fileAttributeType
= CorrelationAttributeInstance.getDefaultCorrelationTypes()
.stream()
.filter(attrType -> attrType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID)
.findAny()
.get();
ret.add(new CorrelationAttributeInstance(fileAttributeType, md5));
} catch (EamDbException ex) {
logger.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS
LOGGER.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS
} catch (CorrelationAttributeNormalizationException ex) {
LOGGER.log(Level.INFO, String.format("Unable to create CorrelationAttributeInstance for value %s", md5), ex); // NON-NLS
}
}
}
}
@ -496,9 +500,9 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
}
} catch (EamDbException ex) {
logger.log(Level.SEVERE, "Error getting list of cases from database.", ex); // NON-NLS
LOGGER.log(Level.SEVERE, "Error getting list of cases from database.", ex); // NON-NLS
} catch (ParseException ex) {
logger.log(Level.SEVERE, "Error parsing date of cases from database.", ex); // NON-NLS
LOGGER.log(Level.SEVERE, "Error parsing date of cases from database.", ex); // NON-NLS
}
}
@ -558,13 +562,15 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
return nodeDataMap;
} catch (EamDbException ex) {
logger.log(Level.SEVERE, "Error getting artifact instances from database.", ex); // NON-NLS
LOGGER.log(Level.SEVERE, "Error getting artifact instances from database.", ex); // NON-NLS
} catch (CorrelationAttributeNormalizationException ex) {
LOGGER.log(Level.INFO, "Error getting artifact instances from database.", ex); // NON-NLS
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS
LOGGER.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS
} catch (TskCoreException ex) {
// do nothing.
// @@@ Review this behavior
logger.log(Level.SEVERE, "Exception while querying open case.", ex); // NON-NLS
LOGGER.log(Level.SEVERE, "Exception while querying open case.", ex); // NON-NLS
}
return new HashMap<>(0);
@ -708,7 +714,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
correlatedNodeDataMap.values().forEach((nodeData) -> {
tableModel.addNodeData(nodeData);
});
}
@ -728,8 +733,8 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
* Adjust a given column for the text provided.
*
* @param columnIndex The index of the column to adjust.
* @param text The text whose length will be used to adjust the
* column width.
* @param text The text whose length will be used to adjust the column
* width.
*/
private void setColumnWidthToText(int columnIndex, String text) {
TableColumn column = otherCasesTable.getColumnModel().getColumn(columnIndex);
@ -766,7 +771,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
exportToCSVMenuItem = new javax.swing.JMenuItem();
showCaseDetailsMenuItem = new javax.swing.JMenuItem();
showCommonalityMenuItem = new javax.swing.JMenuItem();
addCommentMenuItem = new javax.swing.JMenuItem();
CSVFileChooser = new javax.swing.JFileChooser();
otherCasesPanel = new javax.swing.JPanel();
tableContainerPanel = new javax.swing.JPanel();
@ -798,9 +802,6 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
org.openide.awt.Mnemonics.setLocalizedText(showCommonalityMenuItem, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.showCommonalityMenuItem.text")); // NOI18N
rightClickPopupMenu.add(showCommonalityMenuItem);
org.openide.awt.Mnemonics.setLocalizedText(addCommentMenuItem, org.openide.util.NbBundle.getMessage(DataContentViewerOtherCases.class, "DataContentViewerOtherCases.addCommentMenuItem.text")); // NOI18N
rightClickPopupMenu.add(addCommentMenuItem);
setMinimumSize(new java.awt.Dimension(1500, 10));
setOpaque(false);
setPreferredSize(new java.awt.Dimension(1500, 44));
@ -903,15 +904,12 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
enableCentralRepoActions = instanceData.isCentralRepoNode();
}
}
addCommentMenuItem.setVisible(enableCentralRepoActions);
showCaseDetailsMenuItem.setVisible(enableCentralRepoActions);
showCommonalityMenuItem.setVisible(enableCentralRepoActions);
}//GEN-LAST:event_rightClickPopupMenuPopupMenuWillBecomeVisible
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JFileChooser CSVFileChooser;
private javax.swing.JMenuItem addCommentMenuItem;
private javax.swing.JLabel earliestCaseDate;
private javax.swing.JLabel earliestCaseLabel;
private javax.swing.JMenuItem exportToCSVMenuItem;
@ -993,5 +991,4 @@ public class DataContentViewerOtherCases extends JPanel implements DataContentVi
return dataSourceID;
}
}
}

View File

@ -652,7 +652,7 @@ abstract class AbstractSqlEamDb implements EamDb {
preparedStatement.setString(1, eamArtifact.getCorrelationCase().getCaseUUID());
preparedStatement.setString(2, eamArtifact.getCorrelationDataSource().getDeviceID());
preparedStatement.setInt(3, eamArtifact.getCorrelationDataSource().getCaseID());
preparedStatement.setString(4, eamArtifact.getCorrelationValue().toLowerCase());
preparedStatement.setString(4, eamArtifact.getCorrelationValue());
preparedStatement.setString(5, eamArtifact.getFilePath().toLowerCase());
preparedStatement.setByte(6, eamArtifact.getKnownStatus().getFileKnownValue());
if ("".equals(eamArtifact.getComment())) {
@ -712,10 +712,10 @@ abstract class AbstractSqlEamDb implements EamDb {
* @throws EamDbException
*/
@Override
public List<CorrelationAttributeInstance> getArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException {
if (aType == null) {
throw new EamDbException("Correlation type is null");
}
public List<CorrelationAttributeInstance> getArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException {
String normalizedValue = CorrelationAttributeNormalizer.normalize(aType, value);
Connection conn = connect();
List<CorrelationAttributeInstance> artifactInstances = new ArrayList<>();
@ -743,7 +743,7 @@ abstract class AbstractSqlEamDb implements EamDb {
try {
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setString(1, value);
preparedStatement.setString(1, normalizedValue);
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
artifactInstance = getEamArtifactInstanceFromResultSet(resultSet, aType);
@ -809,8 +809,12 @@ abstract class AbstractSqlEamDb implements EamDb {
preparedStatement.setString(1, filePath.toLowerCase());
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
try {
artifactInstance = getEamArtifactInstanceFromResultSet(resultSet, aType);
artifactInstances.add(artifactInstance);
} catch (CorrelationAttributeNormalizationException ex) {
logger.log(Level.INFO, "Unable to get artifact instance from resultset.", ex);
}
}
} catch (SQLException ex) {
throw new EamDbException("Error getting artifact instances by artifactType and artifactValue.", ex); // NON-NLS
@ -834,13 +838,8 @@ abstract class AbstractSqlEamDb implements EamDb {
* ArtifactValue.
*/
@Override
public Long getCountArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException {
if (aType == null) {
throw new EamDbException("Correlation type is null");
}
if (value == null) {
throw new EamDbException("Correlation value is null");
}
public Long getCountArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException {
String normalizedValue = CorrelationAttributeNormalizer.normalize(aType, value);
Connection conn = connect();
@ -856,7 +855,7 @@ abstract class AbstractSqlEamDb implements EamDb {
try {
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setString(1, value.toLowerCase());
preparedStatement.setString(1, normalizedValue);
resultSet = preparedStatement.executeQuery();
resultSet.next();
instanceCount = resultSet.getLong(1);
@ -872,7 +871,7 @@ abstract class AbstractSqlEamDb implements EamDb {
}
@Override
public int getFrequencyPercentage(CorrelationAttributeInstance corAttr) throws EamDbException {
public int getFrequencyPercentage(CorrelationAttributeInstance corAttr) throws EamDbException, CorrelationAttributeNormalizationException {
if (corAttr == null) {
throw new EamDbException("CorrelationAttribute is null");
}
@ -893,10 +892,8 @@ abstract class AbstractSqlEamDb implements EamDb {
* @return Number of unique tuples
*/
@Override
public Long getCountUniqueCaseDataSourceTuplesHavingTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException {
if (aType == null) {
throw new EamDbException("Correlation type is null");
}
public Long getCountUniqueCaseDataSourceTuplesHavingTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException {
String normalizedValue = CorrelationAttributeNormalizer.normalize(aType, value);
Connection conn = connect();
@ -914,7 +911,7 @@ abstract class AbstractSqlEamDb implements EamDb {
try {
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setString(1, value);
preparedStatement.setString(1, normalizedValue);
resultSet = preparedStatement.executeQuery();
resultSet.next();
instanceCount = resultSet.getLong(1);
@ -1262,7 +1259,7 @@ abstract class AbstractSqlEamDb implements EamDb {
preparedQuery.setString(1, eamArtifact.getComment());
preparedQuery.setString(2, eamArtifact.getCorrelationCase().getCaseUUID());
preparedQuery.setString(3, eamArtifact.getCorrelationDataSource().getDeviceID());
preparedQuery.setString(4, eamArtifact.getCorrelationValue().toLowerCase());
preparedQuery.setString(4, eamArtifact.getCorrelationValue());
preparedQuery.setString(5, eamArtifact.getFilePath().toLowerCase());
preparedQuery.executeUpdate();
} catch (SQLException ex) {
@ -1289,20 +1286,14 @@ abstract class AbstractSqlEamDb implements EamDb {
*/
@Override
public CorrelationAttributeInstance getCorrelationAttributeInstance(CorrelationAttributeInstance.Type type, CorrelationCase correlationCase,
CorrelationDataSource correlationDataSource, String value, String filePath) throws EamDbException {
CorrelationDataSource correlationDataSource, String value, String filePath) throws EamDbException, CorrelationAttributeNormalizationException {
if (type == null) {
throw new EamDbException("Correlation type is null");
}
if (correlationCase == null) {
throw new EamDbException("Correlation case is null");
}
if (correlationDataSource == null) {
throw new EamDbException("Correlation data source is null");
}
if (value == null) {
throw new EamDbException("Correlation value is null");
}
if (filePath == null) {
throw new EamDbException("Correlation file path is null");
}
@ -1314,6 +1305,8 @@ abstract class AbstractSqlEamDb implements EamDb {
CorrelationAttributeInstance correlationAttributeInstance = null;
try {
String normalizedValue = CorrelationAttributeNormalizer.normalize(type, value);
String tableName = EamDbUtil.correlationTypeToInstanceTableName(type);
String sql
= "SELECT id, known_status, comment FROM "
@ -1326,7 +1319,7 @@ abstract class AbstractSqlEamDb implements EamDb {
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setInt(1, correlationCase.getID());
preparedStatement.setInt(2, correlationDataSource.getID());
preparedStatement.setString(3, value.toLowerCase());
preparedStatement.setString(3, normalizedValue);
preparedStatement.setString(4, filePath.toLowerCase());
resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
@ -1457,10 +1450,8 @@ abstract class AbstractSqlEamDb implements EamDb {
* @return List with 0 or more matching eamArtifact instances.
*/
@Override
public List<CorrelationAttributeInstance> getArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException {
if (aType == null) {
throw new EamDbException("Correlation type is null");
}
public List<CorrelationAttributeInstance> getArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException {
String normalizedValue = CorrelationAttributeNormalizer.normalize(aType, value);
Connection conn = connect();
@ -1489,7 +1480,7 @@ abstract class AbstractSqlEamDb implements EamDb {
try {
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setString(1, value);
preparedStatement.setString(1, normalizedValue);
preparedStatement.setByte(2, TskData.FileKnown.BAD.getFileKnownValue());
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
@ -1552,8 +1543,12 @@ abstract class AbstractSqlEamDb implements EamDb {
preparedStatement.setByte(1, TskData.FileKnown.BAD.getFileKnownValue());
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
try {
artifactInstance = getEamArtifactInstanceFromResultSet(resultSet, aType);
artifactInstances.add(artifactInstance);
} catch (CorrelationAttributeNormalizationException ex) {
logger.log(Level.INFO, "Unable to get artifact instance from resultset.", ex);
}
}
} catch (SQLException ex) {
throw new EamDbException("Error getting notable artifact instances.", ex); // NON-NLS
@ -1575,10 +1570,9 @@ abstract class AbstractSqlEamDb implements EamDb {
* @return Number of matching eamArtifacts
*/
@Override
public Long getCountArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException {
if (aType == null) {
throw new EamDbException("Correlation type is null");
}
public Long getCountArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException {
String normalizedValue = CorrelationAttributeNormalizer.normalize(aType, value);
Connection conn = connect();
@ -1594,7 +1588,7 @@ abstract class AbstractSqlEamDb implements EamDb {
try {
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setString(1, value);
preparedStatement.setString(1, normalizedValue);
preparedStatement.setByte(2, TskData.FileKnown.BAD.getFileKnownValue());
resultSet = preparedStatement.executeQuery();
resultSet.next();
@ -1623,10 +1617,9 @@ abstract class AbstractSqlEamDb implements EamDb {
* @throws EamDbException
*/
@Override
public List<String> getListCasesHavingArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException {
if (aType == null) {
throw new EamDbException("Correlation type is null");
}
public List<String> getListCasesHavingArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException {
String normalizedValue = CorrelationAttributeNormalizer.normalize(aType, value);
Connection conn = connect();
@ -1649,7 +1642,7 @@ abstract class AbstractSqlEamDb implements EamDb {
try {
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setString(1, value);
preparedStatement.setString(1, normalizedValue);
preparedStatement.setByte(2, TskData.FileKnown.BAD.getFileKnownValue());
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
@ -1768,7 +1761,7 @@ abstract class AbstractSqlEamDb implements EamDb {
* @throws EamDbException
*/
@Override
public boolean isFileHashInReferenceSet(String hash, int referenceSetID) throws EamDbException {
public boolean isFileHashInReferenceSet(String hash, int referenceSetID) throws EamDbException, CorrelationAttributeNormalizationException {
return isValueInReferenceSet(hash, referenceSetID, CorrelationAttributeInstance.FILES_TYPE_ID);
}
@ -1782,7 +1775,9 @@ abstract class AbstractSqlEamDb implements EamDb {
* @return true if the value is found in the reference set
*/
@Override
public boolean isValueInReferenceSet(String value, int referenceSetID, int correlationTypeID) throws EamDbException {
public boolean isValueInReferenceSet(String value, int referenceSetID, int correlationTypeID) throws EamDbException, CorrelationAttributeNormalizationException {
String normalizeValued = CorrelationAttributeNormalizer.normalize(this.getCorrelationTypeById(correlationTypeID), value);
Connection conn = connect();
@ -1795,13 +1790,13 @@ abstract class AbstractSqlEamDb implements EamDb {
try {
preparedStatement = conn.prepareStatement(String.format(sql, fileTableName));
preparedStatement.setString(1, value);
preparedStatement.setString(1, normalizeValued);
preparedStatement.setInt(2, referenceSetID);
resultSet = preparedStatement.executeQuery();
resultSet.next();
matchingInstances = resultSet.getLong(1);
} catch (SQLException ex) {
throw new EamDbException("Error determining if value (" + value + ") is in reference set " + referenceSetID, ex); // NON-NLS
throw new EamDbException("Error determining if value (" + normalizeValued + ") is in reference set " + referenceSetID, ex); // NON-NLS
} finally {
EamDbUtil.closeStatement(preparedStatement);
EamDbUtil.closeResultSet(resultSet);
@ -1820,10 +1815,10 @@ abstract class AbstractSqlEamDb implements EamDb {
* @return Global known status of the artifact
*/
@Override
public boolean isArtifactKnownBadByReference(CorrelationAttributeInstance.Type aType, String value) throws EamDbException {
if (aType == null) {
throw new EamDbException("Correlation type is null");
}
public boolean isArtifactKnownBadByReference(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException {
//this should be done here so that we can be certain that aType and value are valid before we proceed
String normalizeValued = CorrelationAttributeNormalizer.normalize(aType, value);
// TEMP: Only support file correlation type
if (aType.getId() != CorrelationAttributeInstance.FILES_TYPE_ID) {
@ -1839,7 +1834,7 @@ abstract class AbstractSqlEamDb implements EamDb {
try {
preparedStatement = conn.prepareStatement(String.format(sql, EamDbUtil.correlationTypeToReferenceTableName(aType)));
preparedStatement.setString(1, value);
preparedStatement.setString(1, normalizeValued);
preparedStatement.setByte(2, TskData.FileKnown.BAD.getFileKnownValue());
resultSet = preparedStatement.executeQuery();
resultSet.next();
@ -2420,10 +2415,8 @@ abstract class AbstractSqlEamDb implements EamDb {
* @throws EamDbException
*/
@Override
public List<EamGlobalFileInstance> getReferenceInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String aValue) throws EamDbException {
if (aType == null) {
throw new EamDbException("Correlation type is null");
}
public List<EamGlobalFileInstance> getReferenceInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String aValue) throws EamDbException, CorrelationAttributeNormalizationException {
String normalizeValued = CorrelationAttributeNormalizer.normalize(aType, aValue);
Connection conn = connect();
@ -2434,12 +2427,11 @@ abstract class AbstractSqlEamDb implements EamDb {
try {
preparedStatement1 = conn.prepareStatement(String.format(sql1, EamDbUtil.correlationTypeToReferenceTableName(aType)));
preparedStatement1.setString(1, aValue);
preparedStatement1.setString(1, normalizeValued);
resultSet = preparedStatement1.executeQuery();
while (resultSet.next()) {
globalFileInstances.add(getEamGlobalFileInstanceFromResultSet(resultSet));
}
return globalFileInstances;
} catch (SQLException ex) {
throw new EamDbException("Error getting reference instances by type and value.", ex); // NON-NLS
@ -2448,6 +2440,8 @@ abstract class AbstractSqlEamDb implements EamDb {
EamDbUtil.closeResultSet(resultSet);
EamDbUtil.closeConnection(conn);
}
return globalFileInstances;
}
/**
@ -2828,7 +2822,7 @@ abstract class AbstractSqlEamDb implements EamDb {
*
* @throws SQLException when an expected column name is not in the resultSet
*/
private CorrelationAttributeInstance getEamArtifactInstanceFromResultSet(ResultSet resultSet, CorrelationAttributeInstance.Type aType) throws SQLException, EamDbException {
private CorrelationAttributeInstance getEamArtifactInstanceFromResultSet(ResultSet resultSet, CorrelationAttributeInstance.Type aType) throws SQLException, EamDbException, CorrelationAttributeNormalizationException {
if (null == resultSet) {
return null;
}
@ -2876,7 +2870,7 @@ abstract class AbstractSqlEamDb implements EamDb {
);
}
private EamGlobalFileInstance getEamGlobalFileInstanceFromResultSet(ResultSet resultSet) throws SQLException, EamDbException {
private EamGlobalFileInstance getEamGlobalFileInstanceFromResultSet(ResultSet resultSet) throws SQLException, EamDbException, CorrelationAttributeNormalizationException {
if (null == resultSet) {
return null;
}

View File

@ -55,7 +55,7 @@ public class CorrelationAttributeInstance implements Serializable {
CorrelationCase eamCase,
CorrelationDataSource eamDataSource,
String filePath
) throws EamDbException {
) throws EamDbException, CorrelationAttributeNormalizationException {
this(correlationType, correlationValue, -1, eamCase, eamDataSource, filePath, null, TskData.FileKnown.UNKNOWN);
}
@ -67,7 +67,7 @@ public class CorrelationAttributeInstance implements Serializable {
String filePath,
String comment,
TskData.FileKnown knownStatus
) throws EamDbException {
) throws EamDbException, CorrelationAttributeNormalizationException {
this(correlationType, correlationValue, -1, eamCase, eamDataSource, filePath, comment, knownStatus);
}
@ -76,7 +76,7 @@ public class CorrelationAttributeInstance implements Serializable {
String correlationValue,
CorrelationCase correlationCase,
CorrelationDataSource fromTSKDataSource,
String string) throws EamDbException {
String string) throws EamDbException, CorrelationAttributeNormalizationException {
this(correlationType, correlationValue, -1, correlationCase, fromTSKDataSource, string, "", TskData.FileKnown.UNKNOWN);
}
@ -86,7 +86,7 @@ public class CorrelationAttributeInstance implements Serializable {
* @param aType CorrelationAttributeInstance.Type
* @param value correlation value
*/
public CorrelationAttributeInstance(Type aType, String value) throws EamDbException {
public CorrelationAttributeInstance(Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException {
this(aType, value, -1, null, null, "", "", TskData.FileKnown.UNKNOWN);
}
@ -99,17 +99,13 @@ public class CorrelationAttributeInstance implements Serializable {
String filePath,
String comment,
TskData.FileKnown knownStatus
) throws EamDbException {
) throws EamDbException, CorrelationAttributeNormalizationException {
if (filePath == null) {
throw new EamDbException("file path is null");
}
if (value == null) {
throw new EamDbException("correlation value is null");
}
this.correlationType = type;
this.correlationValue = value;
this.correlationValue = CorrelationAttributeNormalizer.normalize(type, value);
this.ID = instanceId;
this.correlationCase = eamCase;
this.correlationDataSource = eamDataSource;
@ -121,6 +117,8 @@ public class CorrelationAttributeInstance implements Serializable {
public Boolean equals(CorrelationAttributeInstance otherInstance) {
return ((this.getID() == otherInstance.getID())
&& (this.getCorrelationValue().equals(otherInstance.getCorrelationValue()))
&& (this.getCorrelationType().equals(otherInstance.getCorrelationType()))
&& (this.getCorrelationCase().equals(otherInstance.getCorrelationCase()))
&& (this.getCorrelationDataSource().equals(otherInstance.getCorrelationDataSource()))
&& (this.getFilePath().equals(otherInstance.getFilePath()))
@ -134,6 +132,8 @@ public class CorrelationAttributeInstance implements Serializable {
+ this.getCorrelationCase().getCaseUUID()
+ this.getCorrelationDataSource().getDeviceID()
+ this.getFilePath()
+ this.getCorrelationType().toString()
+ this.getCorrelationValue()
+ this.getKnownStatus()
+ this.getComment();
}

View File

@ -0,0 +1,53 @@
/*
*
* 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.centralrepository.datamodel;
/**
* Thrown when a given value is not in the expected format.
*/
public class CorrelationAttributeNormalizationException extends Exception {
private static final long serialVersionUID = 1L;
/**
* Construct an exception with the given message.
* @param message error message
*/
public CorrelationAttributeNormalizationException(String message){
super(message);
}
/**
* Construct an exception with the given message and inner exception.
* @param message error message
* @param cause inner exception
*/
public CorrelationAttributeNormalizationException(String message, Throwable cause){
super(message, cause);
}
/**
* Construct an exception with the given inner exception.
* @param cause inner exception
*/
public CorrelationAttributeNormalizationException(Throwable cause){
super(cause);
}
}

View File

@ -0,0 +1,160 @@
/*
*
* 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.centralrepository.datamodel;
import java.util.List;
import java.util.Optional;
import org.apache.commons.validator.routines.DomainValidator;
import org.apache.commons.validator.routines.EmailValidator;
/**
* Provides functions for normalizing data by attribute type before insertion or querying.
*/
final public class CorrelationAttributeNormalizer {
/**
* This is a utility class - no need for constructing or subclassing, etc...
*/
private CorrelationAttributeNormalizer() { }
/**
* Normalize the data. Converts text to lower case, and ensures that the
* data is a valid string of the format expected given the attributeType.
*
* @param attributeType correlation type of data
* @param data data to normalize
*
* @return normalized data
*/
public static String normalize(CorrelationAttributeInstance.Type attributeType, String data) throws CorrelationAttributeNormalizationException {
if(attributeType == null){
throw new CorrelationAttributeNormalizationException("Attribute type was null.");
}
if(data == null){
throw new CorrelationAttributeNormalizationException("Data was null.");
}
switch(attributeType.getId()){
case CorrelationAttributeInstance.FILES_TYPE_ID:
return normalizeMd5(data);
case CorrelationAttributeInstance.DOMAIN_TYPE_ID:
return normalizeDomain(data);
case CorrelationAttributeInstance.EMAIL_TYPE_ID:
return normalizeEmail(data);
case CorrelationAttributeInstance.PHONE_TYPE_ID:
return normalizePhone(data);
case CorrelationAttributeInstance.USBID_TYPE_ID:
return normalizeUsbId(data);
default:
final String errorMessage = String.format(
"Validator function not found for attribute type: %s",
attributeType.getDisplayName());
throw new CorrelationAttributeNormalizationException(errorMessage);
}
}
/**
* Validate the data. Converts text to lower case, and ensures that the
* data is a valid string of the format expected given the attributeType.
*
* @param attributeTypeId correlation type of data
* @param data data to normalize
*
* @return normalized data
*/
public static String normalize(int attributeTypeId, String data) throws CorrelationAttributeNormalizationException {
try {
List<CorrelationAttributeInstance.Type> defaultTypes = CorrelationAttributeInstance.getDefaultCorrelationTypes();
Optional<CorrelationAttributeInstance.Type> typeOption = defaultTypes.stream().filter(attributeType -> attributeType.getId() == attributeTypeId).findAny();
if(typeOption.isPresent()){
CorrelationAttributeInstance.Type type = typeOption.get();
return CorrelationAttributeNormalizer.normalize(type, data);
} else {
throw new CorrelationAttributeNormalizationException(String.format("Given attributeTypeId did not correspond to any known Attribute: %s", attributeTypeId));
}
} catch (EamDbException ex) {
throw new CorrelationAttributeNormalizationException(ex);
}
}
/**
* Verify MD5 is the correct length and values. Make lower case.
*/
private static String normalizeMd5(String data) throws CorrelationAttributeNormalizationException {
final String validMd5Regex = "^[a-f0-9]{32}$";
final String dataLowered = data.toLowerCase();
if(dataLowered.matches(validMd5Regex)){
return dataLowered;
} else {
throw new CorrelationAttributeNormalizationException(String.format("Data purporting to be an MD5 was found not to comform to expected format: %s", data));
}
}
/**
* Verify there are no slashes or invalid domain name characters (such as '?' or \: ). Normalize to lower case.
*/
private static String normalizeDomain(String data) throws CorrelationAttributeNormalizationException {
DomainValidator validator = DomainValidator.getInstance(true);
if(validator.isValid(data)){
return data.toLowerCase();
} else {
final String validIpAddressRegex = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$";
if(data.matches(validIpAddressRegex)){
return data;
} else {
throw new CorrelationAttributeNormalizationException(String.format("Data was expected to be a valid domain: %s", data));
}
}
}
/**
* Verify that there is an '@' and no invalid characters. Should normalize to lower case.
*/
private static String normalizeEmail(String data) throws CorrelationAttributeNormalizationException {
EmailValidator validator = EmailValidator.getInstance(true, true);
if(validator.isValid(data)){
return data.toLowerCase();
} else {
throw new CorrelationAttributeNormalizationException(String.format("Data was expected to be a valid email address: %s", data));
}
}
/**
* Verify it is only numbers and '+'. Strip spaces, dashes, and parentheses.
*/
private static String normalizePhone(String data) throws CorrelationAttributeNormalizationException {
if(data.matches("\\+?[0-9()\\-\\s]+")){
String phoneNumber = data.replaceAll("[^0-9\\+]", "");
return phoneNumber;
} else {
throw new CorrelationAttributeNormalizationException(String.format("Data was expected to be a valid phone number: %s", data));
}
}
/**
* Vacuous - will be replaced with something reasonable later.
*/
private static String normalizeUsbId(String data) throws CorrelationAttributeNormalizationException {
//TODO replace with correct usb id validation at a later date
return data;
}
}

View File

@ -213,7 +213,7 @@ public class EamArtifactUtil {
TskData.FileKnown.UNKNOWN
);
} catch (TskCoreException | EamDbException ex) {
} catch (TskCoreException | EamDbException | CorrelationAttributeNormalizationException ex) {
logger.log(Level.SEVERE, "Error creating artifact instance.", ex); // NON-NLS
return null;
} catch (NoCurrentCaseException ex) {
@ -251,7 +251,8 @@ public class EamArtifactUtil {
type = EamDb.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID);
correlationCase = EamDb.getInstance().getCase(Case.getCurrentCaseThrows());
if (null == correlationCase) {
correlationCase = EamDb.getInstance().newCase(Case.getCurrentCaseThrows());
//if the correlationCase is not in the Central repo then attributes generated in relation to it will not be
return null;
}
correlationDataSource = CorrelationDataSource.fromTSKDataSource(correlationCase, file.getDataSource());
value = file.getMd5Hash();
@ -267,7 +268,7 @@ public class EamArtifactUtil {
CorrelationAttributeInstance correlationAttributeInstance;
try {
correlationAttributeInstance = EamDb.getInstance().getCorrelationAttributeInstance(type, correlationCase, correlationDataSource, value, filePath);
} catch (EamDbException ex) {
} catch (EamDbException | CorrelationAttributeNormalizationException ex) {
logger.log(Level.WARNING, String.format(
"Correlation attribute could not be retrieved for '%s' (id=%d): %s",
content.getName(), content.getId(), ex.getMessage()));
@ -322,7 +323,7 @@ public class EamArtifactUtil {
CorrelationDataSource.fromTSKDataSource(correlationCase, af.getDataSource()),
af.getParentPath() + af.getName());
} catch (TskCoreException | EamDbException ex) {
} catch (TskCoreException | EamDbException | CorrelationAttributeNormalizationException ex) {
logger.log(Level.SEVERE, "Error making correlation attribute.", ex);
return null;
} catch (NoCurrentCaseException ex) {

View File

@ -244,7 +244,7 @@ public interface EamDb {
*
* @return List of artifact instances for a given type/value
*/
List<CorrelationAttributeInstance> getArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException;
List<CorrelationAttributeInstance> getArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException;
/**
* Retrieves eamArtifact instances from the database that are associated
@ -269,7 +269,7 @@ public interface EamDb {
* @return Number of artifact instances having ArtifactType and
* ArtifactValue.
*/
Long getCountArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException;
Long getCountArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException;
/**
* Calculate the percentage of data sources that have this attribute value.
@ -278,7 +278,7 @@ public interface EamDb {
*
* @return Int between 0 and 100
*/
int getFrequencyPercentage(CorrelationAttributeInstance corAttr) throws EamDbException;
int getFrequencyPercentage(CorrelationAttributeInstance corAttr) throws EamDbException, CorrelationAttributeNormalizationException;
/**
* Retrieves number of unique caseDisplayName / dataSource tuples in the
@ -290,7 +290,7 @@ public interface EamDb {
*
* @return Number of unique tuples
*/
Long getCountUniqueCaseDataSourceTuplesHavingTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException;
Long getCountUniqueCaseDataSourceTuplesHavingTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException;
/**
* Retrieves number of data sources in the database.
@ -358,7 +358,7 @@ public interface EamDb {
* @throws EamDbException
*/
CorrelationAttributeInstance getCorrelationAttributeInstance(CorrelationAttributeInstance.Type type, CorrelationCase correlationCase,
CorrelationDataSource correlationDataSource, String value, String filePath) throws EamDbException;
CorrelationDataSource correlationDataSource, String value, String filePath) throws EamDbException, CorrelationAttributeNormalizationException;
/**
* Sets an eamArtifact instance to the given known status. If eamArtifact
@ -378,7 +378,7 @@ public interface EamDb {
*
* @return List with 0 or more matching eamArtifact instances.
*/
List<CorrelationAttributeInstance> getArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException;
List<CorrelationAttributeInstance> getArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException;
/**
* Gets list of matching eamArtifact instances that have knownStatus =
@ -397,7 +397,7 @@ public interface EamDb {
*
* @return Number of matching eamArtifacts
*/
Long getCountArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException;
Long getCountArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException;
/**
* Gets list of distinct case display names, where each case has 1+ Artifact
@ -411,7 +411,7 @@ public interface EamDb {
*
* @throws EamDbException
*/
List<String> getListCasesHavingArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException;
List<String> getListCasesHavingArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException;
/**
* Remove a reference set and all values contained in it.
@ -462,7 +462,7 @@ public interface EamDb {
*
* @throws EamDbException
*/
public boolean isFileHashInReferenceSet(String hash, int referenceSetID) throws EamDbException;
public boolean isFileHashInReferenceSet(String hash, int referenceSetID) throws EamDbException, CorrelationAttributeNormalizationException;
/**
* Check if the given value is in a specific reference set
@ -473,7 +473,7 @@ public interface EamDb {
*
* @return true if the hash is found in the reference set
*/
public boolean isValueInReferenceSet(String value, int referenceSetID, int correlationTypeID) throws EamDbException;
public boolean isValueInReferenceSet(String value, int referenceSetID, int correlationTypeID) throws EamDbException, CorrelationAttributeNormalizationException;
/**
* Is the artifact known as bad according to the reference entries?
@ -483,7 +483,7 @@ public interface EamDb {
*
* @return Global known status of the artifact
*/
boolean isArtifactKnownBadByReference(CorrelationAttributeInstance.Type aType, String value) throws EamDbException;
boolean isArtifactKnownBadByReference(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException;
/**
* Add a new organization
@ -611,7 +611,7 @@ public interface EamDb {
*
* @throws EamDbException
*/
List<EamGlobalFileInstance> getReferenceInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String aValue) throws EamDbException;
List<EamGlobalFileInstance> getReferenceInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String aValue) throws EamDbException, CorrelationAttributeNormalizationException;
/**
* Add a new EamArtifact.Type to the db.

View File

@ -36,7 +36,7 @@ public class EamGlobalFileInstance {
int globalSetID,
String MD5Hash,
TskData.FileKnown knownStatus,
String comment) throws EamDbException {
String comment) throws EamDbException, CorrelationAttributeNormalizationException {
this(-1, globalSetID, MD5Hash, knownStatus, comment);
}
@ -45,17 +45,14 @@ public class EamGlobalFileInstance {
int globalSetID,
String MD5Hash,
TskData.FileKnown knownStatus,
String comment) throws EamDbException {
if(MD5Hash == null){
throw new EamDbException("null MD5 hash");
}
String comment) throws EamDbException, CorrelationAttributeNormalizationException {
if(knownStatus == null){
throw new EamDbException("null known status");
}
this.instanceID = instanceID;
this.globalSetID = globalSetID;
// Normalize hashes by lower casing
this.MD5Hash = MD5Hash.toLowerCase();
this.MD5Hash = CorrelationAttributeNormalizer.normalize(CorrelationAttributeInstance.FILES_TYPE_ID, MD5Hash);
this.knownStatus = knownStatus;
this.comment = comment;
}
@ -117,12 +114,8 @@ public class EamGlobalFileInstance {
/**
* @param MD5Hash the MD5Hash to set
*/
public void setMD5Hash(String MD5Hash) throws EamDbException {
if(MD5Hash == null){
throw new EamDbException("null MD5 hash");
}
// Normalize hashes by lower casing
this.MD5Hash = MD5Hash.toLowerCase();
public void setMD5Hash(String MD5Hash) throws CorrelationAttributeNormalizationException {
this.MD5Hash = CorrelationAttributeNormalizer.normalize(CorrelationAttributeInstance.FILES_TYPE_ID, MD5Hash);
}
/**

View File

@ -106,4 +106,5 @@ public interface InstanceTableCallback {
return resultSet.getString("comment");
}
}

View File

@ -447,7 +447,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
* @return List of artifact instances for a given type/value
*/
@Override
public List<CorrelationAttributeInstance> getArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException {
public List<CorrelationAttributeInstance> getArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException {
try {
acquireSharedLock();
return super.getArtifactInstancesByTypeValue(aType, value);
@ -489,7 +489,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
* @throws EamDbException
*/
@Override
public Long getCountArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException {
public Long getCountArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException {
try {
acquireSharedLock();
return super.getCountArtifactInstancesByTypeValue(aType, value);
@ -499,7 +499,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
}
@Override
public int getFrequencyPercentage(CorrelationAttributeInstance corAttr) throws EamDbException {
public int getFrequencyPercentage(CorrelationAttributeInstance corAttr) throws EamDbException, CorrelationAttributeNormalizationException {
try {
acquireSharedLock();
return super.getFrequencyPercentage(corAttr);
@ -520,7 +520,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
* @throws EamDbException
*/
@Override
public Long getCountUniqueCaseDataSourceTuplesHavingTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException {
public Long getCountUniqueCaseDataSourceTuplesHavingTypeValue(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException {
try {
acquireSharedLock();
return super.getCountUniqueCaseDataSourceTuplesHavingTypeValue(aType, value);
@ -617,7 +617,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
* @return List with 0 or more matching eamArtifact instances.
*/
@Override
public List<CorrelationAttributeInstance> getArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException {
public List<CorrelationAttributeInstance> getArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException {
try {
acquireSharedLock();
return super.getArtifactInstancesKnownBad(aType, value);
@ -654,7 +654,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
* @return Number of matching eamArtifacts
*/
@Override
public Long getCountArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException {
public Long getCountArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException {
try {
acquireSharedLock();
return super.getCountArtifactInstancesKnownBad(aType, value);
@ -676,7 +676,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
* @throws EamDbException
*/
@Override
public List<String> getListCasesHavingArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException {
public List<String> getListCasesHavingArtifactInstancesKnownBad(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException {
try {
acquireSharedLock();
return super.getListCasesHavingArtifactInstancesKnownBad(aType, value);
@ -710,7 +710,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
* @return true if the hash is found in the reference set
*/
@Override
public boolean isValueInReferenceSet(String value, int referenceSetID, int correlationTypeID) throws EamDbException {
public boolean isValueInReferenceSet(String value, int referenceSetID, int correlationTypeID) throws EamDbException, CorrelationAttributeNormalizationException {
try {
acquireSharedLock();
return super.isValueInReferenceSet(value, referenceSetID, correlationTypeID);
@ -782,7 +782,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
* @return Global known status of the artifact
*/
@Override
public boolean isArtifactKnownBadByReference(CorrelationAttributeInstance.Type aType, String value) throws EamDbException {
public boolean isArtifactKnownBadByReference(CorrelationAttributeInstance.Type aType, String value) throws EamDbException, CorrelationAttributeNormalizationException {
try {
acquireSharedLock();
return super.isArtifactKnownBadByReference(aType, value);
@ -967,7 +967,7 @@ final class SqliteEamDb extends AbstractSqlEamDb {
* @throws EamDbException
*/
@Override
public List<EamGlobalFileInstance> getReferenceInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String aValue) throws EamDbException {
public List<EamGlobalFileInstance> getReferenceInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String aValue) throws EamDbException, CorrelationAttributeNormalizationException {
try {
acquireSharedLock();
return super.getReferenceInstancesByTypeValue(aType, aValue);

View File

@ -34,6 +34,7 @@ import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.IngestServices;
@ -259,14 +260,19 @@ public class IngestEventsListener {
if (recentlyAddedCeArtifacts.add(eamArtifact.toString())) {
// Was it previously marked as bad?
// query db for artifact instances having this TYPE/VALUE and knownStatus = "Bad".
// if gettKnownStatus() is "Unknown" and this artifact instance was marked bad in a previous case,
// if getKnownStatus() is "Unknown" and this artifact instance was marked bad in a previous case,
// create TSK_INTERESTING_ARTIFACT_HIT artifact on BB.
if (flagNotableItemsEnabled) {
List<String> caseDisplayNames = dbManager.getListCasesHavingArtifactInstancesKnownBad(eamArtifact.getCorrelationType(), eamArtifact.getCorrelationValue());
List<String> caseDisplayNames;
try {
caseDisplayNames = dbManager.getListCasesHavingArtifactInstancesKnownBad(eamArtifact.getCorrelationType(), eamArtifact.getCorrelationValue());
if (!caseDisplayNames.isEmpty()) {
postCorrelatedBadArtifactToBlackboard(bbArtifact,
caseDisplayNames);
}
} catch (CorrelationAttributeNormalizationException ex) {
LOGGER.log(Level.INFO, String.format("Unable to flag notable item: %s.", eamArtifact.toString()), ex);
}
}
eamArtifacts.add(eamArtifact);
}

View File

@ -28,6 +28,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.Blackboard;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.core.RuntimeProperties;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.ingest.FileIngestModule;
@ -143,6 +144,9 @@ final class IngestModule implements FileIngestModule {
} catch (EamDbException ex) {
logger.log(Level.SEVERE, "Error searching database for artifact.", ex); // NON-NLS
return ProcessResult.ERROR;
} catch (CorrelationAttributeNormalizationException ex){
logger.log(Level.INFO, "Error searching database for artifact.", ex); // NON-NLS
return ProcessResult.ERROR;
}
}
@ -161,6 +165,9 @@ final class IngestModule implements FileIngestModule {
} catch (EamDbException ex) {
logger.log(Level.SEVERE, "Error adding artifact to bulk artifacts.", ex); // NON-NLS
return ProcessResult.ERROR;
} catch (CorrelationAttributeNormalizationException ex) {
logger.log(Level.INFO, "Error adding artifact to bulk artifacts.", ex); // NON-NLS
return ProcessResult.ERROR;
}
return ProcessResult.OK;

View File

@ -72,6 +72,12 @@ public abstract class AbstractCommonAttributeInstance {
this.dataSource = "";
}
/**
* Get the type of common attribute.
* @return
*/
public abstract CorrelationAttributeInstance.Type getCorrelationAttributeInstanceType();
/**
* Get an AbstractFile for this instance if it can be retrieved from the
* CaseDB.

View File

@ -34,8 +34,8 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Prototype for an object which finds files with common attributes.
* Subclass this and implement findFiles in order
* Prototype for an object which finds files with common attributes. Subclass
* this and implement findMatches in order
*/
public abstract class AbstractCommonAttributeSearcher {
@ -57,36 +57,34 @@ public abstract class AbstractCommonAttributeSearcher {
/**
* Implement this to search for files with common attributes. Creates an
* object (CommonAttributeSearchResults) which contains all of the information
* required to display a tree view in the UI. The view will contain 3 layers:
* a top level node, indicating the number matches each of it's children possess,
* a mid level node indicating the matched attribute,
* object (CommonAttributeSearchResults) which contains all of the
* information required to display a tree view in the UI. The view will
* contain 3 layers: a top level node, indicating the number matches each of
* it's children possess, a mid level node indicating the matched attribute,
*
* @return
*
* @throws TskCoreException
* @throws NoCurrentCaseException
* @throws SQLException
* @throws EamDbException
*/
public abstract CommonAttributeSearchResults findFiles() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException;
public abstract CommonAttributeSearchResults findMatches() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException;
/**
* Implement this to create a descriptive string for the tab which will display
* this data.
* Implement this to create a descriptive string for the tab which will
* display this data.
*
* @return an informative string
*/
@NbBundle.Messages({
"AbstractCommonFilesMetadataBuilder.buildTabTitle.titleIntraAll=Common Files (All Data Sources, %s)",
"AbstractCommonFilesMetadataBuilder.buildTabTitle.titleIntraSingle=Common Files (Data Source: %s, %s)",
"AbstractCommonFilesMetadataBuilder.buildTabTitle.titleInterAll=Common Files (All Central Repository Cases, %s)",
"AbstractCommonFilesMetadataBuilder.buildTabTitle.titleInterSingle=Common Files (Central Repository Case: %s, %s)",
})
abstract String buildTabTitle();
abstract String getTabTitle();
@NbBundle.Messages({
"AbstractCommonFilesMetadataBuilder.buildCategorySelectionString.doc=Documents",
"AbstractCommonFilesMetadataBuilder.buildCategorySelectionString.media=Media",
"AbstractCommonFilesMetadataBuilder.buildCategorySelectionString.all=All File Categories"
})
String buildCategorySelectionString() {
if (!this.isFilterByDoc() && !this.isFilterByMedia()) {
return Bundle.AbstractCommonFilesMetadataBuilder_buildCategorySelectionString_all();
@ -102,6 +100,24 @@ public abstract class AbstractCommonAttributeSearcher {
}
}
/**
* Get the portion of the title that will display the frequency percentage
* threshold. Items that existed in over this percent of data sources were
* ommited from the results.
*
* @return A string providing the frequency percentage threshold, or an empty string if no threshold was set
*/
@NbBundle.Messages({
"# {0} - threshold percent",
"AbstractCommonFilesMetadataBuilder.getPercentFilter.thresholdPercent=, Threshold {0}%"})
String getPercentThresholdString() {
if (frequencyPercentageThreshold == 0) {
return "";
} else {
return Bundle.AbstractCommonFilesMetadataBuilder_getPercentFilter_thresholdPercent(frequencyPercentageThreshold);
}
}
static Map<Integer, CommonAttributeValueList> collateMatchesByNumberOfInstances(Map<String, CommonAttributeValue> commonFiles) {
//collate matches by number of matching instances - doing this in sql doesnt seem efficient
Map<Integer, CommonAttributeValueList> instanceCollatedCommonFiles = new TreeMap<>();
@ -122,10 +138,10 @@ public abstract class AbstractCommonAttributeSearcher {
/*
* The set of the MIME types that will be checked for extension mismatches
* when checkType is ONLY_MEDIA.
* ".jpg", ".jpeg", ".png", ".psd", ".nef", ".tiff", ".bmp", ".tec"
* ".aaf", ".3gp", ".asf", ".avi", ".m1v", ".m2v", //NON-NLS
* ".m4v", ".mp4", ".mov", ".mpeg", ".mpg", ".mpe", ".mp4", ".rm", ".wmv", ".mpv", ".flv", ".swf"
* when checkType is ONLY_MEDIA. ".jpg", ".jpeg", ".png", ".psd", ".nef",
* ".tiff", ".bmp", ".tec" ".aaf", ".3gp", ".asf", ".avi", ".m1v", ".m2v",
* //NON-NLS ".m4v", ".mp4", ".mov", ".mpeg", ".mpg", ".mpe", ".mp4", ".rm",
* ".wmv", ".mpv", ".flv", ".swf"
*/
static final Set<String> MEDIA_PICS_VIDEO_MIME_TYPES = Stream.of(
"image/bmp", //NON-NLS
@ -155,11 +171,9 @@ public abstract class AbstractCommonAttributeSearcher {
/*
* The set of the MIME types that will be checked for extension mismatches
* when checkType is ONLY_TEXT_FILES.
* ".doc", ".docx", ".odt", ".xls", ".xlsx", ".ppt", ".pptx"
* ".txt", ".rtf", ".log", ".text", ".xml"
* ".html", ".htm", ".css", ".js", ".php", ".aspx"
* ".pdf"
* when checkType is ONLY_TEXT_FILES. ".doc", ".docx", ".odt", ".xls",
* ".xlsx", ".ppt", ".pptx" ".txt", ".rtf", ".log", ".text", ".xml" ".html",
* ".htm", ".css", ".js", ".php", ".aspx" ".pdf"
*/
static final Set<String> TEXT_FILES_MIME_TYPES = Stream.of(
"text/plain", //NON-NLS

View File

@ -20,12 +20,13 @@
package org.sleuthkit.autopsy.commonfilesearch;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance.Type;
/**
* Algorithm which finds files anywhere in the Central Repo which also occur in
@ -39,23 +40,26 @@ public class AllInterCaseCommonAttributeSearcher extends InterCaseCommonAttribut
* broadly categorized as media types
* @param filterByDocMimeType match only on files whose mime types can be
* broadly categorized as document types
*
* @throws EamDbException
*/
public AllInterCaseCommonAttributeSearcher(Map<Long, String> dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType, int percentageThreshold) throws EamDbException {
super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType, percentageThreshold);
public AllInterCaseCommonAttributeSearcher(Map<Long, String> dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType, Type corAttrType, int percentageThreshold) throws EamDbException {
super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType, corAttrType, percentageThreshold);
}
@Override
public CommonAttributeSearchResults findFiles() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException {
InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor(this.getDataSourceIdToNameMap());
public CommonAttributeSearchResults findMatches() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException {
InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor(this.getDataSourceIdToNameMap(), corAttrType);
Map<Integer, CommonAttributeValueList> interCaseCommonFiles = eamDbAttrInst.findInterCaseCommonAttributeValues(Case.getCurrentCase());
return new CommonAttributeSearchResults(interCaseCommonFiles, this.frequencyPercentageThreshold);
return new CommonAttributeSearchResults(interCaseCommonFiles, this.frequencyPercentageThreshold, this.corAttrType);
}
@NbBundle.Messages({
"# {0} - attr type",
"# {1} - threshold string",
"AllInterCaseCommonAttributeSearcher.buildTabTitle.titleInterAll=Common Properties (All Central Repository Cases, {0}{1})"})
@Override
String buildTabTitle() {
final String buildCategorySelectionString = this.buildCategorySelectionString();
final String titleTemplate = Bundle.AbstractCommonFilesMetadataBuilder_buildTabTitle_titleInterAll();
return String.format(titleTemplate, new Object[]{buildCategorySelectionString});
String getTabTitle() {
return Bundle.AllInterCaseCommonAttributeSearcher_buildTabTitle_titleInterAll(this.corAttrType.getDisplayName(), this.getPercentThresholdString());
}
}

View File

@ -20,6 +20,7 @@
package org.sleuthkit.autopsy.commonfilesearch;
import java.util.Map;
import org.openide.util.NbBundle;
import org.sleuthkit.datamodel.TskData.FileKnown;
/**
@ -34,8 +35,10 @@ final public class AllIntraCaseCommonAttributeSearcher extends IntraCaseCommonAt
* sources.
*
* @param dataSourceIdMap a map of obj_id to datasource name
* @param filterByMediaMimeType match only on files whose mime types can be broadly categorized as media types
* @param filterByDocMimeType match only on files whose mime types can be broadly categorized as document types
* @param filterByMediaMimeType match only on files whose mime types can be
* broadly categorized as media types
* @param filterByDocMimeType match only on files whose mime types can be
* broadly categorized as document types
*/
public AllIntraCaseCommonAttributeSearcher(Map<Long, String> dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType, int percentageThreshold) {
super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType, percentageThreshold);
@ -48,10 +51,13 @@ final public class AllIntraCaseCommonAttributeSearcher extends IntraCaseCommonAt
return String.format(WHERE_CLAUSE, args);
}
@NbBundle.Messages({
"# {0} - build category",
"# {1} - threshold string",
"AllIntraCaseCommonAttributeSearcher.buildTabTitle.titleIntraAll=Common Properties (All Data Sources, {0}{1})"
})
@Override
String buildTabTitle() {
final String buildCategorySelectionString = this.buildCategorySelectionString();
final String titleTemplate = Bundle.AbstractCommonFilesMetadataBuilder_buildTabTitle_titleIntraAll();
return String.format(titleTemplate, new Object[]{buildCategorySelectionString});
String getTabTitle() {
return Bundle.AllIntraCaseCommonAttributeSearcher_buildTabTitle_titleIntraAll(this.buildCategorySelectionString(), this.getPercentThresholdString());
}
}

View File

@ -1,30 +1,42 @@
CommonFilesPanel.commonFilesSearchLabel.text=<html>Find files in multiple data sources in the current case.</html>
CommonFilesPanel.text=Indicate which data sources to consider while searching for duplicates:
CommonFilesPanel.jRadioButton1.text=jRadioButton1
CommonFilesPanel.jRadioButton2.text=With previous cases in the Central Repository
CommonFilesPanel.jRadioButton2.text=Between current case and cases in Central Repository
CommonFilesPanel.intraCaseRadio.label=Correlate within current case only
CommonFilesPanel.interCaseRadio.label=Correlate amongst all known cases (uses Central Repo)
IntraCasePanel.allDataSourcesRadioButton.text=Matches may be from any data source
IntraCasePanel.withinDataSourceRadioButton.text=At least one match must appear in the data source selected below:
IntraCasePanel.selectDataSourceComboBox.actionCommand=
InterCasePanel.specificCentralRepoCaseRadio.text=Matches must be from the following Central Repo case:
InterCasePanel.anyCentralRepoCaseRadio.text=Matches may be from any Central Repo case
CommonAttributePanel.jCheckBox1.text=Hide files found in over
CommonAttributePanel.jLabel1.text=% of data sources in central repository.
CommonAttributePanel.percentageThresholdTextTwo.text_1=% of data sources in central repository.
CommonAttributePanel.percentageThresholdTextOne.text=20
CommonAttributePanel.percentageThresholdCheck.text_1=Hide files found in over
CommonAttributePanel.intraCaseRadio.text=Within current case
CommonAttributePanel.commonFilesSearchLabel1.text=<html>Find common files to correlate data soures or cases.</html>
CommonAttributePanel.jCheckBox1.text=Hide files found in over
CommonAttributePanel.jLabel1.text=% of data sources in central repository.
CommonAttributePanel.percentageThreshold.text=20
CommonAttributePanel.jLabel1.text_1=% of data sources in central repository.
InterCasePanel.correlationTypeComboBox.toolTipText=Selected Correlation Type
IntraCasePanel.selectedFileCategoriesButton.text=Only the selected file types:
IntraCasePanel.categoriesLabel.text=File Types To Show:
IntraCasePanel.allFileCategoriesRadioButton.toolTipText=No filtering applied to results...
IntraCasePanel.allFileCategoriesRadioButton.text=All file types
IntraCasePanel.documentsCheckbox.text=Documents
IntraCasePanel.pictureVideoCheckbox.text=Pictures and Videos
IntraCasePanel.selectedFileCategoriesButton.toolTipText=Select from the options below...
CommonAttributePanel.percentageThresholdTextTwo.text_1=% of data sources in Central Repository.
CommonAttributePanel.percentageThresholdCheck.text_1_1=Hide items found in over
CommonAttributePanel.intraCaseRadio.text=Between data sources in current case
CommonAttributePanel.errorText.text=<html>In order to search, you must select a file category.</html>
CommonAttributePanel.categoriesLabel.text=File Types To Include:
CommonAttributePanel.documentsCheckbox.text=Documents
CommonAttributePanel.pictureVideoCheckbox.text=Pictures and Videos
CommonAttributePanel.selectedFileCategoriesButton.toolTipText=Select from the options below...
CommonAttributePanel.selectedFileCategoriesButton.text=Only the selected file types:
CommonAttributePanel.allFileCategoriesRadioButton.toolTipText=No filtering applied to results...
CommonAttributePanel.allFileCategoriesRadioButton.text=All file types
CommonAttributePanel.cancelButton.actionCommand=Cancel
CommonAttributePanel.cancelButton.text=Cancel
CommonAttributePanel.searchButton.text=Search
CommonAttributePanel.commonFilesSearchLabel2.text=Scope of Search
InterCasePanel.categoriesLabel.text=File Types To Show:
InterCasePanel.documentsCheckbox.text=Documents
InterCasePanel.pictureVideoCheckbox.text=Pictures and Videos
InterCasePanel.selectedFileCategoriesButton.toolTipText=Select from the options below...
InterCasePanel.selectedFileCategoriesButton.text=Only the selected file types:
InterCasePanel.allFileCategoriesRadioButton.toolTipText=No filtering applied to results...
InterCasePanel.allFileCategoriesRadioButton.text=All file types
InterCasePanel.specificCentralRepoCaseCheckbox.text=Common items need to exist in a specific case
IntraCasePanel.onlySpecificDataSourceCheckbox.text=Common items need to exist in a specific data source
CommonAttributePanel.interCasePanel.border.title=Central Repository Options
CommonAttributePanel.intraCasePanel.border.title=Current Case Options
CommonAttributePanel.commonItemSearchDescription.text=<html>Find items that exist in multiple data sources or cases</html>
CommonAttributePanel.scopeLabel.text=Scope of Search
InterCasePanel.correlationComboBoxLabel.text=Property Type To Match
CommonAttributePanel.percentageThresholdInputBox.text=20

View File

@ -23,6 +23,7 @@ import java.util.Arrays;
import java.util.logging.Level;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
import org.sleuthkit.datamodel.AbstractFile;
@ -70,4 +71,10 @@ final public class CaseDBCommonAttributeInstance extends AbstractCommonAttribute
return null;
}
}
@Override
public CorrelationAttributeInstance.Type getCorrelationAttributeInstanceType() {
//may be required at a later date
throw new UnsupportedOperationException("Not supported yet.");
}
}

View File

@ -18,16 +18,19 @@
*/
package org.sleuthkit.autopsy.commonfilesearch;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.openide.nodes.Sheet;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor;
import org.sleuthkit.autopsy.datamodel.FileNode;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.ContentTag;
/**
* Node that wraps CaseDBCommonAttributeInstance to represent a file instance stored
* in the CaseDB.
* Node that wraps CaseDBCommonAttributeInstance to represent a file instance
* stored in the CaseDB.
*/
public class CaseDBCommonAttributeInstanceNode extends FileNode {
@ -38,8 +41,10 @@ public class CaseDBCommonAttributeInstanceNode extends FileNode {
* Create a node which can be used in a multilayer tree table and is based
* on an <code>AbstractFile</code>.
*
* @param fsContent
* @param dataSource
* @param fsContent the file which is being represented by this node
* @param caseName the name of the case
* @param dataSource the datasource which contains the file
*
*/
public CaseDBCommonAttributeInstanceNode(AbstractFile fsContent, String caseName, String dataSource) {
super(fsContent);
@ -74,18 +79,22 @@ public class CaseDBCommonAttributeInstanceNode extends FileNode {
sheetSet = Sheet.createPropertiesSet();
sheet.put(sheetSet);
}
List<ContentTag> tags = getContentTagsFromDatabase();
final String NO_DESCR = Bundle.CommonFilesSearchResultsViewerTable_noDescText();
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), Bundle.CommonFilesSearchResultsViewerTable_filesColLbl(), NO_DESCR, this.getContent().getName()));
CorrelationAttributeInstance correlationAttribute = getCorrelationAttributeInstance();
this.addScoreProperty(sheetSet, tags);
this.addCommentProperty(sheetSet, tags, correlationAttribute);
this.addCountProperty(sheetSet, correlationAttribute);
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), Bundle.CommonFilesSearchResultsViewerTable_pathColLbl(), NO_DESCR, this.getContent().getParentPath()));
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), Bundle.CommonFilesSearchResultsViewerTable_hashsetHitsColLbl(), NO_DESCR, getHashSetHitsCsvList(this.getContent())));
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), Bundle.CommonFilesSearchResultsViewerTable_dataSourceColLbl(), NO_DESCR, this.getDataSource()));
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), Bundle.CommonFilesSearchResultsViewerTable_mimeTypeColLbl(), NO_DESCR, StringUtils.defaultString(this.getContent().getMIMEType())));
this.addTagProperty(sheetSet, tags);
sheetSet.put(new NodeProperty<>(Bundle.CommonFilesSearchResultsViewerTable_caseColLbl1(), Bundle.CommonFilesSearchResultsViewerTable_caseColLbl1(), NO_DESCR, caseName));
this.addTagProperty(sheetSet);
return sheet;
}
}

View File

@ -44,12 +44,19 @@ final public class CentralRepoCommonAttributeInstance extends AbstractCommonAttr
private static final Logger LOGGER = Logger.getLogger(CentralRepoCommonAttributeInstance.class.getName());
private final Integer crFileId;
private CorrelationAttributeInstance currentAttribute;
private final CorrelationAttributeInstance.Type correlationType;
private final Map<String, Long> dataSourceNameToIdMap;
CentralRepoCommonAttributeInstance(Integer attrInstId, Map<Long, String> dataSourceIdToNameMap) {
CentralRepoCommonAttributeInstance(Integer attrInstId, Map<Long, String> dataSourceIdToNameMap, CorrelationAttributeInstance.Type correlationType) {
super();
this.crFileId = attrInstId;
this.dataSourceNameToIdMap = invertMap(dataSourceIdToNameMap);
this.correlationType = correlationType;
}
@Override
public CorrelationAttributeInstance.Type getCorrelationAttributeInstanceType(){
return this.correlationType;
}
void setCurrentAttributeInst(CorrelationAttributeInstance attribute) {
@ -67,7 +74,6 @@ final public class CentralRepoCommonAttributeInstance extends AbstractCommonAttr
String currentFullPath = currentAttributeInstance.getFilePath();
String currentDataSource = currentAttributeInstance.getCorrelationDataSource().getName();
if (this.dataSourceNameToIdMap.containsKey(currentDataSource)) {
Long dataSourceObjectId = this.dataSourceNameToIdMap.get(currentDataSource);
@ -107,7 +113,7 @@ final public class CentralRepoCommonAttributeInstance extends AbstractCommonAttr
public DisplayableItemNode[] generateNodes() {
// @@@ We should be doing more of this work in teh generateKeys method. We want to do as little as possible in generateNodes
InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor();
InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor(correlationType);
CorrelationAttributeInstance corrAttr = eamDbAttrInst.findSingleCorrelationAttribute(crFileId);
List<DisplayableItemNode> attrInstNodeList = new ArrayList<>(0);
String currCaseDbName = Case.getCurrentCase().getDisplayName();

View File

@ -2,17 +2,12 @@
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JDialogFormInfo">
<NonVisualComponents>
<Component class="javax.swing.ButtonGroup" name="fileTypeFilterButtonGroup">
</Component>
<Component class="javax.swing.ButtonGroup" name="interIntraButtonGroup">
</Component>
</NonVisualComponents>
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[450, 375]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[450, 375]"/>
<Dimension value="[450, 460]"/>
</Property>
<Property name="resizable" type="boolean" value="false"/>
</Properties>
@ -32,7 +27,7 @@
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,-81,0,0,2,102"/>
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,2,118,0,0,1,-62"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
@ -40,13 +35,13 @@
<Container class="javax.swing.JPanel" name="jPanel1">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[450, 375]"/>
<Dimension value="[450, 460]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[450, 375]"/>
<Dimension value="[450, 460]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[450, 375]"/>
<Dimension value="[450, 460]"/>
</Property>
<Property name="requestFocusEnabled" type="boolean" value="false"/>
</Properties>
@ -59,55 +54,49 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="1" attributes="0">
<Component id="errorText" pref="300" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="65" max="-2" attributes="0"/>
<Component id="searchButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="cancelButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
<Component id="filler1" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="80" max="-2" attributes="0"/>
<Component id="filler2" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="errorText" alignment="0" max="32767" attributes="0"/>
</Group>
</Group>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="commonFilesSearchLabel2" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="intraCaseRadio" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="interCaseRadio" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="commonFilesSearchLabel1" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="categoriesLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="selectedFileCategoriesButton" alignment="0" min="-2" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="29" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="documentsCheckbox" min="-2" max="-2" attributes="0"/>
<Component id="pictureVideoCheckbox" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<Component id="allFileCategoriesRadioButton" alignment="0" min="-2" max="-2" attributes="0"/>
<Group type="102" alignment="1" attributes="0">
<EmptySpace min="-2" pref="10" max="-2" attributes="0"/>
<Component id="layoutPanel" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="scopeLabel" max="32767" attributes="0"/>
<EmptySpace min="-2" pref="37" max="-2" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<Component id="percentageThresholdCheck" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="percentageThresholdTextOne" min="-2" pref="40" max="-2" attributes="0"/>
<Component id="percentageThresholdInputBox" min="-2" pref="40" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="percentageThresholdTextTwo" min="-2" max="-2" attributes="0"/>
<Component id="percentageThresholdTextTwo" max="32767" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="103" groupAlignment="1" max="-2" attributes="0">
<Component id="commonItemSearchDescription" alignment="0" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="20" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Component id="intraCaseRadio" max="32767" attributes="0"/>
<Component id="interCaseRadio" pref="383" max="32767" attributes="0"/>
</Group>
</Group>
<EmptySpace pref="9" max="32767" attributes="0"/>
</Group>
<Group type="103" alignment="0" groupAlignment="1" max="-2" attributes="0">
<Component id="interCasePanel" alignment="0" max="32767" attributes="0"/>
<Component id="intraCasePanel" alignment="0" max="32767" attributes="0"/>
</Group>
</Group>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
<Group type="102" alignment="1" attributes="0">
<EmptySpace min="21" pref="21" max="-2" attributes="0"/>
<Component id="dataSourcesLabel" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</Group>
@ -117,53 +106,40 @@
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="commonFilesSearchLabel1" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="commonFilesSearchLabel2" min="-2" max="-2" attributes="0"/>
<Component id="commonItemSearchDescription" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="scopeLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="intraCaseRadio" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="interCaseRadio" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="2" max="-2" attributes="0"/>
<Component id="layoutPanel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="categoriesLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="selectedFileCategoriesButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="pictureVideoCheckbox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="documentsCheckbox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="allFileCategoriesRadioButton" min="-2" max="-2" attributes="0"/>
<Component id="interCasePanel" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
<Component id="intraCasePanel" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="percentageThresholdCheck" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="percentageThresholdTextOne" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="percentageThresholdInputBox" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="percentageThresholdTextTwo" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="103" groupAlignment="1" attributes="0">
<Component id="filler2" min="-2" max="-2" attributes="0"/>
<Component id="filler1" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="dataSourcesLabel" min="-2" pref="14" max="-2" attributes="0"/>
<EmptySpace type="separate" max="32767" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="searchButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="cancelButton" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="errorText" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JLabel" name="commonFilesSearchLabel2">
<Component class="javax.swing.JLabel" name="scopeLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="CommonAttributePanel.commonFilesSearchLabel2.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="CommonAttributePanel.scopeLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="focusable" type="boolean" value="false"/>
</Properties>
@ -180,83 +156,6 @@
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="searchButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JButton" name="cancelButton">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="CommonAttributePanel.cancelButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="actionCommand" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="CommonAttributePanel.cancelButton.actionCommand" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="horizontalTextPosition" type="int" value="10"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="cancelButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JRadioButton" name="allFileCategoriesRadioButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="fileTypeFilterButtonGroup"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="CommonAttributePanel.allFileCategoriesRadioButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="CommonAttributePanel.allFileCategoriesRadioButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="allFileCategoriesRadioButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JRadioButton" name="selectedFileCategoriesButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="fileTypeFilterButtonGroup"/>
</Property>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="CommonAttributePanel.selectedFileCategoriesButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="CommonAttributePanel.selectedFileCategoriesButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="selectedFileCategoriesButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JCheckBox" name="pictureVideoCheckbox">
<Properties>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="CommonAttributePanel.pictureVideoCheckbox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pictureVideoCheckboxActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JCheckBox" name="documentsCheckbox">
<Properties>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="CommonAttributePanel.documentsCheckbox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="documentsCheckboxActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JLabel" name="categoriesLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="CommonAttributePanel.categoriesLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="name" type="java.lang.String" value="" noResource="true"/>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="errorText">
<Properties>
<Property name="foreground" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
@ -268,10 +167,10 @@
<Property name="verticalAlignment" type="int" value="1"/>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="commonFilesSearchLabel1">
<Component class="javax.swing.JLabel" name="commonItemSearchDescription">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="CommonAttributePanel.commonFilesSearchLabel1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="CommonAttributePanel.commonItemSearchDescription.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="focusable" type="boolean" value="false"/>
</Properties>
@ -303,40 +202,21 @@
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="interCaseRadioActionPerformed"/>
</Events>
</Component>
<Container class="java.awt.Panel" name="layoutPanel">
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignCardLayout"/>
<SubComponents>
<Component class="org.sleuthkit.autopsy.commonfilesearch.IntraCasePanel" name="intraCasePanel">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignCardLayout" value="org.netbeans.modules.form.compat2.layouts.DesignCardLayout$CardConstraintsDescription">
<CardConstraints cardName="card3"/>
</Constraint>
</Constraints>
</Component>
<Component class="org.sleuthkit.autopsy.commonfilesearch.InterCasePanel" name="interCasePanel">
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignCardLayout" value="org.netbeans.modules.form.compat2.layouts.DesignCardLayout$CardConstraintsDescription">
<CardConstraints cardName="card2"/>
</Constraint>
</Constraints>
</Component>
</SubComponents>
</Container>
<Component class="javax.swing.JCheckBox" name="percentageThresholdCheck">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="CommonAttributePanel.percentageThresholdCheck.text_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="CommonAttributePanel.percentageThresholdCheck.text_1_1" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="percentageThresholdCheckActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JTextField" name="percentageThresholdTextOne">
<Component class="javax.swing.JTextField" name="percentageThresholdInputBox">
<Properties>
<Property name="horizontalAlignment" type="int" value="11"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="CommonAttributePanel.percentageThresholdTextOne.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="CommonAttributePanel.percentageThresholdInputBox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[40, 24]"/>
@ -356,25 +236,48 @@
</Property>
</Properties>
</Component>
<Component class="javax.swing.Box$Filler" name="filler1">
<Component class="org.sleuthkit.autopsy.commonfilesearch.IntraCasePanel" name="intraCasePanel">
<Properties>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[0, 32767]"/>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
<TitledBorder title="Current Case Options">
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="CommonAttributePanel.intraCasePanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32779, 192]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[204, 192]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[430, 192]"/>
</Property>
<Property name="verifyInputWhenFocusTarget" type="boolean" value="false"/>
</Properties>
<AuxValues>
<AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.VerticalGlue"/>
</AuxValues>
</Component>
<Component class="javax.swing.Box$Filler" name="filler2">
<Component class="org.sleuthkit.autopsy.commonfilesearch.InterCasePanel" name="interCasePanel">
<Properties>
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
<Border info="org.netbeans.modules.form.compat2.border.TitledBorderInfo">
<TitledBorder title="Central Repository Options">
<ResourceString PropertyName="titleX" bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="CommonAttributePanel.interCasePanel.border.title" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</TitledBorder>
</Border>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[32767, 32767]"/>
<Dimension value="[32779, 230]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[430, 230]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[430, 230]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="classDetails" type="java.lang.String" value="Box.Filler.Glue"/>
</AuxValues>
</Component>
<Component class="javax.swing.JLabel" name="dataSourcesLabel">
</Component>
</SubComponents>
</Container>

View File

@ -25,6 +25,9 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javax.swing.JFrame;
@ -38,6 +41,7 @@ import org.openide.util.NbBundle;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
@ -55,7 +59,7 @@ import org.sleuthkit.datamodel.TskCoreException;
* logic. Nested within CommonFilesDialog.
*/
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
public final class CommonAttributePanel extends javax.swing.JDialog {
final class CommonAttributePanel extends javax.swing.JDialog implements Observer {
private static final Logger LOGGER = Logger.getLogger(CommonAttributePanel.class.getName());
private static final long serialVersionUID = 1L;
@ -64,48 +68,42 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
private final UserInputErrorManager errorManager;
private boolean pictureViewCheckboxState;
private boolean documentsCheckboxState;
private int percentageThresholdValue = 20;
/**
* Creates new form CommonFilesPanel
*/
@NbBundle.Messages({
"CommonFilesPanel.title=Common Files Panel",
"CommonFilesPanel.exception=Unexpected Exception loading DataSources.",
"CommonFilesPanel.frame.title=Find Common Files",
"CommonFilesPanel.frame.msg=Find Common Files"})
public CommonAttributePanel() {
super(new JFrame(Bundle.CommonFilesPanel_frame_title()),
Bundle.CommonFilesPanel_frame_msg(), true);
"CommonAttributePanel.title=Common Property Panel",
"CommonAttributePanel.exception=Unexpected Exception loading DataSources.",
"CommonAttributePanel.frame.title=Find Common Properties",
"CommonAttributePanel.frame.msg=Find Common Properties",
"CommonAttributePanel.intraCasePanel.title=Curren Case Options"})
CommonAttributePanel() {
super(new JFrame(Bundle.CommonAttributePanel_frame_title()),
Bundle.CommonAttributePanel_frame_msg(), true);
initComponents();
this.setLocationRelativeTo(WindowManager.getDefault().getMainWindow());
this.setupDataSources();
intraCasePanel.setVisible(true);
interCasePanel.setVisible(false);
if (CommonAttributePanel.isEamDbAvailableForIntercaseSearch()) {
this.setupCases();
this.interCasePanel.setupCorrelationTypeFilter();
} else {
this.disableIntercaseSearch();
}
if(CommonAttributePanel.isEamDbAvailableForPercentageFrequencyCalculations()){
this.enablePercentageOptions();
} else {
this.disablePercentageOptions();
}
this.updatePercentageOptions(CommonAttributePanel.getNumberOfDataSourcesAvailable());
this.errorManager = new UserInputErrorManager();
this.percentageThresholdTextOne.getDocument().addDocumentListener(new DocumentListener(){
this.percentageThresholdInputBox.getDocument().addDocumentListener(new DocumentListener() {
private Dimension preferredSize = CommonAttributePanel.this.percentageThresholdTextOne.getPreferredSize();
private final Dimension preferredSize = CommonAttributePanel.this.percentageThresholdInputBox.getPreferredSize();
private void maintainSize() {
CommonAttributePanel.this.percentageThresholdTextOne.setSize(preferredSize);
CommonAttributePanel.this.percentageThresholdInputBox.setSize(preferredSize);
}
@Override
@ -128,7 +126,14 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
});
}
private static boolean isEamDbAvailableForIntercaseSearch() {
/**
* Get whether or not the central repository will be enabled as a search
* option.
*
* @return true if the central repository exists and has at least 2 cases in
* and includes the current case, false otherwise.
*/
static boolean isEamDbAvailableForIntercaseSearch() {
try {
return EamDb.isEnabled()
&& EamDb.getInstance() != null
@ -142,56 +147,61 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
return false;
}
private static boolean isEamDbAvailableForPercentageFrequencyCalculations(){
@Override
public void update(Observable o, Object arg) {
checkFileTypeCheckBoxState();
}
/**
* Get the number of data sources in the central repository if it is
* enabled, zero if it is not enabled.
*
* @return the number of data sources in the current central repo, or 0 if
* it is disabled
*/
private static Long getNumberOfDataSourcesAvailable() {
try {
return EamDb.isEnabled()
&& EamDb.getInstance() != null
&& EamDb.getInstance().getCases().size() > 0;
if (EamDb.isEnabled()
&& EamDb.getInstance() != null) {
return EamDb.getInstance().getCountUniqueDataSources();
}
} catch (EamDbException ex) {
LOGGER.log(Level.SEVERE, "Unexpected exception while checking for EamDB enabled.", ex);
}
return false;
return 0L;
}
/**
* Disable the option to search for common attributes in the central
* repository.
*/
private void disableIntercaseSearch() {
this.intraCaseRadio.setSelected(true);
this.interCaseRadio.setEnabled(false);
}
/**
* Perform the common attribute search.
*/
@NbBundle.Messages({
"CommonFilesPanel.search.results.titleAll=Common Files (All Data Sources)",
"CommonFilesPanel.search.results.titleSingle=Common Files (Match Within Data Source: %s)",
"CommonFilesPanel.search.results.pathText=Common Files Search Results",
"CommonFilesPanel.search.done.searchProgressGathering=Gathering Common Files Search Results.",
"CommonFilesPanel.search.done.searchProgressDisplay=Displaying Common Files Search Results.",
"CommonFilesPanel.search.done.tskCoreException=Unable to run query against DB.",
"CommonFilesPanel.search.done.noCurrentCaseException=Unable to open case file.",
"CommonFilesPanel.search.done.exception=Unexpected exception running Common Files Search.",
"CommonFilesPanel.search.done.interupted=Something went wrong finding common files.",
"CommonFilesPanel.search.done.sqlException=Unable to query db for files or data sources."})
"CommonAttributePanel.search.results.pathText=Common Property Search Results",
"CommonAttributePanel.search.done.searchProgressGathering=Gathering Common Property Search Results.",
"CommonAttributePanel.search.done.searchProgressDisplay=Displaying Common Property Search Results.",
"CommonAttributePanel.search.done.tskCoreException=Unable to run query against DB.",
"CommonAttributePanel.search.done.noCurrentCaseException=Unable to open case file.",
"CommonAttributePanel.search.done.exception=Unexpected exception running Common Property Search.",
"CommonAttributePanel.search.done.interupted=Something went wrong finding common properties.",
"CommonAttributePanel.search.done.sqlException=Unable to query db for properties or data sources."})
private void search() {
String pathText = Bundle.CommonFilesPanel_search_results_pathText();
new SwingWorker<CommonAttributeSearchResults, Void>() {
private String tabTitle;
private ProgressHandle progress;
private void setTitleForAllDataSources() {
this.tabTitle = Bundle.CommonFilesPanel_search_results_titleAll();
}
private void setTitleForSingleSource(Long dataSourceId) {
final String CommonFilesPanel_search_results_titleSingle = Bundle.CommonFilesPanel_search_results_titleSingle();
final Object[] dataSourceName = new Object[]{intraCasePanel.getDataSourceMap().get(dataSourceId)};
this.tabTitle = String.format(CommonFilesPanel_search_results_titleSingle, dataSourceName);
}
@Override
@SuppressWarnings({"BoxedValueEquality", "NumberEquality"})
protected CommonAttributeSearchResults doInBackground() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException {
progress = ProgressHandle.createHandle(Bundle.CommonFilesPanel_search_done_searchProgressGathering());
progress = ProgressHandle.createHandle(Bundle.CommonAttributePanel_search_done_searchProgressGathering());
progress.start();
progress.switchToIndeterminate();
@ -203,14 +213,6 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
boolean filterByMedia = false;
boolean filterByDocuments = false;
if (selectedFileCategoriesButton.isSelected()) {
if (pictureVideoCheckbox.isSelected()) {
filterByMedia = true;
}
if (documentsCheckbox.isSelected()) {
filterByDocuments = true;
}
}
int percentageThreshold = CommonAttributePanel.this.percentageThresholdValue;
@ -220,25 +222,35 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
}
if (CommonAttributePanel.this.interCaseRadio.isSelected()) {
if (caseId == InterCasePanel.NO_CASE_SELECTED) {
builder = new AllInterCaseCommonAttributeSearcher(intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments, percentageThreshold);
} else {
builder = new SingleInterCaseCommonAttributeSearcher(caseId, intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments, percentageThreshold);
CorrelationAttributeInstance.Type corType = interCasePanel.getSelectedCorrelationType();
if (interCasePanel.fileCategoriesButtonIsSelected()) {
filterByMedia = interCasePanel.pictureVideoCheckboxIsSelected();
filterByDocuments = interCasePanel.documentsCheckboxIsSelected();
}
if (corType == null) {
corType = CorrelationAttributeInstance.getDefaultCorrelationTypes().get(0);
}
if (caseId == InterCasePanel.NO_CASE_SELECTED) {
builder = new AllInterCaseCommonAttributeSearcher(intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments, corType, percentageThreshold);
} else {
if (dataSourceId == CommonAttributePanel.NO_DATA_SOURCE_SELECTED) {
builder = new AllIntraCaseCommonAttributeSearcher(intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments, percentageThreshold);
setTitleForAllDataSources();
builder = new SingleInterCaseCommonAttributeSearcher(caseId, intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments, corType, percentageThreshold);
}
} else {
if (intraCasePanel.fileCategoriesButtonIsSelected()) {
filterByMedia = intraCasePanel.pictureVideoCheckboxIsSelected();
filterByDocuments = intraCasePanel.documentsCheckboxIsSelected();
}
if (Objects.equals(dataSourceId, CommonAttributePanel.NO_DATA_SOURCE_SELECTED)) {
builder = new AllIntraCaseCommonAttributeSearcher(intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments, percentageThreshold);
} else {
builder = new SingleIntraCaseCommonAttributeSearcher(dataSourceId, intraCasePanel.getDataSourceMap(), filterByMedia, filterByDocuments, percentageThreshold);
}
setTitleForSingleSource(dataSourceId);
}
}
metadata = builder.findFiles();
this.tabTitle = builder.buildTabTitle();
metadata = builder.findMatches();
this.tabTitle = builder.getTabTitle();
return metadata;
}
@ -262,28 +274,28 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
Collection<DataResultViewer> viewers = new ArrayList<>(1);
viewers.add(table);
progress.setDisplayName(Bundle.CommonFilesPanel_search_done_searchProgressDisplay());
DataResultTopComponent.createInstance(tabTitle, pathText, tableFilterWithDescendantsNode, metadata.size(), viewers);
progress.setDisplayName(Bundle.CommonAttributePanel_search_done_searchProgressDisplay());
DataResultTopComponent.createInstance(tabTitle, Bundle.CommonAttributePanel_search_results_pathText(), tableFilterWithDescendantsNode, metadata.size(), viewers);
progress.finish();
} catch (InterruptedException ex) {
LOGGER.log(Level.SEVERE, "Interrupted while loading Common Files", ex);
MessageNotifyUtil.Message.error(Bundle.CommonFilesPanel_search_done_interupted());
MessageNotifyUtil.Message.error(Bundle.CommonAttributePanel_search_done_interupted());
} catch (ExecutionException ex) {
String errorMessage;
Throwable inner = ex.getCause();
if (inner instanceof TskCoreException) {
LOGGER.log(Level.SEVERE, "Failed to load files from database.", ex);
errorMessage = Bundle.CommonFilesPanel_search_done_tskCoreException();
errorMessage = Bundle.CommonAttributePanel_search_done_tskCoreException();
} else if (inner instanceof NoCurrentCaseException) {
LOGGER.log(Level.SEVERE, "Current case has been closed.", ex);
errorMessage = Bundle.CommonFilesPanel_search_done_noCurrentCaseException();
errorMessage = Bundle.CommonAttributePanel_search_done_noCurrentCaseException();
} else if (inner instanceof SQLException) {
LOGGER.log(Level.SEVERE, "Unable to query db for files.", ex);
errorMessage = Bundle.CommonFilesPanel_search_done_sqlException();
errorMessage = Bundle.CommonAttributePanel_search_done_sqlException();
} else {
LOGGER.log(Level.SEVERE, "Unexpected exception while running Common Files Search.", ex);
errorMessage = Bundle.CommonFilesPanel_search_done_exception();
errorMessage = Bundle.CommonAttributePanel_search_done_exception();
}
MessageNotifyUtil.Message.error(errorMessage);
}
@ -299,16 +311,20 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
* names
*/
@NbBundle.Messages({
"CommonFilesPanel.setupDataSources.done.tskCoreException=Unable to run query against DB.",
"CommonFilesPanel.setupDataSources.done.noCurrentCaseException=Unable to open case file.",
"CommonFilesPanel.setupDataSources.done.exception=Unexpected exception loading data sources.",
"CommonFilesPanel.setupDataSources.done.interupted=Something went wrong building the Common Files Search dialog box.",
"CommonFilesPanel.setupDataSources.done.sqlException=Unable to query db for data sources.",
"CommonFilesPanel.setupDataSources.updateUi.noDataSources=No data sources were found."})
"CommonAttributePanel.setupDataSources.done.tskCoreException=Unable to run query against DB.",
"CommonAttributePanel.setupDataSources.done.noCurrentCaseException=Unable to open case file.",
"CommonAttributePanel.setupDataSources.done.exception=Unexpected exception loading data sources.",
"CommonAttributePanel.setupDataSources.done.interupted=Something went wrong building the Common Files Search dialog box.",
"CommonAttributePanel.setupDataSources.done.sqlException=Unable to query db for data sources.",
"CommonAttributePanel.setupDataSources.updateUi.noDataSources=No data sources were found."})
private void setupDataSources() {
new SwingWorker<Map<Long, String>, Void>() {
/**
* Update the user interface of the panel to reflect the datasources
* available.
*/
private void updateUi() {
final Map<Long, String> dataSourceMap = CommonAttributePanel.this.intraCasePanel.getDataSourceMap();
@ -318,17 +334,26 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
//only enable all this stuff if we actually have datasources
if (dataSourcesNames.length > 0) {
dataSourcesNames = dataSourceMap.values().toArray(dataSourcesNames);
CommonAttributePanel.this.intraCasePanel.setDataModel(new DataSourceComboBoxModel(dataSourcesNames));
CommonAttributePanel.this.intraCasePanel.setDatasourceComboboxModel(new DataSourceComboBoxModel(dataSourcesNames));
boolean multipleDataSources = this.caseHasMultipleSources();
CommonAttributePanel.this.intraCasePanel.rigForMultipleDataSources(multipleDataSources);
CommonAttributePanel.this.updateErrorTextAndSearchBox();
if (!this.caseHasMultipleSources()) { //disable intra case search when only 1 data source in current case
intraCaseRadio.setEnabled(false);
interCaseRadio.setSelected(true);
intraCasePanel.setVisible(false);
interCasePanel.setVisible(true);
}
CommonAttributePanel.this.updateErrorTextAndSearchButton();
}
}
/**
* Check if the case has multiple data sources
*
* @return true if the case has multiple data sources, false
* otherwise
*/
private boolean caseHasMultipleSources() {
return CommonAttributePanel.this.intraCasePanel.getDataSourceMap().size() > 2;
return CommonAttributePanel.this.intraCasePanel.getDataSourceMap().size() > 1;
}
@Override
@ -346,22 +371,22 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
} catch (InterruptedException ex) {
LOGGER.log(Level.SEVERE, "Interrupted while building Common Files Search dialog.", ex);
MessageNotifyUtil.Message.error(Bundle.CommonFilesPanel_setupDataSources_done_interupted());
MessageNotifyUtil.Message.error(Bundle.CommonAttributePanel_setupDataSources_done_interupted());
} catch (ExecutionException ex) {
String errorMessage;
Throwable inner = ex.getCause();
if (inner instanceof TskCoreException) {
LOGGER.log(Level.SEVERE, "Failed to load data sources from database.", ex);
errorMessage = Bundle.CommonFilesPanel_setupDataSources_done_tskCoreException();
errorMessage = Bundle.CommonAttributePanel_setupDataSources_done_tskCoreException();
} else if (inner instanceof NoCurrentCaseException) {
LOGGER.log(Level.SEVERE, "Current case has been closed.", ex);
errorMessage = Bundle.CommonFilesPanel_setupDataSources_done_noCurrentCaseException();
errorMessage = Bundle.CommonAttributePanel_setupDataSources_done_noCurrentCaseException();
} else if (inner instanceof SQLException) {
LOGGER.log(Level.SEVERE, "Unable to query db for data sources.", ex);
errorMessage = Bundle.CommonFilesPanel_setupDataSources_done_sqlException();
errorMessage = Bundle.CommonAttributePanel_setupDataSources_done_sqlException();
} else {
LOGGER.log(Level.SEVERE, "Unexpected exception while building Common Files Search dialog panel.", ex);
errorMessage = Bundle.CommonFilesPanel_setupDataSources_done_exception();
errorMessage = Bundle.CommonAttributePanel_setupDataSources_done_exception();
}
MessageNotifyUtil.Message.error(errorMessage);
}
@ -370,12 +395,16 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
}
@NbBundle.Messages({
"CommonFilesPanel.setupCases.done.interruptedException=Something went wrong building the Common Files Search dialog box.",
"CommonFilesPanel.setupCases.done.exeutionException=Unexpected exception loading cases."})
"CommonAttributePanel.setupCases.done.interruptedException=Something went wrong building the Common Files Search dialog box.",
"CommonAttributePanel.setupCases.done.exeutionException=Unexpected exception loading cases."})
private void setupCases() {
new SwingWorker<Map<Integer, String>, Void>() {
/**
* Update the user interface of the panel to reflect the cases
* available.
*/
private void updateUi() {
final Map<Integer, String> caseMap = CommonAttributePanel.this.interCasePanel.getCaseMap();
@ -384,17 +413,22 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
if (caseNames.length > 0) {
caseNames = caseMap.values().toArray(caseNames);
CommonAttributePanel.this.interCasePanel.setCaseList(new DataSourceComboBoxModel(caseNames));
boolean multipleCases = this.centralRepoHasMultipleCases();
CommonAttributePanel.this.interCasePanel.rigForMultipleCases(multipleCases);
CommonAttributePanel.this.interCasePanel.setCaseComboboxModel(new DataSourceComboBoxModel(caseNames));
} else {
CommonAttributePanel.this.disableIntercaseSearch();
}
}
private Map<Integer, String> mapDataSources(List<CorrelationCase> cases) throws EamDbException {
/**
* Create a map of cases from a list of cases.
*
* @param cases
*
* @return a map of Cases
*
* @throws EamDbException
*/
private Map<Integer, String> mapCases(List<CorrelationCase> cases) throws EamDbException {
Map<Integer, String> casemap = new HashMap<>();
CorrelationCase currentCorCase = EamDb.getInstance().getCase(Case.getCurrentCase());
for (CorrelationCase correlationCase : cases) {
@ -402,7 +436,6 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
casemap.put(correlationCase.getID(), correlationCase.getDisplayName());
}
}
return casemap;
}
@ -410,7 +443,7 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
protected Map<Integer, String> doInBackground() throws EamDbException {
List<CorrelationCase> dataSources = EamDb.getInstance().getCases();
Map<Integer, String> caseMap = mapDataSources(dataSources);
Map<Integer, String> caseMap = mapCases(dataSources);
return caseMap;
}
@ -423,17 +456,13 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
this.updateUi();
} catch (InterruptedException ex) {
LOGGER.log(Level.SEVERE, "Interrupted while building Common Files Search dialog.", ex);
MessageNotifyUtil.Message.error(Bundle.CommonFilesPanel_setupCases_done_interruptedException());
MessageNotifyUtil.Message.error(Bundle.CommonAttributePanel_setupCases_done_interruptedException());
} catch (ExecutionException ex) {
LOGGER.log(Level.SEVERE, "Unexpected exception while building Common Files Search dialog.", ex);
MessageNotifyUtil.Message.error(Bundle.CommonFilesPanel_setupCases_done_exeutionException());
MessageNotifyUtil.Message.error(Bundle.CommonAttributePanel_setupCases_done_exeutionException());
}
}
private boolean centralRepoHasMultipleCases() {
return CommonAttributePanel.this.interCasePanel.centralRepoHasMultipleCases();
}
}.execute();
}
@ -446,32 +475,22 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
fileTypeFilterButtonGroup = new javax.swing.ButtonGroup();
interIntraButtonGroup = new javax.swing.ButtonGroup();
jPanel1 = new javax.swing.JPanel();
commonFilesSearchLabel2 = new javax.swing.JLabel();
scopeLabel = new javax.swing.JLabel();
searchButton = new javax.swing.JButton();
cancelButton = new javax.swing.JButton();
allFileCategoriesRadioButton = new javax.swing.JRadioButton();
selectedFileCategoriesButton = new javax.swing.JRadioButton();
pictureVideoCheckbox = new javax.swing.JCheckBox();
documentsCheckbox = new javax.swing.JCheckBox();
categoriesLabel = new javax.swing.JLabel();
errorText = new javax.swing.JLabel();
commonFilesSearchLabel1 = new javax.swing.JLabel();
commonItemSearchDescription = new javax.swing.JLabel();
intraCaseRadio = new javax.swing.JRadioButton();
interCaseRadio = new javax.swing.JRadioButton();
layoutPanel = new java.awt.Panel();
percentageThresholdCheck = new javax.swing.JCheckBox();
percentageThresholdInputBox = new javax.swing.JTextField();
percentageThresholdTextTwo = new javax.swing.JLabel();
intraCasePanel = new org.sleuthkit.autopsy.commonfilesearch.IntraCasePanel();
interCasePanel = new org.sleuthkit.autopsy.commonfilesearch.InterCasePanel();
percentageThresholdCheck = new javax.swing.JCheckBox();
percentageThresholdTextOne = new javax.swing.JTextField();
percentageThresholdTextTwo = new javax.swing.JLabel();
filler1 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 32767));
filler2 = new javax.swing.Box.Filler(new java.awt.Dimension(0, 0), new java.awt.Dimension(0, 0), new java.awt.Dimension(32767, 32767));
dataSourcesLabel = new javax.swing.JLabel();
setMaximumSize(new java.awt.Dimension(450, 375));
setMinimumSize(new java.awt.Dimension(450, 375));
setMinimumSize(new java.awt.Dimension(450, 460));
setResizable(false);
addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosed(java.awt.event.WindowEvent evt) {
@ -479,13 +498,13 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
}
});
jPanel1.setMaximumSize(new java.awt.Dimension(450, 375));
jPanel1.setMinimumSize(new java.awt.Dimension(450, 375));
jPanel1.setPreferredSize(new java.awt.Dimension(450, 375));
jPanel1.setMaximumSize(new java.awt.Dimension(450, 460));
jPanel1.setMinimumSize(new java.awt.Dimension(450, 460));
jPanel1.setPreferredSize(new java.awt.Dimension(450, 460));
jPanel1.setRequestFocusEnabled(false);
org.openide.awt.Mnemonics.setLocalizedText(commonFilesSearchLabel2, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.commonFilesSearchLabel2.text")); // NOI18N
commonFilesSearchLabel2.setFocusable(false);
org.openide.awt.Mnemonics.setLocalizedText(scopeLabel, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.scopeLabel.text")); // NOI18N
scopeLabel.setFocusable(false);
org.openide.awt.Mnemonics.setLocalizedText(searchButton, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.searchButton.text")); // NOI18N
searchButton.setEnabled(false);
@ -496,59 +515,12 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
}
});
org.openide.awt.Mnemonics.setLocalizedText(cancelButton, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.cancelButton.text")); // NOI18N
cancelButton.setActionCommand(org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.cancelButton.actionCommand")); // NOI18N
cancelButton.setHorizontalTextPosition(javax.swing.SwingConstants.LEADING);
cancelButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
cancelButtonActionPerformed(evt);
}
});
fileTypeFilterButtonGroup.add(allFileCategoriesRadioButton);
org.openide.awt.Mnemonics.setLocalizedText(allFileCategoriesRadioButton, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.allFileCategoriesRadioButton.text")); // NOI18N
allFileCategoriesRadioButton.setToolTipText(org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.allFileCategoriesRadioButton.toolTipText")); // NOI18N
allFileCategoriesRadioButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
allFileCategoriesRadioButtonActionPerformed(evt);
}
});
fileTypeFilterButtonGroup.add(selectedFileCategoriesButton);
selectedFileCategoriesButton.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(selectedFileCategoriesButton, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.selectedFileCategoriesButton.text")); // NOI18N
selectedFileCategoriesButton.setToolTipText(org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.selectedFileCategoriesButton.toolTipText")); // NOI18N
selectedFileCategoriesButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
selectedFileCategoriesButtonActionPerformed(evt);
}
});
pictureVideoCheckbox.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(pictureVideoCheckbox, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.pictureVideoCheckbox.text")); // NOI18N
pictureVideoCheckbox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
pictureVideoCheckboxActionPerformed(evt);
}
});
documentsCheckbox.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(documentsCheckbox, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.documentsCheckbox.text")); // NOI18N
documentsCheckbox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
documentsCheckboxActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(categoriesLabel, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.categoriesLabel.text")); // NOI18N
categoriesLabel.setName(""); // NOI18N
errorText.setForeground(new java.awt.Color(255, 0, 0));
org.openide.awt.Mnemonics.setLocalizedText(errorText, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.errorText.text")); // NOI18N
errorText.setVerticalAlignment(javax.swing.SwingConstants.TOP);
org.openide.awt.Mnemonics.setLocalizedText(commonFilesSearchLabel1, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.commonFilesSearchLabel1.text")); // NOI18N
commonFilesSearchLabel1.setFocusable(false);
org.openide.awt.Mnemonics.setLocalizedText(commonItemSearchDescription, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.commonItemSearchDescription.text")); // NOI18N
commonItemSearchDescription.setFocusable(false);
interIntraButtonGroup.add(intraCaseRadio);
intraCaseRadio.setSelected(true);
@ -567,24 +539,32 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
}
});
layoutPanel.setLayout(new java.awt.CardLayout());
layoutPanel.add(intraCasePanel, "card3");
layoutPanel.add(interCasePanel, "card2");
org.openide.awt.Mnemonics.setLocalizedText(percentageThresholdCheck, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.percentageThresholdCheck.text_1")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(percentageThresholdCheck, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.percentageThresholdCheck.text_1_1")); // NOI18N
percentageThresholdCheck.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
percentageThresholdCheckActionPerformed(evt);
}
});
percentageThresholdTextOne.setText(org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.percentageThresholdTextOne.text")); // NOI18N
percentageThresholdTextOne.setMaximumSize(new java.awt.Dimension(40, 24));
percentageThresholdTextOne.setMinimumSize(new java.awt.Dimension(40, 24));
percentageThresholdTextOne.setPreferredSize(new java.awt.Dimension(40, 24));
percentageThresholdInputBox.setHorizontalAlignment(javax.swing.JTextField.TRAILING);
percentageThresholdInputBox.setText(org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.percentageThresholdInputBox.text")); // NOI18N
percentageThresholdInputBox.setMaximumSize(new java.awt.Dimension(40, 24));
percentageThresholdInputBox.setMinimumSize(new java.awt.Dimension(40, 24));
percentageThresholdInputBox.setPreferredSize(new java.awt.Dimension(40, 24));
org.openide.awt.Mnemonics.setLocalizedText(percentageThresholdTextTwo, org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.percentageThresholdTextTwo.text_1")); // NOI18N
intraCasePanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.intraCasePanel.border.title"))); // NOI18N
intraCasePanel.setMaximumSize(new java.awt.Dimension(32779, 192));
intraCasePanel.setMinimumSize(new java.awt.Dimension(204, 192));
intraCasePanel.setPreferredSize(new java.awt.Dimension(430, 192));
intraCasePanel.setVerifyInputWhenFocusTarget(false);
interCasePanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(CommonAttributePanel.class, "CommonAttributePanel.interCasePanel.border.title"))); // NOI18N
interCasePanel.setMaximumSize(new java.awt.Dimension(32779, 230));
interCasePanel.setMinimumSize(new java.awt.Dimension(430, 230));
interCasePanel.setPreferredSize(new java.awt.Dimension(430, 230));
javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
jPanel1.setLayout(jPanel1Layout);
jPanel1Layout.setHorizontalGroup(
@ -593,80 +573,63 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
.addContainerGap()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
.addComponent(errorText, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE)
.addGap(65, 65, 65)
.addComponent(searchButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(cancelButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addContainerGap())
.addGroup(jPanel1Layout.createSequentialGroup()
.addGap(0, 0, Short.MAX_VALUE)
.addComponent(filler1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(80, 80, 80)
.addComponent(filler2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(errorText)))
.addGroup(jPanel1Layout.createSequentialGroup()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(commonFilesSearchLabel2)
.addComponent(intraCaseRadio)
.addComponent(interCaseRadio)
.addComponent(commonFilesSearchLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(categoriesLabel)
.addComponent(selectedFileCategoriesButton)
.addGroup(jPanel1Layout.createSequentialGroup()
.addGap(29, 29, 29)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(documentsCheckbox)
.addComponent(pictureVideoCheckbox)))
.addComponent(allFileCategoriesRadioButton)
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
.addGap(10, 10, 10)
.addComponent(layoutPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addComponent(scopeLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGap(37, 37, 37))
.addGroup(jPanel1Layout.createSequentialGroup()
.addComponent(percentageThresholdCheck)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(percentageThresholdTextOne, javax.swing.GroupLayout.PREFERRED_SIZE, 40, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(percentageThresholdInputBox, javax.swing.GroupLayout.PREFERRED_SIZE, 40, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(percentageThresholdTextTwo)))
.addContainerGap(9, Short.MAX_VALUE))))
.addComponent(percentageThresholdTextTwo, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(jPanel1Layout.createSequentialGroup()
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
.addComponent(commonItemSearchDescription, javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, jPanel1Layout.createSequentialGroup()
.addGap(20, 20, 20)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addComponent(intraCaseRadio, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(interCaseRadio, javax.swing.GroupLayout.DEFAULT_SIZE, 383, Short.MAX_VALUE))))
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
.addComponent(interCasePanel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(intraCasePanel, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
.addGap(0, 0, Short.MAX_VALUE))
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
.addGap(21, 21, 21)
.addComponent(dataSourcesLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addContainerGap())))
);
jPanel1Layout.setVerticalGroup(
jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createSequentialGroup()
.addContainerGap()
.addComponent(commonFilesSearchLabel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(commonFilesSearchLabel2)
.addComponent(commonItemSearchDescription, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(scopeLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(intraCaseRadio)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(interCaseRadio)
.addGap(2, 2, 2)
.addComponent(layoutPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(categoriesLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(selectedFileCategoriesButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(pictureVideoCheckbox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(documentsCheckbox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(allFileCategoriesRadioButton)
.addComponent(interCasePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(0, 0, 0)
.addComponent(intraCasePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(percentageThresholdCheck)
.addComponent(percentageThresholdTextOne, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(percentageThresholdInputBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(percentageThresholdTextTwo))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(filler2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(filler1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(dataSourcesLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 14, javax.swing.GroupLayout.PREFERRED_SIZE)
.addGap(18, 18, Short.MAX_VALUE)
.addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(searchButton)
.addComponent(cancelButton)
.addComponent(errorText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addComponent(errorText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap())
);
@ -679,62 +642,51 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
private void percentageThresholdCheckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_percentageThresholdCheckActionPerformed
if (this.percentageThresholdCheck.isSelected()) {
this.percentageThresholdTextOne.setEnabled(true);
this.percentageThresholdInputBox.setEnabled(true);
} else {
this.percentageThresholdTextOne.setEnabled(false);
this.percentageThresholdInputBox.setEnabled(false);
}
this.handleFrequencyPercentageState();
}//GEN-LAST:event_percentageThresholdCheckActionPerformed
private void interCaseRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_interCaseRadioActionPerformed
((java.awt.CardLayout) this.layoutPanel.getLayout()).last(this.layoutPanel);
intraCasePanel.setVisible(false);
interCasePanel.setVisible(true);
}//GEN-LAST:event_interCaseRadioActionPerformed
private void intraCaseRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_intraCaseRadioActionPerformed
((java.awt.CardLayout) this.layoutPanel.getLayout()).first(this.layoutPanel);
intraCasePanel.setVisible(true);
interCasePanel.setVisible(false);
}//GEN-LAST:event_intraCaseRadioActionPerformed
private void documentsCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_documentsCheckboxActionPerformed
this.handleFileTypeCheckBoxState();
}//GEN-LAST:event_documentsCheckboxActionPerformed
private void pictureVideoCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pictureVideoCheckboxActionPerformed
this.handleFileTypeCheckBoxState();
}//GEN-LAST:event_pictureVideoCheckboxActionPerformed
private void selectedFileCategoriesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectedFileCategoriesButtonActionPerformed
this.handleFileTypeCheckBoxState();
}//GEN-LAST:event_selectedFileCategoriesButtonActionPerformed
private void allFileCategoriesRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allFileCategoriesRadioButtonActionPerformed
this.handleFileTypeCheckBoxState();
}//GEN-LAST:event_allFileCategoriesRadioButtonActionPerformed
private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed
SwingUtilities.windowForComponent(this).dispose();
}//GEN-LAST:event_cancelButtonActionPerformed
private void searchButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchButtonActionPerformed
search();
SwingUtilities.windowForComponent(this).dispose();
}//GEN-LAST:event_searchButtonActionPerformed
/**
* Convert the text in the percentage threshold input box into an integer,
* -1 is used when the string can not be converted to an integer.
*/
private void percentageThresholdChanged() {
String percentageString = this.percentageThresholdTextOne.getText();
String percentageString = this.percentageThresholdInputBox.getText();
try {
this.percentageThresholdValue = Integer.parseInt(percentageString);
} catch (NumberFormatException exception) {
} catch (NumberFormatException ignored) {
this.percentageThresholdValue = -1;
}
this.handleFrequencyPercentageState();
}
private void updateErrorTextAndSearchBox() {
/**
* Update the error text and the enabled status of the search button to
* reflect the current validity of the search settings.
*/
private void updateErrorTextAndSearchButton() {
if (this.errorManager.anyErrors()) {
this.searchButton.setEnabled(false);
//grab the first error error and show it
@ -746,83 +698,90 @@ public final class CommonAttributePanel extends javax.swing.JDialog {
}
}
private void enablePercentageOptions() {
this.percentageThresholdTextOne.setEnabled(true);
this.percentageThresholdCheck.setEnabled(true);
this.percentageThresholdCheck.setSelected(true);
this.percentageThresholdTextTwo.setEnabled(true);
}
private void disablePercentageOptions() {
this.percentageThresholdTextOne.setEnabled(false);
this.percentageThresholdCheck.setEnabled(false);
this.percentageThresholdCheck.setSelected(false);
this.percentageThresholdTextTwo.setEnabled(false);
}
private void handleFileTypeCheckBoxState() {
this.pictureViewCheckboxState = this.pictureVideoCheckbox.isSelected();
this.documentsCheckboxState = this.documentsCheckbox.isSelected();
if (this.allFileCategoriesRadioButton.isSelected()) {
this.pictureVideoCheckbox.setEnabled(false);
this.documentsCheckbox.setEnabled(false);
this.errorManager.setError(UserInputErrorManager.NO_FILE_CATEGORIES_SELECTED_KEY, false);
}
if (this.selectedFileCategoriesButton.isSelected()) {
this.pictureVideoCheckbox.setSelected(this.pictureViewCheckboxState);
this.documentsCheckbox.setSelected(this.documentsCheckboxState);
this.pictureVideoCheckbox.setEnabled(true);
this.documentsCheckbox.setEnabled(true);
if (!this.pictureVideoCheckbox.isSelected() && !this.documentsCheckbox.isSelected() && !this.allFileCategoriesRadioButton.isSelected()) {
this.errorManager.setError(UserInputErrorManager.NO_FILE_CATEGORIES_SELECTED_KEY, true);
} else {
this.errorManager.setError(UserInputErrorManager.NO_FILE_CATEGORIES_SELECTED_KEY, false);
}
}
this.updateErrorTextAndSearchBox();
/**
* Update the percentage options to reflect the number of data sources
* available.
*
* @param numberOfDataSources the number of data sources available in the
* central repository
*/
@NbBundle.Messages({
"# {0} - number of datasources",
"CommonAttributePanel.dataSourcesLabel.text=The current Central Repository contains {0} data source(s)."})
private void updatePercentageOptions(Long numberOfDataSources) {
boolean enabled = numberOfDataSources > 0L;
String numberOfDataSourcesText = enabled ? Bundle.CommonAttributePanel_dataSourcesLabel_text(numberOfDataSources) : "";
this.dataSourcesLabel.setText(numberOfDataSourcesText);
this.percentageThresholdInputBox.setEnabled(enabled);
this.percentageThresholdCheck.setEnabled(enabled);
this.percentageThresholdCheck.setSelected(enabled);
this.percentageThresholdTextTwo.setEnabled(enabled);
}
/**
* Check that the integer value of what is entered in the percentage
* threshold text box is a valid percentage and update the errorManager to
* reflect the validity.
*/
private void handleFrequencyPercentageState() {
if (this.percentageThresholdValue > 0 && this.percentageThresholdValue <= 100) {
this.errorManager.setError(UserInputErrorManager.FREQUENCY_PERCENTAGE_OUT_OF_RANGE_KEY, false);
} else {
this.errorManager.setError(UserInputErrorManager.FREQUENCY_PERCENTAGE_OUT_OF_RANGE_KEY, true);
}
this.updateErrorTextAndSearchBox();
this.updateErrorTextAndSearchButton();
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JRadioButton allFileCategoriesRadioButton;
private javax.swing.JButton cancelButton;
private javax.swing.JLabel categoriesLabel;
private javax.swing.JLabel commonFilesSearchLabel1;
private javax.swing.JLabel commonFilesSearchLabel2;
private javax.swing.JCheckBox documentsCheckbox;
private javax.swing.JLabel commonItemSearchDescription;
private javax.swing.JLabel dataSourcesLabel;
private javax.swing.JLabel errorText;
private javax.swing.ButtonGroup fileTypeFilterButtonGroup;
private javax.swing.Box.Filler filler1;
private javax.swing.Box.Filler filler2;
private org.sleuthkit.autopsy.commonfilesearch.InterCasePanel interCasePanel;
private javax.swing.JRadioButton interCaseRadio;
private javax.swing.ButtonGroup interIntraButtonGroup;
private org.sleuthkit.autopsy.commonfilesearch.IntraCasePanel intraCasePanel;
private javax.swing.JRadioButton intraCaseRadio;
private javax.swing.JPanel jPanel1;
private java.awt.Panel layoutPanel;
private javax.swing.JCheckBox percentageThresholdCheck;
private javax.swing.JTextField percentageThresholdTextOne;
private javax.swing.JTextField percentageThresholdInputBox;
private javax.swing.JLabel percentageThresholdTextTwo;
private javax.swing.JCheckBox pictureVideoCheckbox;
private javax.swing.JLabel scopeLabel;
private javax.swing.JButton searchButton;
private javax.swing.JRadioButton selectedFileCategoriesButton;
// End of variables declaration//GEN-END:variables
/**
* Add this panel as an observer of it's sub panels so that errors can be
* indicated accurately.
*/
void observeSubPanels() {
intraCasePanel.addObserver(this);
interCasePanel.addObserver(this);
}
/**
* Check that the sub panels have valid options selected regarding their
* file type filtering options, and update the errorManager with the
* validity.
*/
private void checkFileTypeCheckBoxState() {
boolean validCheckBoxState = true;
if (CommonAttributePanel.this.interCaseRadio.isSelected()) {
if (interCasePanel.fileCategoriesButtonIsSelected()) {
validCheckBoxState = interCasePanel.pictureVideoCheckboxIsSelected() || interCasePanel.documentsCheckboxIsSelected();
}
} else {
if (intraCasePanel.fileCategoriesButtonIsSelected()) {
validCheckBoxState = intraCasePanel.pictureVideoCheckboxIsSelected() || intraCasePanel.documentsCheckboxIsSelected();
}
}
if (validCheckBoxState) {
this.errorManager.setError(UserInputErrorManager.NO_FILE_CATEGORIES_SELECTED_KEY, false);
} else {
this.errorManager.setError(UserInputErrorManager.NO_FILE_CATEGORIES_SELECTED_KEY, true);
}
this.updateErrorTextAndSearchButton();
}
}

View File

@ -24,22 +24,36 @@ import org.openide.util.HelpCtx;
import org.openide.util.NbBundle;
import org.openide.util.actions.CallableSystemAction;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
* Encapsulates a menu action which triggers the common files search dialog.
*/
final public class CommonFilesSearchAction extends CallableSystemAction {
final public class CommonAttributeSearchAction extends CallableSystemAction {
private static final Logger LOGGER = Logger.getLogger(CommonFilesSearchAction.class.getName());
private static final Logger LOGGER = Logger.getLogger(CommonAttributeSearchAction.class.getName());
private static CommonFilesSearchAction instance = null;
private static CommonAttributeSearchAction instance = null;
private static final long serialVersionUID = 1L;
CommonFilesSearchAction() {
/**
* Get the default CommonAttributeSearchAction.
*
* @return the default instance of this action
*/
public static synchronized CommonAttributeSearchAction getDefault() {
if (instance == null) {
instance = new CommonAttributeSearchAction();
}
return instance;
}
/**
* Create a CommonAttributeSearchAction for opening the common attribute
* search dialog
*/
private CommonAttributeSearchAction() {
super();
this.setEnabled(false);
}
@ -49,47 +63,42 @@ final public class CommonFilesSearchAction extends CallableSystemAction {
boolean shouldBeEnabled = false;
try {
//dont refactor any of this to pull out common expressions - order of evaluation of each expression is significant
shouldBeEnabled =
(Case.isCaseOpen() &&
Case.getCurrentCase().getDataSources().size() > 1)
||
(EamDb.isEnabled() &&
EamDb.getInstance() != null &&
EamDb.getInstance().getCases().size() > 1 &&
Case.isCaseOpen() &&
Case.getCurrentCase() != null &&
EamDb.getInstance().getCase(Case.getCurrentCase()) != null);
shouldBeEnabled
= (Case.isCaseOpen()
&& Case.getCurrentCase().getDataSources().size() > 1)
|| CommonAttributePanel.isEamDbAvailableForIntercaseSearch();
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Error getting data sources for action enabled check", ex);
} catch (EamDbException ex) {
LOGGER.log(Level.SEVERE, "Error getting CR cases for action enabled check", ex);
}
return super.isEnabled() && shouldBeEnabled;
}
public static synchronized CommonFilesSearchAction getDefault() {
if (instance == null) {
instance = new CommonFilesSearchAction();
}
return instance;
}
@Override
public void actionPerformed(ActionEvent event) {
new CommonAttributePanel().setVisible(true);
createAndShowPanel();
}
@Override
public void performAction() {
new CommonAttributePanel().setVisible(true);
createAndShowPanel();
}
/**
* Create the commonAttributePanel and diplay it.
*/
private void createAndShowPanel() {
CommonAttributePanel commonAttributePanel = new CommonAttributePanel();
//In order to update errors the CommonAttributePanel needs to observe its sub panels
commonAttributePanel.observeSubPanels();
commonAttributePanel.setVisible(true);
}
@NbBundle.Messages({
"CommonFilesAction.getName.text=Common Files Search"})
"CommonAttributeSearchAction.getName.text=Common Property Search"})
@Override
public String getName() {
return Bundle.CommonFilesAction_getName_text();
return Bundle.CommonAttributeSearchAction_getName_text();
}
@Override

View File

@ -25,9 +25,12 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
* Stores the results from the various types of common attribute searching
@ -35,10 +38,26 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
*/
final public class CommonAttributeSearchResults {
private static final Logger LOGGER = Logger.getLogger(CommonAttributeSearchResults.class.getName());
// maps instance count to list of attribute values.
private final Map<Integer, CommonAttributeValueList> instanceCountToAttributeValues;
private final int percentageThreshold;
private final int resultTypeId;
/**
* Create a values object which can be handed off to the node factories.
*
* @param values list of CommonAttributeValue indexed by size of
* CommonAttributeValue
*/
CommonAttributeSearchResults(Map<Integer, CommonAttributeValueList> metadata, int percentageThreshold, CorrelationAttributeInstance.Type resultType) {
//wrap in a new object in case any client code has used an unmodifiable collection
this.instanceCountToAttributeValues = new HashMap<>(metadata);
this.percentageThreshold = percentageThreshold;
this.resultTypeId = resultType.getId();
}
/**
* Create a values object which can be handed off to the node factories.
@ -50,6 +69,7 @@ final public class CommonAttributeSearchResults {
//wrap in a new object in case any client code has used an unmodifiable collection
this.instanceCountToAttributeValues = new HashMap<>(metadata);
this.percentageThreshold = percentageThreshold;
this.resultTypeId = CorrelationAttributeInstance.FILES_TYPE_ID;
}
/**
@ -86,7 +106,7 @@ final public class CommonAttributeSearchResults {
* search.
*
* Remove results which are not found in the portion of available data
sources described by maximumPercentageThreshold.
* sources described by maximumPercentageThreshold.
*
* @return metadata
*/
@ -96,15 +116,18 @@ final public class CommonAttributeSearchResults {
return Collections.unmodifiableMap(this.instanceCountToAttributeValues);
}
CorrelationAttributeInstance.Type fileAttributeType = CorrelationAttributeInstance
CorrelationAttributeInstance.Type attributeType = CorrelationAttributeInstance
.getDefaultCorrelationTypes()
.stream()
.filter(filterType -> filterType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID)
.filter(filterType -> filterType.getId() == this.resultTypeId)
.findFirst().get();
EamDb eamDb = EamDb.getInstance();
Map<Integer, List<CommonAttributeValue>> itemsToRemove = new HashMap<>();
//Call countUniqueDataSources once to reduce the number of DB queries needed to get
//the frequencyPercentage
Double uniqueCaseDataSourceTuples = eamDb.getCountUniqueDataSources().doubleValue();
for(Entry<Integer, CommonAttributeValueList> listOfValues : Collections.unmodifiableMap(this.instanceCountToAttributeValues).entrySet()){
@ -113,7 +136,11 @@ final public class CommonAttributeSearchResults {
for(CommonAttributeValue value : values.getDelayedMetadataList()){ // Need the real metadata
int frequencyPercentage = eamDb.getFrequencyPercentage(new CorrelationAttributeInstance(fileAttributeType, value.getValue()));
try {
Double uniqueTypeValueTuples = eamDb.getCountUniqueCaseDataSourceTuplesHavingTypeValue(
attributeType, value.getValue()).doubleValue();
Double commonalityPercentage = uniqueTypeValueTuples / uniqueCaseDataSourceTuples * 100;
int frequencyPercentage = commonalityPercentage.intValue();
if(frequencyPercentage > maximumPercentageThreshold){
if(itemsToRemove.containsKey(key)){
@ -124,6 +151,9 @@ final public class CommonAttributeSearchResults {
itemsToRemove.put(key, toRemove);
}
}
} catch(CorrelationAttributeNormalizationException ex){
LOGGER.log(Level.WARNING, "Unable to determine frequency percentage attribute - frequency filter may not be accurate for these results.", ex);
}
}
}
@ -154,7 +184,7 @@ final public class CommonAttributeSearchResults {
int count = 0;
for (CommonAttributeValueList data : this.instanceCountToAttributeValues.values()) {
for(CommonAttributeValue md5 : data.getMetadataList()){
for(CommonAttributeValue md5 : data.getDelayedMetadataList()){
count += md5.getInstanceCount();
}
}

View File

@ -86,7 +86,4 @@ final public class CommonAttributeValue {
public int getInstanceCount() {
return this.fileInstances.size();
}
}

View File

@ -41,7 +41,7 @@ public class CommonAttributeValueNode extends DisplayableItemNode {
private final String dataSources;
@NbBundle.Messages({
"Md5Node.Md5Node.format=MD5: %s"
"CommonAttributeValueNode.CommonAttributeValueNode.format=Value: %s"
})
/**
* Create a Match node whose children will all have this object in common.
@ -57,7 +57,7 @@ public class CommonAttributeValueNode extends DisplayableItemNode {
this.dataSources = String.join(", ", data.getDataSources());
this.value = data.getValue();
this.setDisplayName(String.format(Bundle.Md5Node_Md5Node_format(), this.value));
this.setDisplayName(String.format(Bundle.CommonAttributeValueNode_CommonAttributeValueNode_format(), this.value));
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/fileset-icon-16.png"); //NON-NLS
}

View File

@ -23,6 +23,7 @@ import java.util.Map;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance.Type;
/**
* Provides logic for selecting common files from all data sources and all cases
@ -31,6 +32,10 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
abstract class InterCaseCommonAttributeSearcher extends AbstractCommonAttributeSearcher {
private final EamDb dbManager;
/**
* The Correlation Type to find matches on.
*/
final Type corAttrType;
/**
* Implements the algorithm for getting common files across all data sources
@ -43,9 +48,10 @@ abstract class InterCaseCommonAttributeSearcher extends AbstractCommonAttributeS
*
* @throws EamDbException
*/
InterCaseCommonAttributeSearcher(Map<Long, String> dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType, int percentageThreshold) throws EamDbException {
InterCaseCommonAttributeSearcher(Map<Long, String> dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType, Type corAttrType, int percentageThreshold) throws EamDbException {
super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType, percentageThreshold);
dbManager = EamDb.getInstance();
this.corAttrType = corAttrType;
}
protected CorrelationCase getCorrelationCaseFromId(int correlationCaseId) throws EamDbException {
@ -56,4 +62,5 @@ abstract class InterCaseCommonAttributeSearcher extends AbstractCommonAttributeS
}
throw new IllegalArgumentException("Cannot locate case.");
}
}

View File

@ -20,60 +20,72 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="specificCentralRepoCaseCheckbox" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="correlationComboBoxLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
<Group type="102" alignment="1" attributes="0">
<Group type="103" groupAlignment="1" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="anyCentralRepoCaseRadio" alignment="0" min="-2" max="-2" attributes="0"/>
<Group type="102" attributes="0">
<Component id="categoriesLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="19" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="allFileCategoriesRadioButton" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="selectedFileCategoriesButton" alignment="0" min="-2" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="21" pref="21" max="-2" attributes="0"/>
<Component id="caseComboBox" min="-2" pref="261" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="documentsCheckbox" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="pictureVideoCheckbox" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="specificCentralRepoCaseRadio" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="32767" attributes="0"/>
</Group>
</Group>
</Group>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<EmptySpace min="-2" pref="21" max="-2" attributes="0"/>
<Group type="103" groupAlignment="1" attributes="0">
<Component id="caseComboBox" max="32767" attributes="0"/>
<Component id="correlationTypeComboBox" alignment="1" pref="353" max="32767" attributes="0"/>
</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">
<Component id="anyCentralRepoCaseRadio" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="specificCentralRepoCaseRadio" min="-2" max="-2" attributes="0"/>
<Component id="specificCentralRepoCaseCheckbox" min="-2" pref="18" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="caseComboBox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="correlationComboBoxLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="correlationTypeComboBox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="categoriesLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="allFileCategoriesRadioButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="selectedFileCategoriesButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="pictureVideoCheckbox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="documentsCheckbox" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JRadioButton" name="anyCentralRepoCaseRadio">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="buttonGroup"/>
</Property>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="InterCasePanel.anyCentralRepoCaseRadio.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="anyCentralRepoCaseRadioActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JRadioButton" name="specificCentralRepoCaseRadio">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="buttonGroup"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="InterCasePanel.specificCentralRepoCaseRadio.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="specificCentralRepoCaseRadioActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JComboBox" name="caseComboBox">
<Properties>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
@ -85,5 +97,106 @@
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues>
</Component>
<Component class="javax.swing.JLabel" name="correlationComboBoxLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="InterCasePanel.correlationComboBoxLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JComboBox" name="correlationTypeComboBox">
<Properties>
<Property name="selectedItem" type="java.lang.Object" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="null" type="code"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="InterCasePanel.correlationTypeComboBox.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="correlationTypeComboBoxActionPerformed"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues>
</Component>
<Component class="javax.swing.JLabel" name="categoriesLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="InterCasePanel.categoriesLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
<Property name="name" type="java.lang.String" value="" noResource="true"/>
</Properties>
</Component>
<Component class="javax.swing.JRadioButton" name="allFileCategoriesRadioButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="buttonGroup"/>
</Property>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="InterCasePanel.allFileCategoriesRadioButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="InterCasePanel.allFileCategoriesRadioButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="allFileCategoriesRadioButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JRadioButton" name="selectedFileCategoriesButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="buttonGroup"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="InterCasePanel.selectedFileCategoriesButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="InterCasePanel.selectedFileCategoriesButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="selectedFileCategoriesButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JCheckBox" name="pictureVideoCheckbox">
<Properties>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="InterCasePanel.pictureVideoCheckbox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pictureVideoCheckboxActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JCheckBox" name="documentsCheckbox">
<Properties>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="InterCasePanel.documentsCheckbox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="documentsCheckboxActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JCheckBox" name="specificCentralRepoCaseCheckbox">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="InterCasePanel.specificCentralRepoCaseCheckbox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="specificCentralRepoCaseCheckboxActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Form>

View File

@ -21,27 +21,31 @@ package org.sleuthkit.autopsy.commonfilesearch;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Observable;
import java.util.Observer;
import javax.swing.ComboBoxModel;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
/**
* UI controls for Common Files Search scenario where the user intends to find
* common files between cases in addition to the present case.
*/
public class InterCasePanel extends javax.swing.JPanel {
public final class InterCasePanel extends javax.swing.JPanel {
private static final long serialVersionUID = 1L;
private final Observable fileTypeFilterObservable;
static final int NO_CASE_SELECTED = -1;
private ComboBoxModel<String> casesList = new DataSourceComboBoxModel();
private final Map<Integer, String> caseMap;
//True if we are looking in any or all cases,
// false if we must find matches in a given case plus the current case
private boolean anyCase;
private Map<String, CorrelationAttributeInstance.Type> correlationTypeFilters;
/**
* Creates new form InterCasePanel
@ -49,15 +53,76 @@ public class InterCasePanel extends javax.swing.JPanel {
public InterCasePanel() {
initComponents();
this.caseMap = new HashMap<>();
this.anyCase = true;
fileTypeFilterObservable = new Observable() {
@Override
public void notifyObservers() {
//set changed before notify observers
//we want this observerable to always cause the observer to update when notified
this.setChanged();
super.notifyObservers();
}
};
}
private void specificCaseSelected(boolean selected) {
this.specificCentralRepoCaseRadio.setEnabled(selected);
if (this.specificCentralRepoCaseRadio.isEnabled()) {
this.caseComboBox.setEnabled(true);
this.caseComboBox.setSelectedIndex(0);
/**
* Add an Observer to the Observable portion of this panel so that it can be
* notified of changes to this panel.
*
* @param observer the object which is observing this panel
*/
void addObserver(Observer observer) {
fileTypeFilterObservable.addObserver(observer);
}
/**
* If the user has selected to show only results of specific file types.
*
* @return if the selected file categories button is enabled AND selected,
* true for enabled AND selected false for not selected OR not
* enabled
*/
boolean fileCategoriesButtonIsSelected() {
return selectedFileCategoriesButton.isEnabled() && selectedFileCategoriesButton.isSelected();
}
/**
* If the user has selected selected to show Picture and Video files as part
* of the filtered results.
*
* @return if the pictures and video checkbox is enabled AND selected, true
* for enabled AND selected false for not selected OR not enabled
*/
boolean pictureVideoCheckboxIsSelected() {
return pictureVideoCheckbox.isEnabled() && pictureVideoCheckbox.isSelected();
}
/**
* If the user has selected selected to show Document files as part of the
* filtered results.
*
* @return if the documents checkbox is enabled AND selected, true for
* enabled AND selected false for not selected OR not enabled
*/
boolean documentsCheckboxIsSelected() {
return documentsCheckbox.isEnabled() && documentsCheckbox.isSelected();
}
/**
* If the EamDB is enabled, the UI will populate the correlation type
* ComboBox with available types in the CR.
*/
void setupCorrelationTypeFilter() {
this.correlationTypeFilters = new HashMap<>();
try {
List<CorrelationAttributeInstance.Type> types = CorrelationAttributeInstance.getDefaultCorrelationTypes();
for (CorrelationAttributeInstance.Type type : types) {
correlationTypeFilters.put(type.getDisplayName(), type);
this.correlationTypeComboBox.addItem(type.getDisplayName());
}
} catch (EamDbException ex) {
Exceptions.printStackTrace(ex);
}
this.correlationTypeComboBox.setSelectedIndex(0);
}
/**
@ -70,115 +135,261 @@ public class InterCasePanel extends javax.swing.JPanel {
private void initComponents() {
buttonGroup = new javax.swing.ButtonGroup();
anyCentralRepoCaseRadio = new javax.swing.JRadioButton();
specificCentralRepoCaseRadio = new javax.swing.JRadioButton();
caseComboBox = new javax.swing.JComboBox<>();
buttonGroup.add(anyCentralRepoCaseRadio);
anyCentralRepoCaseRadio.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(anyCentralRepoCaseRadio, org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.anyCentralRepoCaseRadio.text")); // NOI18N
anyCentralRepoCaseRadio.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
anyCentralRepoCaseRadioActionPerformed(evt);
}
});
buttonGroup.add(specificCentralRepoCaseRadio);
org.openide.awt.Mnemonics.setLocalizedText(specificCentralRepoCaseRadio, org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.specificCentralRepoCaseRadio.text")); // NOI18N
specificCentralRepoCaseRadio.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
specificCentralRepoCaseRadioActionPerformed(evt);
}
});
correlationComboBoxLabel = new javax.swing.JLabel();
correlationTypeComboBox = new javax.swing.JComboBox<>();
categoriesLabel = new javax.swing.JLabel();
allFileCategoriesRadioButton = new javax.swing.JRadioButton();
selectedFileCategoriesButton = new javax.swing.JRadioButton();
pictureVideoCheckbox = new javax.swing.JCheckBox();
documentsCheckbox = new javax.swing.JCheckBox();
specificCentralRepoCaseCheckbox = new javax.swing.JCheckBox();
caseComboBox.setModel(casesList);
caseComboBox.setEnabled(false);
org.openide.awt.Mnemonics.setLocalizedText(correlationComboBoxLabel, org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.correlationComboBoxLabel.text")); // NOI18N
correlationTypeComboBox.setSelectedItem(null);
correlationTypeComboBox.setToolTipText(org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.correlationTypeComboBox.toolTipText")); // NOI18N
correlationTypeComboBox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
correlationTypeComboBoxActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(categoriesLabel, org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.categoriesLabel.text")); // NOI18N
categoriesLabel.setEnabled(false);
categoriesLabel.setName(""); // NOI18N
buttonGroup.add(allFileCategoriesRadioButton);
allFileCategoriesRadioButton.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(allFileCategoriesRadioButton, org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.allFileCategoriesRadioButton.text")); // NOI18N
allFileCategoriesRadioButton.setToolTipText(org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.allFileCategoriesRadioButton.toolTipText")); // NOI18N
allFileCategoriesRadioButton.setEnabled(false);
allFileCategoriesRadioButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
allFileCategoriesRadioButtonActionPerformed(evt);
}
});
buttonGroup.add(selectedFileCategoriesButton);
org.openide.awt.Mnemonics.setLocalizedText(selectedFileCategoriesButton, org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.selectedFileCategoriesButton.text")); // NOI18N
selectedFileCategoriesButton.setToolTipText(org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.selectedFileCategoriesButton.toolTipText")); // NOI18N
selectedFileCategoriesButton.setEnabled(false);
selectedFileCategoriesButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
selectedFileCategoriesButtonActionPerformed(evt);
}
});
pictureVideoCheckbox.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(pictureVideoCheckbox, org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.pictureVideoCheckbox.text")); // NOI18N
pictureVideoCheckbox.setEnabled(false);
pictureVideoCheckbox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
pictureVideoCheckboxActionPerformed(evt);
}
});
documentsCheckbox.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(documentsCheckbox, org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.documentsCheckbox.text")); // NOI18N
documentsCheckbox.setEnabled(false);
documentsCheckbox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
documentsCheckboxActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(specificCentralRepoCaseCheckbox, org.openide.util.NbBundle.getMessage(InterCasePanel.class, "InterCasePanel.specificCentralRepoCaseCheckbox.text")); // NOI18N
specificCentralRepoCaseCheckbox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
specificCentralRepoCaseCheckboxActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(specificCentralRepoCaseCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(correlationComboBoxLabel)
.addGap(0, 0, Short.MAX_VALUE))
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(anyCentralRepoCaseRadio)
.addComponent(categoriesLabel)
.addGroup(layout.createSequentialGroup()
.addGap(19, 19, 19)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(allFileCategoriesRadioButton)
.addComponent(selectedFileCategoriesButton)
.addGroup(layout.createSequentialGroup()
.addGap(21, 21, 21)
.addComponent(caseComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 261, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(specificCentralRepoCaseRadio))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(documentsCheckbox)
.addComponent(pictureVideoCheckbox))))))
.addGap(0, 0, Short.MAX_VALUE))
.addGroup(layout.createSequentialGroup()
.addGap(21, 21, 21)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(caseComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(correlationTypeComboBox, 0, 353, Short.MAX_VALUE))))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(anyCentralRepoCaseRadio)
.addContainerGap()
.addComponent(specificCentralRepoCaseCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(specificCentralRepoCaseRadio)
.addComponent(caseComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(caseComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(correlationComboBoxLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(correlationTypeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(categoriesLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(allFileCategoriesRadioButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(selectedFileCategoriesButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(pictureVideoCheckbox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(documentsCheckbox)
.addGap(0, 0, 0))
);
}// </editor-fold>//GEN-END:initComponents
private void specificCentralRepoCaseRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_specificCentralRepoCaseRadioActionPerformed
this.caseComboBox.setEnabled(true);
if(this.caseComboBox.isEnabled() && this.caseComboBox.getSelectedItem() == null){
private void allFileCategoriesRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allFileCategoriesRadioButtonActionPerformed
//When the allFileCategoriesRadioButton is selected disable the options
//related to selected file categories and notify observers that the panel has changed
//incase the current settings are invalid
pictureVideoCheckbox.setEnabled(false);
documentsCheckbox.setEnabled(false);
fileTypeFilterObservable.notifyObservers();
}//GEN-LAST:event_allFileCategoriesRadioButtonActionPerformed
private void selectedFileCategoriesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectedFileCategoriesButtonActionPerformed
//When the selectedFileCategoriesButton is selected enable its related options
//and notify observers that the panel has changed incase the current settings are invalid
pictureVideoCheckbox.setEnabled(true);
documentsCheckbox.setEnabled(true);
fileTypeFilterObservable.notifyObservers();
}//GEN-LAST:event_selectedFileCategoriesButtonActionPerformed
private void specificCentralRepoCaseCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_specificCentralRepoCaseCheckboxActionPerformed
//When the specificCentralRepoCaseCheckbox is clicked update its related options
this.caseComboBox.setEnabled(specificCentralRepoCaseCheckbox.isSelected());
if (specificCentralRepoCaseCheckbox.isSelected()) {
this.caseComboBox.setSelectedIndex(0);
}
this.anyCase = false;
}//GEN-LAST:event_specificCentralRepoCaseRadioActionPerformed
}//GEN-LAST:event_specificCentralRepoCaseCheckboxActionPerformed
private void anyCentralRepoCaseRadioActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_anyCentralRepoCaseRadioActionPerformed
this.caseComboBox.setEnabled(false);
this.anyCase = true;
}//GEN-LAST:event_anyCentralRepoCaseRadioActionPerformed
private void correlationTypeComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_correlationTypeComboBoxActionPerformed
//we do not currenlty have mime type in the central repository so this section will always be disabled
boolean enableFileTypesFilter = false; //this.correlationTypeComboBox.getSelectedItem().equals("Files");
categoriesLabel.setEnabled(enableFileTypesFilter);
allFileCategoriesRadioButton.setEnabled(enableFileTypesFilter);
selectedFileCategoriesButton.setEnabled(enableFileTypesFilter);
boolean enableIndividualFilters = (enableFileTypesFilter && selectedFileCategoriesButton.isSelected());
pictureVideoCheckbox.setEnabled(enableIndividualFilters);
documentsCheckbox.setEnabled(enableIndividualFilters);
}//GEN-LAST:event_correlationTypeComboBoxActionPerformed
private void pictureVideoCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pictureVideoCheckboxActionPerformed
//notify observers that the panel has changed incase the current settings are invalid
fileTypeFilterObservable.notifyObservers();
}//GEN-LAST:event_pictureVideoCheckboxActionPerformed
private void documentsCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_documentsCheckboxActionPerformed
//notify observers that the panel has changed incase the current settings are invalid
fileTypeFilterObservable.notifyObservers();
}//GEN-LAST:event_documentsCheckboxActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JRadioButton anyCentralRepoCaseRadio;
private javax.swing.JRadioButton allFileCategoriesRadioButton;
private javax.swing.ButtonGroup buttonGroup;
private javax.swing.JComboBox<String> caseComboBox;
private javax.swing.JRadioButton specificCentralRepoCaseRadio;
private javax.swing.JLabel categoriesLabel;
private javax.swing.JLabel correlationComboBoxLabel;
private javax.swing.JComboBox<String> correlationTypeComboBox;
private javax.swing.JCheckBox documentsCheckbox;
private javax.swing.JCheckBox pictureVideoCheckbox;
private javax.swing.JRadioButton selectedFileCategoriesButton;
private javax.swing.JCheckBox specificCentralRepoCaseCheckbox;
// End of variables declaration//GEN-END:variables
/**
* Get the map of cases which was used to populate the combo box on
* this panel.
*
* @return an unmodifiable copy of the map of cases
*/
Map<Integer, String> getCaseMap() {
return Collections.unmodifiableMap(this.caseMap);
}
void setCaseList(DataSourceComboBoxModel dataSourceComboBoxModel) {
/**
* Set the datamodel for the combo box which displays the cases in
* the central repository
*
* @param dataSourceComboBoxModel the DataSourceComboBoxModel to use
*/
void setCaseComboboxModel(DataSourceComboBoxModel dataSourceComboBoxModel) {
this.casesList = dataSourceComboBoxModel;
this.caseComboBox.setModel(dataSourceComboBoxModel);
}
void rigForMultipleCases(boolean multipleCases) {
this.anyCentralRepoCaseRadio.setEnabled(multipleCases);
this.anyCentralRepoCaseRadio.setEnabled(multipleCases);
if(!multipleCases){
this.specificCentralRepoCaseRadio.setSelected(true);
this.specificCaseSelected(true);
}
}
/**
* Update the map of cases that this panel allows the user to select from
*
* @param caseMap A map of cases
*/
void setCaseMap(Map<Integer, String> caseMap) {
this.caseMap.clear();
this.caseMap.putAll(caseMap);
}
/**
* Whether or not the central repository has multiple cases in it.
*
* @return true when the central repository has 2 or more case, false if it
* has 0 or 1 case in it.
*/
boolean centralRepoHasMultipleCases() {
return this.caseMap.size() >= 2;
}
/**
* Get the ID for the selected case
*
* @return the ID of the selected case or InterCasePanel.NO_CASE_SELECTED
* when none selected
*/
Integer getSelectedCaseId() {
if(this.anyCase){
return InterCasePanel.NO_CASE_SELECTED;
}
if (specificCentralRepoCaseCheckbox.isSelected()) {
for (Entry<Integer, String> entry : this.caseMap.entrySet()) {
if (entry.getValue().equals(this.caseComboBox.getSelectedItem())) {
return entry.getKey();
}
}
}
return InterCasePanel.NO_CASE_SELECTED;
}
/**
* Returns the selected Correlation Type by getting the Type from the stored
* HashMap.
*
* @return Type the selected Correlation Type to query for.
*/
CorrelationAttributeInstance.Type getSelectedCorrelationType() {
return correlationTypeFilters.get(this.correlationTypeComboBox.getSelectedItem().toString());
}
}

View File

@ -26,10 +26,13 @@ import java.util.Map;
import java.util.logging.Level;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance.Type;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil;
import org.sleuthkit.autopsy.centralrepository.datamodel.InstanceTableCallback;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.TskData;
@ -43,45 +46,86 @@ final class InterCaseSearchResultsProcessor {
private Map<Long, String> dataSources;
/**
* The CorrelationAttributeInstance.Type this Processor will query on
*/
private final Type correlationType;
private static final Logger LOGGER = Logger.getLogger(CommonAttributePanel.class.getName());
private final String interCaseWhereClause = "value IN (SELECT value FROM file_instances"
+ " WHERE value IN (SELECT value FROM file_instances"
+ " WHERE case_id=%s AND (known_status !=%s OR known_status IS NULL) GROUP BY value)"
+ " GROUP BY value HAVING COUNT(DISTINCT case_id) > 1) ORDER BY value";
private final String singleInterCaseWhereClause = "value IN (SELECT value FROM file_instances "
+ "WHERE value IN (SELECT value FROM file_instances "
+ "WHERE case_id=%s AND (known_status !=%s OR known_status IS NULL) GROUP BY value) "
+ "AND (case_id=%s OR case_id=%s) GROUP BY value HAVING COUNT(DISTINCT case_id) > 1) ORDER BY value";
/**
* The initial CorrelationAttributeInstance ids lookup query.
*/
private final String interCaseWhereClause;
/**
* Used in the InterCaseCommonAttributeSearchers to find common attribute instances and generate nodes at the UI level.
* @param dataSources
* The single CorrelationAttributeInstance object retrieval query
*/
InterCaseSearchResultsProcessor(Map<Long, String> dataSources){
private final String singleInterCaseWhereClause;
/**
* Used in the InterCaseCommonAttributeSearchers to find common attribute
* instances and generate nodes at the UI level.
*
* @param dataSources the cases to filter and correlate on
* @param theType the type of CR data to search
*/
InterCaseSearchResultsProcessor(Map<Long, String> dataSources, CorrelationAttributeInstance.Type theType) {
this.correlationType = theType;
this.dataSources = dataSources;
interCaseWhereClause = getInterCaseWhereClause();
singleInterCaseWhereClause = getSingleInterCaseWhereClause();
}
private String getInterCaseWhereClause() {
String tableName = EamDbUtil.correlationTypeToInstanceTableName(correlationType);
StringBuilder sqlString = new StringBuilder(250);
sqlString.append("value IN (SELECT value FROM ")
.append(tableName)
.append(" WHERE value IN (SELECT value FROM ")
.append(tableName)
.append(" WHERE case_id=%s AND (known_status !=%s OR known_status IS NULL) GROUP BY value)")
.append(" GROUP BY value HAVING COUNT(DISTINCT case_id) > 1) ORDER BY value");
return sqlString.toString();
}
private String getSingleInterCaseWhereClause() {
String tableName = EamDbUtil.correlationTypeToInstanceTableName(correlationType);
StringBuilder sqlString = new StringBuilder(250);
sqlString.append("value IN (SELECT value FROM ")
.append(tableName)
.append(" WHERE value IN (SELECT value FROM ")
.append(tableName)
.append(" WHERE case_id=%s AND (known_status !=%s OR known_status IS NULL) GROUP BY value)")
.append(" AND (case_id=%s OR case_id=%s) GROUP BY value HAVING COUNT(DISTINCT case_id) > 1) ORDER BY value");
return sqlString.toString();
}
/**
* Used in the CentralRepoCommonAttributeInstance to find common attribute instances and generate nodes at the UI level.
* Used in the CentralRepoCommonAttributeInstance to find common attribute
* instances and generate nodes at the UI level.
*
* @param theType the type of CR data to search
*/
InterCaseSearchResultsProcessor(){
//intentionally emtpy - we need a constructor which does not set the data sources field
InterCaseSearchResultsProcessor(CorrelationAttributeInstance.Type theType) {
this.correlationType = theType;
interCaseWhereClause = getInterCaseWhereClause();
singleInterCaseWhereClause = getSingleInterCaseWhereClause();
}
/**
* Finds a single CorrelationAttribute given an id.
*
* @param attrbuteId Row of CorrelationAttribute to retrieve from the EamDb
*
* @return CorrelationAttribute object representation of retrieved match
*/
CorrelationAttributeInstance findSingleCorrelationAttribute(int attrbuteId) {
try {
InterCaseCommonAttributeRowCallback instancetableCallback = new InterCaseCommonAttributeRowCallback();
EamDb DbManager = EamDb.getInstance();
CorrelationAttributeInstance.Type fileType = DbManager.getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID);
DbManager.processInstanceTableWhere(fileType, String.format("id = %s", attrbuteId), instancetableCallback);
DbManager.processInstanceTableWhere(correlationType, String.format("id = %s", attrbuteId), instancetableCallback);
return instancetableCallback.getCorrelationAttribute();
@ -102,10 +146,10 @@ final class InterCaseSearchResultsProcessor {
try {
InterCaseCommonAttributesCallback instancetableCallback = new InterCaseCommonAttributesCallback();
EamDb DbManager = EamDb.getInstance();
CorrelationAttributeInstance.Type fileType = DbManager.getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID);
int caseId = DbManager.getCase(currentCase).getID();
DbManager.processInstanceTableWhere(fileType, String.format(interCaseWhereClause, caseId,
DbManager.processInstanceTableWhere(correlationType, String.format(interCaseWhereClause, caseId,
TskData.FileKnown.KNOWN.getFileKnownValue()),
instancetableCallback);
@ -129,10 +173,9 @@ final class InterCaseSearchResultsProcessor {
try {
InterCaseCommonAttributesCallback instancetableCallback = new InterCaseCommonAttributesCallback();
EamDb DbManager = EamDb.getInstance();
CorrelationAttributeInstance.Type fileType = DbManager.getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID);
int caseId = DbManager.getCase(currentCase).getID();
int targetCaseId = singleCase.getID();
DbManager.processInstanceTableWhere(fileType, String.format(singleInterCaseWhereClause, caseId,
DbManager.processInstanceTableWhere(correlationType, String.format(singleInterCaseWhereClause, caseId,
TskData.FileKnown.KNOWN.getFileKnownValue(), caseId, targetCaseId), instancetableCallback);
return instancetableCallback.getInstanceCollatedCommonFiles();
} catch (EamDbException ex) {
@ -158,27 +201,42 @@ final class InterCaseSearchResultsProcessor {
while (resultSet.next()) {
int resultId = InstanceTableCallback.getId(resultSet);
String md5Value = InstanceTableCallback.getValue(resultSet);
String corValue = InstanceTableCallback.getValue(resultSet);
if (previousRowMd5.isEmpty()) {
previousRowMd5 = md5Value;
previousRowMd5 = corValue;
}
if (md5Value == null || HashUtility.isNoDataMd5(md5Value)) {
if (corValue == null || HashUtility.isNoDataMd5(corValue)) {
continue;
}
countAndAddCommonAttributes(md5Value, resultId);
countAndAddCommonAttributes(corValue, resultId);
}
//Add the final instances
CommonAttributeValueList value = new CommonAttributeValueList();
if (commonAttributeValue != null) {
value.addMetadataToList(commonAttributeValue);
instanceCollatedCommonFiles.put(commonAttributeValue.getInstanceCount(), value);
}
} catch (SQLException ex) {
LOGGER.log(Level.WARNING, "Error getting artifact instances from database.", ex); // NON-NLS
}
}
private void countAndAddCommonAttributes(String md5Value, int resultId) {
/**
* Add a resultId to the list of matches for a given corValue, which
* counts to number of instances of that match, determining which
* InstanceCountNode the match will be added to.
*
* @param corValue the value which matches
* @param resultId the CorrelationAttributeInstance id to be retrieved
* later.
*/
private void countAndAddCommonAttributes(String corValue, int resultId) {
if (commonAttributeValue == null) {
commonAttributeValue = new CommonAttributeValue(md5Value);
commonAttributeValue = new CommonAttributeValue(corValue);
}
if (!md5Value.equals(previousRowMd5)) {
if (!corValue.equals(previousRowMd5)) {
int size = commonAttributeValue.getInstanceCount();
if (instanceCollatedCommonFiles.containsKey(size)) {
instanceCollatedCommonFiles.get(size).addMetadataToList(commonAttributeValue);
@ -188,13 +246,13 @@ final class InterCaseSearchResultsProcessor {
instanceCollatedCommonFiles.put(size, value);
}
commonAttributeValue = new CommonAttributeValue(md5Value);
previousRowMd5 = md5Value;
commonAttributeValue = new CommonAttributeValue(corValue);
previousRowMd5 = corValue;
}
// we don't *have* all the information for the rows in the CR,
// so we need to consult the present case via the SleuthkitCase object
// Later, when the FileInstanceNode is built. Therefore, build node generators for now.
AbstractCommonAttributeInstance searchResult = new CentralRepoCommonAttributeInstance(resultId, InterCaseSearchResultsProcessor.this.dataSources);
AbstractCommonAttributeInstance searchResult = new CentralRepoCommonAttributeInstance(resultId, InterCaseSearchResultsProcessor.this.dataSources, correlationType);
commonAttributeValue.addInstance(searchResult);
}
@ -215,16 +273,19 @@ final class InterCaseSearchResultsProcessor {
public void process(ResultSet resultSet) {
try {
EamDb DbManager = EamDb.getInstance();
CorrelationAttributeInstance.Type fileType = DbManager.getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID);
while (resultSet.next()) {
CorrelationCase correlationCase = DbManager.getCaseById(InstanceTableCallback.getCaseId(resultSet));
CorrelationDataSource dataSource = DbManager.getDataSourceById(correlationCase, InstanceTableCallback.getDataSourceId(resultSet));
correlationAttributeInstance = DbManager.getCorrelationAttributeInstance(fileType,
try {
correlationAttributeInstance = DbManager.getCorrelationAttributeInstance(correlationType,
correlationCase,
dataSource,
InstanceTableCallback.getValue(resultSet),
InstanceTableCallback.getFilePath(resultSet));
} catch (CorrelationAttributeNormalizationException ex) {
LOGGER.log(Level.INFO, "Unable to get CorrelationAttributeInstance.", ex); // NON-NLS
}
}
} catch (SQLException | EamDbException ex) {

View File

@ -36,8 +36,8 @@ import org.sleuthkit.datamodel.TskCoreException;
/**
*
* Generates a <code>List<CommonFilesMetadata></code> when
* <code>findFiles()</code> is called, which organizes files by md5 to
* prepare to display in viewer.
* <code>findMatches()</code> is called, which organizes files by md5 to prepare
* to display in viewer.
*
* This entire thing runs on a background thread where exceptions are handled.
*/
@ -93,7 +93,7 @@ public abstract class IntraCaseCommonAttributeSearcher extends AbstractCommonAtt
* @throws SQLException
*/
@Override
public CommonAttributeSearchResults findFiles() throws TskCoreException, NoCurrentCaseException, SQLException {
public CommonAttributeSearchResults findMatches() throws TskCoreException, NoCurrentCaseException, SQLException {
Map<String, CommonAttributeValue> commonFiles = new HashMap<>();
final Case currentCase = Case.getCurrentCaseThrows();
@ -139,9 +139,9 @@ public abstract class IntraCaseCommonAttributeSearcher extends AbstractCommonAtt
* expression which will filter our matches based on mime type. The
* expression will be conjoined to base query with an AND operator.
*
* @return sql fragment of the form:
* 'and "mime_type" in ( [comma delimited list of mime types] )'
* or empty string in the event that no types to filter on were given.
* @return sql fragment of the form: 'and "mime_type" in ( [comma delimited
* list of mime types] )' or empty string in the event that no types to
* filter on were given.
*/
String determineMimeTypeFilter() {

View File

@ -23,62 +23,58 @@
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<EmptySpace min="-2" pref="21" max="-2" attributes="0"/>
<Component id="selectDataSourceComboBox" max="32767" attributes="0"/>
</Group>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="allDataSourcesRadioButton" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="withinDataSourceRadioButton" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<Component id="categoriesLabel" min="-2" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="27" max="-2" attributes="0"/>
<Component id="selectDataSourceComboBox" min="-2" pref="261" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="19" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="allFileCategoriesRadioButton" min="-2" max="-2" attributes="0"/>
<Component id="selectedFileCategoriesButton" min="-2" max="-2" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="21" pref="21" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="documentsCheckbox" min="-2" max="-2" attributes="0"/>
<Component id="pictureVideoCheckbox" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<EmptySpace max="32767" attributes="0"/>
</Group>
</Group>
</Group>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
<Component id="onlySpecificDataSourceCheckbox" pref="384" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="allDataSourcesRadioButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="withinDataSourceRadioButton" min="-2" max="-2" attributes="0"/>
<Component id="onlySpecificDataSourceCheckbox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="selectDataSourceComboBox" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/>
<Component id="categoriesLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="allFileCategoriesRadioButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="selectedFileCategoriesButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="pictureVideoCheckbox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="documentsCheckbox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JRadioButton" name="allDataSourcesRadioButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="buttonGroup"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="IntraCasePanel.allDataSourcesRadioButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="horizontalAlignment" type="int" value="0"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="allDataSourcesRadioButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JRadioButton" name="withinDataSourceRadioButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="buttonGroup"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="IntraCasePanel.withinDataSourceRadioButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="horizontalAlignment" type="int" value="0"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="withinDataSourceRadioButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JComboBox" name="selectDataSourceComboBox">
<Properties>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
@ -86,12 +82,93 @@
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="selectDataSourceComboBoxActionPerformed"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues>
</Component>
<Component class="javax.swing.JLabel" name="categoriesLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="IntraCasePanel.categoriesLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="name" type="java.lang.String" value="" noResource="true"/>
</Properties>
</Component>
<Component class="javax.swing.JRadioButton" name="selectedFileCategoriesButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="buttonGroup"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="IntraCasePanel.selectedFileCategoriesButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="IntraCasePanel.selectedFileCategoriesButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="selectedFileCategoriesButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JCheckBox" name="pictureVideoCheckbox">
<Properties>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="IntraCasePanel.pictureVideoCheckbox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="pictureVideoCheckboxActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JCheckBox" name="documentsCheckbox">
<Properties>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="IntraCasePanel.documentsCheckbox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="documentsCheckboxActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JRadioButton" name="allFileCategoriesRadioButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="buttonGroup"/>
</Property>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="IntraCasePanel.allFileCategoriesRadioButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="IntraCasePanel.allFileCategoriesRadioButton.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="allFileCategoriesRadioButtonActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JCheckBox" name="onlySpecificDataSourceCheckbox">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/commonfilesearch/Bundle.properties" key="IntraCasePanel.onlySpecificDataSourceCheckbox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[243, 23]"/>
</Property>
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[243, 23]"/>
</Property>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[243, 23]"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="onlySpecificDataSourceCheckboxActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Form>

View File

@ -23,19 +23,21 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Observable;
import java.util.Observer;
import javax.swing.ComboBoxModel;
/**
* UI controls for Common Files Search scenario where the user intends to find
* common files between datasources. It is an inner panel which provides the ability
* to select all datasources or a single datasource from a dropdown list of
* sources in the current case.
* common files between datasources. It is an inner panel which provides the
* ability to select all datasources or a single datasource from a dropdown list
* of sources in the current case.
*/
public class IntraCasePanel extends javax.swing.JPanel {
public final class IntraCasePanel extends javax.swing.JPanel {
private static final long serialVersionUID = 1L;
static final long NO_DATA_SOURCE_SELECTED = -1;
private boolean singleDataSource;
private final Observable fileTypeFilterObservable;
private ComboBoxModel<String> dataSourcesList = new DataSourceComboBoxModel();
private final Map<Long, String> dataSourceMap;
@ -45,27 +47,87 @@ public class IntraCasePanel extends javax.swing.JPanel {
public IntraCasePanel() {
initComponents();
this.dataSourceMap = new HashMap<>();
this.singleDataSource = true;
this.onlySpecificDataSourceCheckbox.setEnabled(true);
fileTypeFilterObservable = new Observable() {
@Override
public void notifyObservers() {
//set changed before notify observers
//we want this observerable to always cause the observer to update when notified
this.setChanged();
super.notifyObservers();
}
};
}
public Map<Long, String> getDataSourceMap(){
/**
* Add an Observer to the Observable portion of this panel so that it can be
* notified of changes to this panel.
*
* @param observer the object which is observing this panel
*/
void addObserver(Observer observer) {
fileTypeFilterObservable.addObserver(observer);
}
/**
* Get the map of datasources which was used to populate the combo box on
* this panel.
*
* @return an unmodifiable copy of the map of datasources
*/
Map<Long, String> getDataSourceMap() {
return Collections.unmodifiableMap(this.dataSourceMap);
}
/**
* Get the ID for the datasource selected in the combo box.
*
* @return the selected datasource ID or
* IntraCasePanel.NO_DATA_SOURCE_SELECTED if none is selected
*/
Long getSelectedDataSourceId() {
if(!this.singleDataSource){
return IntraCasePanel.NO_DATA_SOURCE_SELECTED;
}
if (onlySpecificDataSourceCheckbox.isSelected()) {
for (Entry<Long, String> entry : this.dataSourceMap.entrySet()) {
if (entry.getValue().equals(this.selectDataSourceComboBox.getSelectedItem())) {
return entry.getKey();
}
}
}
return IntraCasePanel.NO_DATA_SOURCE_SELECTED;
}
/**
* If the user has selected to show only results of specific file types.
*
* @return if the selected file categories button is selected, true for
* selected false for not selected
*/
boolean fileCategoriesButtonIsSelected() {
return selectedFileCategoriesButton.isSelected();
}
/**
* If the user has selected selected to show Picture and Video files as part
* of the filtered results.
*
* @return if the pictures and video checkbox is enabled AND selected, true
* for enabled AND selected false for not selected OR not enabled
*/
boolean pictureVideoCheckboxIsSelected() {
return pictureVideoCheckbox.isEnabled() && pictureVideoCheckbox.isSelected();
}
/**
* If the user has selected selected to show Document files as part of the
* filtered results.
*
* @return if the documents checkbox is enabled AND selected, true for
* enabled AND selected false for not selected OR not enabled
*/
boolean documentsCheckboxIsSelected() {
return documentsCheckbox.isEnabled() && documentsCheckbox.isSelected();
}
/**
* 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
@ -76,32 +138,67 @@ public class IntraCasePanel extends javax.swing.JPanel {
private void initComponents() {
buttonGroup = new javax.swing.ButtonGroup();
allDataSourcesRadioButton = new javax.swing.JRadioButton();
withinDataSourceRadioButton = new javax.swing.JRadioButton();
selectDataSourceComboBox = new javax.swing.JComboBox<>();
buttonGroup.add(allDataSourcesRadioButton);
org.openide.awt.Mnemonics.setLocalizedText(allDataSourcesRadioButton, org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.allDataSourcesRadioButton.text")); // NOI18N
allDataSourcesRadioButton.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
allDataSourcesRadioButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
allDataSourcesRadioButtonActionPerformed(evt);
}
});
buttonGroup.add(withinDataSourceRadioButton);
org.openide.awt.Mnemonics.setLocalizedText(withinDataSourceRadioButton, org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.withinDataSourceRadioButton.text")); // NOI18N
withinDataSourceRadioButton.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
withinDataSourceRadioButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
withinDataSourceRadioButtonActionPerformed(evt);
}
});
categoriesLabel = new javax.swing.JLabel();
selectedFileCategoriesButton = new javax.swing.JRadioButton();
pictureVideoCheckbox = new javax.swing.JCheckBox();
documentsCheckbox = new javax.swing.JCheckBox();
allFileCategoriesRadioButton = new javax.swing.JRadioButton();
onlySpecificDataSourceCheckbox = new javax.swing.JCheckBox();
selectDataSourceComboBox.setModel(dataSourcesList);
selectDataSourceComboBox.setActionCommand(org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.selectDataSourceComboBox.actionCommand")); // NOI18N
selectDataSourceComboBox.setEnabled(false);
org.openide.awt.Mnemonics.setLocalizedText(categoriesLabel, org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.categoriesLabel.text")); // NOI18N
categoriesLabel.setName(""); // NOI18N
buttonGroup.add(selectedFileCategoriesButton);
org.openide.awt.Mnemonics.setLocalizedText(selectedFileCategoriesButton, org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.selectedFileCategoriesButton.text")); // NOI18N
selectedFileCategoriesButton.setToolTipText(org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.selectedFileCategoriesButton.toolTipText")); // NOI18N
selectedFileCategoriesButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
selectedFileCategoriesButtonActionPerformed(evt);
}
});
pictureVideoCheckbox.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(pictureVideoCheckbox, org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.pictureVideoCheckbox.text")); // NOI18N
pictureVideoCheckbox.setEnabled(false);
pictureVideoCheckbox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
pictureVideoCheckboxActionPerformed(evt);
}
});
documentsCheckbox.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(documentsCheckbox, org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.documentsCheckbox.text")); // NOI18N
documentsCheckbox.setEnabled(false);
documentsCheckbox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
documentsCheckboxActionPerformed(evt);
}
});
buttonGroup.add(allFileCategoriesRadioButton);
allFileCategoriesRadioButton.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(allFileCategoriesRadioButton, org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.allFileCategoriesRadioButton.text")); // NOI18N
allFileCategoriesRadioButton.setToolTipText(org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.allFileCategoriesRadioButton.toolTipText")); // NOI18N
allFileCategoriesRadioButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
allFileCategoriesRadioButtonActionPerformed(evt);
}
});
org.openide.awt.Mnemonics.setLocalizedText(onlySpecificDataSourceCheckbox, org.openide.util.NbBundle.getMessage(IntraCasePanel.class, "IntraCasePanel.onlySpecificDataSourceCheckbox.text")); // NOI18N
onlySpecificDataSourceCheckbox.setMaximumSize(new java.awt.Dimension(243, 23));
onlySpecificDataSourceCheckbox.setMinimumSize(new java.awt.Dimension(243, 23));
onlySpecificDataSourceCheckbox.setPreferredSize(new java.awt.Dimension(243, 23));
onlySpecificDataSourceCheckbox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
onlySpecificDataSourceCheckboxActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
@ -109,63 +206,109 @@ public class IntraCasePanel extends javax.swing.JPanel {
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(allDataSourcesRadioButton)
.addComponent(withinDataSourceRadioButton)))
.addGap(21, 21, 21)
.addComponent(selectDataSourceComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(layout.createSequentialGroup()
.addGap(27, 27, 27)
.addComponent(selectDataSourceComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 261, javax.swing.GroupLayout.PREFERRED_SIZE)))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(categoriesLabel)
.addGroup(layout.createSequentialGroup()
.addGap(19, 19, 19)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(allFileCategoriesRadioButton)
.addComponent(selectedFileCategoriesButton)
.addGroup(layout.createSequentialGroup()
.addGap(21, 21, 21)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(documentsCheckbox)
.addComponent(pictureVideoCheckbox))))))
.addGap(0, 0, Short.MAX_VALUE)))
.addContainerGap())
.addComponent(onlySpecificDataSourceCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, 384, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(allDataSourcesRadioButton)
.addContainerGap()
.addComponent(onlySpecificDataSourceCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(withinDataSourceRadioButton)
.addComponent(selectDataSourceComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(categoriesLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(selectDataSourceComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(allFileCategoriesRadioButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(selectedFileCategoriesButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(pictureVideoCheckbox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(documentsCheckbox)
.addContainerGap())
);
}// </editor-fold>//GEN-END:initComponents
private void allDataSourcesRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allDataSourcesRadioButtonActionPerformed
selectDataSourceComboBox.setEnabled(!allDataSourcesRadioButton.isSelected());
singleDataSource = false;
}//GEN-LAST:event_allDataSourcesRadioButtonActionPerformed
private void selectedFileCategoriesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectedFileCategoriesButtonActionPerformed
//When the selectedFileCategoriesButton is selected enable its related options
//and notify observers that the panel has changed incase the current settings are invalid
pictureVideoCheckbox.setEnabled(true);
documentsCheckbox.setEnabled(true);
fileTypeFilterObservable.notifyObservers();
}//GEN-LAST:event_selectedFileCategoriesButtonActionPerformed
private void withinDataSourceRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_withinDataSourceRadioButtonActionPerformed
withinDataSourceSelected(withinDataSourceRadioButton.isSelected());
}//GEN-LAST:event_withinDataSourceRadioButtonActionPerformed
private void allFileCategoriesRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allFileCategoriesRadioButtonActionPerformed
//When the allFileCategoriesRadioButton is selected disable the options
//related to selected file categories and notify observers that the panel has changed
//incase the current settings are invalid
pictureVideoCheckbox.setEnabled(false);
documentsCheckbox.setEnabled(false);
fileTypeFilterObservable.notifyObservers();
}//GEN-LAST:event_allFileCategoriesRadioButtonActionPerformed
private void withinDataSourceSelected(boolean selected) {
selectDataSourceComboBox.setEnabled(selected);
private void onlySpecificDataSourceCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_onlySpecificDataSourceCheckboxActionPerformed
//When the onlySpecificDataSourceCheckbox is clicked update its related options
selectDataSourceComboBox.setEnabled(onlySpecificDataSourceCheckbox.isSelected());
if (selectDataSourceComboBox.isEnabled()) {
selectDataSourceComboBox.setSelectedIndex(0);
singleDataSource = true;
}
}
}//GEN-LAST:event_onlySpecificDataSourceCheckboxActionPerformed
private void pictureVideoCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pictureVideoCheckboxActionPerformed
//notify observers that the panel has changed incase the current settings are invalid
fileTypeFilterObservable.notifyObservers();
}//GEN-LAST:event_pictureVideoCheckboxActionPerformed
private void documentsCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_documentsCheckboxActionPerformed
//notify observers that the panel has changed incase the current settings are invalid
fileTypeFilterObservable.notifyObservers();
}//GEN-LAST:event_documentsCheckboxActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JRadioButton allDataSourcesRadioButton;
private javax.swing.JRadioButton allFileCategoriesRadioButton;
private javax.swing.ButtonGroup buttonGroup;
private javax.swing.JLabel categoriesLabel;
private javax.swing.JCheckBox documentsCheckbox;
private javax.swing.JCheckBox onlySpecificDataSourceCheckbox;
private javax.swing.JCheckBox pictureVideoCheckbox;
private javax.swing.JComboBox<String> selectDataSourceComboBox;
private javax.swing.JRadioButton withinDataSourceRadioButton;
private javax.swing.JRadioButton selectedFileCategoriesButton;
// End of variables declaration//GEN-END:variables
void setDataModel(DataSourceComboBoxModel dataSourceComboBoxModel) {
/**
* Set the datamodel for the combo box which displays the data sources in
* the current case
*
* @param dataSourceComboBoxModel the DataSourceComboBoxModel to use
*/
void setDatasourceComboboxModel(DataSourceComboBoxModel dataSourceComboBoxModel) {
this.dataSourcesList = dataSourceComboBoxModel;
this.selectDataSourceComboBox.setModel(dataSourcesList);
}
void rigForMultipleDataSources(boolean multipleDataSources) {
this.withinDataSourceRadioButton.setEnabled(multipleDataSources);
this.allDataSourcesRadioButton.setSelected(!multipleDataSources);
this.withinDataSourceRadioButton.setSelected(multipleDataSources);
this.withinDataSourceSelected(multipleDataSources);
}
/**
* Update the map of datasources that this panel allows the user to select
* from
*
* @param dataSourceMap A map of datasources
*/
void setDataSourceMap(Map<Long, String> dataSourceMap) {
this.dataSourceMap.clear();
this.dataSourceMap.putAll(dataSourceMap);

View File

@ -20,13 +20,14 @@
package org.sleuthkit.autopsy.commonfilesearch;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance.Type;
/**
*
@ -42,10 +43,12 @@ public class SingleInterCaseCommonAttributeSearcher extends InterCaseCommonAttri
* @param correlationCaseId
* @param filterByMediaMimeType
* @param filterByDocMimeType
*
* @throws EamDbException
*/
public SingleInterCaseCommonAttributeSearcher(int correlationCaseId, Map<Long, String> dataSourceIdMap, boolean filterByMediaMimeType, boolean filterByDocMimeType, int percentageThreshold) throws EamDbException {
super(dataSourceIdMap,filterByMediaMimeType, filterByDocMimeType, percentageThreshold);
public SingleInterCaseCommonAttributeSearcher(int correlationCaseId, Map<Long, String> dataSourceIdMap, boolean filterByMediaMimeType,
boolean filterByDocMimeType, Type corAttrType, int percentageThreshold) throws EamDbException {
super(dataSourceIdMap, filterByMediaMimeType, filterByDocMimeType, corAttrType, percentageThreshold);
this.corrleationCaseId = correlationCaseId;
this.correlationCaseName = "";
@ -55,32 +58,39 @@ public class SingleInterCaseCommonAttributeSearcher extends InterCaseCommonAttri
* Collect metadata required to render the tree table where matches must
* occur in the case with the given ID.
*
* @param correlationCaseId id of case where matches must occur (no other matches will be shown)
* @param correlationCaseId id of case where matches must occur (no other
* matches will be shown)
*
* @return business object needed to populate tree table with results
*
* @throws TskCoreException
* @throws NoCurrentCaseException
* @throws SQLException
* @throws EamDbException
*/
@Override
public CommonAttributeSearchResults findFiles() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException {
public CommonAttributeSearchResults findMatches() throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException {
CorrelationCase cCase = this.getCorrelationCaseFromId(this.corrleationCaseId);
correlationCaseName = cCase.getDisplayName();
this.correlationCaseName = cCase.getDisplayName();
return this.findFiles(cCase);
}
CommonAttributeSearchResults findFiles(CorrelationCase correlationCase) throws TskCoreException, NoCurrentCaseException, SQLException, EamDbException {
InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor(this.getDataSourceIdToNameMap());
InterCaseSearchResultsProcessor eamDbAttrInst = new InterCaseSearchResultsProcessor(this.getDataSourceIdToNameMap(), this.corAttrType);
Map<Integer, CommonAttributeValueList> interCaseCommonFiles = eamDbAttrInst.findSingleInterCaseCommonAttributeValues(Case.getCurrentCase(), correlationCase);
return new CommonAttributeSearchResults(interCaseCommonFiles, this.frequencyPercentageThreshold);
return new CommonAttributeSearchResults(interCaseCommonFiles, this.frequencyPercentageThreshold, this.corAttrType);
}
@NbBundle.Messages({
"# {0} - case name",
"# {1} - attr type",
"# {2} - threshold string",
"SingleInterCaseCommonAttributeSearcher.buildTabTitle.titleInterSingle=Common Properties (Central Repository Case: {0}, {1}{2})"})
@Override
String buildTabTitle() {
final String buildCategorySelectionString = this.buildCategorySelectionString();
final String titleTemplate = Bundle.AbstractCommonFilesMetadataBuilder_buildTabTitle_titleInterSingle();
return String.format(titleTemplate, new Object[]{correlationCaseName, buildCategorySelectionString});
String getTabTitle() {
return Bundle.SingleInterCaseCommonAttributeSearcher_buildTabTitle_titleInterSingle(this.correlationCaseName, this.corAttrType.getDisplayName(), this.getPercentThresholdString());
}
}

View File

@ -20,6 +20,7 @@
package org.sleuthkit.autopsy.commonfilesearch;
import java.util.Map;
import org.openide.util.NbBundle;
import org.sleuthkit.datamodel.TskData.FileKnown;
/**
@ -34,7 +35,9 @@ final public class SingleIntraCaseCommonAttributeSearcher extends IntraCaseCommo
/**
* Implements the algorithm for getting common files that appear at least
* once in the given data source
* @param dataSourceId data source id for which common files must appear at least once
*
* @param dataSourceId data source id for which common files must
* appear at least once
* @param dataSourceIdMap a map of obj_id to datasource name
* @param filterByMediaMimeType match only on files whose mime types can be
* broadly categorized as media types
@ -53,10 +56,13 @@ final public class SingleIntraCaseCommonAttributeSearcher extends IntraCaseCommo
return String.format(SingleIntraCaseCommonAttributeSearcher.WHERE_CLAUSE, args);
}
@NbBundle.Messages({
"# {0} - data source name",
"# {1} - build category",
"# {2} - threshold string",
"SingleIntraCaseCommonAttributeSearcher.buildTabTitle.titleIntraSingle=Common Properties (Data Source: {0}, {1}{2})"})
@Override
public String buildTabTitle() {
final String buildCategorySelectionString = this.buildCategorySelectionString();
final String titleTemplate = Bundle.AbstractCommonFilesMetadataBuilder_buildTabTitle_titleIntraSingle();
return String.format(titleTemplate, new Object[]{this.dataSourceName, buildCategorySelectionString});
String getTabTitle() {
return Bundle.SingleIntraCaseCommonAttributeSearcher_buildTabTitle_titleIntraSingle(this.dataSourceName, this.buildCategorySelectionString(), this.getPercentThresholdString());
}
}

View File

@ -18,10 +18,12 @@
*/
package org.sleuthkit.autopsy.communications;
import java.util.List;
import java.util.TimeZone;
import java.util.logging.Level;
import org.apache.commons.lang3.StringUtils;
import org.openide.nodes.Sheet;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
@ -37,6 +39,7 @@ import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHO
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT;
import static org.sleuthkit.datamodel.BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME;
import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.TimeUtilities;
import org.sleuthkit.datamodel.TskCoreException;
@ -57,6 +60,7 @@ final class RelationshipNode extends BlackboardArtifactNode {
@Override
protected Sheet createSheet() {
Sheet sheet = new Sheet();
List<Tag> tags = getAllTagsFromDatabase();
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
if (sheetSet == null) {
sheetSet = Sheet.createPropertiesSet();
@ -64,7 +68,10 @@ final class RelationshipNode extends BlackboardArtifactNode {
}
sheetSet.put(new NodeProperty<>("Type", "Type", "Type", getDisplayName()));
CorrelationAttributeInstance correlationAttribute = getCorrelationAttributeInstance();
addScoreProperty(sheetSet, tags);
addCommentProperty(sheetSet, tags, correlationAttribute);
addCountProperty(sheetSet, correlationAttribute);
final BlackboardArtifact artifact = getArtifact();
BlackboardArtifact.ARTIFACT_TYPE fromID = BlackboardArtifact.ARTIFACT_TYPE.fromID(getArtifact().getArtifactTypeID());
if (null != fromID) {
@ -113,8 +120,7 @@ final class RelationshipNode extends BlackboardArtifactNode {
break;
}
}
addTagProperty(sheetSet);
addTagProperty(sheetSet, tags);
return sheet;
}

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.7" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[100, 58]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
<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="jScrollPane5" alignment="0" pref="907" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="jScrollPane5" alignment="1" pref="435" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="jScrollPane5">
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTextPane" name="jTextPane1">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="name" type="java.lang.String" value="" noResource="true"/>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[600, 52]"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@ -0,0 +1,466 @@
/*
* 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.contentviewers;
import java.awt.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import org.apache.commons.lang3.StringEscapeUtils;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
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.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Annotations view of file contents.
*/
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
@ServiceProvider(service = DataContentViewer.class, position = 8)
@NbBundle.Messages({
"AnnotationsContentViewer.title=Annotations",
"AnnotationsContentViewer.toolTip=Displays tags and comments associated with the selected content."
})
public class AnnotationsContentViewer extends javax.swing.JPanel implements DataContentViewer {
private static final Logger logger = Logger.getLogger(AnnotationsContentViewer.class.getName());
/**
* Creates an instance of AnnotationsContentViewer.
*/
public AnnotationsContentViewer() {
initComponents();
Utilities.configureTextPaneAsHtml(jTextPane1);
}
@Override
public void setNode(Node node) {
if ((node == null) || (!isSupported(node))) {
resetComponent();
return;
}
StringBuilder html = new StringBuilder();
BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class);
Content sourceFile = null;
try {
if (artifact != null) {
/*
* Get the source content based on the artifact to ensure we
* display the correct data instead of whatever was in the node.
*/
sourceFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID());
} else {
/*
* No artifact is present, so get the content based on what's
* present in the node. In this case, the selected item IS the
* source file.
*/
sourceFile = node.getLookup().lookup(AbstractFile.class);
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format(
"Exception while trying to retrieve a Content instance from the BlackboardArtifact '%s' (id=%d).",
artifact.getDisplayName(), artifact.getArtifactID()), ex);
}
if (artifact != null) {
populateTagData(html, artifact, sourceFile);
} else {
populateTagData(html, sourceFile);
}
if (sourceFile instanceof AbstractFile) {
populateCentralRepositoryData(html, artifact, (AbstractFile) sourceFile);
}
setText(html.toString());
jTextPane1.setCaretPosition(0);
}
/**
* Populate the "Selected Item" sections with tag data for the supplied
* content.
*
* @param html The HTML text to update.
* @param content Selected content.
*/
private void populateTagData(StringBuilder html, Content content) {
try {
SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
startSection(html, "Selected Item");
List<ContentTag> fileTagsList = tskCase.getContentTagsByContent(content);
if (fileTagsList.isEmpty()) {
addMessage(html, "There are no tags for the selected content.");
} else {
for (ContentTag tag : fileTagsList) {
addTagEntry(html, tag);
}
}
endSection(html);
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Exception while getting tags from the case database.", ex); //NON-NLS
}
}
/**
* Populate the "Selected Item" and "Source File" sections with tag data for
* a supplied artifact.
*
* @param html The HTML text to update.
* @param artifact A selected artifact.
* @param sourceFile The source content of the selected artifact.
*/
private void populateTagData(StringBuilder html, BlackboardArtifact artifact, Content sourceFile) {
try {
SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
startSection(html, "Selected Item");
List<BlackboardArtifactTag> artifactTagsList = tskCase.getBlackboardArtifactTagsByArtifact(artifact);
if (artifactTagsList.isEmpty()) {
addMessage(html, "There are no tags for the selected artifact.");
} else {
for (BlackboardArtifactTag tag : artifactTagsList) {
addTagEntry(html, tag);
}
}
endSection(html);
if (sourceFile != null) {
startSection(html, "Source File");
List<ContentTag> fileTagsList = tskCase.getContentTagsByContent(sourceFile);
if (fileTagsList.isEmpty()) {
addMessage(html, "There are no tags for the source content.");
} else {
for (ContentTag tag : fileTagsList) {
addTagEntry(html, tag);
}
}
endSection(html);
}
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Exception while getting open case.", ex); // NON-NLS
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Exception while getting tags from the case database.", ex); //NON-NLS
}
}
/**
* Populate the "Central Repository Comments" section with data.
*
* @param html The HTML text to update.
* @param artifact A selected artifact (can be null).
* @param sourceFile A selected file, or a source file of the selected
* artifact.
*/
private void populateCentralRepositoryData(StringBuilder html, BlackboardArtifact artifact, AbstractFile sourceFile) {
if (EamDb.isEnabled()) {
startSection(html, "Central Repository Comments");
List<CorrelationAttributeInstance> instancesList = new ArrayList<>();
if (artifact != null) {
instancesList.addAll(EamArtifactUtil.makeInstancesFromBlackboardArtifact(artifact, false));
}
try {
List<CorrelationAttributeInstance.Type> artifactTypes = EamDb.getInstance().getDefinedCorrelationTypes();
String md5 = sourceFile.getMd5Hash();
if (md5 != null && !md5.isEmpty() && null != artifactTypes && !artifactTypes.isEmpty()) {
for (CorrelationAttributeInstance.Type attributeType : artifactTypes) {
if (attributeType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) {
CorrelationCase correlationCase = EamDb.getInstance().getCase(Case.getCurrentCase());
instancesList.add(new CorrelationAttributeInstance(
md5,
attributeType,
correlationCase,
CorrelationDataSource.fromTSKDataSource(correlationCase, sourceFile.getDataSource()),
sourceFile.getParentPath() + sourceFile.getName(),
"",
sourceFile.getKnown()));
break;
}
}
}
boolean commentDataFound = false;
for (CorrelationAttributeInstance instance : instancesList) {
List<CorrelationAttributeInstance> correlatedInstancesList =
EamDb.getInstance().getArtifactInstancesByTypeValue(instance.getCorrelationType(), instance.getCorrelationValue());
for (CorrelationAttributeInstance correlatedInstance : correlatedInstancesList) {
if (correlatedInstance.getComment() != null && correlatedInstance.getComment().isEmpty() == false) {
commentDataFound = true;
addCentralRepositoryEntry(html, correlatedInstance);
}
}
}
if (commentDataFound == false) {
addMessage(html, "There is no comment data for the selected content in the central repository.");
}
} catch (EamDbException | TskCoreException ex) {
logger.log(Level.SEVERE, "Error connecting to the central repository database.", ex); // NON-NLS
} catch (CorrelationAttributeNormalizationException ex) {
logger.log(Level.SEVERE, "Error normalizing instance from repository database.", ex); // NON-NLS
}
endSection(html);
}
}
/**
* Set the text of the text panel.
*
* @param text The text to set to the text panel.
*/
private void setText(String text) {
jTextPane1.setText("<html><body>" + text + "</body></html>"); //NON-NLS
}
/**
* Start a new data section.
*
* @param html The HTML text to add the section to.
* @param sectionName The name of the section.
*/
private void startSection(StringBuilder html, String sectionName) {
html.append("<p style=\"font-size:14px;font-weight:bold;\">")
.append(sectionName)
.append("</p><br>"); //NON-NLS
}
/**
* Add a message.
*
* @param html The HTML text to add the message to.
* @param message The message text.
*/
private void addMessage(StringBuilder html, String message) {
html.append("<p style=\"font-style:italic;\">")
.append(message)
.append("</p><br>"); //NON-NLS
}
/**
* Add a data table containing information about a tag.
*
* @param html The HTML text to add the table to.
* @param tag The tag whose information will be used to populate the table.
*/
@NbBundle.Messages({
"AnnotationsContentViewer.tagEntryDataLabel.tag=Tag:",
"AnnotationsContentViewer.tagEntryDataLabel.tagUser=Tag User:",
"AnnotationsContentViewer.tagEntryDataLabel.comment=Comment:"
})
private void addTagEntry(StringBuilder html, Tag tag) {
startTable(html);
addRow(html, Bundle.AnnotationsContentViewer_tagEntryDataLabel_tag(), tag.getName().getDisplayName());
addRow(html, Bundle.AnnotationsContentViewer_tagEntryDataLabel_tagUser(), tag.getUserName());
addRow(html, Bundle.AnnotationsContentViewer_tagEntryDataLabel_comment(), formatHtmlString(tag.getComment()));
endTable(html);
}
/**
* Add a data table containing information about a correlation attribute
* instance in the central repository.
*
* @param html The HTML text to add the table to.
* @param attributeInstance The attribute instance whose information will be
* used to populate the table.
* @param correlationType The correlation data type.
*/
@NbBundle.Messages({
"AnnotationsContentViewer.centralRepositoryEntryDataLabel.case=Case:",
"AnnotationsContentViewer.centralRepositoryEntryDataLabel.type=Type:",
"AnnotationsContentViewer.centralRepositoryEntryDataLabel.comment=Comment:",
"AnnotationsContentViewer.centralRepositoryEntryDataLabel.path=Path:"
})
private void addCentralRepositoryEntry(StringBuilder html, CorrelationAttributeInstance attributeInstance) {
startTable(html);
addRow(html, Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_case(), attributeInstance.getCorrelationCase().getDisplayName());
addRow(html, Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_type(), attributeInstance.getCorrelationType().getDisplayName());
addRow(html, Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_comment(), formatHtmlString(attributeInstance.getComment()));
addRow(html, Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_path(), attributeInstance.getFilePath());
endTable(html);
}
/**
* Start a data table.
*
* @param html The HTML text to add the table to.
*/
private void startTable(StringBuilder html) {
html.append("<table>"); //NON-NLS
}
/**
* Add a data row to a table.
*
* @param html The HTML text to add the row to.
* @param key The key for the left column of the data row.
* @param value The value for the right column of the data row.
*/
private void addRow(StringBuilder html, String key, String value) {
html.append("<tr><td valign=\"top\">"); //NON-NLS
html.append(key);
html.append("</td><td>"); //NON-NLS
html.append(value);
html.append("</td></tr>"); //NON-NLS
}
/**
* End a data table.
*
* @param html The HTML text on which to end a table.
*/
private void endTable(StringBuilder html) {
html.append("</table><br><br>"); //NON-NLS
}
/**
* End a data section.
*
* @param html The HTML text on which to end a section.
*/
private void endSection(StringBuilder html) {
html.append("<br>"); //NON-NLS
}
/**
* Apply escape sequence to special characters. Line feed and carriage
* return character combinations will be converted to HTML line breaks.
*
* @param text The text to format.
* @return The formatted text.
*/
private String formatHtmlString(String text) {
String formattedString = StringEscapeUtils.escapeHtml4(text);
return formattedString.replaceAll("(\r\n|\r|\n|\n\r)", "<br>");
}
/**
* 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() {
jScrollPane5 = new javax.swing.JScrollPane();
jTextPane1 = new javax.swing.JTextPane();
setPreferredSize(new java.awt.Dimension(100, 58));
jTextPane1.setEditable(false);
jTextPane1.setName(""); // NOI18N
jTextPane1.setPreferredSize(new java.awt.Dimension(600, 52));
jScrollPane5.setViewportView(jTextPane1);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jScrollPane5, javax.swing.GroupLayout.DEFAULT_SIZE, 907, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(jScrollPane5, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 435, Short.MAX_VALUE)
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JScrollPane jScrollPane5;
private javax.swing.JTextPane jTextPane1;
// End of variables declaration//GEN-END:variables
@Override
public String getTitle() {
return Bundle.AnnotationsContentViewer_title();
}
@Override
public String getToolTip() {
return Bundle.AnnotationsContentViewer_toolTip();
}
@Override
public DataContentViewer createInstance() {
return new AnnotationsContentViewer();
}
@Override
public boolean isSupported(Node node) {
BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class);
try {
if (artifact != null) {
if (artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()) != null) {
return true;
}
} else {
if (node.getLookup().lookup(AbstractFile.class) != null) {
return true;
}
}
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, String.format(
"Exception while trying to retrieve a Content instance from the BlackboardArtifact '%s' (id=%d).",
artifact.getDisplayName(), artifact.getArtifactID()), ex);
}
return false;
}
@Override
public int isPreferred(Node node) {
return 1;
}
@Override
public Component getComponent() {
return this;
}
@Override
public void resetComponent() {
setText("");
}
}

View File

@ -36,6 +36,7 @@ import org.openide.nodes.Node;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
import org.sleuthkit.autopsy.corecomponents.DataResultPanel;
import org.sleuthkit.autopsy.corecomponents.TableFilterNode;
@ -65,6 +66,7 @@ import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHO
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TskCoreException;
/**
@ -723,13 +725,19 @@ public class MessageContentViewer extends javax.swing.JPanel implements DataCont
sheetSet = Sheet.createPropertiesSet();
sheet.put(sheetSet);
}
List<ContentTag> tags = getContentTagsFromDatabase();
AbstractFile file = getContent();
sheetSet.put(new NodeProperty<>("Name", "Name", "Name", file.getName()));
CorrelationAttributeInstance correlationAttribute = getCorrelationAttributeInstance();
addScoreProperty(sheetSet, tags);
addCommentProperty(sheetSet, tags, correlationAttribute);
addCountProperty(sheetSet, correlationAttribute);
sheetSet.put(new NodeProperty<>("Size", "Size", "Size", file.getSize()));
sheetSet.put(new NodeProperty<>("Mime Type", "Mime Type", "Mime Type", StringUtils.defaultString(file.getMIMEType())));
sheetSet.put(new NodeProperty<>("Known", "Known", "Known", file.getKnown().getName()));
addTagProperty(sheetSet);
addTagProperty(sheetSet, tags);
return sheet;
}
}

View File

@ -41,6 +41,7 @@ public class AutopsyOptionProcessor extends OptionProcessor {
private static final Logger logger = Logger.getLogger(AutopsyOptionProcessor.class.getName());
private final Option liveAutopsyOption = Option.optionalArgument('l', "liveAutopsy");
// @@@ We should centralize where we store this. It is defined in 2 other places.
private final static String PROP_BASECASE = "LBL_BaseCase_PATH";
@ -56,13 +57,20 @@ public class AutopsyOptionProcessor extends OptionProcessor {
if(values.containsKey(liveAutopsyOption)){
try {
RuntimeProperties.setRunningInTarget(true);
String[] dir= values.get(liveAutopsyOption);
String directory = dir == null ? PlatformUtil.getUserDirectory().toString() : dir[0];
ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE, directory);
// get the starting folder to store cases in
String[] argDirs= values.get(liveAutopsyOption);
String startingCaseDir;
if (argDirs == null || argDirs.length == 0) {
startingCaseDir = PlatformUtil.getUserDirectory().toString();
}
else {
startingCaseDir = argDirs[0];
}
ModuleSettings.setConfigSetting(ModuleSettings.MAIN_SETTINGS, PROP_BASECASE, startingCaseDir);
} catch (RuntimeProperties.RuntimePropertiesException ex) {
logger.log(Level.SEVERE, ex.getMessage(), ex);
}
}
}
}

View File

@ -77,7 +77,7 @@
</folder>
<folder name="Tools">
<file name="org-sleuthkit-autopsy-filesearch-FileSearchAction.instance"/>
<file name="org-sleuthkit-autopsy-commonfilesearch-CommonFilesSearchAction.instance"/>
<file name="org-sleuthkit-autopsy-commonfilesearch-CommonAttributeSearchAction.instance"/>
<file name="org-sleuthkit-autopsy-ingest-IngestMessagesAction.instance">
<attr name="delegate" newvalue="org.sleuthkit.autopsy.ingest.IngestMessagesAction"/>
</file>
@ -198,7 +198,7 @@
<file name="org-netbeans-modules-templates-actions-TemplatesAction.shadow_hidden"/>
<file name="org-openide-actions-ToolsAction.shadow_hidden"/>
<file name="org-sleuthkit-autopsy-commonfilesearch-CommonFilesAction.shadow">
<attr name="originalFile" stringvalue="Actions/Tools/org-sleuthkit-autopsy-commonfilesearch-CommonFilesSearchAction.instance"/>
<attr name="originalFile" stringvalue="Actions/Tools/org-sleuthkit-autopsy-commonfilesearch-CommonAttributeSearchAction.instance"/>
<attr name="position" intvalue="103"/>
</file>
<file name="org-sleuthkit-autopsy-filesearch-FileSearchAction.shadow">

View File

@ -26,6 +26,7 @@ import java.awt.Graphics;
import java.awt.dnd.DnDConstants;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.FeatureDescriptor;
import java.beans.PropertyVetoException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
@ -37,8 +38,10 @@ import java.util.TreeMap;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.prefs.Preferences;
import javax.swing.ImageIcon;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import static javax.swing.SwingConstants.CENTER;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
@ -58,12 +61,14 @@ import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.Node.Property;
import org.openide.util.ImageUtilities;
import org.openide.util.NbBundle;
import org.openide.util.NbPreferences;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.autopsy.datamodel.NodeSelectionInfo;
/**
@ -82,6 +87,13 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
private static final long serialVersionUID = 1L;
private static final Logger LOGGER = Logger.getLogger(DataResultViewerTable.class.getName());
private static final String NOTEPAD_ICON_PATH = "org/sleuthkit/autopsy/images/notepad16.png";
private static final String RED_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/red-circle-exclamation.png";
private static final String YELLOW_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/yellow-circle-yield.png";
private static final ImageIcon COMMENT_ICON = new ImageIcon(ImageUtilities.loadImage(NOTEPAD_ICON_PATH, false));
private static final ImageIcon INTERESTING_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(YELLOW_CIRCLE_ICON_PATH, false));
private static final ImageIcon NOTABLE_ICON_SCORE = new ImageIcon(ImageUtilities.loadImage(RED_CIRCLE_ICON_PATH, false));
@NbBundle.Messages("DataResultViewerTable.firstColLbl=Name")
static private final String FIRST_COLUMN_LABEL = Bundle.DataResultViewerTable_firstColLbl();
static private final Color TAGGED_ROW_COLOR = new Color(255, 255, 195);
@ -90,6 +102,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
private final Map<Integer, Property<?>> propertiesMap;
private final Outline outline;
private final TableListener outlineViewListener;
private final IconRendererTableListener iconRendererListener;
private Node rootNode;
/**
@ -103,7 +116,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
this(null, Bundle.DataResultViewerTable_title());
}
/**
* Constructs a tabular result viewer that displays the children of a given
* root node using an OutlineView. The viewer should have an ancestor top
@ -157,6 +169,9 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
outlineViewListener = new TableListener();
outline.getColumnModel().addColumnModelListener(outlineViewListener);
iconRendererListener = new IconRendererTableListener();
outline.getColumnModel().addColumnModelListener(iconRendererListener);
/*
* Add a mouse listener to the child OutlineView (explorer view) to make
* sure the first column of the table is kept in place.
@ -180,7 +195,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
/**
* Gets the title of this tabular result viewer.
* @return
*
* @return title of tab.
*/
@Override
@NbBundle.Messages("DataResultViewerTable.title=Table")
@ -188,7 +204,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
return title;
}
/**
* Indicates whether a given node is supported as a root node for this
* tabular viewer.
@ -254,9 +269,9 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
}
/**
* Sets up the Outline view of this tabular result viewer by creating
* column headers based on the children of the current root node. The
* persisted column order, sorting and visibility is used.
* Sets up the Outline view of this tabular result viewer by creating column
* headers based on the children of the current root node. The persisted
* column order, sorting and visibility is used.
*/
private void setupTable() {
/*
@ -347,12 +362,13 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
* treated as un-hide/hide.
*/
outlineViewListener.listenToVisibilityChanges(true);
}
/*
* Populates the column map for the child OutlineView of this tabular
* result viewer with references to the column objects for use when
* loading/storing the visibility info.
* Populates the column map for the child OutlineView of this tabular result
* viewer with references to the column objects for use when loading/storing
* the visibility info.
*/
private void populateColumnMap() {
columnMap.clear();
@ -364,6 +380,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
if (entry.getKey() < columnCount) {
final ETableColumn column = (ETableColumn) columnModel.getColumn(entry.getKey());
columnMap.put(propName, column);
}
}
}
@ -640,6 +657,57 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
}
}
/**
* Listener which sets the custom icon renderer on columns which contain
* icons instead of text when a column is added.
*/
private class IconRendererTableListener implements TableColumnModelListener {
@NbBundle.Messages({"DataResultViewerTable.commentRender.name=C",
"DataResultViewerTable.scoreRender.name=S",
"DataResultViewerTable.countRender.name=O"})
@Override
public void columnAdded(TableColumnModelEvent e) {
if (e.getSource() instanceof ETableColumnModel) {
TableColumn column = ((TableColumnModel) e.getSource()).getColumn(e.getToIndex());
if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_commentRender_name())) {
//if the current column is a comment column set the cell renderer to be the HasCommentCellRenderer
column.setCellRenderer(new HasCommentCellRenderer());
} else if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_scoreRender_name())) {
//if the current column is a score column set the cell renderer to be the ScoreCellRenderer
column.setCellRenderer(new ScoreCellRenderer());
} else if (column.getHeaderValue().toString().equals(Bundle.DataResultViewerTable_countRender_name())) {
column.setCellRenderer(new CountCellRenderer());
}
}
}
@Override
public void columnRemoved(TableColumnModelEvent e
) {
//Don't do anything when column removed
}
@Override
public void columnMoved(TableColumnModelEvent e
) {
//Don't do anything when column moved
}
@Override
public void columnMarginChanged(ChangeEvent e
) {
//Don't do anything when column margin changed
}
@Override
public void columnSelectionChanged(ListSelectionEvent e
) {
//Don't do anything when column selection changed
}
}
/**
* Listens to mouse events and table column events and persists column order
* sorting, and visibility changes.
@ -801,6 +869,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
try {
tagFound = !prop.getValue().equals("");
} catch (IllegalAccessException | InvocationTargetException ignore) {
//if unable to get the tags property value, treat it like it not having a comment
}
break;
}
@ -816,6 +885,174 @@ public class DataResultViewerTable extends AbstractDataResultViewer {
}
}
/*
* A renderer which based on the contents of the cell will display an icon
* to indicate the presence of a comment related to the content.
*/
private final class HasCommentCellRenderer extends ColorTagCustomRenderer {
private static final long serialVersionUID = 1L;
@NbBundle.Messages({"DataResultViewerTable.commentRenderer.crComment.toolTip=Comment exists in Central Repository",
"DataResultViewerTable.commentRenderer.tagComment.toolTip=Comment exists on associated tag(s)",
"DataResultViewerTable.commentRenderer.crAndTagComment.toolTip=Comments exist both in Central Repository and on associated tag(s)",
"DataResultViewerTable.commentRenderer.noComment.toolTip=No comments found"})
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
setBackground(component.getBackground()); //inherit highlighting
setHorizontalAlignment(CENTER);
Object switchValue = null;
if ((value instanceof NodeProperty)) {
//The Outline view has properties in the cell, the value contained in the property is what we want
try {
switchValue = ((Node.Property) value).getValue();
} catch (IllegalAccessException | InvocationTargetException ex) {
//Unable to get the value from the NodeProperty no Icon will be displayed
}
} else {
//JTables contain the value we want directly in the cell
switchValue = value;
}
setText("");
if ((switchValue instanceof HasCommentStatus)) {
switch ((HasCommentStatus) switchValue) {
case CR_COMMENT:
setIcon(COMMENT_ICON);
setToolTipText(Bundle.DataResultViewerTable_commentRenderer_crComment_toolTip());
break;
case TAG_COMMENT:
setIcon(COMMENT_ICON);
setToolTipText(Bundle.DataResultViewerTable_commentRenderer_tagComment_toolTip());
break;
case CR_AND_TAG_COMMENTS:
setIcon(COMMENT_ICON);
setToolTipText(Bundle.DataResultViewerTable_commentRenderer_crAndTagComment_toolTip());
break;
case TAG_NO_COMMENT:
case NO_COMMENT:
default:
setIcon(null);
setToolTipText(Bundle.DataResultViewerTable_commentRenderer_noComment_toolTip());
}
} else {
setIcon(null);
}
return this;
}
}
/*
* A renderer which based on the contents of the cell will display an icon
* to indicate the score associated with the item.
*/
private final class ScoreCellRenderer extends ColorTagCustomRenderer {
private static final long serialVersionUID = 1L;
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
setBackground(component.getBackground()); //inherit highlighting
setHorizontalAlignment(CENTER);
Object switchValue = null;
if ((value instanceof NodeProperty)) {
//The Outline view has properties in the cell, the value contained in the property is what we want
try {
switchValue = ((Node.Property) value).getValue();
setToolTipText(((FeatureDescriptor) value).getShortDescription());
} catch (IllegalAccessException | InvocationTargetException ex) {
//Unable to get the value from the NodeProperty no Icon will be displayed
}
} else {
//JTables contain the value we want directly in the cell
switchValue = value;
}
setText("");
if ((switchValue instanceof Score)) {
switch ((Score) switchValue) {
case INTERESTING_SCORE:
setIcon(INTERESTING_SCORE_ICON);
break;
case NOTABLE_SCORE:
setIcon(NOTABLE_ICON_SCORE);
break;
case NO_SCORE:
default:
setIcon(null);
}
} else {
setIcon(null);
}
return this;
}
}
/*
* A renderer which based on the contents of the cell will display an empty
* cell if no count was available.
*/
private final class CountCellRenderer extends ColorTagCustomRenderer {
private static final long serialVersionUID = 1L;
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
setBackground(component.getBackground()); //inherit highlighting
setHorizontalAlignment(LEFT);
Object countValue = null;
if ((value instanceof NodeProperty)) {
//The Outline view has properties in the cell, the value contained in the property is what we want
try {
countValue = ((Node.Property) value).getValue();
setToolTipText(((FeatureDescriptor) value).getShortDescription());
} catch (IllegalAccessException | InvocationTargetException ex) {
//Unable to get the value from the NodeProperty no Icon will be displayed
}
} else {
//JTables contain the value we want directly in the cell
countValue = value;
}
setText("");
if ((countValue instanceof Long)) {
//Don't display value if value is negative used so that sorting will behave as desired
if ((Long) countValue >= 0) {
setText(countValue.toString());
}
}
return this;
}
}
/**
* Enum to denote the presence of a comment associated with the content or
* artifacts generated from it.
*/
public enum HasCommentStatus {
NO_COMMENT,
TAG_NO_COMMENT,
CR_COMMENT,
TAG_COMMENT,
CR_AND_TAG_COMMENTS
}
/**
* Enum to denote the score given to an item to draw the users attention
*/
public enum Score {
NO_SCORE,
INTERESTING_SCORE,
NOTABLE_SCORE
}
/**
* 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

View File

@ -413,7 +413,7 @@ public class ImageUtils {
String cacheDirectory = Case.getCurrentCaseThrows().getCacheDirectory();
return Paths.get(cacheDirectory, "thumbnails", fileID + ".png").toFile(); //NON-NLS
} catch (NoCurrentCaseException e) {
LOGGER.log(Level.WARNING, "Could not get cached thumbnail location. No case is open."); //NON-NLS
LOGGER.log(Level.INFO, "Could not get cached thumbnail location. No case is open."); //NON-NLS
return null;
}
});

View File

@ -34,17 +34,28 @@ import org.openide.util.NbBundle;
import org.openide.util.WeakListeners;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.events.CommentChangedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil;
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score;
import org.sleuthkit.autopsy.coreutils.Logger;
import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType.*;
import static org.sleuthkit.autopsy.datamodel.Bundle.*;
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.HasCommentStatus;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
/**
* An abstract node that encapsulates AbstractFile data
@ -58,7 +69,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
private static final String NO_DESCR = AbstractAbstractFileNode_addFileProperty_desc();
private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE,
Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_DELETED);
Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_DELETED, Case.Events.CR_COMMENT_CHANGED);
/**
* @param abstractFile file to wrap
@ -145,6 +156,11 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
if (event.getDeletedTagInfo().getContentID() == content.getId()) {
updateSheet();
}
} else if (eventType.equals(Case.Events.CR_COMMENT_CHANGED.toString())) {
CommentChangedEvent event = (CommentChangedEvent) evt;
if (event.getContentID() == content.getId()) {
updateSheet();
}
}
};
@ -253,6 +269,133 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
map.put(EXTENSION.toString(), content.getNameExtension());
}
/**
* Get all tags from the case database that are associated with the file
*
* @return a list of tags that are associated with the file
*/
protected final List<ContentTag> getContentTagsFromDatabase() {
List<ContentTag> tags = new ArrayList<>();
try {
tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(content));
} catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Failed to get tags for content " + content.getName(), ex);
}
return tags;
}
protected final CorrelationAttributeInstance getCorrelationAttributeInstance() {
CorrelationAttributeInstance correlationAttribute = null;
if (EamDbUtil.useCentralRepo()) {
correlationAttribute = EamArtifactUtil.getInstanceFromContent(content);
}
return correlationAttribute;
}
/**
* Used by subclasses of AbstractAbstractFileNode to add the comment
* property to their sheets.
*
* @param sheetSet the modifiable Sheet.Set returned by
* Sheet.get(Sheet.PROPERTIES)
* @param tags the list of tags associated with the file
* @param attribute the correlation attribute associated with this file,
* null if central repo is not enabled
*/
@NbBundle.Messages({"AbstractAbstractFileNode.createSheet.comment.name=C",
"AbstractAbstractFileNode.createSheet.comment.displayName=C"})
protected final void addCommentProperty(Sheet.Set sheetSet, List<ContentTag> tags, CorrelationAttributeInstance attribute) {
HasCommentStatus status = tags.size() > 0 ? HasCommentStatus.TAG_NO_COMMENT : HasCommentStatus.NO_COMMENT;
for (ContentTag tag : tags) {
if (!StringUtils.isBlank(tag.getComment())) {
//if the tag is null or empty or contains just white space it will indicate there is not a comment
status = HasCommentStatus.TAG_COMMENT;
break;
}
}
if (attribute != null && !StringUtils.isBlank(attribute.getComment())) {
if (status == HasCommentStatus.TAG_COMMENT) {
status = HasCommentStatus.CR_AND_TAG_COMMENTS;
} else {
status = HasCommentStatus.CR_COMMENT;
}
}
sheetSet.put(new NodeProperty<>(AbstractAbstractFileNode_createSheet_comment_name(), AbstractAbstractFileNode_createSheet_comment_displayName(), NO_DESCR,
status));
}
/**
* Used by subclasses of AbstractAbstractFileNode to add the Score property
* to their sheets.
*
* @param sheetSet the modifiable Sheet.Set returned by
* Sheet.get(Sheet.PROPERTIES)
* @param tags the list of tags associated with the file
*/
@NbBundle.Messages({"AbstractAbstractFileNode.createSheet.score.name=S",
"AbstractAbstractFileNode.createSheet.score.displayName=S",
"AbstractAbstractFileNode.createSheet.notableFile.description=File recognized as notable.",
"AbstractAbstractFileNode.createSheet.interestingResult.description=File has interesting result associated with it.",
"AbstractAbstractFileNode.createSheet.taggedFile.description=File has been tagged.",
"AbstractAbstractFileNode.createSheet.notableTaggedFile.description=File tagged with notable tag."})
protected final void addScoreProperty(Sheet.Set sheetSet, List<ContentTag> tags) {
Score score = Score.NO_SCORE;
String description = NO_DESCR;
if (content.getKnown() == TskData.FileKnown.BAD) {
score = Score.NOTABLE_SCORE;
description = Bundle.AbstractAbstractFileNode_createSheet_notableFile_description();
}
try {
if (score == Score.NO_SCORE && !content.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT).isEmpty()) {
score = Score.INTERESTING_SCORE;
description = Bundle.AbstractAbstractFileNode_createSheet_interestingResult_description();
}
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Error getting artifacts for file: " + content.getName(), ex);
}
if (tags.size() > 0 && (score == Score.NO_SCORE || score == Score.INTERESTING_SCORE)) {
score = Score.INTERESTING_SCORE;
description = Bundle.AbstractAbstractFileNode_createSheet_taggedFile_description();
for (ContentTag tag : tags) {
if (tag.getName().getKnownStatus() == TskData.FileKnown.BAD) {
score = Score.NOTABLE_SCORE;
description = Bundle.AbstractAbstractFileNode_createSheet_notableTaggedFile_description();
break;
}
}
}
sheetSet.put(new NodeProperty<>(Bundle.AbstractAbstractFileNode_createSheet_score_name(), Bundle.AbstractAbstractFileNode_createSheet_score_displayName(), description, score));
}
@NbBundle.Messages({"AbstractAbstractFileNode.createSheet.count.name=O",
"AbstractAbstractFileNode.createSheet.count.displayName=O",
"AbstractAbstractFileNode.createSheet.count.noCentralRepo.description=Central repository was not enabled when this column was populated",
"AbstractAbstractFileNode.createSheet.count.hashLookupNotRun.description=Hash lookup had not been run on this file when the column was populated",
"# {0} - occuranceCount",
"AbstractAbstractFileNode.createSheet.count.description=There were {0} datasource(s) found with occurances of the correlation value"})
protected final void addCountProperty(Sheet.Set sheetSet, CorrelationAttributeInstance attribute) {
Long count = -1L; //The column renderer will not display negative values, negative value used when count unavailble to preserve sorting
String description = Bundle.AbstractAbstractFileNode_createSheet_count_noCentralRepo_description();
try {
//don't perform the query if there is no correlation value
if (attribute != null && StringUtils.isNotBlank(attribute.getCorrelationValue())) {
count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(attribute.getCorrelationType(), attribute.getCorrelationValue());
description = Bundle.AbstractAbstractFileNode_createSheet_count_description(count);
} else if (attribute != null) {
description = Bundle.AbstractAbstractFileNode_createSheet_count_hashLookupNotRun_description();
}
} catch (EamDbException ex) {
logger.log(Level.WARNING, "Error getting count of datasources with correlation attribute", ex);
} catch (CorrelationAttributeNormalizationException ex) {
logger.log(Level.WARNING, "Unable to normalize data to get count of datasources with correlation attribute", ex);
}
sheetSet.put(
new NodeProperty<>(Bundle.AbstractAbstractFileNode_createSheet_count_name(), Bundle.AbstractAbstractFileNode_createSheet_count_displayName(), description, count));
}
/**
* Used by subclasses of AbstractAbstractFileNode to add the tags property
* to their sheets.
@ -261,6 +404,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
* Sheet.get(Sheet.PROPERTIES)
*/
@NbBundle.Messages("AbstractAbstractFileNode.tagsProperty.displayName=Tags")
@Deprecated
protected void addTagProperty(Sheet.Set sheetSet) {
List<ContentTag> tags = new ArrayList<>();
try {
@ -274,6 +418,21 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
.collect(Collectors.joining(", "))));
}
/**
* Used by subclasses of AbstractAbstractFileNode to add the tags property
* to their sheets.
*
* @param sheetSet the modifiable Sheet.Set returned by
* Sheet.get(Sheet.PROPERTIES)
* @param tags the list of tags associated with the file
*/
protected final void addTagProperty(Sheet.Set sheetSet, List<ContentTag> tags) {
sheetSet.put(new NodeProperty<>("Tags", AbstractAbstractFileNode_tagsProperty_displayName(),
NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName())
.distinct()
.collect(Collectors.joining(", "))));
}
private static String getContentPath(AbstractFile file) {
try {
return file.getUniquePath();

View File

@ -19,11 +19,14 @@
package org.sleuthkit.autopsy.datamodel;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.ContentTag;
/**
* Abstract class that implements the commonality between File and Directory
@ -70,11 +73,21 @@ public abstract class AbstractFsContentNode<T extends AbstractFile> extends Abst
sheetSet = Sheet.createPropertiesSet();
sheet.put(sheetSet);
}
List<ContentTag> tags = getContentTagsFromDatabase();
Map<String, Object> map = new LinkedHashMap<>();
fillPropertyMap(map, getContent());
final String NO_DESCR = Bundle.AbstractFsContentNode_noDesc_text();
//add the name property before the comment property to ensure it is first column
sheetSet.put(new NodeProperty<>(AbstractFilePropertyType.NAME.toString(),
AbstractFilePropertyType.NAME.toString(),
NO_DESCR,
getName()));
//add the cr status property before the propertyMap to ensure it is early in column order
addScoreProperty(sheetSet, tags);
//add the comment property before the propertyMap to ensure it is early in column order
CorrelationAttributeInstance correlationAttribute = getCorrelationAttributeInstance();
addCommentProperty(sheetSet, tags, correlationAttribute);
addCountProperty(sheetSet, correlationAttribute);
for (AbstractFilePropertyType propType : AbstractFilePropertyType.values()) {
final String propString = propType.toString();
sheetSet.put(new NodeProperty<>(propString, propString, NO_DESCR, map.get(propString)));
@ -84,7 +97,7 @@ public abstract class AbstractFsContentNode<T extends AbstractFile> extends Abst
}
// add tags property to the sheet
addTagProperty(sheetSet);
addTagProperty(sheetSet, tags);
return sheet;
}

View File

@ -46,11 +46,20 @@ import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent;
import org.sleuthkit.autopsy.casemodule.events.CommentChangedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil;
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import static org.sleuthkit.autopsy.datamodel.DisplayableItemNode.findLinked;
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.HasCommentStatus;
import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction;
import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction;
import org.sleuthkit.datamodel.AbstractFile;
@ -61,6 +70,7 @@ import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.Tag;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
/**
* Node wrapping a blackboard artifact object. This is generated from several
@ -73,7 +83,8 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED,
Case.Events.CONTENT_TAG_ADDED,
Case.Events.CONTENT_TAG_DELETED,
Case.Events.CURRENT_CASE);
Case.Events.CURRENT_CASE,
Case.Events.CR_COMMENT_CHANGED);
private static Cache<Long, Content> contentCache = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES).
@ -127,6 +138,11 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
if (event.getDeletedTagInfo().getContentID() == associated.getId()) {
updateSheet();
}
} else if (eventType.equals(Case.Events.CR_COMMENT_CHANGED.toString())) {
CommentChangedEvent event = (CommentChangedEvent) evt;
if (event.getContentID() == associated.getId()) {
updateSheet();
}
} else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
if (evt.getNewValue() == null) {
// case was closed. Remove listeners so that we don't get called with a stale case handle
@ -319,6 +335,8 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
@Override
protected Sheet createSheet() {
Sheet sheet = super.createSheet();
List<Tag> tags = getAllTagsFromDatabase();
Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
if (sheetSet == null) {
sheetSet = Sheet.createPropertiesSet();
@ -332,6 +350,10 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.displayName"),
NO_DESCR,
this.getSourceName()));
CorrelationAttributeInstance correlationAttribute = getCorrelationAttributeInstance();
addScoreProperty(sheetSet, tags);
addCommentProperty(sheetSet, tags, correlationAttribute);
addCountProperty(sheetSet, correlationAttribute);
if (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()) {
try {
BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
@ -479,12 +501,29 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
NO_DESCR,
path));
}
addTagProperty(sheetSet);
addTagProperty(sheetSet, tags);
return sheet;
}
/**
* Get all tags from the case database relating to the artifact and the file
* it is associated with.
*
* @return a list of tags which on the artifact or the file it is associated
* with
*/
protected final List<Tag> getAllTagsFromDatabase() {
List<Tag> tags = new ArrayList<>();
try {
tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact));
tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(associated));
} catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Failed to get tags for artifact " + artifact.getDisplayName(), ex);
}
return tags;
}
/**
* Used by (subclasses of) BlackboardArtifactNode to add the tags property
* to their sheets.
@ -494,6 +533,7 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
*/
@NbBundle.Messages({
"BlackboardArtifactNode.createSheet.tags.displayName=Tags"})
@Deprecated
protected void addTagProperty(Sheet.Set sheetSet) throws MissingResourceException {
// add properties for tags
List<Tag> tags = new ArrayList<>();
@ -507,6 +547,137 @@ public class BlackboardArtifactNode extends AbstractContentNode<BlackboardArtifa
NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName()).collect(Collectors.joining(", "))));
}
/**
* Used by (subclasses of) BlackboardArtifactNode to add the tags property
* to their sheets.
*
* @param sheetSet the modifiable Sheet.Set returned by
* Sheet.get(Sheet.PROPERTIES)
* @param tags the list of tags which should appear as the value for the
* property
*/
protected final void addTagProperty(Sheet.Set sheetSet, List<Tag> tags) {
sheetSet.put(new NodeProperty<>("Tags", Bundle.BlackboardArtifactNode_createSheet_tags_displayName(),
NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName()).collect(Collectors.joining(", "))));
}
protected final CorrelationAttributeInstance getCorrelationAttributeInstance() {
CorrelationAttributeInstance correlationAttribute = null;
if (EamDbUtil.useCentralRepo()) {
correlationAttribute = EamArtifactUtil.getInstanceFromContent(associated);
}
return correlationAttribute;
}
/**
* Used by (subclasses of) BlackboardArtifactNode to add the comment
* property to their sheets.
*
* @param sheetSet the modifiable Sheet.Set returned by
* Sheet.get(Sheet.PROPERTIES)
* @param tags the list of tags associated with the file
* @param attribute the correlation attribute associated with this
* artifact's associated file, null if central repo is not
* enabled
*/
@NbBundle.Messages({"BlackboardArtifactNode.createSheet.comment.name=C",
"BlackboardArtifactNode.createSheet.comment.displayName=C"})
protected final void addCommentProperty(Sheet.Set sheetSet, List<Tag> tags, CorrelationAttributeInstance attribute) {
HasCommentStatus status = tags.size() > 0 ? HasCommentStatus.TAG_NO_COMMENT : HasCommentStatus.NO_COMMENT;
for (Tag tag : tags) {
if (!StringUtils.isBlank(tag.getComment())) {
//if the tag is null or empty or contains just white space it will indicate there is not a comment
status = HasCommentStatus.TAG_COMMENT;
break;
}
}
//currently checks for a comment on the associated file in the central repo not the artifact itself
//what we want the column property to reflect should be revisted when we have added a way to comment
//on the artifact itself
if (attribute != null && !StringUtils.isBlank(attribute.getComment())) {
if (status == HasCommentStatus.TAG_COMMENT) {
status = HasCommentStatus.CR_AND_TAG_COMMENTS;
} else {
status = HasCommentStatus.CR_COMMENT;
}
}
sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_comment_name(), Bundle.BlackboardArtifactNode_createSheet_comment_displayName(), NO_DESCR,
status));
}
/**
* Used by (subclasses of) BlackboardArtifactNode to add the Score property
* to their sheets.
*
* @param sheetSet the modifiable Sheet.Set returned by
* Sheet.get(Sheet.PROPERTIES)
* @param tags the list of tags associated with the file
*/
@NbBundle.Messages({"BlackboardArtifactNode.createSheet.score.name=S",
"BlackboardArtifactNode.createSheet.score.displayName=S",
"BlackboardArtifactNode.createSheet.notableFile.description=Associated file recognized as notable.",
"BlackboardArtifactNode.createSheet.interestingResult.description=Result has an interesting result associated with it.",
"BlackboardArtifactNode.createSheet.taggedItem.description=Result or associated file has been tagged.",
"BlackboardArtifactNode.createSheet.notableTaggedItem.description=Result or associated file tagged with notable tag."})
protected final void addScoreProperty(Sheet.Set sheetSet, List<Tag> tags) {
Score score = Score.NO_SCORE;
String description = "";
if (associated instanceof AbstractFile) {
if (((AbstractFile) associated).getKnown() == TskData.FileKnown.BAD) {
score = Score.NOTABLE_SCORE;
description = Bundle.BlackboardArtifactNode_createSheet_notableFile_description();
}
}
try {
if (score == Score.NO_SCORE && !content.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT).isEmpty()) {
score = Score.INTERESTING_SCORE;
description = Bundle.BlackboardArtifactNode_createSheet_interestingResult_description();
}
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Error getting artifacts for artifact: " + content.getName(), ex);
}
if (tags.size() > 0 && (score == Score.NO_SCORE || score == Score.INTERESTING_SCORE)) {
score = Score.INTERESTING_SCORE;
description = Bundle.BlackboardArtifactNode_createSheet_taggedItem_description();
for (Tag tag : tags) {
if (tag.getName().getKnownStatus() == TskData.FileKnown.BAD) {
score = Score.NOTABLE_SCORE;
description = Bundle.BlackboardArtifactNode_createSheet_notableTaggedItem_description();
break;
}
}
}
sheetSet.put(new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_score_name(), Bundle.BlackboardArtifactNode_createSheet_score_displayName(), description, score));
}
@NbBundle.Messages({"BlackboardArtifactNode.createSheet.count.name=O",
"BlackboardArtifactNode.createSheet.count.displayName=O",
"BlackboardArtifactNode.createSheet.count.noCentralRepo.description=Central repository was not enabled when this column was populated",
"BlackboardArtifactNode.createSheet.count.hashLookupNotRun.description=Hash lookup had not been run on this artifact's associated file when the column was populated",
"# {0} - occuranceCount",
"BlackboardArtifactNode.createSheet.count.description=There were {0} datasource(s) found with occurances of the correlation value"})
protected final void addCountProperty(Sheet.Set sheetSet, CorrelationAttributeInstance attribute) {
Long count = -1L; //The column renderer will not display negative values, negative value used when count unavailble to preserve sorting
String description = Bundle.BlackboardArtifactNode_createSheet_count_noCentralRepo_description();
try {
//don't perform the query if there is no correlation value
if (attribute != null && StringUtils.isNotBlank(attribute.getCorrelationValue())) {
count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(attribute.getCorrelationType(), attribute.getCorrelationValue());
description = Bundle.BlackboardArtifactNode_createSheet_count_description(count);
} else if (attribute != null) {
description = Bundle.BlackboardArtifactNode_createSheet_count_hashLookupNotRun_description();
}
} catch (EamDbException ex) {
logger.log(Level.WARNING, "Error getting count of datasources with correlation attribute", ex);
}
catch (CorrelationAttributeNormalizationException ex) {
logger.log(Level.WARNING, "Unable to normalize data to get count of datasources with correlation attribute", ex);
}
sheetSet.put(
new NodeProperty<>(Bundle.BlackboardArtifactNode_createSheet_count_name(), Bundle.BlackboardArtifactNode_createSheet_count_displayName(), description, count));
}
private void updateSheet() {
this.setSheet(createSheet());
}

View File

@ -31,11 +31,13 @@ import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.sleuthkit.autopsy.actions.AddContentTagAction;
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
import org.sleuthkit.autopsy.directorytree.ExtractAction;
import org.sleuthkit.autopsy.directorytree.NewWindowViewAction;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.LayoutFile;
import org.sleuthkit.datamodel.TskData;
@ -79,6 +81,8 @@ public class LayoutFileNode extends AbstractAbstractFileNode<LayoutFile> {
sheet.put(sheetSet);
}
List<ContentTag> tags = getContentTagsFromDatabase();
Map<String, Object> map = new LinkedHashMap<>();
fillPropertyMap(map);
@ -86,14 +90,17 @@ public class LayoutFileNode extends AbstractAbstractFileNode<LayoutFile> {
NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.name.displayName"),
NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.name.desc"),
getName()));
CorrelationAttributeInstance correlationAttribute = getCorrelationAttributeInstance();
addScoreProperty(sheetSet, tags);
addCommentProperty(sheetSet, tags, correlationAttribute);
addCountProperty(sheetSet, correlationAttribute);
final String NO_DESCR = NbBundle.getMessage(this.getClass(), "LayoutFileNode.createSheet.noDescr.text");
for (Map.Entry<String, Object> entry : map.entrySet()) {
sheetSet.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue()));
}
// add tags property to the sheet
addTagProperty(sheetSet);
addTagProperty(sheetSet, tags);
return sheet;
}

View File

@ -19,9 +19,12 @@
package org.sleuthkit.autopsy.datamodel;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.LocalDirectory;
/**
@ -55,11 +58,15 @@ public class LocalDirectoryNode extends SpecialDirectoryNode {
sheet.put(sheetSet);
}
List<ContentTag> tags = getContentTagsFromDatabase();
sheetSet.put(new NodeProperty<>(Bundle.LocalDirectoryNode_createSheet_name_name(),
Bundle.LocalDirectoryNode_createSheet_name_displayName(),
Bundle.LocalDirectoryNode_createSheet_name_desc(),
getName()));
CorrelationAttributeInstance correlationAttribute = getCorrelationAttributeInstance();
addScoreProperty(sheetSet, tags);
addCommentProperty(sheetSet, tags, correlationAttribute);
addCountProperty(sheetSet, correlationAttribute);
// At present, a LocalDirectory will never be a datasource - the top level of a logical
// file set is a VirtualDirectory
Map<String, Object> map = new LinkedHashMap<>();
@ -69,7 +76,7 @@ public class LocalDirectoryNode extends SpecialDirectoryNode {
for (Map.Entry<String, Object> entry : map.entrySet()) {
sheetSet.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue()));
}
addTagProperty(sheetSet);
addTagProperty(sheetSet, tags);
return sheet;
}

View File

@ -32,6 +32,7 @@ import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.sleuthkit.autopsy.actions.AddContentTagAction;
import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.directorytree.ExternalViewerAction;
@ -41,6 +42,7 @@ import org.sleuthkit.autopsy.directorytree.ViewContextAction;
import org.sleuthkit.autopsy.modules.embeddedfileextractor.ExtractArchiveWithPasswordAction;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TskCoreException;
/**
@ -72,7 +74,7 @@ public class LocalFileNode extends AbstractAbstractFileNode<AbstractFile> {
sheetSet = Sheet.createPropertiesSet();
sheet.put(sheetSet);
}
List<ContentTag> tags = getContentTagsFromDatabase();
Map<String, Object> map = new LinkedHashMap<>();
fillPropertyMap(map, getContent());
@ -80,14 +82,17 @@ public class LocalFileNode extends AbstractAbstractFileNode<AbstractFile> {
NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.name.displayName"),
NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.name.desc"),
getName()));
CorrelationAttributeInstance correlationAttribute = getCorrelationAttributeInstance();
addScoreProperty(sheetSet, tags);
addCommentProperty(sheetSet, tags, correlationAttribute);
addCountProperty(sheetSet, correlationAttribute);
final String NO_DESCR = NbBundle.getMessage(this.getClass(), "LocalFileNode.createSheet.noDescr.text");
for (Map.Entry<String, Object> entry : map.entrySet()) {
sheetSet.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue()));
}
// add tags property to the sheet
addTagProperty(sheetSet);
addTagProperty(sheetSet, tags);
return sheet;
}

View File

@ -21,13 +21,16 @@ package org.sleuthkit.autopsy.datamodel;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.openide.nodes.Sheet;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.VirtualDirectory;
@ -79,14 +82,18 @@ public class VirtualDirectoryNode extends SpecialDirectoryNode {
sheetSet = Sheet.createPropertiesSet();
sheet.put(sheetSet);
}
List<ContentTag> tags = getContentTagsFromDatabase();
sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.createSheet.name.name"),
NbBundle.getMessage(this.getClass(),
"VirtualDirectoryNode.createSheet.name.displayName"),
NbBundle.getMessage(this.getClass(), "VirtualDirectoryNode.createSheet.name.desc"),
getName()));
if (!this.content.isDataSource()) {
CorrelationAttributeInstance correlationAttribute = getCorrelationAttributeInstance();
addScoreProperty(sheetSet, tags);
addCommentProperty(sheetSet, tags, correlationAttribute);
addCountProperty(sheetSet, correlationAttribute);
Map<String, Object> map = new LinkedHashMap<>();
fillPropertyMap(map, getContent());
@ -94,7 +101,7 @@ public class VirtualDirectoryNode extends SpecialDirectoryNode {
for (Map.Entry<String, Object> entry : map.entrySet()) {
sheetSet.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue()));
}
addTagProperty(sheetSet);
addTagProperty(sheetSet, tags);
} else {
sheetSet.put(new NodeProperty<>(Bundle.VirtualDirectoryNode_createSheet_type_name(),
Bundle.VirtualDirectoryNode_createSheet_type_displayName(),

View File

@ -23,11 +23,13 @@ import org.openide.util.NbBundle;
import static org.sleuthkit.autopsy.directorytree.Bundle.*;
@NbBundle.Messages({"SelectionContext.dataSources=Data Sources",
"SelectionContext.dataSourceFiles=Data Source Files",
"SelectionContext.views=Views"})
enum SelectionContext {
DATA_SOURCES(SelectionContext_dataSources()),
VIEWS(SelectionContext_views()),
OTHER(""); // Subnode of another node.
OTHER(""), // Subnode of another node.
DATA_SOURCE_FILES(SelectionContext_dataSourceFiles());
private final String displayName;
@ -36,7 +38,7 @@ enum SelectionContext {
}
public static SelectionContext getContextFromName(String name) {
if (name.equals(DATA_SOURCES.getName())) {
if (name.equals(DATA_SOURCES.getName()) || name.equals(DATA_SOURCE_FILES.getName())) {
return DATA_SOURCES;
} else if (name.equals(VIEWS.getName())) {
return VIEWS;
@ -64,6 +66,16 @@ enum SelectionContext {
// One level below root node. Should be one of DataSources, Views, or Results
return SelectionContext.getContextFromName(n.getDisplayName());
} else {
// In Group by Data Source mode, the node under root is the data source name, and
// under that is Data Source Files, Views, or Results. Before moving up the tree, check
// if one of those applies.
if (n.getParentNode().getParentNode().getParentNode() == null) {
SelectionContext context = SelectionContext.getContextFromName(n.getDisplayName());
if (context != SelectionContext.OTHER) {
return context;
}
}
return getSelectionContext(n.getParentNode());
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

View File

@ -26,7 +26,7 @@ import org.sleuthkit.datamodel.Content;
* Event published when analysis (ingest) of a data source included in an ingest
* job is completed.
*/
public class DataSourceAnalysisCompletedEvent extends DataSourceAnalysisEvent implements Serializable {
public final class DataSourceAnalysisCompletedEvent extends DataSourceAnalysisEvent implements Serializable {
/**
* The reason why the analysis of the data source completed.

View File

@ -26,7 +26,7 @@ import org.sleuthkit.datamodel.Content;
* Event published when analysis (ingest) of a data source included in an ingest
* job is started.
*/
public class DataSourceAnalysisStartedEvent extends DataSourceAnalysisEvent implements Serializable {
public final class DataSourceAnalysisStartedEvent extends DataSourceAnalysisEvent implements Serializable {
private static final long serialVersionUID = 1L;

View File

@ -86,6 +86,7 @@ HashDbImportDatabaseDialog.failedToGetDbPathMsg=Failed to get the path of the se
HashDbImportDatabaseDialog.importHashDbErr=Import Hash Set Error
HashDbImportDatabaseDialog.mustSelectHashDbFilePathMsg=A hash set file path must be selected.
HashDbImportDatabaseDialog.hashDbDoesNotExistMsg=The selected hash set does not exist.
HashDbImportDatabaseDialog.unableToCopyToUserDirMsg=Unable to copy the hash set to user configuration directory {0}.
HashDbImportDatabaseDialog.errorMessage.failedToOpenHashDbMsg=Failed to open hash set at {0}.
HashLookupModuleFactory.moduleName.text=Hash Lookup
HashLookupModuleFactory.moduleDescription.text=Identifies known and notable files using supplied hash sets, such as a standard NSRL hash set.
@ -237,3 +238,5 @@ HashDbCreateDatabaseDialog.lbOrg.text=Source Organization:
HashDbCreateDatabaseDialog.orgButton.text=Manage Organizations
HashDbCreateDatabaseDialog.databasePathLabel.text=Hash Set Path:
AddHashValuesToDatabaseDialog.okButton.text_2=OK
HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.text=Copy hash set into user configuration folder
HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.toolTipText=In Live Triage situations, this option ensures that path to the hash set will be valid

View File

@ -60,6 +60,7 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog {
private final static String LAST_FILE_PATH_KEY = "HashDbCreate_Path";
private EamOrganization selectedOrg = null;
private List<EamOrganization> orgs = null;
static final String HASH_DATABASE_DIR_NAME = "HashDatabases";
/**
* Displays a dialog that allows a user to create a new hash database and
@ -404,7 +405,7 @@ final class HashDbCreateDatabaseDialog extends javax.swing.JDialog {
private void saveAsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveAsButtonActionPerformed
try {
String lastBaseDirectory = Paths.get(PlatformUtil.getUserConfigDirectory(), "HashDatabases").toString();
String lastBaseDirectory = Paths.get(PlatformUtil.getUserConfigDirectory(), HASH_DATABASE_DIR_NAME).toString();
if (ModuleSettings.settingExists(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY)) {
lastBaseDirectory = ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY);
}

View File

@ -29,7 +29,7 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0">
@ -86,6 +86,7 @@
</Group>
<Group type="102" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="saveInUserConfigFolderCheckbox" min="-2" max="-2" attributes="0"/>
<Component id="jLabel2" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="readOnlyCheckbox" alignment="0" min="-2" max="-2" attributes="0"/>
<Group type="102" attributes="0">
@ -145,7 +146,9 @@
<Component id="readOnlyCheckbox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="sendIngestMessagesCheckbox" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="39" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="saveInUserConfigFolderCheckbox" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="29" max="32767" attributes="0"/>
</Group>
<Group type="102" alignment="1" attributes="0">
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
@ -354,5 +357,15 @@
</Property>
</Properties>
</Component>
<Component class="javax.swing.JCheckBox" name="saveInUserConfigFolderCheckbox">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties" key="HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/hashdatabase/Bundle.properties" key="HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.toolTipText" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Form>

View File

@ -27,6 +27,7 @@ import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.openide.util.NbBundle;
import org.openide.windows.WindowManager;
@ -180,6 +181,7 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog {
fileTypeRadioButton = new javax.swing.JRadioButton();
centralRepoRadioButton = new javax.swing.JRadioButton();
jLabel4 = new javax.swing.JLabel();
saveInUserConfigFolderCheckbox = new javax.swing.JCheckBox();
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
@ -286,6 +288,9 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog {
org.openide.awt.Mnemonics.setLocalizedText(jLabel4, org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.jLabel4.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(saveInUserConfigFolderCheckbox, org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.text")); // NOI18N
saveInUserConfigFolderCheckbox.setToolTipText(org.openide.util.NbBundle.getMessage(HashDbImportDatabaseDialog.class, "HashDbImportDatabaseDialog.saveInUserConfigFolderCheckbox.toolTipText")); // NOI18N
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
@ -334,6 +339,7 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog {
.addComponent(cancelButton))
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(saveInUserConfigFolderCheckbox)
.addComponent(jLabel2)
.addComponent(readOnlyCheckbox)
.addGroup(layout.createSequentialGroup()
@ -384,7 +390,9 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog {
.addComponent(readOnlyCheckbox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(sendIngestMessagesCheckbox)
.addGap(0, 39, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(saveInUserConfigFolderCheckbox)
.addGap(0, 29, Short.MAX_VALUE))
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
.addGap(0, 0, Short.MAX_VALUE)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
@ -397,7 +405,7 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog {
}// </editor-fold>//GEN-END:initComponents
private void openButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_openButtonActionPerformed
String lastBaseDirectory = Paths.get(PlatformUtil.getUserConfigDirectory(), "HashDatabases").toString();
String lastBaseDirectory = Paths.get(PlatformUtil.getUserConfigDirectory(), HashDbCreateDatabaseDialog.HASH_DATABASE_DIR_NAME).toString();
if (ModuleSettings.settingExists(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY)) {
lastBaseDirectory = ModuleSettings.getConfigSetting(ModuleSettings.MAIN_SETTINGS, LAST_FILE_PATH_KEY);
}
@ -492,6 +500,7 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog {
JOptionPane.ERROR_MESSAGE);
return;
}
File file = new File(selectedFilePath);
if (!file.exists()) {
JOptionPane.showMessageDialog(this,
@ -503,6 +512,22 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog {
return;
}
if (saveInUserConfigFolderCheckbox.isSelected()) {
// copy the hash database to user configuration directory and use that path instead (JIRA-4177)
String locationInUserConfigDir = Paths.get(PlatformUtil.getUserConfigDirectory(), HashDbCreateDatabaseDialog.HASH_DATABASE_DIR_NAME, hashSetNameTextField.getText(), file.getName()).toString();
try {
FileUtils.copyFile(file, new File(locationInUserConfigDir));
// update the hash database location
selectedFilePath = locationInUserConfigDir;
} catch (IOException ex) {
String errorMessage = NbBundle.getMessage(this.getClass(), "HashDbImportDatabaseDialog.unableToCopyToUserDirMsg", locationInUserConfigDir);
Logger.getLogger(HashDbImportDatabaseDialog.class.getName()).log(Level.SEVERE, errorMessage, ex);
JOptionPane.showMessageDialog(this, errorMessage, NbBundle.getMessage(this.getClass(), "HashDbImportDatabaseDialog.importHashDbErr"),
JOptionPane.ERROR_MESSAGE);
return;
}
}
KnownFilesType type;
if (knownRadioButton.isSelected()) {
type = KnownFilesType.KNOWN;
@ -622,6 +647,7 @@ final class HashDbImportDatabaseDialog extends javax.swing.JDialog {
private javax.swing.JButton orgButton;
private javax.swing.JComboBox<String> orgComboBox;
private javax.swing.JCheckBox readOnlyCheckbox;
private javax.swing.JCheckBox saveInUserConfigFolderCheckbox;
private javax.swing.JCheckBox sendIngestMessagesCheckbox;
private javax.swing.ButtonGroup storageTypeButtonGroup;
private javax.swing.JTextField versionTextField;

View File

@ -39,6 +39,7 @@ import org.netbeans.api.progress.ProgressHandle;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
@ -1238,8 +1239,8 @@ public class HashDbManager implements PropertyChangeListener {
EamGlobalFileInstance fileInstance = new EamGlobalFileInstance(referenceSetID, file.getMd5Hash(),
type, comment);
EamDb.getInstance().addReferenceInstance(fileInstance,EamDb.getInstance().getCorrelationTypeById(CorrelationAttributeInstance.FILES_TYPE_ID));
} catch (EamDbException ex){
throw new TskCoreException("Error adding hashes to " + getDisplayName(), ex);
} catch (EamDbException | CorrelationAttributeNormalizationException ex){
throw new TskCoreException("Error adding hashes to " + getDisplayName(), ex); //NON-NLS
}
}
}
@ -1264,7 +1265,7 @@ public class HashDbManager implements PropertyChangeListener {
}
try {
globalFileInstances.add(new EamGlobalFileInstance(referenceSetID, hashEntry.getMd5Hash(), type, hashEntry.getComment()));
} catch (EamDbException ex){
} catch (EamDbException | CorrelationAttributeNormalizationException ex){
throw new TskCoreException("Error adding hashes to " + getDisplayName(), ex);
}
}
@ -1295,7 +1296,7 @@ public class HashDbManager implements PropertyChangeListener {
if (null != file.getMd5Hash()) {
try{
return EamDb.getInstance().isFileHashInReferenceSet(file.getMd5Hash(), this.referenceSetID);
} catch (EamDbException ex){
} catch (EamDbException | CorrelationAttributeNormalizationException ex){
Logger.getLogger(SleuthkitHashSet.class.getName()).log(Level.SEVERE, "Error performing central reposiotry hash lookup for hash "
+ file.getMd5Hash() + " in reference set " + referenceSetID, ex); //NON-NLS
throw new TskCoreException("Error performing central reposiotry hash lookup", ex);
@ -1327,7 +1328,7 @@ public class HashDbManager implements PropertyChangeListener {
// Make a bare-bones HashHitInfo for now
result = new HashHitInfo(file.getMd5Hash(), "", "");
}
} catch (EamDbException ex){
} catch (EamDbException | CorrelationAttributeNormalizationException ex){
Logger.getLogger(SleuthkitHashSet.class.getName()).log(Level.SEVERE, "Error performing central reposiotry hash lookup for hash "
+ file.getMd5Hash() + " in reference set " + referenceSetID, ex); //NON-NLS
throw new TskCoreException("Error performing central reposiotry hash lookup", ex);

View File

@ -63,6 +63,9 @@ final class HashLookupSettings implements Serializable {
private static final String configFilePath = PlatformUtil.getUserConfigDirectory() + File.separator + CONFIG_FILE_NAME;
private static final Logger logger = Logger.getLogger(HashDbManager.class.getName());
private static final String USER_DIR_PLACEHOLDER = "[UserConfigFolder]";
private static final String CURRENT_USER_DIR = PlatformUtil.getUserConfigDirectory();
private static final long serialVersionUID = 1L;
private final List<HashDbInfo> hashDbInfoList;
@ -126,6 +129,13 @@ final class HashLookupSettings implements Serializable {
try {
try (NbObjectInputStream in = new NbObjectInputStream(new FileInputStream(SERIALIZATION_FILE_PATH))) {
HashLookupSettings filesSetsSettings = (HashLookupSettings) in.readObject();
/* NOTE: to support JIRA-4177, we need to check if any of the hash
database paths are in Windows user directory. If so, we replace the path
with USER_DIR_PLACEHOLDER before saving to disk. When reading from disk,
USER_DIR_PLACEHOLDER needs to be replaced with current user directory path.
*/
convertPlaceholderToPath(filesSetsSettings);
return filesSetsSettings;
}
} catch (IOException | ClassNotFoundException ex) {
@ -282,8 +292,16 @@ final class HashLookupSettings implements Serializable {
*/
static boolean writeSettings(HashLookupSettings settings) {
/* NOTE: to support JIRA-4177, we need to check if any of the hash
database paths are in Windows user directory. If so, replace the path
with USER_DIR_PLACEHOLDER so that when it is read, it gets updated to be
the current user directory path.
*/
convertPathToPlaceholder(settings);
try (NbObjectOutputStream out = new NbObjectOutputStream(new FileOutputStream(SERIALIZATION_FILE_PATH))) {
out.writeObject(settings);
// restore the paths, in case they are going to be used somewhere
convertPlaceholderToPath(settings);
return true;
} catch (Exception ex) {
logger.log(Level.SEVERE, "Could not write hash set settings.");
@ -291,6 +309,47 @@ final class HashLookupSettings implements Serializable {
}
}
/**
* For file type hash sets, check if hash set paths needs to be modified
* per JIRA-4177. If the file path is in current Windows user directory,
* replace the path with USER_DIR_PLACEHOLDER.
*
* @param settings HashLookupSettings settings object to examiner and modify
*/
static void convertPathToPlaceholder(HashLookupSettings settings) {
for (HashDbInfo hashDbInfo : settings.getHashDbInfo()) {
if (hashDbInfo.isFileDatabaseType()) {
String dbPath = hashDbInfo.getPath();
if (dbPath.startsWith(CURRENT_USER_DIR)) {
// replace the current user directory with place holder
String remainingPath = dbPath.substring(CURRENT_USER_DIR.length());
hashDbInfo.setPath(USER_DIR_PLACEHOLDER + remainingPath);
}
}
}
}
/**
* For file type hash sets, check if hash set paths needs to be modified per
* JIRA-4177. Replace USER_DIR_PLACEHOLDER with path to current Windows user
* directory.
*
* @param settings HashLookupSettings settings object to examiner and modify
*/
static void convertPlaceholderToPath(HashLookupSettings settings) {
for (HashDbInfo hashDbInfo : settings.getHashDbInfo()) {
if (hashDbInfo.isFileDatabaseType()) {
String dbPath = hashDbInfo.getPath();
if (dbPath.startsWith(USER_DIR_PLACEHOLDER)) {
// replace the place holder with current user directory
String remainingPath = dbPath.substring(USER_DIR_PLACEHOLDER.length());
hashDbInfo.setPath(CURRENT_USER_DIR + remainingPath);
}
}
}
}
/**
* Represents the serializable information within a hash lookup in order to
* be written to disk. Used to hand off information when loading and saving
@ -308,7 +367,7 @@ final class HashLookupSettings implements Serializable {
private final HashDbManager.HashDb.KnownFilesType knownFilesType;
private boolean searchDuringIngest;
private final boolean sendIngestMessages;
private final String path;
private String path;
private final String version;
private final boolean readOnly;
private final int referenceSetID;
@ -447,6 +506,14 @@ final class HashLookupSettings implements Serializable {
return path;
}
/**
* Sets the path.
* @param path the path to set
*/
public void setPath(String path) {
this.path = path;
}
int getReferenceSetID(){
return referenceSetID;
}

View File

@ -1068,13 +1068,17 @@ class TableReportGenerator {
orderedRowData.add(makeCommaSeparatedList(getTags()));
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID() == getArtifact().getArtifactTypeID()) {
String[] attributeDataArray = new String[3];
String[] attributeDataArray = new String[5];
// Array is used so that order of the attributes is maintained.
for (BlackboardAttribute attr : attributes) {
if (attr.getAttributeType().equals(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME))) {
attributeDataArray[0] = attr.getDisplayString();
} else if (attr.getAttributeType().equals(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY))) {
attributeDataArray[1] = attr.getDisplayString();
} else if (attr.getAttributeType().equals(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT))) {
attributeDataArray[3] = attr.getDisplayString();
} else if (attr.getAttributeType().equals(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION))) {
attributeDataArray[4] = attr.getDisplayString();
}
}
@ -1163,6 +1167,7 @@ class TableReportGenerator {
*
* @return List<String> row titles
*/
@Messages({"ReportGenerator.artTableColHdr.comment=Comment"})
private List<Column> getArtifactTableColumns(int artifactTypeId, Set<BlackboardAttribute.Type> attributeTypeSet) {
ArrayList<Column> columns = new ArrayList<>();
@ -1558,6 +1563,12 @@ class TableReportGenerator {
columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskPath"),
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH)));
columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.comment"),
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT)));
columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.description"),
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION)));
} else if (BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE.getTypeID() == artifactTypeId) {
columns.add(new AttributeColumn(NbBundle.getMessage(this.getClass(), "ReportGenerator.artTableColHdr.tskGpsRouteCategory"),
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY)));

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2015-17 Basis Technology Corp.
* Copyright 2015-18 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -56,9 +56,7 @@ public final class PromptDialogManager {
@NbBundle.Messages("PrompDialogManager.buttonType.update=Update DB")
private static final ButtonType UPDATE = new ButtonType(Bundle.PrompDialogManager_buttonType_update(), ButtonBar.ButtonData.OK_DONE);
/**
* Image to use as title bar icon in dialogs
*/
/** Image to use as title bar icon in dialogs */
private static final Image AUTOPSY_ICON;
static {

View File

@ -0,0 +1,315 @@
/*
*
* 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.centralrepository.datamodel;
import junit.framework.Test;
import org.junit.Assert;
import org.netbeans.junit.NbModuleSuite;
import org.netbeans.junit.NbTestCase;
import org.openide.util.Exceptions;
/**
* Tests for validation on each correlation attribute type.
*/
public class CorrelationAttributeNormalizerTest extends NbTestCase {
public static Test suite() {
NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(CorrelationAttributeNormalizerTest.class).
clusters(".*").
enableModules(".*");
return conf.suite();
}
public CorrelationAttributeNormalizerTest(String name) {
super(name);
}
public void testValidateMd5() {
final String aValidHash = "e34a8899ef6468b74f8a1048419ccc8b"; //should pass
final String anInValidHash = "e34asdfa8899ef6468b74f8a1048419ccc8b"; //should fail
final String aValidHashWithCaps = "E34A8899EF6468B74F8A1048419CCC8B"; //should pass and be lowered
final String emptyHash = ""; //should fail
final String nullHash = null; //should fail
final int FILES_TYPE_ID = CorrelationAttributeInstance.FILES_TYPE_ID;
try {
assertTrue("This hash should just work", CorrelationAttributeNormalizer.normalize(FILES_TYPE_ID, aValidHash).equals(aValidHash));
} catch (CorrelationAttributeNormalizationException ex) {
Exceptions.printStackTrace(ex);
fail(ex.getMessage());
}
try {
assertTrue("This hash just needs to be converted to lower case", CorrelationAttributeNormalizer.normalize(CorrelationAttributeInstance.FILES_TYPE_ID, aValidHashWithCaps).equals(aValidHash));
} catch (CorrelationAttributeNormalizationException ex) {
Exceptions.printStackTrace(ex);
fail(ex.getMessage());
}
try {
CorrelationAttributeNormalizer.normalize(FILES_TYPE_ID, anInValidHash);
fail(THIS_SHOULD_HAVE_THROWN_AN_EXCEPTION);
} catch (CorrelationAttributeNormalizationException ex) {
assertTrue(WE_EXPECT_AN_EXCEPTION_HERE, true);
}
try {
CorrelationAttributeNormalizer.normalize(FILES_TYPE_ID, emptyHash);
fail(THIS_SHOULD_HAVE_THROWN_AN_EXCEPTION);
} catch (CorrelationAttributeNormalizationException ex) {
assertTrue(WE_EXPECT_AN_EXCEPTION_HERE, true);
}
try {
CorrelationAttributeNormalizer.normalize(FILES_TYPE_ID, nullHash);
fail(THIS_SHOULD_HAVE_THROWN_AN_EXCEPTION);
} catch (CorrelationAttributeNormalizationException ex) {
assertTrue(WE_EXPECT_AN_EXCEPTION_HERE, true);
}
}
private static final String WE_EXPECT_AN_EXCEPTION_HERE = "We expect an exception here.";
private static final String THIS_SHOULD_HAVE_THROWN_AN_EXCEPTION = "This should have thrown an exception.";
public void testValidateDomain() {
final String goodDomainOne = "www.test.com"; //should pass
final String badDomainTwo = "http://www.test.com"; //should fail (includes protocol)
final String goodDomainThree = "test.com"; //should pass
final String badDomainFour = "http://1270.0.1"; //should fail
final String badDomainFive = "?>\\/)(*&.com"; //should fail
final String badDomainSix = null; //should fail
final String badDomainSeven = ""; //should fail
final String badDomainEight = "HTTP://tests.com"; //should fail
final String badDomainNine = "http://www.test.com/aPage?aQuestion=aParam&anotherQuestion=anotherParam"; //should fail
final String goodDomainTen = "WWW.TEST.COM"; //should pass but be lowered
final String goodDomainEleven = "TEST.COM"; //should pass but be lowered
final int DOMAIN_TYPE_ID = CorrelationAttributeInstance.DOMAIN_TYPE_ID;
try {
assertTrue(THIS_DOMAIN_SHOULD_PASS, CorrelationAttributeNormalizer.normalize(DOMAIN_TYPE_ID, goodDomainOne).equals(goodDomainOne));
} catch (CorrelationAttributeNormalizationException ex) {
Exceptions.printStackTrace(ex);
fail(ex.getMessage());
}
try {
CorrelationAttributeNormalizer.normalize(DOMAIN_TYPE_ID, badDomainTwo);
fail(THIS_SHOULD_HAVE_THROWN_AN_EXCEPTION);
} catch (CorrelationAttributeNormalizationException ex) {
assertTrue(WE_EXPECT_AN_EXCEPTION_HERE, true);
}
try {
assertTrue(THIS_DOMAIN_SHOULD_PASS, CorrelationAttributeNormalizer.normalize(DOMAIN_TYPE_ID, goodDomainThree).equals(goodDomainThree));
} catch (CorrelationAttributeNormalizationException ex) {
Exceptions.printStackTrace(ex);
fail(ex.getMessage());
}
try {
assertTrue(THIS_DOMAIN_SHOULD_PASS, CorrelationAttributeNormalizer.normalize(DOMAIN_TYPE_ID, badDomainFour).equals(badDomainFour));
fail(THIS_SHOULD_HAVE_THROWN_AN_EXCEPTION);
} catch (CorrelationAttributeNormalizationException ex) {
assertTrue(WE_EXPECT_AN_EXCEPTION_HERE, true);
}
try {
CorrelationAttributeNormalizer.normalize(DOMAIN_TYPE_ID, badDomainFive);
fail(THIS_SHOULD_HAVE_THROWN_AN_EXCEPTION);
} catch (CorrelationAttributeNormalizationException ex) {
assertTrue(WE_EXPECT_AN_EXCEPTION_HERE, true);
}
try {
CorrelationAttributeNormalizer.normalize(DOMAIN_TYPE_ID, badDomainSix);
fail(THIS_SHOULD_HAVE_THROWN_AN_EXCEPTION);
} catch (CorrelationAttributeNormalizationException ex) {
assertTrue(WE_EXPECT_AN_EXCEPTION_HERE, true);
}
try {
CorrelationAttributeNormalizer.normalize(DOMAIN_TYPE_ID, badDomainSeven);
fail(THIS_SHOULD_HAVE_THROWN_AN_EXCEPTION);
} catch (CorrelationAttributeNormalizationException ex) {
assertTrue(WE_EXPECT_AN_EXCEPTION_HERE, true);
}
try {
CorrelationAttributeNormalizer.normalize(DOMAIN_TYPE_ID, badDomainEight);
fail("This should have thrown an exception");
} catch (CorrelationAttributeNormalizationException ex) {
assertTrue(WE_EXPECT_AN_EXCEPTION_HERE, true);
}
try {
CorrelationAttributeNormalizer.normalize(DOMAIN_TYPE_ID, badDomainNine);
fail("This should have thrown an exception");
} catch (CorrelationAttributeNormalizationException ex) {
assertTrue(WE_EXPECT_AN_EXCEPTION_HERE, true);
}
try {
assertTrue(THIS_DOMAIN_SHOULD_PASS, CorrelationAttributeNormalizer.normalize(DOMAIN_TYPE_ID, goodDomainTen).equals(goodDomainTen.toLowerCase()));
} catch (CorrelationAttributeNormalizationException ex) {
Exceptions.printStackTrace(ex);
fail(ex.getMessage());
}
try {
assertTrue(THIS_DOMAIN_SHOULD_PASS, CorrelationAttributeNormalizer.normalize(DOMAIN_TYPE_ID, goodDomainEleven).equals(goodDomainEleven.toLowerCase()));
} catch (CorrelationAttributeNormalizationException ex) {
Exceptions.printStackTrace(ex);
fail(ex.getMessage());
}
}
private static final String THIS_DOMAIN_SHOULD_PASS = "This domain should pass.";
public void testValidateEmail() {
final String goodEmailOne = "bsweeney@cipehrtechsolutions.com"; //should pass
final String goodEmailTwo = "BSWEENEY@ciphertechsolutions.com"; //should pass and be lowered
final String badEmailThree = ""; //should fail
final String badEmailFour = null; //should fail
final String badEmailFive = "asdf"; //should fail
final String goodEmailSix = "asdf@asdf"; //TODO looks bad but the lib accepts it...
final String badEmailSeven = "asdf.asdf"; //should
final int EMAIL_TYPE_ID = CorrelationAttributeInstance.EMAIL_TYPE_ID;
try {
assertTrue("This email should pass.", CorrelationAttributeNormalizer.normalize(EMAIL_TYPE_ID, goodEmailOne).equals(goodEmailOne));
} catch (CorrelationAttributeNormalizationException ex) {
Exceptions.printStackTrace(ex);
fail(ex.getMessage());
}
try {
assertTrue("This email should pass.", CorrelationAttributeNormalizer.normalize(EMAIL_TYPE_ID, goodEmailTwo).equals(goodEmailTwo.toLowerCase()));
} catch (CorrelationAttributeNormalizationException ex) {
Exceptions.printStackTrace(ex);
fail(ex.getMessage());
}
try {
CorrelationAttributeNormalizer.normalize(EMAIL_TYPE_ID, badEmailThree);
fail(THIS_SHOULD_HAVE_THROWN_AN_EXCEPTION);
} catch (CorrelationAttributeNormalizationException ex) {
assertTrue(WE_EXPECT_AN_EXCEPTION_HERE, true);
}
try {
CorrelationAttributeNormalizer.normalize(EMAIL_TYPE_ID, badEmailFour);
fail(THIS_SHOULD_HAVE_THROWN_AN_EXCEPTION);
} catch (CorrelationAttributeNormalizationException ex) {
assertTrue(WE_EXPECT_AN_EXCEPTION_HERE, true);
}
try {
CorrelationAttributeNormalizer.normalize(EMAIL_TYPE_ID, badEmailFive);
fail(THIS_SHOULD_HAVE_THROWN_AN_EXCEPTION);
} catch (CorrelationAttributeNormalizationException ex) {
assertTrue(WE_EXPECT_AN_EXCEPTION_HERE, true);
}
try { //TODO consider a better library?
assertTrue("This email should pass", CorrelationAttributeNormalizer.normalize(EMAIL_TYPE_ID, goodEmailSix).equals(goodEmailSix));
} catch (CorrelationAttributeNormalizationException ex) {
fail(ex.getMessage());
}
try {
CorrelationAttributeNormalizer.normalize(EMAIL_TYPE_ID, badEmailSeven);
fail(THIS_SHOULD_HAVE_THROWN_AN_EXCEPTION);
} catch (CorrelationAttributeNormalizationException ex) {
assertTrue(WE_EXPECT_AN_EXCEPTION_HERE, true);
}
}
public void testValidatePhone() {
final String goodPnOne = "19784740486";
final String goodPnTwo = "1(978) 474-0486";
final String goodPnThree = "+19784740486";
final String goodPnFour = "1 978-474-0486";
final String badPnFive = "9879879819784740486";
final String goodPnSix = "+1(978) 474-0486";
final String goodPnSeven = "+1(978) 474-0486";
final String badPnEight = "asdfasdfasdf";
final String badPnNine = "asdf19784740486adsf";
final int PHONE_TYPE_ID = CorrelationAttributeInstance.PHONE_TYPE_ID;
try {
assertTrue(THIS_PHONE_NUMBER_SHOULD_PASS, CorrelationAttributeNormalizer.normalize(PHONE_TYPE_ID, goodPnOne).equals(goodPnOne));
} catch (CorrelationAttributeNormalizationException ex) {
Exceptions.printStackTrace(ex);
fail(ex.getMessage());
}
try {
assertTrue(THIS_PHONE_NUMBER_SHOULD_PASS, CorrelationAttributeNormalizer.normalize(PHONE_TYPE_ID, goodPnTwo).equals(goodPnOne));
} catch (CorrelationAttributeNormalizationException ex) {
Exceptions.printStackTrace(ex);
fail(ex.getMessage());
}
try {
assertTrue(THIS_PHONE_NUMBER_SHOULD_PASS, CorrelationAttributeNormalizer.normalize(PHONE_TYPE_ID, goodPnThree).equals(goodPnThree));
} catch (CorrelationAttributeNormalizationException ex) {
Exceptions.printStackTrace(ex);
fail(ex.getMessage());
}
try {
assertTrue(THIS_PHONE_NUMBER_SHOULD_PASS, CorrelationAttributeNormalizer.normalize(PHONE_TYPE_ID, goodPnFour).equals(goodPnOne));
} catch (CorrelationAttributeNormalizationException ex) {
Exceptions.printStackTrace(ex);
fail(ex.getMessage());
}
try {
CorrelationAttributeNormalizer.normalize(PHONE_TYPE_ID, badPnFive);
//fail("This should have thrown an exception."); //this will eventually pass when we do a better job at this
} catch (CorrelationAttributeNormalizationException ex) {
assertTrue(WE_EXPECT_AN_EXCEPTION_HERE, true);
}
try {
assertTrue(THIS_PHONE_NUMBER_SHOULD_PASS, CorrelationAttributeNormalizer.normalize(PHONE_TYPE_ID, goodPnSix).equals(goodPnThree));
} catch (CorrelationAttributeNormalizationException ex) {
Exceptions.printStackTrace(ex);
fail(ex.getMessage());
}
try {
assertTrue(THIS_PHONE_NUMBER_SHOULD_PASS, CorrelationAttributeNormalizer.normalize(PHONE_TYPE_ID, goodPnSeven).equals(goodPnThree));
} catch (CorrelationAttributeNormalizationException ex) {
Exceptions.printStackTrace(ex);
fail(ex.getMessage());
}
try {
CorrelationAttributeNormalizer.normalize(PHONE_TYPE_ID, badPnEight);
fail("This should have thrown an exception.");
} catch (CorrelationAttributeNormalizationException ex) {
assertTrue(WE_EXPECT_AN_EXCEPTION_HERE, true);
}
try {
CorrelationAttributeNormalizer.normalize(PHONE_TYPE_ID, badPnNine);
fail("This should have thrown an exception.");
} catch (CorrelationAttributeNormalizationException ex) {
assertTrue(WE_EXPECT_AN_EXCEPTION_HERE, true);
}
}
private static final String THIS_PHONE_NUMBER_SHOULD_PASS = "This phone number should pass.";
public void testValidateUsbId() {
//TODO will need to be updated once usb validation does somethign interesting
final String goodIdOne = "0202:AAFF"; //should pass
/*final String goodIdTwo = "0202:aaff"; //should pass
final String badIdThree = "0202:axxf"; //should fail
final String badIdFour = ""; //should fail
final String badIdFive = null; //should fail
final String goodIdSix = "0202 AAFF"; //should pass
final String goodIdSeven = "0202AAFF"; //should pass
final String goodIdEight = "0202-AAFF"; //should pass*/
final int USBID_TYPE_ID = CorrelationAttributeInstance.USBID_TYPE_ID;
try {
assertTrue(THIS_USB_ID_SHOULD_PASS, CorrelationAttributeNormalizer.normalize(USBID_TYPE_ID, goodIdOne).equals(goodIdOne));
} catch (CorrelationAttributeNormalizationException ex) {
Assert.fail(ex.getMessage());
}
}
private static final String THIS_USB_ID_SHOULD_PASS = "This USB ID should pass.";
}

View File

@ -0,0 +1,174 @@
/*
*
* 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.commonfilessearch;
import java.nio.file.Path;
import java.sql.SQLException;
import java.util.Map;
import junit.framework.Assert;
import junit.framework.Test;
import org.netbeans.junit.NbModuleSuite;
import org.netbeans.junit.NbTestCase;
import org.openide.util.Exceptions;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
import org.sleuthkit.autopsy.commonfilesearch.AbstractCommonAttributeSearcher;
import org.sleuthkit.autopsy.commonfilesearch.AllInterCaseCommonAttributeSearcher;
import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeSearchResults;
import static org.sleuthkit.autopsy.commonfilessearch.InterCaseTestUtils.CASE1;
import static org.sleuthkit.autopsy.commonfilessearch.InterCaseTestUtils.CASE2;
import static org.sleuthkit.autopsy.commonfilessearch.InterCaseTestUtils.CASE3;
import static org.sleuthkit.autopsy.commonfilessearch.InterCaseTestUtils.CASE4;
import static org.sleuthkit.autopsy.commonfilessearch.InterCaseTestUtils.verifyInstanceCount;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Search for commonality in different sorts of attributes (files, usb devices,
* emails, domains). Observe that frequency filtering works for various types.
*
* TODO (JIRA-4166): The following tests are commented out because the
* functional test framework needs to be able to configure the keyword search
* ingest module to produce instances of the correlation attributes for the
* tests. This cannot be easily done at present because the keyword search
* module resides in an NBM with a dependency on the Autopsy-Core NBM; the
* otherwise obvious solution of publicly exposing the keyword search module
* settings fails due to a circular dependency.
*
*/
public class CommonAttributeSearchInterCaseTests extends NbTestCase {
private final InterCaseTestUtils utils;
public static Test suite() {
NbModuleSuite.Configuration conf = NbModuleSuite.createConfiguration(CommonAttributeSearchInterCaseTests.class).
clusters(".*").
enableModules(".*");
return conf.suite();
}
public CommonAttributeSearchInterCaseTests(String name) {
super(name);
this.utils = new InterCaseTestUtils(this);
}
@Override
public void setUp() {
this.utils.clearTestDir();
try {
this.utils.enableCentralRepo();
String[] cases = new String[]{
CASE1,
CASE2,
CASE3,
CASE4};
Path[][] paths = {
{this.utils.attrCase1Path},
{this.utils.attrCase2Path},
{this.utils.attrCase3Path},
{this.utils.attrCase4Path}};
this.utils.createCases(cases, paths, this.utils.getIngestSettingsForKitchenSink(), InterCaseTestUtils.CASE1);
} catch (TskCoreException | EamDbException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex.getMessage());
}
}
@Override
public void tearDown() {
this.utils.clearTestDir();
this.utils.tearDown();
}
/**
* Run a search on the given type and ensure that all results are off that
* type.
*
* No frequency filtering applied.
*
* @param type
*/
private void assertResultsAreOfType(CorrelationAttributeInstance.Type type) {
try {
Map<Long, String> dataSources = this.utils.getDataSourceMap();
AbstractCommonAttributeSearcher builder = new AllInterCaseCommonAttributeSearcher(dataSources, false, false, type, 0);
CommonAttributeSearchResults metadata = builder.findMatches();
metadata.size();
assertFalse(verifyInstanceCount(metadata, 0));
assertTrue(this.utils.areAllResultsOfType(metadata, type));
} catch (TskCoreException | NoCurrentCaseException | SQLException | EamDbException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex.getMessage());
}
}
/**
* Test that a search for each type returns results of that type only.
*/
public void testOne() {
// assertResultsAreOfType(this.utils.USB_ID_TYPE);
// assertResultsAreOfType(this.utils.DOMAIN_TYPE);
// assertResultsAreOfType(this.utils.FILE_TYPE);
// assertResultsAreOfType(this.utils.EMAIL_TYPE);
// assertResultsAreOfType(this.utils.PHONE_TYPE);
}
/**
* Test that the frequency filter behaves reasonably for attributes other
* than the file type.
*/
public void testTwo() {
try {
Map<Long, String> dataSources = this.utils.getDataSourceMap();
AbstractCommonAttributeSearcher builder;
CommonAttributeSearchResults metadata;
builder = new AllInterCaseCommonAttributeSearcher(dataSources, false, false, this.utils.USB_ID_TYPE, 100);
metadata = builder.findMatches();
metadata.size();
//assertTrue("This should yield 13 results.", verifyInstanceCount(metadata, 13));
builder = new AllInterCaseCommonAttributeSearcher(dataSources, false, false, this.utils.USB_ID_TYPE, 20);
metadata = builder.findMatches();
metadata.size();
//assertTrue("This should yield no results.", verifyInstanceCount(metadata, 0));
builder = new AllInterCaseCommonAttributeSearcher(dataSources, false, false, this.utils.USB_ID_TYPE, 90);
metadata = builder.findMatches();
metadata.size();
//assertTrue("This should yield 2 results.", verifyInstanceCount(metadata, 2));
} catch (TskCoreException | NoCurrentCaseException | SQLException | EamDbException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex.getMessage());
}
}
}

View File

@ -19,6 +19,7 @@
*/
package org.sleuthkit.autopsy.commonfilessearch;
import java.nio.file.Path;
import java.sql.SQLException;
import java.util.Map;
import junit.framework.Test;
@ -27,6 +28,7 @@ import org.netbeans.junit.NbTestCase;
import org.openide.util.Exceptions;
import junit.framework.Assert;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
import org.sleuthkit.autopsy.commonfilesearch.AbstractCommonAttributeSearcher;
import org.sleuthkit.autopsy.commonfilesearch.AllInterCaseCommonAttributeSearcher;
@ -65,7 +67,18 @@ public class IngestedWithHashAndFileTypeInterCaseTests extends NbTestCase {
this.utils.clearTestDir();
try {
this.utils.enableCentralRepo();
this.utils.createCases(this.utils.getIngestSettingsForHashAndFileType(), InterCaseTestUtils.CASE3);
String[] cases = new String[]{
CASE1,
CASE2,
CASE3};
Path[][] paths = {
{this.utils.case1DataSet1Path, this.utils.case1DataSet2Path},
{this.utils.case2DataSet1Path, this.utils.case2DataSet2Path},
{this.utils.case3DataSet1Path, this.utils.case3DataSet2Path}};
this.utils.createCases(cases, paths, this.utils.getIngestSettingsForHashAndFileType(), InterCaseTestUtils.CASE3);
} catch (TskCoreException | EamDbException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex.getMessage());
@ -86,9 +99,8 @@ public class IngestedWithHashAndFileTypeInterCaseTests extends NbTestCase {
Map<Long, String> dataSources = this.utils.getDataSourceMap();
//note that the params false and false are presently meaningless because that feature is not supported yet
AbstractCommonAttributeSearcher builder = new AllInterCaseCommonAttributeSearcher(dataSources, false, false, 0);
CommonAttributeSearchResults metadata = builder.findFiles();
AbstractCommonAttributeSearcher builder = new AllInterCaseCommonAttributeSearcher(dataSources, false, false, this.utils.FILE_TYPE, 0);
CommonAttributeSearchResults metadata = builder.findMatches();
assertTrue("Results should not be empty", metadata.size() != 0);
@ -123,7 +135,6 @@ public class IngestedWithHashAndFileTypeInterCaseTests extends NbTestCase {
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_C_PDF, CASE3_DATASET_2, CASE3, 0));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_D_DOC, CASE3_DATASET_2, CASE3, 1));
} catch (TskCoreException | NoCurrentCaseException | SQLException | EamDbException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex.getMessage());
@ -138,10 +149,10 @@ public class IngestedWithHashAndFileTypeInterCaseTests extends NbTestCase {
Map<Long, String> dataSources = this.utils.getDataSourceMap();
int matchesMustAlsoBeFoundInThisCase = this.utils.getCaseMap().get(CASE2);
CorrelationAttributeInstance.Type fileType = CorrelationAttributeInstance.getDefaultCorrelationTypes().get(0);
AbstractCommonAttributeSearcher builder = new SingleInterCaseCommonAttributeSearcher(matchesMustAlsoBeFoundInThisCase, dataSources, false, false, fileType, 0);
AbstractCommonAttributeSearcher builder = new SingleInterCaseCommonAttributeSearcher(matchesMustAlsoBeFoundInThisCase, dataSources, false, false, 0);
CommonAttributeSearchResults metadata = builder.findFiles();
CommonAttributeSearchResults metadata = builder.findMatches();
assertTrue("Results should not be empty", metadata.size() != 0);
@ -176,7 +187,6 @@ public class IngestedWithHashAndFileTypeInterCaseTests extends NbTestCase {
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_C_PDF, CASE3_DATASET_2, CASE3, 0));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_D_DOC, CASE3_DATASET_2, CASE3, 1));
} catch (TskCoreException | NoCurrentCaseException | SQLException | EamDbException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex.getMessage());
@ -184,7 +194,7 @@ public class IngestedWithHashAndFileTypeInterCaseTests extends NbTestCase {
}
/**
* We should be able to observe that certain files o no longer returned
* We should be able to observe that certain files are no longer returned
* in the result set since they do not appear frequently enough.
*/
public void testThree(){
@ -192,34 +202,35 @@ public class IngestedWithHashAndFileTypeInterCaseTests extends NbTestCase {
Map<Long, String> dataSources = this.utils.getDataSourceMap();
//note that the params false and false are presently meaningless because that feature is not supported yet
AbstractCommonAttributeSearcher builder = new AllInterCaseCommonAttributeSearcher(dataSources, false, false, 50);
CorrelationAttributeInstance.Type fileType = CorrelationAttributeInstance.getDefaultCorrelationTypes().get(0);
AbstractCommonAttributeSearcher builder = new AllInterCaseCommonAttributeSearcher(dataSources, false, false, fileType, 50);
CommonAttributeSearchResults metadata = builder.findFiles();
CommonAttributeSearchResults metadata = builder.findMatches();
assertTrue("Results should not be empty", metadata.size() != 0);
//case 1 data set 1
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_0_DAT, CASE1_DATASET_1, CASE1, 0));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE1_DATASET_1, CASE1, 1));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE1_DATASET_1, CASE1, 1));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE1_DATASET_1, CASE1, 0));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE1_DATASET_1, CASE1, 0));
//case 1 data set 2
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_0_DAT, CASE1_DATASET_2, CASE1, 0));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE1_DATASET_2, CASE1, 1));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE1_DATASET_2, CASE1, 1));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE1_DATASET_2, CASE1, 0));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE1_DATASET_2, CASE1, 0));
//case 2 data set 1
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_B_PDF, CASE2_DATASET_1, CASE2, 0));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_B_JPG, CASE2_DATASET_1, CASE2, 0));
//case 2 data set 2
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE2_DATASET_2, CASE2, 1));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE2_DATASET_2, CASE2, 1));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_D_DOC, CASE2_DATASET_2, CASE2, 0));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE2_DATASET_2, CASE2, 0));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE2_DATASET_2, CASE2, 0));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_D_DOC, CASE2_DATASET_2, CASE2, 1));
//case 3 data set 1
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE3_DATASET_1, CASE3, 1));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE3_DATASET_1, CASE3, 1));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_JPG, CASE3_DATASET_1, CASE3, 0));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_A_PDF, CASE3_DATASET_1, CASE3, 0));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_C_JPG, CASE3_DATASET_1, CASE3, 0));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_C_PDF, CASE3_DATASET_1, CASE3, 0));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_D_JPG, CASE3_DATASET_1, CASE3, 0));
@ -227,7 +238,7 @@ public class IngestedWithHashAndFileTypeInterCaseTests extends NbTestCase {
//case 3 data set 2
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_C_JPG, CASE3_DATASET_2, CASE3, 0));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_C_PDF, CASE3_DATASET_2, CASE3, 0));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_D_DOC, CASE3_DATASET_2, CASE3, 0));
assertTrue(verifyInstanceExistanceAndCount(metadata, HASH_D_DOC, CASE3_DATASET_2, CASE3, 1));
} catch (TskCoreException | NoCurrentCaseException | SQLException | EamDbException ex) {
Exceptions.printStackTrace(ex);

View File

@ -100,7 +100,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTests extends NbTestCase {
Map<Long, String> dataSources = this.utils.getDataSourceMap();
AbstractCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, false, false, 0);
CommonAttributeSearchResults metadata = allSourcesBuilder.findFiles();
CommonAttributeSearchResults metadata = allSourcesBuilder.findMatches();
Map<Long, String> objectIdToDataSource = IntraCaseTestUtils.mapFileInstancesToDataSources(metadata);
@ -141,7 +141,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTests extends NbTestCase {
Map<Long, String> dataSources = this.utils.getDataSourceMap();
AbstractCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, true, false, 0);
CommonAttributeSearchResults metadata = allSourcesBuilder.findFiles();
CommonAttributeSearchResults metadata = allSourcesBuilder.findMatches();
Map<Long, String> objectIdToDataSource = mapFileInstancesToDataSources(metadata);
@ -182,7 +182,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTests extends NbTestCase {
Map<Long, String> dataSources = this.utils.getDataSourceMap();
AbstractCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, false, true, 0);
CommonAttributeSearchResults metadata = allSourcesBuilder.findFiles();
CommonAttributeSearchResults metadata = allSourcesBuilder.findMatches();
Map<Long, String> objectIdToDataSource = mapFileInstancesToDataSources(metadata);
@ -224,7 +224,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTests extends NbTestCase {
Long first = getDataSourceIdByName(SET1, dataSources);
AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(first, dataSources, false, false, 0);
CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles();
CommonAttributeSearchResults metadata = singleSourceBuilder.findMatches();
Map<Long, String> objectIdToDataSource = mapFileInstancesToDataSources(metadata);
@ -266,7 +266,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTests extends NbTestCase {
Long first = getDataSourceIdByName(SET1, dataSources);
AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(first, dataSources, true, false, 0);
CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles();
CommonAttributeSearchResults metadata = singleSourceBuilder.findMatches();
Map<Long, String> objectIdToDataSource = mapFileInstancesToDataSources(metadata);
@ -308,7 +308,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTests extends NbTestCase {
Long first = getDataSourceIdByName(SET1, dataSources);
AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(first, dataSources, false, true, 0);
CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles();
CommonAttributeSearchResults metadata = singleSourceBuilder.findMatches();
Map<Long, String> objectIdToDataSource = mapFileInstancesToDataSources(metadata);
@ -350,7 +350,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTests extends NbTestCase {
Long second = getDataSourceIdByName(SET2, dataSources);
AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(second, dataSources, false, false, 0);
CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles();
CommonAttributeSearchResults metadata = singleSourceBuilder.findMatches();
Map<Long, String> objectIdToDataSource = mapFileInstancesToDataSources(metadata);
@ -391,7 +391,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTests extends NbTestCase {
Long last = getDataSourceIdByName(SET4, dataSources);
AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(last, dataSources, false, false, 0);
CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles();
CommonAttributeSearchResults metadata = singleSourceBuilder.findMatches();
Map<Long, String> objectIdToDataSource = mapFileInstancesToDataSources(metadata);
@ -432,7 +432,7 @@ public class IngestedWithHashAndFileTypeIntraCaseTests extends NbTestCase {
Long third = getDataSourceIdByName(SET3, dataSources);
AbstractCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(third, dataSources, false, false, 0);
CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles();
CommonAttributeSearchResults metadata = singleSourceBuilder.findMatches();
Map<Long, String> objectIdToDataSource = mapFileInstancesToDataSources(metadata);

View File

@ -102,7 +102,7 @@ public class IngestedWithNoFileTypesIntraCaseTests extends NbTestCase {
Map<Long, String> dataSources = this.utils.getDataSourceMap();
IntraCaseCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, true, false, 0);
CommonAttributeSearchResults metadata = allSourcesBuilder.findFiles();
CommonAttributeSearchResults metadata = allSourcesBuilder.findMatches();
Map<Long, String> objectIdToDataSource = IntraCaseTestUtils.mapFileInstancesToDataSources(metadata);
@ -126,7 +126,7 @@ public class IngestedWithNoFileTypesIntraCaseTests extends NbTestCase {
Long third = IntraCaseTestUtils.getDataSourceIdByName(IntraCaseTestUtils.SET3, dataSources);
IntraCaseCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(third, dataSources, true, false, 0);
CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles();
CommonAttributeSearchResults metadata = singleSourceBuilder.findMatches();
Map<Long, String> objectIdToDataSource = IntraCaseTestUtils.mapFileInstancesToDataSources(metadata);

View File

@ -25,8 +25,8 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.netbeans.junit.NbTestCase;
@ -59,6 +59,14 @@ import org.sleuthkit.autopsy.commonfilesearch.DataSourceLoader;
import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeValue;
import org.sleuthkit.autopsy.commonfilesearch.CommonAttributeValueList;
import org.sleuthkit.autopsy.datamodel.DisplayableItemNode;
import org.sleuthkit.autopsy.modules.e01verify.E01VerifierModuleFactory;
import org.sleuthkit.autopsy.modules.embeddedfileextractor.EmbeddedFileExtractorModuleFactory;
import org.sleuthkit.autopsy.modules.exif.ExifParserModuleFactory;
import org.sleuthkit.autopsy.modules.fileextmismatch.FileExtMismatchDetectorModuleFactory;
import org.sleuthkit.autopsy.modules.iOS.iOSModuleFactory;
import org.sleuthkit.autopsy.modules.interestingitems.InterestingItemsIngestModuleFactory;
import org.sleuthkit.autopsy.modules.photoreccarver.PhotoRecCarverIngestModuleFactory;
import org.sleuthkit.autopsy.modules.vmextractor.VMExtractorIngestModuleFactory;
import org.sleuthkit.datamodel.AbstractFile;
/**
@ -73,19 +81,50 @@ import org.sleuthkit.datamodel.AbstractFile;
* Description of Test Data: (Note: files of the same name and extension are
* identical; files of the same name and differing extension are not identical.)
*
* Case 1 +Data Set 1 - Hash-0.dat [testFile of size 0] - Hash-A.jpg -
* Hash-A.pdf +Data Set2 - Hash-0.dat [testFile of size 0] - Hash-A.jpg -
* Hash-A.pdf Case 2 +Data Set 1 - Hash-B.jpg - Hash-B.pdf +Data Set 2 -
* Hash-A.jpg - Hash-A.pdf - Hash_D.doc Case 3 +Data Set 1 - Hash-A.jpg -
* Hash-A.pdf - Hash-C.jpg - Hash-C.pdf - Hash-D.jpg +Data Set 2 - Hash-C.jpg -
* Hash-C.pdf - Hash-D.doc
* Case 1
* +Data Set 1
* - Hash-0.dat [testFile of size 0]
* - Hash-A.jpg
* - Hash-A.pdf
*
* +Data Set2
* - Hash-0.dat [testFile of size 0]
* - Hash-A.jpg
* - Hash-A.pdf
*
* Case 2
* +Data Set 1
* - Hash-B.jpg
* - Hash-B.pdf
* +Data Set 2
* - Hash-A.jpg
* - Hash-A.pdf
* - Hash_D.doc
*
* Case 3
* +Data Set 1
* - Hash-A.jpg
* - Hash-A.pdf
* - Hash-C.jpg
* - Hash-C.pdf
* - Hash-D.jpg
* +Data Set 2
* - Hash-C.jpg
* - Hash-C.pdf
* - Hash-D.doc
*
* Frequency Breakdown (ratio of datasources a given file appears in to total
* number of datasources):
*
* Hash-0.dat - moot; these are always excluded Hash-A.jpg - 4/6 Hash-A.pdf -
* 4/6 Hash-B.jpg - 1/6 Hash-B.pdf - 1/6 Hash-C.jpg - 2/6 Hash-C.pdf - 2/6
* Hash_D.doc - 2/6 Hash-D.jpg - 1/6
* Hash-0.dat - moot; these are always excluded
* Hash-A.jpg - 4/6
* Hash-A.pdf - 4/6
* Hash-B.jpg - 1/6
* Hash-B.pdf - 1/6
* Hash-C.jpg - 2/6
* Hash-C.pdf - 2/6
* Hash_D.doc - 2/6
* Hash-D.jpg - 1/6
*
*/
class InterCaseTestUtils {
@ -122,12 +161,30 @@ class InterCaseTestUtils {
static final String CASE3_DATASET_1 = "c3ds1_v1.vhd";
static final String CASE3_DATASET_2 = "c3ds2_v1.vhd";
final Path attrCase1Path;
final Path attrCase2Path;
final Path attrCase3Path;
final Path attrCase4Path;
static final String ATTR_CASE1 = "CommonFilesAttrs_img1_v1.vhd";
static final String ATTR_CASE2 = "CommonFilesAttrs_img2_v1.vhd";
static final String ATTR_CASE3 = "CommonFilesAttrs_img3_v1.vhd";
static final String ATTR_CASE4 = "CommonFilesAttrs_img4_v1.vhd";
private final ImageDSProcessor imageDSProcessor;
private final IngestJobSettings hashAndFileType;
private final IngestJobSettings hashAndNoFileType;
private final IngestJobSettings kitchenShink;
private final DataSourceLoader dataSourceLoader;
CorrelationAttributeInstance.Type FILE_TYPE;
CorrelationAttributeInstance.Type DOMAIN_TYPE;
CorrelationAttributeInstance.Type USB_ID_TYPE;
CorrelationAttributeInstance.Type EMAIL_TYPE;
CorrelationAttributeInstance.Type PHONE_TYPE;
InterCaseTestUtils(NbTestCase testCase) {
this.case1DataSet1Path = Paths.get(testCase.getDataDir().toString(), CASE1_DATASET_1);
@ -137,12 +194,31 @@ class InterCaseTestUtils {
this.case3DataSet1Path = Paths.get(testCase.getDataDir().toString(), CASE3_DATASET_1);
this.case3DataSet2Path = Paths.get(testCase.getDataDir().toString(), CASE3_DATASET_2);
this.attrCase1Path = Paths.get(testCase.getDataDir().toString(), ATTR_CASE1);
this.attrCase2Path = Paths.get(testCase.getDataDir().toString(), ATTR_CASE2);
this.attrCase3Path = Paths.get(testCase.getDataDir().toString(), ATTR_CASE3);
this.attrCase4Path = Paths.get(testCase.getDataDir().toString(), ATTR_CASE4);
this.imageDSProcessor = new ImageDSProcessor();
final IngestModuleTemplate hashLookupTemplate = IngestUtils.getIngestModuleTemplate(new HashLookupModuleFactory());
final IngestModuleTemplate exifTemplate = IngestUtils.getIngestModuleTemplate(new ExifParserModuleFactory());
final IngestModuleTemplate iOsTemplate = IngestUtils.getIngestModuleTemplate(new iOSModuleFactory());
final IngestModuleTemplate embeddedFileExtractorTemplate = IngestUtils.getIngestModuleTemplate(new EmbeddedFileExtractorModuleFactory());
final IngestModuleTemplate interestingItemsTemplate = IngestUtils.getIngestModuleTemplate(new InterestingItemsIngestModuleFactory());
final IngestModuleTemplate mimeTypeLookupTemplate = IngestUtils.getIngestModuleTemplate(new FileTypeIdModuleFactory());
final IngestModuleTemplate hashLookupTemplate = IngestUtils.getIngestModuleTemplate(new HashLookupModuleFactory());
final IngestModuleTemplate vmExtractorTemplate = IngestUtils.getIngestModuleTemplate(new VMExtractorIngestModuleFactory());
final IngestModuleTemplate photoRecTemplate = IngestUtils.getIngestModuleTemplate(new PhotoRecCarverIngestModuleFactory());
final IngestModuleTemplate e01VerifierTemplate = IngestUtils.getIngestModuleTemplate(new E01VerifierModuleFactory());
final IngestModuleTemplate eamDbTemplate = IngestUtils.getIngestModuleTemplate(new org.sleuthkit.autopsy.centralrepository.ingestmodule.IngestModuleFactory());
final IngestModuleTemplate fileExtMismatchDetectorTemplate = IngestUtils.getIngestModuleTemplate(new FileExtMismatchDetectorModuleFactory());
//TODO we need to figure out how to get ahold of these objects because they are required for properly filling the CR with test data
// final IngestModuleTemplate objectDetectorTemplate = IngestUtils.getIngestModuleTemplate(new org.sleuthkit.autopsy.experimental.objectdetection.ObjectDetectionModuleFactory());
// final IngestModuleTemplate emailParserTemplate = IngestUtils.getIngestModuleTemplate(new org.sleuthkit.autopsy.thunderbirdparser.EmailParserModuleFactory());
// final IngestModuleTemplate recentActivityTemplate = IngestUtils.getIngestModuleTemplate(new org.sleuthkit.autopsy.recentactivity.RecentActivityExtracterModuleFactory());
// final IngestModuleTemplate keywordSearchTemplate = IngestUtils.getIngestModuleTemplate(new org.sleuthkit.autopsy.keywordsearch.KeywordSearchModuleFactory());
//hash and mime
ArrayList<IngestModuleTemplate> hashAndMimeTemplate = new ArrayList<>(2);
hashAndMimeTemplate.add(hashLookupTemplate);
hashAndMimeTemplate.add(mimeTypeLookupTemplate);
@ -150,13 +226,56 @@ class InterCaseTestUtils {
this.hashAndFileType = new IngestJobSettings(InterCaseTestUtils.class.getCanonicalName(), IngestType.FILES_ONLY, hashAndMimeTemplate);
//hash and no mime
ArrayList<IngestModuleTemplate> hashAndNoMimeTemplate = new ArrayList<>(1);
hashAndNoMimeTemplate.add(hashLookupTemplate);
hashAndMimeTemplate.add(eamDbTemplate);
this.hashAndNoFileType = new IngestJobSettings(InterCaseTestUtils.class.getCanonicalName(), IngestType.FILES_ONLY, hashAndNoMimeTemplate);
//kitchen sink
ArrayList<IngestModuleTemplate> kitchenSink = new ArrayList<>();
kitchenSink.add(exifTemplate);
kitchenSink.add(iOsTemplate);
kitchenSink.add(embeddedFileExtractorTemplate);
kitchenSink.add(interestingItemsTemplate);
kitchenSink.add(mimeTypeLookupTemplate);
kitchenSink.add(hashLookupTemplate);
kitchenSink.add(vmExtractorTemplate);
kitchenSink.add(photoRecTemplate);
kitchenSink.add(e01VerifierTemplate);
kitchenSink.add(eamDbTemplate);
kitchenSink.add(fileExtMismatchDetectorTemplate);
//TODO this list should probably be populated by way of loading the appropriate modules based on finding all of the @ServiceProvider(service = IngestModuleFactory.class) types
// kitchenSink.add(objectDetectorTemplate);
// kitchenSink.add(emailParserTemplate);
// kitchenSink.add(recentActivityTemplate);
// kitchenSink.add(keywordSearchTemplate);
this.kitchenShink = new IngestJobSettings(InterCaseTestUtils.class.getCanonicalName(), IngestType.ALL_MODULES, kitchenSink);
this.dataSourceLoader = new DataSourceLoader();
try {
Collection<CorrelationAttributeInstance.Type> types = CorrelationAttributeInstance.getDefaultCorrelationTypes();
//TODO use ids instead of strings
FILE_TYPE = types.stream().filter(type -> type.getDisplayName().equals("Files")).findAny().get();
DOMAIN_TYPE = types.stream().filter(type -> type.getDisplayName().equals("Domains")).findAny().get();
USB_ID_TYPE = types.stream().filter(type -> type.getDisplayName().equals("USB Devices")).findAny().get();
EMAIL_TYPE = types.stream().filter(type -> type.getDisplayName().equals("Email Addresses")).findAny().get();
PHONE_TYPE = types.stream().filter(type -> type.getDisplayName().equals("Phone Numbers")).findAny().get();
} catch (EamDbException ex) {
Assert.fail(ex.getMessage());
//none of this really matters but satisfies the compiler
FILE_TYPE = null;
DOMAIN_TYPE = null;
USB_ID_TYPE = null;
EMAIL_TYPE = null;
PHONE_TYPE = null;
}
}
void clearTestDir() {
@ -202,6 +321,10 @@ class InterCaseTestUtils {
return this.hashAndNoFileType;
}
IngestJobSettings getIngestSettingsForKitchenSink(){
return this.kitchenShink;
}
void enableCentralRepo() throws EamDbException {
SqliteEamDbSettings crSettings = new SqliteEamDbSettings();
@ -222,33 +345,33 @@ class InterCaseTestUtils {
}
/**
* Create 3 cases and ingest each with the given settings. Null settings are
* permitted but IngestUtils will not be run.
* Create the cases defined by caseNames and caseDataSourcePaths and ingest
* each with the given settings. Null settings are permitted but
* IngestUtils will not be run.
*
* The length of caseNames and caseDataSourcePaths should be the same, and
* cases should appear in the same order.
*
* @param caseNames list case names
* @param caseDataSourcePaths two dimensional array listing the datasources in each case
* @param ingestJobSettings HashLookup FileType etc...
* @param caseReferenceToStore
*/
Case createCases(IngestJobSettings ingestJobSettings, String caseReferenceToStore) throws TskCoreException {
Case createCases(String[] caseNames, Path[][] caseDataSourcePaths, IngestJobSettings ingestJobSettings, String caseReferenceToStore) throws TskCoreException {
Case currentCase = null;
String[] cases = new String[]{
CASE1,
CASE2,
CASE3};
Path[][] paths = {
{this.case1DataSet1Path, this.case1DataSet2Path},
{this.case2DataSet1Path, this.case2DataSet2Path},
{this.case3DataSet1Path, this.case3DataSet2Path}};
if(caseNames.length != caseDataSourcePaths.length){
Assert.fail(new IllegalArgumentException("caseReferenceToStore should be one of the values given in the 'cases' parameter.").getMessage());
}
String lastCaseName = null;
Path[] lastPathsForCase = null;
//iterate over the collections above, creating cases, and storing
// just one of them for future reference
for (int i = 0; i < cases.length; i++) {
String caseName = cases[i];
Path[] pathsForCase = paths[i];
for (int i = 0; i < caseNames.length; i++) {
String caseName = caseNames[i];
Path[] pathsForCase = caseDataSourcePaths[i];
if (caseName.equals(caseReferenceToStore)) {
//put aside and do this one last so we can hang onto the case
@ -266,7 +389,7 @@ class InterCaseTestUtils {
}
if (currentCase == null) {
Assert.fail(new IllegalArgumentException("caseReferenceToStore should be one of: CASE1, CASE2, CASE3").getMessage());
Assert.fail(new IllegalArgumentException("caseReferenceToStore should be one of the values given in the 'cases' parameter.").getMessage());
return null;
} else {
return currentCase;
@ -290,6 +413,27 @@ class InterCaseTestUtils {
}
}
static boolean verifyInstanceCount(CommonAttributeSearchResults searchDomain, int instanceCount){
try {
int tally = 0;
for (Map.Entry<Integer, CommonAttributeValueList> entry : searchDomain.getMetadata().entrySet()) {
entry.getValue().displayDelayedMetadata();
for (CommonAttributeValue value : entry.getValue().getMetadataList()) {
tally += value.getInstanceCount();
}
}
return tally == instanceCount;
} catch (EamDbException ex) {
Exceptions.printStackTrace(ex);
Assert.fail(ex.getMessage());
return false;
}
}
static boolean verifyInstanceExistanceAndCount(CommonAttributeSearchResults searchDomain, String fileName, String dataSource, String crCase, int instanceCount) {
try {
@ -381,4 +525,29 @@ class InterCaseTestUtils {
Assert.fail(ex.getMessage());
}
}
/**
* Is everything in metadata a result of the given attribute type?
*
* @param metadata
* @param attributeType
* @return true if yes, else false
*/
boolean areAllResultsOfType(CommonAttributeSearchResults metadata, CorrelationAttributeInstance.Type attributeType) {
try {
for(CommonAttributeValueList matches : metadata.getMetadata().values()){
for(CommonAttributeValue value : matches.getMetadataList()){
return value
.getInstances()
.stream()
.allMatch(inst -> inst.getCorrelationAttributeInstanceType().equals(attributeType));
}
return false;
}
return false;
} catch (EamDbException ex) {
Assert.fail(ex.getMessage());
return false;
}
}
}

View File

@ -106,7 +106,7 @@ public class MatchesInAtLeastTwoSourcesIntraCaseTests extends NbTestCase {
Map<Long, String> dataSources = this.utils.getDataSourceMap();
AbstractCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, false, false, 0);
CommonAttributeSearchResults metadata = allSourcesBuilder.findFiles();
CommonAttributeSearchResults metadata = allSourcesBuilder.findMatches();
Map<Long, String> objectIdToDataSource = IntraCaseTestUtils.mapFileInstancesToDataSources(metadata);

View File

@ -81,7 +81,7 @@ public class UningestedCasesIntraCaseTests extends NbTestCase {
Map<Long, String> dataSources = this.utils.getDataSourceMap();
IntraCaseCommonAttributeSearcher allSourcesBuilder = new AllIntraCaseCommonAttributeSearcher(dataSources, false, false, 0);
CommonAttributeSearchResults metadata = allSourcesBuilder.findFiles();
CommonAttributeSearchResults metadata = allSourcesBuilder.findMatches();
int resultCount = metadata.size();
assertEquals(resultCount, 0);
@ -101,7 +101,7 @@ public class UningestedCasesIntraCaseTests extends NbTestCase {
Long first = getDataSourceIdByName(SET1, dataSources);
IntraCaseCommonAttributeSearcher singleSourceBuilder = new SingleIntraCaseCommonAttributeSearcher(first, dataSources, false, false, 0);
CommonAttributeSearchResults metadata = singleSourceBuilder.findFiles();
CommonAttributeSearchResults metadata = singleSourceBuilder.findMatches();
int resultCount = metadata.size();
assertEquals(resultCount, 0);

View File

@ -39,7 +39,7 @@ public final class IngestJobRunner {
* Runs an ingest job, blocking until the job is completed.
*
* @param dataSources The data sources for the ingest job.
* @param settings The settings for the ingst job
* @param settings The settings for the ingest job
*
* @return A list of ingest module start up error messages, empty if the job
* was started sucessfully.
@ -68,7 +68,7 @@ public final class IngestJobRunner {
}
/**
* IngestRunner instances cannot be instatiated.
* IngestRunner instances cannot be instantiated.
*/
private IngestJobRunner() {
}

View File

@ -230,6 +230,7 @@
<package>org.apache.commons.codec.digest</package>
<package>org.apache.commons.codec.language</package>
<package>org.apache.commons.codec.net</package>
<package>org.apache.commons.collections4</package>
<package>org.apache.commons.csv</package>
<package>org.apache.commons.io</package>
<package>org.apache.commons.io.comparator</package>

View File

@ -21,21 +21,24 @@ package org.sleuthkit.autopsy.imagegallery;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.beans.property.ReadOnlyLongWrapper;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleBooleanProperty;
@ -43,25 +46,14 @@ import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.concurrent.Worker;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javax.annotation.Nullable;
import javax.swing.SwingUtilities;
import javax.annotation.Nonnull;
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
import org.netbeans.api.progress.ProgressHandle;
import org.openide.util.Cancellable;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.Case.CaseType;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.core.RuntimeProperties;
import org.sleuthkit.autopsy.coreutils.History;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
@ -69,18 +61,18 @@ import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.actions.UndoRedoManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.CategoryManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB.DrawableDbBuildStatusEnum;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.HashSetManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
import org.sleuthkit.autopsy.imagegallery.gui.NoGroupsDialog;
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.SleuthkitCase.CaseDbTransaction;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
@ -90,8 +82,7 @@ import org.sleuthkit.datamodel.TskData;
*/
public final class ImageGalleryController {
private static final Logger LOGGER = Logger.getLogger(ImageGalleryController.class.getName());
private static ImageGalleryController instance;
private static final Logger logger = Logger.getLogger(ImageGalleryController.class.getName());
/**
* true if Image Gallery should listen to ingest events, false if it should
@ -103,7 +94,7 @@ public final class ImageGalleryController {
private final ReadOnlyBooleanWrapper stale = new ReadOnlyBooleanWrapper(false);
private final ReadOnlyBooleanWrapper metaDataCollapsed = new ReadOnlyBooleanWrapper(false);
private final ReadOnlyDoubleWrapper thumbnailSize = new ReadOnlyDoubleWrapper(100);
private final SimpleDoubleProperty thumbnailSizeProp = new SimpleDoubleProperty(100);
private final ReadOnlyBooleanWrapper regroupDisabled = new ReadOnlyBooleanWrapper(false);
private final ReadOnlyIntegerWrapper dbTaskQueueSize = new ReadOnlyIntegerWrapper(0);
@ -111,36 +102,23 @@ public final class ImageGalleryController {
private final History<GroupViewState> historyManager = new History<>();
private final UndoRedoManager undoManager = new UndoRedoManager();
private final GroupManager groupManager = new GroupManager(this);
private final HashSetManager hashSetManager = new HashSetManager();
private final CategoryManager categoryManager = new CategoryManager(this);
private final DrawableTagsManager tagsManager = new DrawableTagsManager(null);
private Runnable showTree;
private Toolbar toolbar;
private StackPane fullUIStackPane;
private StackPane centralStackPane;
private Node infoOverlay;
private final Region infoOverLayBackground = new Region() {
{
setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
setOpacity(.4);
}
};
private final ThumbnailCache thumbnailCache = new ThumbnailCache(this);
private final GroupManager groupManager;
private final HashSetManager hashSetManager;
private final CategoryManager categoryManager;
private final DrawableTagsManager tagsManager;
private ListeningExecutorService dbExecutor;
private SleuthkitCase sleuthKitCase;
private DrawableDB db;
private final Case autopsyCase;
private final SleuthkitCase sleuthKitCase;
private final DrawableDB drawableDB;
public static synchronized ImageGalleryController getDefault() {
if (instance == null) {
instance = new ImageGalleryController();
}
return instance;
public Case getAutopsyCase() {
return autopsyCase;
}
public ReadOnlyBooleanProperty getMetaDataCollapsed() {
public ReadOnlyBooleanProperty metaDataCollapsedProperty() {
return metaDataCollapsed.getReadOnlyProperty();
}
@ -148,19 +126,19 @@ public final class ImageGalleryController {
this.metaDataCollapsed.set(metaDataCollapsed);
}
public ReadOnlyDoubleProperty thumbnailSizeProperty() {
return thumbnailSize.getReadOnlyProperty();
public DoubleProperty thumbnailSizeProperty() {
return thumbnailSizeProp;
}
private GroupViewState getViewState() {
public GroupViewState getViewState() {
return historyManager.getCurrentState();
}
public ReadOnlyBooleanProperty regroupDisabled() {
public ReadOnlyBooleanProperty regroupDisabledProperty() {
return regroupDisabled.getReadOnlyProperty();
}
public ReadOnlyObjectProperty<GroupViewState> viewState() {
public ReadOnlyObjectProperty<GroupViewState> viewStateProperty() {
return historyManager.currentState();
}
@ -172,8 +150,8 @@ public final class ImageGalleryController {
return groupManager;
}
synchronized public DrawableDB getDatabase() {
return db;
public DrawableDB getDatabase() {
return drawableDB;
}
public void setListeningEnabled(boolean enabled) {
@ -193,14 +171,9 @@ public final class ImageGalleryController {
Platform.runLater(() -> {
stale.set(b);
});
try {
new PerCaseProperties(Case.getCurrentCaseThrows()).setConfigSetting(ImageGalleryModule.getModuleName(), PerCaseProperties.STALE, b.toString());
} catch (NoCurrentCaseException ex) {
Logger.getLogger(ImageGalleryController.class.getName()).log(Level.WARNING, "Exception while getting open case."); //NON-NLS
}
}
public ReadOnlyBooleanProperty stale() {
public ReadOnlyBooleanProperty staleProperty() {
return stale.getReadOnlyProperty();
}
@ -209,50 +182,57 @@ public final class ImageGalleryController {
return stale.get();
}
private ImageGalleryController() {
ImageGalleryController(@Nonnull Case newCase) throws TskCoreException {
listeningEnabled.addListener((observable, oldValue, newValue) -> {
this.autopsyCase = Objects.requireNonNull(newCase);
this.sleuthKitCase = newCase.getSleuthkitCase();
setListeningEnabled(ImageGalleryModule.isEnabledforCase(newCase));
groupManager = new GroupManager(this);
this.drawableDB = DrawableDB.getDrawableDB(this);
categoryManager = new CategoryManager(this);
tagsManager = new DrawableTagsManager(this);
tagsManager.registerListener(groupManager);
tagsManager.registerListener(categoryManager);
hashSetManager = new HashSetManager(drawableDB);
setStale(isDataSourcesTableStale());
dbExecutor = getNewDBExecutor();
// listener for the boolean property about when IG is listening / enabled
listeningEnabled.addListener((observable, wasPreviouslyEnabled, isEnabled) -> {
try {
//if we just turned on listening and a case is open and that case is not up to date
if (newValue && !oldValue && ImageGalleryModule.isDrawableDBStale(Case.getCurrentCaseThrows())) {
// if we just turned on listening and a single-user case is open and that case is not up to date, then rebuild it
// For multiuser cases, we defer DB rebuild till the user actually opens Image Gallery
if (isEnabled && !wasPreviouslyEnabled
&& isDataSourcesTableStale()
&& (Case.getCurrentCaseThrows().getCaseType() == CaseType.SINGLE_USER_CASE)) {
//populate the db
queueDBTask(new CopyAnalyzedFiles(instance, db, sleuthKitCase));
this.rebuildDB();
}
} catch (NoCurrentCaseException ex) {
LOGGER.log(Level.WARNING, "Exception while getting open case.", ex);
logger.log(Level.WARNING, "Exception while getting open case.", ex);
}
});
groupManager.getAnalyzedGroups().addListener((Observable o) -> {
//analyzed groups is confined to JFX thread
if (Case.isCaseOpen()) {
checkForGroups();
}
});
groupManager.getUnSeenGroups().addListener((Observable observable) -> {
//if there are unseen groups and none being viewed
if (groupManager.getUnSeenGroups().isEmpty() == false && (getViewState() == null || getViewState().getGroup() == null)) {
advance(GroupViewState.tile(groupManager.getUnSeenGroups().get(0)), true);
}
});
viewState().addListener((Observable observable) -> {
viewStateProperty().addListener((Observable observable) -> {
//when the viewed group changes, clear the selection and the undo/redo history
selectionModel.clearSelection();
undoManager.clear();
});
regroupDisabled.addListener(observable -> checkForGroups());
IngestManager ingestManager = IngestManager.getInstance();
PropertyChangeListener ingestEventHandler =
propertyChangeEvent -> Platform.runLater(this::updateRegroupDisabled);
PropertyChangeListener ingestEventHandler
= propertyChangeEvent -> Platform.runLater(this::updateRegroupDisabled);
ingestManager.addIngestModuleEventListener(ingestEventHandler);
ingestManager.addIngestJobEventListener(ingestEventHandler);
dbTaskQueueSize.addListener(obs -> this.updateRegroupDisabled());
}
public ReadOnlyBooleanProperty getCanAdvance() {
@ -264,10 +244,7 @@ public final class ImageGalleryController {
}
@ThreadConfined(type = ThreadConfined.ThreadType.ANY)
public void advance(GroupViewState newState, boolean forceShowTree) {
if (forceShowTree && showTree != null) {
showTree.run();
}
public void advance(GroupViewState newState) {
historyManager.advance(newState);
}
@ -284,131 +261,92 @@ public final class ImageGalleryController {
regroupDisabled.set((dbTaskQueueSize.get() > 0) || IngestManager.getInstance().isIngestRunning());
}
/**
* Check if there are any fully analyzed groups available from the
* GroupManager and remove blocking progress spinners if there are. If there
* aren't, add a blocking progress spinner with appropriate message.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
@NbBundle.Messages({"ImageGalleryController.noGroupsDlg.msg1=No groups are fully analyzed; but listening to ingest is disabled. "
+ " No groups will be available until ingest is finished and listening is re-enabled.",
"ImageGalleryController.noGroupsDlg.msg2=No groups are fully analyzed yet, but ingest is still ongoing. Please Wait.",
"ImageGalleryController.noGroupsDlg.msg3=No groups are fully analyzed yet, but image / video data is still being populated. Please Wait.",
"ImageGalleryController.noGroupsDlg.msg4=There are no images/videos available from the added datasources; but listening to ingest is disabled. "
+ " No groups will be available until ingest is finished and listening is re-enabled.",
"ImageGalleryController.noGroupsDlg.msg5=There are no images/videos in the added datasources.",
"ImageGalleryController.noGroupsDlg.msg6=There are no fully analyzed groups to display:"
+ " the current Group By setting resulted in no groups, "
+ "or no groups are fully analyzed but ingest is not running."})
synchronized private void checkForGroups() {
if (groupManager.getAnalyzedGroups().isEmpty()) {
if (IngestManager.getInstance().isIngestRunning()) {
if (listeningEnabled.not().get()) {
replaceNotification(fullUIStackPane,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1()));
} else {
replaceNotification(fullUIStackPane,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(),
new ProgressIndicator()));
}
} else if (dbTaskQueueSize.get() > 0) {
replaceNotification(fullUIStackPane,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(),
new ProgressIndicator()));
} else if (db != null && db.countAllFiles() <= 0) { // there are no files in db
if (listeningEnabled.not().get()) {
replaceNotification(fullUIStackPane,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4()));
} else {
replaceNotification(fullUIStackPane,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5()));
}
} else if (!groupManager.isRegrouping()) {
replaceNotification(centralStackPane,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6()));
}
} else {
clearNotification();
}
}
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void clearNotification() {
//remove the ingest spinner
if (fullUIStackPane != null) {
fullUIStackPane.getChildren().remove(infoOverlay);
}
//remove the ingest spinner
if (centralStackPane != null) {
centralStackPane.getChildren().remove(infoOverlay);
}
}
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void replaceNotification(StackPane stackPane, Node newNode) {
clearNotification();
infoOverlay = new StackPane(infoOverLayBackground, newNode);
if (stackPane != null) {
stackPane.getChildren().add(infoOverlay);
}
}
/**
* configure the controller for a specific case.
*
* @param theNewCase the case to configure the controller for
*
* @throws org.sleuthkit.datamodel.TskCoreException
*/
public synchronized void setCase(Case theNewCase) {
if (null == theNewCase) {
reset();
} else {
this.sleuthKitCase = theNewCase.getSleuthkitCase();
this.db = DrawableDB.getDrawableDB(ImageGalleryModule.getModuleOutputDir(theNewCase), this);
setListeningEnabled(ImageGalleryModule.isEnabledforCase(theNewCase));
setStale(ImageGalleryModule.isDrawableDBStale(theNewCase));
// if we add this line icons are made as files are analyzed rather than on demand.
// db.addUpdatedFileListener(IconCache.getDefault());
historyManager.clear();
groupManager.setDB(db);
hashSetManager.setDb(db);
categoryManager.setDb(db);
tagsManager.setAutopsyTagsManager(theNewCase.getServices().getTagsManager());
tagsManager.registerListener(groupManager);
tagsManager.registerListener(categoryManager);
shutDownDBExecutor();
dbExecutor = getNewDBExecutor();
}
/**
* Rebuilds the DrawableDB database.
*
*/
public void rebuildDB() {
// queue a rebuild task for each stale data source
getStaleDataSourceIds().forEach(dataSourceObjId -> queueDBTask(new CopyAnalyzedFiles(dataSourceObjId, this)));
}
/**
* reset the state of the controller (eg if the case is closed)
*/
public synchronized void reset() {
LOGGER.info("resetting ImageGalleryControler to initial state."); //NON-NLS
logger.info("Closing ImageGalleryControler for case."); //NON-NLS
selectionModel.clearSelection();
setListeningEnabled(false);
ThumbnailCache.getDefault().clearCache();
thumbnailCache.clearCache();
historyManager.clear();
groupManager.clear();
tagsManager.clearFollowUpTagName();
tagsManager.unregisterListener(groupManager);
tagsManager.unregisterListener(categoryManager);
groupManager.reset();
shutDownDBExecutor();
if (toolbar != null) {
toolbar.reset();
dbExecutor = getNewDBExecutor();
}
if (db != null) {
db.closeDBCon();
/**
* Checks if the datasources table in drawable DB is stale.
*
* @return true if datasources table is stale
*/
public boolean isDataSourcesTableStale() {
return isNotEmpty(getStaleDataSourceIds());
}
db = null;
/**
* Returns a set of data source object ids that are stale.
*
* This includes any data sources already in the table, that are not in
* COMPLETE status, or any data sources that might have been added to the
* case, but are not in the datasources table.
*
* @return list of data source object ids that are stale.
*/
Set<Long> getStaleDataSourceIds() {
Set<Long> staleDataSourceIds = new HashSet<>();
// no current case open to check
if ((null == getDatabase()) || (null == getSleuthKitCase())) {
return staleDataSourceIds;
}
try {
Map<Long, DrawableDbBuildStatusEnum> knownDataSourceIds = getDatabase().getDataSourceDbBuildStatus();
List<DataSource> dataSources = getSleuthKitCase().getDataSources();
Set<Long> caseDataSourceIds = new HashSet<>();
dataSources.stream().map(DataSource::getId).forEach(caseDataSourceIds::add);
// collect all data sources already in the table, that are not yet COMPLETE
knownDataSourceIds.entrySet().stream().forEach((Map.Entry<Long, DrawableDbBuildStatusEnum> t) -> {
DrawableDbBuildStatusEnum status = t.getValue();
if (DrawableDbBuildStatusEnum.COMPLETE != status) {
staleDataSourceIds.add(t.getKey());
}
});
// collect any new data sources in the case.
caseDataSourceIds.forEach((Long id) -> {
if (!knownDataSourceIds.containsKey(id)) {
staleDataSourceIds.add(id);
}
});
return staleDataSourceIds;
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Image Gallery failed to check if datasources table is stale.", ex);
return staleDataSourceIds;
}
}
synchronized private void shutDownDBExecutor() {
@ -417,7 +355,7 @@ public final class ImageGalleryController {
try {
dbExecutor.awaitTermination(30, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
LOGGER.log(Level.WARNING, "Image Gallery failed to shutdown DB Task Executor in a timely fashion.", ex);
logger.log(Level.WARNING, "Image Gallery failed to shutdown DB Task Executor in a timely fashion.", ex);
}
}
}
@ -449,46 +387,14 @@ public final class ImageGalleryController {
Platform.runLater(() -> dbTaskQueueSize.set(dbTaskQueueSize.get() - 1));
}
@Nullable
synchronized public DrawableFile getFileFromId(Long fileID) throws TskCoreException {
if (Objects.isNull(db)) {
LOGGER.log(Level.WARNING, "Could not get file from id, no DB set. The case is probably closed."); //NON-NLS
return null;
}
return db.getFileFromID(fileID);
}
public void setStacks(StackPane fullUIStack, StackPane centralStack) {
fullUIStackPane = fullUIStack;
this.centralStackPane = centralStack;
Platform.runLater(this::checkForGroups);
}
public synchronized void setToolbar(Toolbar toolbar) {
if (this.toolbar != null) {
throw new IllegalStateException("Can not set the toolbar a second time!");
}
this.toolbar = toolbar;
thumbnailSize.bind(toolbar.thumbnailSizeProperty());
public DrawableFile getFileFromID(Long fileID) throws TskCoreException {
return drawableDB.getFileFromID(fileID);
}
public ReadOnlyDoubleProperty regroupProgress() {
return groupManager.regroupProgress();
}
/**
* invoked by {@link OnStart} to make sure that the ImageGallery listeners
* get setup as early as possible, and do other setup stuff.
*/
void onStart() {
Platform.setImplicitExit(false);
LOGGER.info("setting up ImageGallery listeners"); //NON-NLS
//TODO can we do anything usefull in an InjestJobEventListener?
//IngestManager.getInstance().addIngestJobEventListener((PropertyChangeEvent evt) -> {});
IngestManager.getInstance().addIngestModuleEventListener(new IngestModuleEventListener());
Case.addPropertyChangeListener(new CaseEventListener());
}
public HashSetManager getHashSetManager() {
return hashSetManager;
}
@ -501,10 +407,6 @@ public final class ImageGalleryController {
return tagsManager;
}
public void setShowTree(Runnable showTree) {
this.showTree = showTree;
}
public UndoRedoManager getUndoManager() {
return undoManager;
}
@ -515,6 +417,12 @@ public final class ImageGalleryController {
public synchronized SleuthkitCase getSleuthKitCase() {
return sleuthKitCase;
}
public ThumbnailCache getThumbsCache() {
return thumbnailCache;
}
/**
@ -594,7 +502,7 @@ public final class ImageGalleryController {
return file;
}
public FileTask(AbstractFile f, DrawableDB taskDB) {
FileTask(AbstractFile f, DrawableDB taskDB) {
super();
this.file = f;
this.taskDB = taskDB;
@ -604,7 +512,7 @@ public final class ImageGalleryController {
/**
* task that updates one file in database with results from ingest
*/
static private class UpdateFileTask extends FileTask {
static class UpdateFileTask extends FileTask {
UpdateFileTask(AbstractFile f, DrawableDB taskDB) {
super(f, taskDB);
@ -631,7 +539,7 @@ public final class ImageGalleryController {
/**
* task that updates one file in database with results from ingest
*/
static private class RemoveFileTask extends FileTask {
static class RemoveFileTask extends FileTask {
RemoveFileTask(AbstractFile f, DrawableDB taskDB) {
super(f, taskDB);
@ -654,51 +562,74 @@ public final class ImageGalleryController {
}
}
/**
* Base abstract class for various methods of copying image files data, for
* a given data source, into the Image gallery DB.
*/
@NbBundle.Messages({"BulkTask.committingDb.status=committing image/video database",
"BulkTask.stopCopy.status=Stopping copy to drawable db task.",
"BulkTask.errPopulating.errMsg=There was an error populating Image Gallery database."})
/* Base abstract class for various methods of copying data into the Image gallery DB */
abstract static private class BulkTransferTask extends BackgroundTask {
abstract static class BulkTransferTask extends BackgroundTask {
static private final String FILE_EXTENSION_CLAUSE =
"(name LIKE '%." //NON-NLS
+ String.join("' OR name LIKE '%.", FileTypeUtils.getAllSupportedExtensions()) //NON-NLS
static private final String FILE_EXTENSION_CLAUSE
= "(extension LIKE '" //NON-NLS
+ String.join("' OR extension LIKE '", FileTypeUtils.getAllSupportedExtensions()) //NON-NLS
+ "') ";
static private final String MIMETYPE_CLAUSE =
"(mime_type LIKE '" //NON-NLS
static private final String MIMETYPE_CLAUSE
= "(mime_type LIKE '" //NON-NLS
+ String.join("' OR mime_type LIKE '", FileTypeUtils.getAllSupportedMimeTypes()) //NON-NLS
+ "') ";
static final String DRAWABLE_QUERY =
//grab files with supported extension
"(" + FILE_EXTENSION_CLAUSE
private final String DRAWABLE_QUERY;
private final String DATASOURCE_CLAUSE;
protected final ImageGalleryController controller;
protected final DrawableDB taskDB;
protected final SleuthkitCase tskCase;
protected final long dataSourceObjId;
private ProgressHandle progressHandle;
private boolean taskCompletionStatus;
BulkTransferTask(long dataSourceObjId, ImageGalleryController controller) {
this.controller = controller;
this.taskDB = controller.getDatabase();
this.tskCase = controller.getSleuthKitCase();
this.dataSourceObjId = dataSourceObjId;
DATASOURCE_CLAUSE = " (data_source_obj_id = " + dataSourceObjId + ") ";
DRAWABLE_QUERY
= DATASOURCE_CLAUSE
+ " AND ( "
+ //grab files with supported extension
FILE_EXTENSION_CLAUSE
//grab files with supported mime-types
+ " OR " + MIMETYPE_CLAUSE //NON-NLS
//grab files with image or video mime-types even if we don't officially support them
+ " OR mime_type LIKE 'video/%' OR mime_type LIKE 'image/%' )"; //NON-NLS
final ImageGalleryController controller;
final DrawableDB taskDB;
final SleuthkitCase tskCase;
ProgressHandle progressHandle;
BulkTransferTask(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
this.controller = controller;
this.taskDB = taskDB;
this.tskCase = tskCase;
}
/**
* Do any cleanup for this task.
*
* @param success true if the transfer was successful
*/
abstract void cleanup(boolean success);
abstract List<AbstractFile> getFiles() throws TskCoreException;
abstract void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDBTransaction) throws TskCoreException;
abstract void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr) throws TskCoreException;
/**
* Gets a list of files to process.
*
* @return list of files to process
*
* @throws TskCoreException
*/
List<AbstractFile> getFiles() throws TskCoreException {
return tskCase.findAllFilesWhere(DRAWABLE_QUERY);
}
@Override
public void run() {
@ -706,24 +637,32 @@ public final class ImageGalleryController {
progressHandle.start();
updateMessage(Bundle.CopyAnalyzedFiles_populatingDb_status());
DrawableDB.DrawableTransaction drawableDbTransaction = null;
CaseDbTransaction caseDbTransaction = null;
try {
//grab all files with supported extension or detected mime types
final List<AbstractFile> files = getFiles();
progressHandle.switchToDeterminate(files.size());
taskDB.insertOrUpdateDataSource(dataSourceObjId, DrawableDB.DrawableDbBuildStatusEnum.IN_PROGRESS);
updateProgress(0.0);
taskCompletionStatus = true;
int workDone = 0;
//do in transaction
DrawableDB.DrawableTransaction tr = taskDB.beginTransaction();
int workDone = 0;
drawableDbTransaction = taskDB.beginTransaction();
caseDbTransaction = tskCase.beginTransaction();
for (final AbstractFile f : files) {
if (isCancelled() || Thread.interrupted()) {
LOGGER.log(Level.WARNING, "Task cancelled: not all contents may be transfered to drawable database."); //NON-NLS
logger.log(Level.WARNING, "Task cancelled or interrupted: not all contents may be transfered to drawable database."); //NON-NLS
taskCompletionStatus = false;
progressHandle.finish();
break;
}
processFile(f, tr);
processFile(f, drawableDbTransaction, caseDbTransaction);
workDone++;
progressHandle.progress(f.getName(), workDone);
@ -737,23 +676,42 @@ public final class ImageGalleryController {
updateProgress(1.0);
progressHandle.start();
taskDB.commitTransaction(tr, true);
caseDbTransaction.commit();
taskDB.commitTransaction(drawableDbTransaction, true);
} catch (TskCoreException ex) {
if (null != drawableDbTransaction) {
taskDB.rollbackTransaction(drawableDbTransaction);
}
if (null != caseDbTransaction) {
try {
caseDbTransaction.rollback();
} catch (TskCoreException ex2) {
logger.log(Level.SEVERE, "Error in trying to rollback transaction", ex2); //NON-NLS
}
}
progressHandle.progress(Bundle.BulkTask_stopCopy_status());
LOGGER.log(Level.WARNING, "Stopping copy to drawable db task. Failed to transfer all database contents", ex); //NON-NLS
logger.log(Level.WARNING, "Stopping copy to drawable db task. Failed to transfer all database contents", ex); //NON-NLS
MessageNotifyUtil.Notify.warn(Bundle.BulkTask_errPopulating_errMsg(), ex.getMessage());
cleanup(false);
return;
} finally {
progressHandle.finish();
if (taskCompletionStatus) {
taskDB.insertOrUpdateDataSource(dataSourceObjId, DrawableDB.DrawableDbBuildStatusEnum.COMPLETE);
}
updateMessage("");
updateProgress(-1.0);
}
cleanup(true);
cleanup(taskCompletionStatus);
}
abstract ProgressHandle getInitialProgressHandle();
protected void setTaskCompletionStatus(boolean status) {
taskCompletionStatus = status;
}
}
/**
@ -766,24 +724,21 @@ public final class ImageGalleryController {
@NbBundle.Messages({"CopyAnalyzedFiles.committingDb.status=committing image/video database",
"CopyAnalyzedFiles.stopCopy.status=Stopping copy to drawable db task.",
"CopyAnalyzedFiles.errPopulating.errMsg=There was an error populating Image Gallery database."})
static private class CopyAnalyzedFiles extends BulkTransferTask {
static class CopyAnalyzedFiles extends BulkTransferTask {
CopyAnalyzedFiles(ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
super(controller, taskDB, tskCase);
CopyAnalyzedFiles(long dataSourceObjId, ImageGalleryController controller) {
super(dataSourceObjId, controller);
}
@Override
protected void cleanup(boolean success) {
controller.setStale(!success);
// at the end of the task, set the stale status based on the
// cumulative status of all data sources
controller.setStale(controller.isDataSourcesTableStale());
}
@Override
List<AbstractFile> getFiles() throws TskCoreException {
return tskCase.findAllFilesWhere(DRAWABLE_QUERY);
}
@Override
void processFile(AbstractFile f, DrawableDB.DrawableTransaction tr) {
void processFile(AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDbTransaction) throws TskCoreException {
final boolean known = f.getKnown() == TskData.FileKnown.KNOWN;
if (known) {
@ -791,13 +746,21 @@ public final class ImageGalleryController {
} else {
try {
if (FileTypeUtils.hasDrawableMIMEType(f)) { //supported mimetype => analyzed
taskDB.updateFile(DrawableFile.create(f, true, false), tr);
} else { //unsupported mimtype => analyzed but shouldn't include
//supported mimetype => analyzed
if (null != f.getMIMEType() && FileTypeUtils.hasDrawableMIMEType(f)) {
taskDB.updateFile(DrawableFile.create(f, true, false), tr, caseDbTransaction);
} else {
// if mimetype of the file hasn't been ascertained, ingest might not have completed yet.
if (null == f.getMIMEType()) {
// set to false to force the DB to be marked as stale
this.setTaskCompletionStatus(false);
} else {
//unsupported mimtype => analyzed but shouldn't include
taskDB.removeFile(f.getId(), tr);
}
}
} catch (FileTypeDetector.FileTypeDetectorInitException ex) {
throw new RuntimeException(ex);
throw new TskCoreException("Failed to initialize FileTypeDetector.", ex);
}
}
}
@ -813,24 +776,17 @@ public final class ImageGalleryController {
* Copy files from a newly added data source into the DB. Get all "drawable"
* files, based on extension and mime-type. After ingest we use file type id
* module and if necessary jpeg/png signature matching to add/remove files
*
* TODO: create methods to simplify progress value/text updates to both
* netbeans and ImageGallery progress/status
*/
@NbBundle.Messages({"PrePopulateDataSourceFiles.committingDb.status=committing image/video database"})
static private class PrePopulateDataSourceFiles extends BulkTransferTask {
private static final Logger LOGGER = Logger.getLogger(PrePopulateDataSourceFiles.class.getName());
private final Content dataSource;
static class PrePopulateDataSourceFiles extends BulkTransferTask {
/**
*
* @param dataSourceId Data source object ID
* @param dataSourceObjId The object ID of the DataSource that is being
* pre-populated into the DrawableDB.
* @param controller The controller for this task.
*/
PrePopulateDataSourceFiles(Content dataSource, ImageGalleryController controller, DrawableDB taskDB, SleuthkitCase tskCase) {
super(controller, taskDB, tskCase);
this.dataSource = dataSource;
PrePopulateDataSourceFiles(long dataSourceObjId, ImageGalleryController controller) {
super(dataSourceObjId, controller);
}
@Override
@ -838,14 +794,8 @@ public final class ImageGalleryController {
}
@Override
void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr) {
taskDB.insertFile(DrawableFile.create(f, false, false), tr);
}
@Override
List<AbstractFile> getFiles() throws TskCoreException {
long datasourceID = dataSource.getDataSource().getId();
return tskCase.findAllFilesWhere("data_source_obj_id = " + datasourceID + " AND " + DRAWABLE_QUERY);
void processFile(final AbstractFile f, DrawableDB.DrawableTransaction tr, CaseDbTransaction caseDBTransaction) {
taskDB.insertFile(DrawableFile.create(f, false, false), tr, caseDBTransaction);
}
@Override
@ -854,116 +804,4 @@ public final class ImageGalleryController {
return ProgressHandle.createHandle(Bundle.PrePopulateDataSourceFiles_prepopulatingDb_status(), this);
}
}
private class IngestModuleEventListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (RuntimeProperties.runningWithGUI() == false) {
/*
* Running in "headless" mode, no need to process any events.
* This cannot be done earlier because the switch to core
* components inactive may not have been made at start up.
*/
IngestManager.getInstance().removeIngestModuleEventListener(this);
return;
}
switch (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName())) {
case CONTENT_CHANGED:
//TODO: do we need to do anything here? -jm
case DATA_ADDED:
/*
* we could listen to DATA events and progressivly update
* files, and get data from DataSource ingest modules, but
* given that most modules don't post new artifacts in the
* events and we would have to query for them, without
* knowing which are the new ones, we just ignore these
* events for now. The relevant data should all be captured
* by file done event, anyways -jm
*/
break;
case FILE_DONE:
/**
* getOldValue has fileID getNewValue has
* {@link Abstractfile}
*/
AbstractFile file = (AbstractFile) evt.getNewValue();
if (isListeningEnabled()) {
if (file.isFile()) {
try {
synchronized (ImageGalleryController.this) {
if (ImageGalleryModule.isDrawableAndNotKnown(file)) {
//this file should be included and we don't already know about it from hash sets (NSRL)
queueDBTask(new UpdateFileTask(file, db));
} else if (FileTypeUtils.getAllSupportedExtensions().contains(file.getNameExtension())) {
//doing this check results in fewer tasks queued up, and faster completion of db update
//this file would have gotten scooped up in initial grab, but actually we don't need it
queueDBTask(new RemoveFileTask(file, db));
}
}
} catch (TskCoreException | FileTypeDetector.FileTypeDetectorInitException ex) {
//TODO: What to do here?
LOGGER.log(Level.SEVERE, "Unable to determine if file is drawable and not known. Not making any changes to DB", ex); //NON-NLS
MessageNotifyUtil.Notify.error("Image Gallery Error",
"Unable to determine if file is drawable and not known. Not making any changes to DB. See the logs for details.");
}
}
} else { //TODO: keep track of what we missed for later
setStale(true);
}
break;
}
}
}
private class CaseEventListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (RuntimeProperties.runningWithGUI() == false) {
/*
* Running in "headless" mode, no need to process any events.
* This cannot be done earlier because the switch to core
* components inactive may not have been made at start up.
*/
Case.removePropertyChangeListener(this);
return;
}
switch (Case.Events.valueOf(evt.getPropertyName())) {
case CURRENT_CASE:
Case newCase = (Case) evt.getNewValue();
if (newCase == null) { // case is closing
//close window, reset everything
SwingUtilities.invokeLater(ImageGalleryTopComponent::closeTopComponent);
reset();
} else { // a new case has been opened
setCase(newCase); //connect db, groupmanager, start worker thread
}
break;
case DATA_SOURCE_ADDED:
//copy all file data to drawable databse
Content newDataSource = (Content) evt.getNewValue();
if (isListeningEnabled()) {
queueDBTask(new PrePopulateDataSourceFiles(newDataSource, ImageGalleryController.this, getDatabase(), getSleuthKitCase()));
} else {//TODO: keep track of what we missed for later
setStale(true);
}
break;
case CONTENT_TAG_ADDED:
final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) evt;
if (getDatabase().isInDB(tagAddedEvent.getAddedTag().getContent().getId())) {
getTagsManager().fireTagAddedEvent(tagAddedEvent);
}
break;
case CONTENT_TAG_DELETED:
final ContentTagDeletedEvent tagDeletedEvent = (ContentTagDeletedEvent) evt;
if (getDatabase().isInDB(tagDeletedEvent.getDeletedTagInfo().getContentID())) {
getTagsManager().fireTagDeletedEvent(tagDeletedEvent);
}
break;
}
}
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-15 Basis Technology Corp.
* Copyright 2013-18 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,32 +18,77 @@
*/
package org.sleuthkit.autopsy.imagegallery;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.commons.lang3.StringUtils;
import java.util.logging.Level;
import javafx.application.Platform;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.core.RuntimeProperties;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
import org.sleuthkit.autopsy.events.AutopsyEvent;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableDB;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.IngestManager.IngestJobEvent;
import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.FILE_DONE;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
/** static definitions and utilities for the ImageGallery module */
/** static definitions, utilities, and listeners for the ImageGallery module */
@NbBundle.Messages({"ImageGalleryModule.moduleName=Image Gallery"})
public class ImageGalleryModule {
private static final Logger LOGGER = Logger.getLogger(ImageGalleryModule.class.getName());
private static final Logger logger = Logger.getLogger(ImageGalleryModule.class.getName());
private static final String MODULE_NAME = Bundle.ImageGalleryModule_moduleName();
private static final Object controllerLock = new Object();
private static ImageGalleryController controller;
public static ImageGalleryController getController() throws NoCurrentCaseException {
synchronized (controllerLock) {
if (controller == null) {
try {
controller = new ImageGalleryController(Case.getCurrentCaseThrows());
} catch (NoCurrentCaseException | TskCoreException ex) {
throw new NoCurrentCaseException("Error getting ImageGalleryController for the current case.", ex);
}
}
return controller;
}
}
/**
*
*
* This method is invoked by virtue of the OnStart annotation on the OnStart
* class class
*/
static void onStart() {
Platform.setImplicitExit(false);
logger.info("Setting up ImageGallery listeners"); //NON-NLS
IngestManager.getInstance().addIngestJobEventListener(new IngestJobEventListener());
IngestManager.getInstance().addIngestModuleEventListener(new IngestModuleEventListener());
Case.addPropertyChangeListener(new CaseEventListener());
}
static String getModuleName() {
return MODULE_NAME;
}
/**
* get the Path to the Case's ImageGallery ModuleOutput subfolder; ie
* ".../[CaseName]/ModuleOutput/Image Gallery/"
@ -53,7 +98,7 @@ public class ImageGalleryModule {
*
* @return the Path to the ModuleOuput subfolder for Image Gallery
*/
static Path getModuleOutputDir(Case theCase) {
public static Path getModuleOutputDir(Case theCase) {
return Paths.get(theCase.getModuleDirectory(), getModuleName());
}
@ -83,18 +128,9 @@ public class ImageGalleryModule {
* @return true if the drawable db is out of date for the given case, false
* otherwise
*/
public static boolean isDrawableDBStale(Case c) {
if (c != null) {
String stale = new PerCaseProperties(c).getConfigSetting(ImageGalleryModule.MODULE_NAME, PerCaseProperties.STALE);
return StringUtils.isNotBlank(stale) ? Boolean.valueOf(stale) : true;
} else {
return false;
public static boolean isDrawableDBStale(Case c) throws TskCoreException {
return new ImageGalleryController(c).isDataSourcesTableStale();
}
}
/**
* Is the given file 'supported' and not 'known'(nsrl hash hit). If so we
@ -105,7 +141,187 @@ public class ImageGalleryModule {
* @return true if the given {@link AbstractFile} is "drawable" and not
* 'known', else false
*/
public static boolean isDrawableAndNotKnown(AbstractFile abstractFile) throws TskCoreException, FileTypeDetector.FileTypeDetectorInitException {
public static boolean isDrawableAndNotKnown(AbstractFile abstractFile) throws FileTypeDetector.FileTypeDetectorInitException {
return (abstractFile.getKnown() != TskData.FileKnown.KNOWN) && FileTypeUtils.isDrawable(abstractFile);
}
/**
* Listener for IngestModuleEvents
*/
static private class IngestModuleEventListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (RuntimeProperties.runningWithGUI() == false) {
/*
* Running in "headless" mode, no need to process any events.
* This cannot be done earlier because the switch to core
* components inactive may not have been made at start up.
*/
IngestManager.getInstance().removeIngestModuleEventListener(this);
return;
}
if (IngestManager.IngestModuleEvent.valueOf(evt.getPropertyName()) != FILE_DONE) {
return;
}
// getOldValue has fileID getNewValue has Abstractfile
AbstractFile file = (AbstractFile) evt.getNewValue();
if (false == file.isFile()) {
return;
}
/* only process individual files in realtime on the node that is
* running the ingest. on a remote node, image files are processed
* enblock when ingest is complete */
if (((AutopsyEvent) evt).getSourceType() != AutopsyEvent.SourceType.LOCAL) {
return;
}
try {
ImageGalleryController con = getController();
if (con.isListeningEnabled()) {
try {
if (isDrawableAndNotKnown(file)) {
//this file should be included and we don't already know about it from hash sets (NSRL)
con.queueDBTask(new ImageGalleryController.UpdateFileTask(file, controller.getDatabase()));
} else if (FileTypeUtils.getAllSupportedExtensions().contains(file.getNameExtension())) {
/* Doing this check results in fewer tasks queued
* up, and faster completion of db update. This file
* would have gotten scooped up in initial grab, but
* actually we don't need it */
con.queueDBTask(new ImageGalleryController.RemoveFileTask(file, controller.getDatabase()));
}
} catch (FileTypeDetector.FileTypeDetectorInitException ex) {
logger.log(Level.SEVERE, "Unable to determine if file is drawable and not known. Not making any changes to DB", ex); //NON-NLS
MessageNotifyUtil.Notify.error("Image Gallery Error",
"Unable to determine if file is drawable and not known. Not making any changes to DB. See the logs for details.");
}
}
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex); //NON-NLS
}
}
}
/**
* Listener for case events.
*/
static private class CaseEventListener implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (RuntimeProperties.runningWithGUI() == false) {
/*
* Running in "headless" mode, no need to process any events.
* This cannot be done earlier because the switch to core
* components inactive may not have been made at start up.
*/
Case.removePropertyChangeListener(this);
return;
}
ImageGalleryController con;
try {
con = getController();
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex); //NON-NLS
return;
}
switch (Case.Events.valueOf(evt.getPropertyName())) {
case CURRENT_CASE:
synchronized (controllerLock) {
// case has changes: close window, reset everything
SwingUtilities.invokeLater(ImageGalleryTopComponent::closeTopComponent);
if (controller != null) {
controller.reset();
}
controller = null;
Case newCase = (Case) evt.getNewValue();
if (newCase != null) {
// a new case has been opened: connect db, groupmanager, start worker thread
try {
controller = new ImageGalleryController(newCase);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error changing case in ImageGallery.", ex);
}
}
}
break;
case DATA_SOURCE_ADDED:
//For a data source added on the local node, prepopulate all file data to drawable database
if (((AutopsyEvent) evt).getSourceType() == AutopsyEvent.SourceType.LOCAL) {
Content newDataSource = (Content) evt.getNewValue();
if (con.isListeningEnabled()) {
con.queueDBTask(new ImageGalleryController.PrePopulateDataSourceFiles(newDataSource.getId(), controller));
}
}
break;
case CONTENT_TAG_ADDED:
final ContentTagAddedEvent tagAddedEvent = (ContentTagAddedEvent) evt;
if (con.getDatabase().isInDB(tagAddedEvent.getAddedTag().getContent().getId())) {
con.getTagsManager().fireTagAddedEvent(tagAddedEvent);
}
break;
case CONTENT_TAG_DELETED:
final ContentTagDeletedEvent tagDeletedEvent = (ContentTagDeletedEvent) evt;
if (con.getDatabase().isInDB(tagDeletedEvent.getDeletedTagInfo().getContentID())) {
con.getTagsManager().fireTagDeletedEvent(tagDeletedEvent);
}
break;
default:
//we don't need to do anything for other events.
break;
}
}
}
/**
* Listener for Ingest Job events.
*/
static private class IngestJobEventListener implements PropertyChangeListener {
@NbBundle.Messages({
"ImageGalleryController.dataSourceAnalyzed.confDlg.msg= A new data source was added and finished ingest.\n"
+ "The image / video database may be out of date. "
+ "Do you want to update the database with ingest results?\n",
"ImageGalleryController.dataSourceAnalyzed.confDlg.title=Image Gallery"
})
@Override
public void propertyChange(PropertyChangeEvent evt) {
IngestJobEvent eventType = IngestJobEvent.valueOf(evt.getPropertyName());
if (eventType != IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED
|| ((AutopsyEvent) evt).getSourceType() != AutopsyEvent.SourceType.REMOTE) {
return;
}
// A remote node added a new data source and just finished ingest on it.
//drawable db is stale, and if ImageGallery is open, ask user what to do
ImageGalleryController con;
try {
con = getController();
} catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Attempted to access ImageGallery with no case open.", ex); //NON-NLS
return;
}
con.setStale(true);
if (con.isListeningEnabled() && ImageGalleryTopComponent.isImageGalleryOpen()) {
SwingUtilities.invokeLater(() -> {
int showAnswer = JOptionPane.showConfirmDialog(ImageGalleryTopComponent.getTopComponent(),
Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_msg(),
Bundle.ImageGalleryController_dataSourceAnalyzed_confDlg_title(),
JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
switch (showAnswer) {
case JOptionPane.YES_OPTION:
con.rebuildDB();
break;
case JOptionPane.NO_OPTION:
case JOptionPane.CANCEL_OPTION:
default:
break; //do nothing
}
});
}
}
}
}

View File

@ -18,13 +18,10 @@
*/
package org.sleuthkit.autopsy.imagegallery;
import java.awt.event.ActionEvent;
import java.util.logging.Level;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
* The Image/Video Gallery panel in the NetBeans provided Options Dialogs
@ -45,13 +42,8 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
enabledForCaseBox.setEnabled(Case.isCaseOpen() && IngestManager.getInstance().isIngestRunning() == false);
});
enabledByDefaultBox.addActionListener((ActionEvent e) -> {
controller.changed();
});
enabledForCaseBox.addActionListener((ActionEvent e) -> {
controller.changed();
});
enabledByDefaultBox.addActionListener(actionEvent -> controller.changed());
enabledForCaseBox.addActionListener(actionEvent -> controller.changed());
}
/**
@ -204,19 +196,18 @@ final class ImageGalleryOptionsPanel extends javax.swing.JPanel {
void store() {
ImageGalleryPreferences.setEnabledByDefault(enabledByDefaultBox.isSelected());
ImageGalleryController.getDefault().setListeningEnabled(enabledForCaseBox.isSelected());
ImageGalleryPreferences.setGroupCategorizationWarningDisabled(groupCategorizationWarningBox.isSelected());
// If a case is open, save the per case setting
try {
Case openCase = Case.getCurrentCaseThrows();
ImageGalleryModule.getController().setListeningEnabled(enabledForCaseBox.isSelected());
new PerCaseProperties(openCase).setConfigSetting(ImageGalleryModule.getModuleName(), PerCaseProperties.ENABLED, Boolean.toString(enabledForCaseBox.isSelected()));
} catch (NoCurrentCaseException ex) {
// It's not an error if there's no case open
}
}
/**

View File

@ -46,8 +46,7 @@ public class ImageGalleryPreferences {
* @return true if new cases should have image analyzer enabled.
*/
public static boolean isEnabledByDefault() {
final boolean aBoolean = preferences.getBoolean(ENABLED_BY_DEFAULT, true);
return aBoolean;
return preferences.getBoolean(ENABLED_BY_DEFAULT, true);
}
public static void setEnabledByDefault(boolean b) {

View File

@ -18,27 +18,52 @@
*/
package org.sleuthkit.autopsy.imagegallery;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.embed.swing.JFXPanel;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.ChoiceDialog;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TabPane;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.stage.Modality;
import javax.swing.SwingUtilities;
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
import static org.apache.commons.lang3.ObjectUtils.notEqual;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.openide.windows.Mode;
import org.openide.windows.RetainLocation;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableAttribute;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils;
import org.sleuthkit.autopsy.imagegallery.gui.NoGroupsDialog;
import org.sleuthkit.autopsy.imagegallery.gui.StatusBar;
import org.sleuthkit.autopsy.imagegallery.gui.SummaryTablePane;
import org.sleuthkit.autopsy.imagegallery.gui.Toolbar;
@ -46,6 +71,9 @@ import org.sleuthkit.autopsy.imagegallery.gui.drawableviews.GroupPane;
import org.sleuthkit.autopsy.imagegallery.gui.drawableviews.MetaDataPane;
import org.sleuthkit.autopsy.imagegallery.gui.navpanel.GroupTree;
import org.sleuthkit.autopsy.imagegallery.gui.navpanel.HashHitGroupList;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Top component which displays ImageGallery interface.
@ -69,17 +97,17 @@ import org.sleuthkit.autopsy.imagegallery.gui.navpanel.HashHitGroupList;
public final class ImageGalleryTopComponent extends TopComponent implements ExplorerManager.Provider, Lookup.Provider {
public final static String PREFERRED_ID = "ImageGalleryTopComponent"; // NON-NLS
private static final Logger LOGGER = Logger.getLogger(ImageGalleryTopComponent.class.getName());
private static final Logger logger = Logger.getLogger(ImageGalleryTopComponent.class.getName());
private static volatile boolean topComponentInitialized = false;
private final ExplorerManager em = new ExplorerManager();
private final Lookup lookup = (ExplorerUtils.createLookup(em, getActionMap()));
private final ImageGalleryController controller = ImageGalleryController.getDefault();
private ImageGalleryController controller;
private SplitPane splitPane;
private StackPane centralStack;
private BorderPane borderPane = new BorderPane();
private final BorderPane borderPane = new BorderPane();
private StackPane fullUIStack;
private MetaDataPane metaDataTable;
private GroupPane groupPane;
@ -88,24 +116,97 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
private VBox leftPane;
private Scene myScene;
public static void openTopComponent() {
//TODO:eventually move to this model, throwing away everything and rebuilding controller groupmanager etc for each case.
// synchronized (OpenTimelineAction.class) {
// if (timeLineController == null) {
// timeLineController = new TimeLineController();
// LOGGER.log(Level.WARNING, "Failed to get TimeLineController from lookup. Instantiating one directly.S");
// }
// }
// timeLineController.openTimeLine();
final TopComponent tc = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
if (tc != null) {
private Node infoOverlay;
private final Region infoOverLayBackground = new TranslucentRegion();
/**
* Returns whether the ImageGallery window is open or not.
*
* @return true, if Image gallery is opened, false otherwise
*/
public static boolean isImageGalleryOpen() {
final TopComponent topComponent = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
if (topComponent != null) {
return topComponent.isOpened();
}
return false;
}
/**
* Returns the top component window.
*
* @return Image gallery top component window, null if it's not open
*/
public static TopComponent getTopComponent() {
return WindowManager.getDefault().findTopComponent(PREFERRED_ID);
}
@Messages({
"ImageGalleryTopComponent.openTopCommponent.chooseDataSourceDialog.headerText=Choose a data source to view.",
"ImageGalleryTopComponent.openTopCommponent.chooseDataSourceDialog.contentText=Data source:",
"ImageGalleryTopComponent.openTopCommponent.chooseDataSourceDialog.all=All",
"ImageGalleryTopComponent.openTopCommponent.chooseDataSourceDialog.titleText=Image Gallery",})
public static void openTopComponent() throws NoCurrentCaseException {
final TopComponent topComponent = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
if (topComponent == null) {
return;
}
topComponentInitialized = true;
if (tc.isOpened() == false) {
tc.open();
if (topComponent.isOpened()) {
showTopComponent(topComponent);
return;
}
tc.toFront();
tc.requestActive();
List<DataSource> dataSources = Collections.emptyList();
ImageGalleryController controller = ImageGalleryModule.getController();
((ImageGalleryTopComponent) topComponent).setController(controller);
try {
dataSources = controller.getSleuthKitCase().getDataSources();
} catch (TskCoreException tskCoreException) {
logger.log(Level.SEVERE, "Unable to get data sourcecs.", tskCoreException);
}
GroupManager groupManager = controller.getGroupManager();
synchronized (groupManager) {
if (dataSources.size() <= 1
|| groupManager.getGroupBy() != DrawableAttribute.PATH) {
/* if there is only one datasource or the grouping is already
* set to something other than path , don't both to ask for
* datasource */
groupManager.regroup(null, groupManager.getGroupBy(), groupManager.getSortBy(), groupManager.getSortOrder(), true);
showTopComponent(topComponent);
return;
}
}
Map<String, DataSource> dataSourceNames = new HashMap<>();
dataSourceNames.put("All", null);
dataSources.forEach(dataSource -> dataSourceNames.put(dataSource.getName(), dataSource));
Platform.runLater(() -> {
ChoiceDialog<String> datasourceDialog = new ChoiceDialog<>(null, dataSourceNames.keySet());
datasourceDialog.setTitle(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_titleText());
datasourceDialog.setHeaderText(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_headerText());
datasourceDialog.setContentText(Bundle.ImageGalleryTopComponent_openTopCommponent_chooseDataSourceDialog_contentText());
datasourceDialog.initModality(Modality.APPLICATION_MODAL);
GuiUtils.setDialogIcons(datasourceDialog);
Optional<String> dataSourceName = datasourceDialog.showAndWait();
DataSource dataSource = dataSourceName.map(dataSourceNames::get).orElse(null);
synchronized (groupManager) {
groupManager.regroup(dataSource, groupManager.getGroupBy(), groupManager.getSortBy(), groupManager.getSortOrder(), true);
}
SwingUtilities.invokeLater(() -> showTopComponent(topComponent));
});
}
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
public static void showTopComponent(TopComponent topComponent) {
topComponent.open();
topComponent.toFront();
topComponent.requestActive();
}
public static void closeTopComponent() {
@ -115,17 +216,27 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
try {
etc.close();
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "failed to close " + PREFERRED_ID, e); // NON-NLS
logger.log(Level.SEVERE, "failed to close " + PREFERRED_ID, e); // NON-NLS
}
}
}
}
public ImageGalleryTopComponent() {
public ImageGalleryTopComponent() throws NoCurrentCaseException {
setName(Bundle.CTL_ImageGalleryTopComponent());
initComponents();
setController(ImageGalleryModule.getController());
}
Platform.runLater(() -> {//initialize jfx ui
synchronized private void setController(ImageGalleryController controller) {
if (this.controller != null && notEqual(this.controller, controller)) {
this.controller.reset();
}
this.controller = controller;
Platform.runLater(new Runnable() {
@Override
public void run() {
//initialize jfx ui
fullUIStack = new StackPane(); //this is passed into controller
myScene = new Scene(fullUIStack);
jfxPanel.setScene(myScene);
@ -137,12 +248,9 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
Toolbar toolbar = new Toolbar(controller);
borderPane.setTop(toolbar);
borderPane.setBottom(new StatusBar(controller));
metaDataTable = new MetaDataPane(controller);
groupTree = new GroupTree(controller);
hashHitList = new HashHitGroupList(controller);
TabPane tabPane = new TabPane(groupTree, hashHitList);
tabPane.setPrefWidth(TabPane.USE_COMPUTED_SIZE);
tabPane.setMinWidth(TabPane.USE_PREF_SIZE);
@ -154,9 +262,11 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
splitPane.getItems().addAll(leftPane, centralStack, metaDataTable);
splitPane.setDividerPositions(0.1, 1.0);
ImageGalleryController.getDefault().setStacks(fullUIStack, centralStack);
ImageGalleryController.getDefault().setToolbar(toolbar);
ImageGalleryController.getDefault().setShowTree(() -> tabPane.getSelectionModel().select(groupTree));
controller.regroupDisabledProperty().addListener((Observable observable) -> checkForGroups());
controller.getGroupManager().getAnalyzedGroups().addListener((Observable observable) -> Platform.runLater(() -> checkForGroups()));
Platform.runLater(() -> checkForGroups());
}
});
}
@ -212,4 +322,100 @@ public final class ImageGalleryTopComponent extends TopComponent implements Expl
public Lookup getLookup() {
return lookup;
}
/**
* Check if there are any fully analyzed groups available from the
* GroupManager and remove blocking progress spinners if there are. If there
* aren't, add a blocking progress spinner with appropriate message.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
@NbBundle.Messages({
"ImageGalleryController.noGroupsDlg.msg1=No groups are fully analyzed; but listening to ingest is disabled. "
+ " No groups will be available until ingest is finished and listening is re-enabled.",
"ImageGalleryController.noGroupsDlg.msg2=No groups are fully analyzed yet, but ingest is still ongoing. Please Wait.",
"ImageGalleryController.noGroupsDlg.msg3=No groups are fully analyzed yet, but image / video data is still being populated. Please Wait.",
"ImageGalleryController.noGroupsDlg.msg4=There are no images/videos available from the added datasources; but listening to ingest is disabled. "
+ " No groups will be available until ingest is finished and listening is re-enabled.",
"ImageGalleryController.noGroupsDlg.msg5=There are no images/videos in the added datasources.",
"ImageGalleryController.noGroupsDlg.msg6=There are no fully analyzed groups to display:"
+ " the current Group By setting resulted in no groups, "
+ "or no groups are fully analyzed but ingest is not running."})
private void checkForGroups() {
GroupManager groupManager = controller.getGroupManager();
synchronized (groupManager) {
if (isNotEmpty(groupManager.getAnalyzedGroups())) {
clearNotification();
return;
}
if (IngestManager.getInstance().isIngestRunning()) {
if (controller.isListeningEnabled()) {
replaceNotification(centralStack,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg2(),
new ProgressIndicator()));
} else {
replaceNotification(fullUIStack,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg1()));
}
return;
}
if (controller.getDBTasksQueueSizeProperty().get() > 0) {
replaceNotification(fullUIStack,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg3(),
new ProgressIndicator()));
return;
}
try {
if (controller.getDatabase().countAllFiles() <= 0) {
// there are no files in db
if (controller.isListeningEnabled()) {
replaceNotification(fullUIStack,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg5()));
} else {
replaceNotification(fullUIStack,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg4()));
}
return;
}
} catch (TskCoreException tskCoreException) {
logger.log(Level.SEVERE, "Error counting files in the database.", tskCoreException);
}
if (false == groupManager.isRegrouping()) {
replaceNotification(centralStack,
new NoGroupsDialog(Bundle.ImageGalleryController_noGroupsDlg_msg6()));
}
}
}
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void replaceNotification(StackPane stackPane, Node newNode) {
clearNotification();
infoOverlay = new StackPane(infoOverLayBackground, newNode);
if (stackPane != null) {
stackPane.getChildren().add(infoOverlay);
}
}
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void clearNotification() {
//remove the ingest spinner
fullUIStack.getChildren().remove(infoOverlay);
//remove the ingest spinner
centralStack.getChildren().remove(infoOverlay);
}
/**
* Region with partialy opacity used to block out parts of the UI behind a
* pseudo dialog.
*/
static final private class TranslucentRegion extends Region {
TranslucentRegion() {
setBackground(new Background(new BackgroundFill(Color.GREY, CornerRadii.EMPTY, Insets.EMPTY)));
setOpacity(.4);
}
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013 Basis Technology Corp.
* Copyright 2013-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,26 +18,19 @@
*/
package org.sleuthkit.autopsy.imagegallery;
import org.sleuthkit.autopsy.coreutils.Logger;
/**
*
* The {@link org.openide.modules.OnStart} annotation tells NetBeans to invoke
* this class's {@link OnStart#run()} method
* The org.openide.modules.OnStart annotation tells NetBeans to invoke this
* class's run method.
*/
@org.openide.modules.OnStart
public class OnStart implements Runnable {
static private final Logger LOGGER = Logger.getLogger(OnStart.class.getName());
/**
*
*
* This method is invoked by virtue of the {@link OnStart} annotation on the
* {@link ImageGalleryModule} class
* This method is invoked by virtue of the OnStart annotation on the this
* class
*/
@Override
public void run() {
ImageGalleryController.getDefault().onStart();
ImageGalleryModule.onStart();
}
}

View File

@ -40,8 +40,6 @@ class PerCaseProperties {
public static final String ENABLED = "enabled"; //NON-NLS
public static final String STALE = "stale"; //NON-NLS
private final Case theCase;
PerCaseProperties(Case c) {

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-15 Basis Technology Corp.
* Copyright 2013-18 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -54,9 +54,13 @@ import org.sleuthkit.datamodel.TskCoreException;
* TODO: this was only a singleton for convenience, convert this to
* non-singleton class -jm?
*/
public enum ThumbnailCache {
public class ThumbnailCache {
instance;
private final ImageGalleryController controller;
public ThumbnailCache(ImageGalleryController controller) {
this.controller = controller;
}
private static final int MAX_THUMBNAIL_SIZE = 300;
@ -71,10 +75,6 @@ public enum ThumbnailCache {
.softValues()
.expireAfterAccess(10, TimeUnit.MINUTES).build();
public static ThumbnailCache getDefault() {
return instance;
}
/**
* currently desired icon size. is bound in {@link Toolbar}
*/
@ -109,7 +109,7 @@ public enum ThumbnailCache {
@Nullable
public Image get(Long fileID) {
try {
return get(ImageGalleryController.getDefault().getFileFromId(fileID));
return get(controller.getFileFromID(fileID));
} catch (TskCoreException ex) {
LOGGER.log(Level.WARNING, "Failed to load thumbnail for file: " + fileID, ex.getCause()); //NON-NLS
return null;

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2013-2017 Basis Technology Corp.
* Copyright 2013-2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -36,7 +36,6 @@ import javax.swing.SwingWorker;
import org.controlsfx.control.action.Action;
import org.controlsfx.control.action.ActionUtils;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.actions.GetTagNameAndCommentDialog;
@ -50,8 +49,8 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableFile;
import org.sleuthkit.autopsy.imagegallery.datamodel.DrawableTagsManager;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
/**
* Instances of this Action allow users to apply tags to content.
@ -75,7 +74,7 @@ public class AddTagAction extends Action {
setEventHandler(actionEvent -> addTagWithComment(""));
}
static public Menu getTagMenu(ImageGalleryController controller) {
static public Menu getTagMenu(ImageGalleryController controller) throws TskCoreException {
return new TagMenu(controller);
}
@ -94,7 +93,7 @@ public class AddTagAction extends Action {
DrawableTagsManager tagsManager = controller.getTagsManager();
for (Long fileID : selectedFiles) {
try {
final DrawableFile file = controller.getFileFromId(fileID);
final DrawableFile file = controller.getFileFromID(fileID);
LOGGER.log(Level.INFO, "tagging {0} with {1} and comment {2}", new Object[]{file.getName(), tagName.getDisplayName(), comment}); //NON-NLS
List<ContentTag> contentTags = tagsManager.getContentTags(file);
@ -141,7 +140,7 @@ public class AddTagAction extends Action {
"AddDrawableTagAction.displayName.singular=Tag File"})
private static class TagMenu extends Menu {
TagMenu(ImageGalleryController controller) {
TagMenu(ImageGalleryController controller) throws TskCoreException {
setGraphic(new ImageView(DrawableAttribute.TAGS.getIcon()));
ObservableSet<Long> selectedFileIDs = controller.getSelectionModel().getSelected();
setText(selectedFileIDs.size() > 1
@ -163,11 +162,10 @@ public class AddTagAction extends Action {
empty.setDisable(true);
quickTagMenu.getItems().add(empty);
} else {
for (final TagName tagName : tagNames) {
AddTagAction addDrawableTagAction = new AddTagAction(controller, tagName, selectedFileIDs);
MenuItem tagNameItem = ActionUtils.createMenuItem(addDrawableTagAction);
quickTagMenu.getItems().add(tagNameItem);
}
tagNames.stream()
.map(tagName -> new AddTagAction(controller, tagName, selectedFileIDs))
.map(ActionUtils::createMenuItem)
.forEachOrdered(quickTagMenu.getItems()::add);
}
/*

View File

@ -138,7 +138,7 @@ public class CategorizeAction extends Action {
TagName catZeroTagName = categoryManager.getTagName(DhsImageCategory.ZERO);
for (long fileID : fileIDs) {
try {
DrawableFile file = controller.getFileFromId(fileID); //drawable db access
DrawableFile file = controller.getFileFromID(fileID); //drawable db access
if (createUndo) {
DhsImageCategory oldCat = file.getCategory(); //drawable db access
TagName oldCatTagName = categoryManager.getTagName(oldCat);

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2015-16 Basis Technology Corp.
* Copyright 2015-18 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -34,11 +34,12 @@ import javafx.scene.control.Label;
import javafx.scene.control.Separator;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import static org.apache.commons.lang.ObjectUtils.notEqual;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryPreferences;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.datamodel.TskCoreException;
/**
@ -52,7 +53,8 @@ public class CategorizeGroupAction extends CategorizeAction {
public CategorizeGroupAction(DhsImageCategory newCat, ImageGalleryController controller) {
super(controller, newCat, null);
setEventHandler(actionEvent -> {
ObservableList<Long> fileIDs = controller.viewState().get().getGroup().getFileIDs();
controller.getViewState().getGroup().ifPresent(group -> {
ObservableList<Long> fileIDs = group.getFileIDs();
if (ImageGalleryPreferences.isGroupCategorizationWarningDisabled()) {
//if they have preveiously disabled the warning, just go ahead and apply categories.
@ -62,7 +64,7 @@ public class CategorizeGroupAction extends CategorizeAction {
for (Long fileID : fileIDs) {
try {
DhsImageCategory category = controller.getFileFromId(fileID).getCategory();
DhsImageCategory category = controller.getFileFromID(fileID).getCategory();
if (false == DhsImageCategory.ZERO.equals(category) && newCat.equals(category) == false) {
catCountMap.merge(category, 1L, Long::sum);
}
@ -79,6 +81,8 @@ public class CategorizeGroupAction extends CategorizeAction {
}
}
});
});
}
@NbBundle.Messages({"CategorizeGroupAction.OverwriteButton.text=Overwrite",
@ -88,21 +92,20 @@ public class CategorizeGroupAction extends CategorizeAction {
"CategorizeGroupAction.fileCountHeader=Files in the following categories will have their categories overwritten: "})
private void showConfirmationDialog(final Map<DhsImageCategory, Long> catCountMap, DhsImageCategory newCat, ObservableList<Long> fileIDs) {
ButtonType categorizeButtonType =
new ButtonType(Bundle.CategorizeGroupAction_OverwriteButton_text(), ButtonBar.ButtonData.APPLY);
ButtonType categorizeButtonType
= new ButtonType(Bundle.CategorizeGroupAction_OverwriteButton_text(), ButtonBar.ButtonData.APPLY);
VBox textFlow = new VBox();
for (Map.Entry<DhsImageCategory, Long> entry : catCountMap.entrySet()) {
if (entry.getKey().equals(newCat) == false) {
if (entry.getValue() > 0) {
if (entry.getValue() > 0
&& notEqual(entry.getKey(), newCat)) {
Label label = new Label(Bundle.CategorizeGroupAction_fileCountMessage(entry.getValue(), entry.getKey().getDisplayName()),
entry.getKey().getGraphic());
label.setContentDisplay(ContentDisplay.RIGHT);
textFlow.getChildren().add(label);
}
}
}
CheckBox checkBox = new CheckBox(Bundle.CategorizeGroupAction_dontShowAgain());
Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "", categorizeButtonType, ButtonType.CANCEL); //NON-NLS

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-17 Basis Technology Corp.
* Copyright 2011-18 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -18,15 +18,16 @@
*/
package org.sleuthkit.autopsy.imagegallery.actions;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.Optional;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.binding.ObjectExpression;
import javafx.collections.ObservableList;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import org.apache.commons.collections4.CollectionUtils;
import org.controlsfx.control.action.Action;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.DrawableGroup;
import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupManager;
@ -36,58 +37,87 @@ import org.sleuthkit.autopsy.imagegallery.datamodel.grouping.GroupViewState;
* Marks the currently displayed group as "seen" and advances to the next unseen
* group
*/
@NbBundle.Messages({"NextUnseenGroup.markGroupSeen=Mark Group Seen",
"NextUnseenGroup.nextUnseenGroup=Next Unseen group"})
@NbBundle.Messages({
"NextUnseenGroup.markGroupSeen=Mark Group Seen",
"NextUnseenGroup.nextUnseenGroup=Next Unseen group",
"NextUnseenGroup.allGroupsSeen=All Groups Have Been Seen"})
public class NextUnseenGroup extends Action {
private static final Image END =
new Image(NextUnseenGroup.class.getResourceAsStream("/org/sleuthkit/autopsy/imagegallery/images/control-stop.png")); //NON-NLS
private static final Image ADVANCE =
new Image(NextUnseenGroup.class.getResourceAsStream("/org/sleuthkit/autopsy/imagegallery/images/control-double.png")); //NON-NLS
private static final String IMAGE_PATH = "/org/sleuthkit/autopsy/imagegallery/images/"; //NON-NLS
private static final Image END_IMAGE = new Image(NextUnseenGroup.class.getResourceAsStream(
IMAGE_PATH + "control-stop.png")); //NON-NLS
private static final Image ADVANCE_IMAGE = new Image(NextUnseenGroup.class.getResourceAsStream(
IMAGE_PATH + "control-double.png")); //NON-NLS
private static final String MARK_GROUP_SEEN = Bundle.NextUnseenGroup_markGroupSeen();
private static final String NEXT_UNSEEN_GROUP = Bundle.NextUnseenGroup_nextUnseenGroup();
private static final String ALL_GROUPS_SEEN = Bundle.NextUnseenGroup_allGroupsSeen();
private final GroupManager groupManager;
private final ImageGalleryController controller;
private final ObservableList<DrawableGroup> unSeenGroups;
private final ObservableList<DrawableGroup> analyzedGroups;
private final GroupManager groupManager;
public NextUnseenGroup(ImageGalleryController controller) {
super(NEXT_UNSEEN_GROUP);
setGraphic(new ImageView(ADVANCE_IMAGE));
this.controller = controller;
groupManager = controller.getGroupManager();
unSeenGroups = groupManager.getUnSeenGroups();
analyzedGroups = groupManager.getAnalyzedGroups();
setGraphic(new ImageView(ADVANCE));
unSeenGroups.addListener((Observable observable) -> updateButton());
controller.viewStateProperty().addListener((Observable observable) -> updateButton());
//TODO: do we need both these listeners?
analyzedGroups.addListener((Observable o) -> this.updateButton());
unSeenGroups.addListener((Observable o) -> this.updateButton());
setEventHandler(event -> {
//fx-thread
setEventHandler(event -> { //on fx-thread
//if there is a group assigned to the view, mark it as seen
Optional.ofNullable(controller.viewState())
.map(ObjectExpression<GroupViewState>::getValue)
.map(GroupViewState::getGroup)
.ifPresent(group -> groupManager.markGroupSeen(group, true));
if (unSeenGroups.isEmpty() == false) {
controller.advance(GroupViewState.tile(unSeenGroups.get(0)), true);
updateButton();
}
Optional.ofNullable(controller.getViewState())
.flatMap(GroupViewState::getGroup)
.ifPresent(group -> {
groupManager.markGroupSeen(group, true)
.addListener(this::advanceToNextUnseenGroup, MoreExecutors.newDirectExecutorService());
});
});
updateButton();
}
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private void advanceToNextUnseenGroup() {
synchronized (groupManager) {
if (CollectionUtils.isNotEmpty(unSeenGroups)) {
controller.advance(GroupViewState.tile(unSeenGroups.get(0)));
}
updateButton();
}
}
private void updateButton() {
setDisabled(unSeenGroups.isEmpty());
if (unSeenGroups.size() <= 1) {
setText(MARK_GROUP_SEEN);
setGraphic(new ImageView(END));
int size = unSeenGroups.size();
if (size < 1) {
//there are no unseen groups.
Platform.runLater(() -> {
setDisabled(true);
setText(ALL_GROUPS_SEEN);
setGraphic(null);
});
} else {
DrawableGroup get = unSeenGroups.get(0);
DrawableGroup orElse = Optional.ofNullable(controller.getViewState()).flatMap(GroupViewState::getGroup).orElse(null);
boolean equals = get.equals(orElse);
if (size == 1 & equals) {
//The only unseen group is the one that is being viewed.
Platform.runLater(() -> {
setDisabled(false);
setText(MARK_GROUP_SEEN);
setGraphic(new ImageView(END_IMAGE));
});
} else {
//there are more unseen groups.
Platform.runLater(() -> {
setDisabled(false);
setText(NEXT_UNSEEN_GROUP);
setGraphic(new ImageView(ADVANCE));
setGraphic(new ImageView(ADVANCE_IMAGE));
});
}
}
}
}

View File

@ -21,16 +21,11 @@ package org.sleuthkit.autopsy.imagegallery.actions;
import java.awt.Component;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.net.URL;
import java.util.logging.Level;
import javafx.application.Platform;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.image.Image;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JMenuItem;
@ -49,42 +44,27 @@ import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.core.Installer;
import org.sleuthkit.autopsy.core.RuntimeProperties;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryModule;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryTopComponent;
import org.sleuthkit.autopsy.imagegallery.gui.GuiUtils;
import org.sleuthkit.datamodel.TskCoreException;
@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.imagegallery.OpenAction")
@ActionReferences(value = {
@ActionReference(path = "Menu/Tools", position = 101),
@ActionReference(path = "Menu/Tools", position = 101)
,
@ActionReference(path = "Toolbars/Case", position = 101)})
@ActionRegistration(displayName = "#CTL_OpenAction", lazy = false)
@Messages({"CTL_OpenAction=Images/Videos",
"OpenAction.stale.confDlg.msg=The image / video database may be out of date. " +
"Do you want to update and listen for further ingest results?\n" +
"Choosing 'yes' will update the database and enable listening to future ingests.",
"OpenAction.stale.confDlg.msg=The image / video database may be out of date. "
+ "Do you want to update and listen for further ingest results?\n"
+ "Choosing 'yes' will update the database and enable listening to future ingests.",
"OpenAction.stale.confDlg.title=Image Gallery"})
public final class OpenAction extends CallableSystemAction {
private static final Logger logger = Logger.getLogger(OpenAction.class.getName());
private static final String VIEW_IMAGES_VIDEOS = Bundle.CTL_OpenAction();
/**
* Image to use as title bar icon in dialogs
*/
private static final Image AUTOPSY_ICON;
static {
Image tempImg = null;
try {
tempImg = new Image(new URL("nbresloc:/org/netbeans/core/startup/frame.gif").openStream()); //NON-NLS
} catch (IOException ex) {
logger.log(Level.WARNING, "Failed to load branded icon for progress dialog.", ex); //NON-NLS
}
AUTOPSY_ICON = tempImg;
}
private static final long FILE_LIMIT = 6_000_000;
private final PropertyChangeListener pcl;
@ -145,10 +125,7 @@ public final class OpenAction extends CallableSystemAction {
}
@Override
@SuppressWarnings("fallthrough")
@NbBundle.Messages({
"OpenAction.dialogTitle=Image Gallery"
})
@NbBundle.Messages({"OpenAction.dialogTitle=Image Gallery"})
public void performAction() {
//check case
@ -165,18 +142,40 @@ public final class OpenAction extends CallableSystemAction {
setEnabled(false);
return;
}
if (ImageGalleryModule.isDrawableDBStale(currentCase)) {
try {
ImageGalleryController controller = ImageGalleryModule.getController();
if (controller.isDataSourcesTableStale()) {
//drawable db is stale, ask what to do
int answer = JOptionPane.showConfirmDialog(WindowManager.getDefault().getMainWindow(), Bundle.OpenAction_stale_confDlg_msg(),
Bundle.OpenAction_stale_confDlg_title(), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
int answer = JOptionPane.showConfirmDialog(
WindowManager.getDefault().getMainWindow(),
Bundle.OpenAction_stale_confDlg_msg(),
Bundle.OpenAction_stale_confDlg_title(),
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE);
switch (answer) {
case JOptionPane.YES_OPTION:
ImageGalleryController.getDefault().setListeningEnabled(true);
//fall through
case JOptionPane.NO_OPTION:
/* For a single-user case, we favor user experience, and
* rebuild the database as soon as Image Gallery is
* enabled for the case. For a multi-user case, we favor
* overall performance and user experience, not every
* user may want to review images, so we rebuild the
* database only when a user launches Image Gallery.
*/
if (currentCase.getCaseType() == Case.CaseType.SINGLE_USER_CASE) {
controller.setListeningEnabled(true);
} else {
controller.rebuildDB();
}
ImageGalleryTopComponent.openTopComponent();
break;
case JOptionPane.NO_OPTION: {
ImageGalleryTopComponent.openTopComponent();
}
break;
case JOptionPane.CANCEL_OPTION:
break; //do nothing
}
@ -184,6 +183,9 @@ public final class OpenAction extends CallableSystemAction {
//drawable db is not stale, just open it
ImageGalleryTopComponent.openTopComponent();
}
} catch (NoCurrentCaseException noCurrentCaseException) {
logger.log(Level.WARNING, "There was no case open when Image Gallery was opened.", noCurrentCaseException);
}
}
private boolean tooManyFiles() {
@ -198,16 +200,6 @@ public final class OpenAction extends CallableSystemAction {
return false;
}
/**
* Set the title bar icon for the given Dialog to be the Autopsy logo icon.
*
* @param dialog The dialog to set the title bar icon for.
*/
@ThreadConfined(type = ThreadConfined.ThreadType.JFX)
private static void setDialogIcons(Dialog<?> dialog) {
((Stage) dialog.getDialogPane().getScene().getWindow()).getIcons().setAll(AUTOPSY_ICON);
}
@NbBundle.Messages({
"ImageGallery.showTooManyFiles.contentText="
+ "There are too many files in the DB to ensure reasonable performance."
@ -218,7 +210,7 @@ public final class OpenAction extends CallableSystemAction {
Bundle.ImageGallery_showTooManyFiles_contentText(), ButtonType.OK);
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.setTitle(Bundle.OpenAction_dialogTitle());
setDialogIcons(dialog);
GuiUtils.setDialogIcons(dialog);
dialog.setHeaderText(Bundle.ImageGallery_showTooManyFiles_headerText());
dialog.showAndWait();
}

View File

@ -29,9 +29,10 @@ public class TagGroupAction extends AddTagAction {
public TagGroupAction(final TagName tagName, ImageGalleryController controller) {
super(controller, tagName, null);
setEventHandler(actionEvent ->
new AddTagAction(controller, tagName, ImmutableSet.copyOf(controller.viewState().get().getGroup().getFileIDs())).
handle(actionEvent)
);
setEventHandler(actionEvent -> {
controller.getViewState().getGroup().ifPresent(group -> {
new AddTagAction(controller, tagName, ImmutableSet.copyOf(group.getFileIDs())).handle(actionEvent);
});
});
}
}

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2015-16 Basis Technology Corp.
* Copyright 2015-18 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -34,20 +34,19 @@ import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.autopsy.datamodel.DhsImageCategory;
import org.sleuthkit.autopsy.imagegallery.ImageGalleryController;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.TagName;
import org.sleuthkit.datamodel.TskCoreException;
/**
* Provides a cached view of the number of files per category, and fires
* {@link CategoryChangeEvent}s when files are categorized.
* CategoryChangeEvents when files are categorized.
*
* To receive CategoryChangeEvents, a listener must register itself, and
* implement a public method annotated with {@link Subscribe} that accepts one
* argument of type CategoryChangeEvent
* implement a public method annotated with Subscribe that accepts one argument
* of type CategoryChangeEvent
*
* TODO: currently these two functions (cached counts and events) are separate
* although they are related. Can they be integrated more?
@ -64,14 +63,14 @@ public class CategoryManager {
* initialized from this, and the counting of CAT-0 is always delegated to
* this db.
*/
private DrawableDB db;
private final DrawableDB drawableDb;
/**
* Used to distribute {@link CategoryChangeEvent}s
* Used to distribute CategoryChangeEvents
*/
private final EventBus categoryEventBus = new AsyncEventBus(Executors.newSingleThreadExecutor(
new BasicThreadFactory.Builder().namingPattern("Category Event Bus").uncaughtExceptionHandler((Thread t, Throwable e) -> { //NON-NLS
LOGGER.log(Level.SEVERE, "Uncaught exception in category event bus handler", e); //NON-NLS
new BasicThreadFactory.Builder().namingPattern("Category Event Bus").uncaughtExceptionHandler((Thread thread, Throwable throwable) -> { //NON-NLS
LOGGER.log(Level.SEVERE, "Uncaught exception in category event bus handler", throwable); //NON-NLS
}).build()
));
@ -80,38 +79,29 @@ public class CategoryManager {
* the count related methods go through this cache, which loads initial
* values from the database if needed.
*/
private final LoadingCache<DhsImageCategory, LongAdder> categoryCounts =
CacheBuilder.newBuilder().build(CacheLoader.from(this::getCategoryCountHelper));
private final LoadingCache<DhsImageCategory, LongAdder> categoryCounts
= CacheBuilder.newBuilder().build(CacheLoader.from(this::getCategoryCountHelper));
/**
* cached TagNames corresponding to Categories, looked up from
* autopsyTagManager at initial request or if invalidated by case change.
*/
private final LoadingCache<DhsImageCategory, TagName> catTagNameMap =
CacheBuilder.newBuilder().build(CacheLoader.from(
cat -> getController().getTagsManager().getTagName(cat)
));
private final LoadingCache<DhsImageCategory, TagName> catTagNameMap
= CacheBuilder.newBuilder().build(new CacheLoader<DhsImageCategory, TagName>() {
@Override
public TagName load(DhsImageCategory cat) throws TskCoreException {
return getController().getTagsManager().getTagName(cat);
}
});
public CategoryManager(ImageGalleryController controller) {
this.controller = controller;
this.drawableDb = controller.getDatabase();
}
private ImageGalleryController getController() {
return controller;
}
/**
* assign a new db. the counts cache is invalidated and all subsequent db
* lookups go to the new db.
*
* Also clears the Category TagNames
*
* @param db
*/
synchronized public void setDb(DrawableDB db) {
this.db = db;
invalidateCaches();
}
synchronized public void invalidateCaches() {
categoryCounts.invalidateAll();
catTagNameMap.invalidateAll();
@ -131,7 +121,7 @@ public class CategoryManager {
// is going on, so always use the list of file IDs we already have along with the
// other category counts instead of trying to track it separately.
long allOtherCatCount = getCategoryCount(DhsImageCategory.ONE) + getCategoryCount(DhsImageCategory.TWO) + getCategoryCount(DhsImageCategory.THREE) + getCategoryCount(DhsImageCategory.FOUR) + getCategoryCount(DhsImageCategory.FIVE);
return db.getNumberOfImageFilesInList() - allOtherCatCount;
return drawableDb.getNumberOfImageFilesInList() - allOtherCatCount;
} else {
return categoryCounts.getUnchecked(cat).sum();
}
@ -151,7 +141,7 @@ public class CategoryManager {
/**
* decrement the cached value for the number of files with the given
* {@link DhsImageCategory}
* DhsImageCategory
*
* @param cat the Category to decrement
*/
@ -175,7 +165,7 @@ public class CategoryManager {
LongAdder longAdder = new LongAdder();
longAdder.decrement();
try {
longAdder.add(db.getCategoryCount(cat));
longAdder.add(drawableDb.getCategoryCount(cat));
longAdder.increment();
} catch (IllegalStateException ex) {
LOGGER.log(Level.WARNING, "Case closed while getting files"); //NON-NLS
@ -212,15 +202,14 @@ public class CategoryManager {
try {
categoryEventBus.unregister(listener);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("missing event subscriber for an annotated method. Is " + listener + " registered?")) { //NON-NLS
/*
* We don't fully understand why we are getting this exception
* when the groups should all be registered. To avoid cluttering
* the logs we have disabled recording this exception. This
* documented in issues 738 and 802.
* We don't fully understand why we are getting this exception when
* the groups should all be registered. To avoid cluttering the logs
* we have disabled recording this exception. This documented in
* issues 738 and 802.
*/
//LOGGER.log(Level.WARNING, "Attempted to unregister {0} for category change events, but it was not registered.", listener.toString()); //NON-NLS
} else {
if (!e.getMessage().contains("missing event subscriber for an annotated method. Is " + listener + " registered?")) { //NON-NLS
throw e;
}
}

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