Merge branch 'develop' of github.com:sleuthkit/autopsy into 7592-analysisResultsScore

This commit is contained in:
Greg DiCristofaro 2021-05-25 15:50:18 -04:00
commit 718738832e
34 changed files with 2615 additions and 1757 deletions

View File

@ -0,0 +1,8 @@
OtherOccurrences.csvHeader.attribute=Matched Attribute
OtherOccurrences.csvHeader.case=Case
OtherOccurrences.csvHeader.comment=Comment
OtherOccurrences.csvHeader.dataSource=Data Source
OtherOccurrences.csvHeader.device=Device
OtherOccurrences.csvHeader.known=Known
OtherOccurrences.csvHeader.path=Path
OtherOccurrences.csvHeader.value=Attribute Value

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.centralrepository.contentviewer;
package org.sleuthkit.autopsy.centralrepository.application;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
@ -30,7 +30,7 @@ import org.sleuthkit.datamodel.TskDataException;
/**
* Class for populating the Other Occurrences tab
*/
class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
public class NodeData {
// For now hard code the string for the central repo files type, since
// getting it dynamically can fail.
@ -56,7 +56,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
* @param type The type of the instance
* @param value The value of the instance
*/
OtherOccurrenceNodeInstanceData(CorrelationAttributeInstance instance, CorrelationAttributeInstance.Type type, String value) {
public NodeData(CorrelationAttributeInstance instance, CorrelationAttributeInstance.Type type, String value) {
caseName = instance.getCorrelationCase().getDisplayName();
deviceID = instance.getCorrelationDataSource().getDeviceID();
dataSourceName = instance.getCorrelationDataSource().getName();
@ -77,7 +77,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
*
* @throws CentralRepoException
*/
OtherOccurrenceNodeInstanceData(AbstractFile newFile, Case autopsyCase) throws CentralRepoException {
NodeData(AbstractFile newFile, Case autopsyCase) throws CentralRepoException {
caseName = autopsyCase.getDisplayName();
try {
DataSource dataSource = autopsyCase.getSleuthkitCase().getDataSource(newFile.getDataSource().getId());
@ -119,7 +119,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
*
* @param newComment The new comment
*/
void updateComment(String newComment) {
public void updateComment(String newComment) {
comment = newComment;
}
@ -129,7 +129,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
* @return true if this node was created from a central repo instance, false
* otherwise
*/
boolean isCentralRepoNode() {
public boolean isCentralRepoNode() {
return (originalCorrelationInstance != null);
}
@ -138,7 +138,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
*
* @return the case name
*/
String getCaseName() {
public String getCaseName() {
return caseName;
}
@ -147,7 +147,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
*
* @return the device ID
*/
String getDeviceID() {
public String getDeviceID() {
return deviceID;
}
@ -156,7 +156,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
*
* @return the data source name
*/
String getDataSourceName() {
public String getDataSourceName() {
return dataSourceName;
}
@ -165,7 +165,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
*
* @return the file path
*/
String getFilePath() {
public String getFilePath() {
return filePath;
}
@ -174,7 +174,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
*
* @return the type
*/
String getType() {
public String getType() {
return typeStr;
}
@ -183,7 +183,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
*
* @return the value
*/
String getValue() {
public String getValue() {
return value;
}
@ -192,7 +192,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
*
* @return the known status
*/
TskData.FileKnown getKnown() {
public TskData.FileKnown getKnown() {
return known;
}
@ -201,7 +201,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
*
* @return the comment
*/
String getComment() {
public String getComment() {
return comment;
}
@ -211,7 +211,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
*
* @return the original abstract file
*/
AbstractFile getAbstractFile() throws CentralRepoException {
public AbstractFile getAbstractFile() throws CentralRepoException {
if (originalAbstractFile == null) {
throw new CentralRepoException("AbstractFile is null");
}
@ -226,7 +226,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
*
* @throws CentralRepoException
*/
CorrelationAttributeInstance getCorrelationAttributeInstance() throws CentralRepoException {
public CorrelationAttributeInstance getCorrelationAttributeInstance() throws CentralRepoException {
if (originalCorrelationInstance == null) {
throw new CentralRepoException("CorrelationAttributeInstance is null");
}
@ -239,7 +239,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
*
* @return the CSV_ITEM_SEPARATOR string
*/
static String getCsvItemSeparator() {
public static String getCsvItemSeparator() {
return CSV_ITEM_SEPARATOR;
}

View File

@ -0,0 +1,438 @@
/*
* Central Repository
*
* Copyright 2021 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.application;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDateTime;
import org.openide.nodes.Node;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource;
import org.sleuthkit.autopsy.coreutils.Logger;
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.TskCoreException;
import org.sleuthkit.datamodel.TskData;
/**
* Contains most of the methods for gathering data from the DB and CR for the
* OtherOccurrencesPanel.
*/
public final class OtherOccurrences {
private static final Logger logger = Logger.getLogger(OtherOccurrences.class.getName());
private static final String UUID_PLACEHOLDER_STRING = "NoCorrelationAttributeInstance";
private OtherOccurrences() {
}
/**
* Determine what attributes can be used for correlation based on the node.
* If EamDB is not enabled, get the default Files correlation.
*
* @param node The node to correlate
*
* @return A list of attributes that can be used for correlation
*/
public static Collection<CorrelationAttributeInstance> getCorrelationAttributesFromNode(Node node, AbstractFile file) {
Collection<CorrelationAttributeInstance> ret = new ArrayList<>();
// correlate on blackboard artifact attributes if they exist and supported
BlackboardArtifact bbArtifact = getBlackboardArtifactFromNode(node);
if (bbArtifact != null && CentralRepository.isEnabled()) {
ret.addAll(CorrelationAttributeUtil.makeCorrAttrsForCorrelation(bbArtifact));
}
// we can correlate based on the MD5 if it is enabled
if (file != null && CentralRepository.isEnabled() && file.getSize() > 0) {
try {
List<CorrelationAttributeInstance.Type> artifactTypes = CentralRepository.getInstance().getDefinedCorrelationTypes();
String md5 = file.getMd5Hash();
if (md5 != null && !md5.isEmpty() && null != artifactTypes && !artifactTypes.isEmpty()) {
for (CorrelationAttributeInstance.Type aType : artifactTypes) {
if (aType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) {
CorrelationCase corCase = CentralRepository.getInstance().getCase(Case.getCurrentCase());
try {
ret.add(new CorrelationAttributeInstance(
aType,
md5,
corCase,
CorrelationDataSource.fromTSKDataSource(corCase, file.getDataSource()),
file.getParentPath() + file.getName(),
"",
file.getKnown(),
file.getId()));
} catch (CorrelationAttributeNormalizationException ex) {
logger.log(Level.INFO, String.format("Unable to check create CorrelationAttribtueInstance for value %s and type %s.", md5, aType.toString()), ex);
}
break;
}
}
}
} catch (CentralRepoException | TskCoreException ex) {
logger.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS
}
// If EamDb not enabled, get the Files default correlation type to allow Other Occurances to be enabled.
} else if (file != null && file.getSize() > 0) {
String md5 = file.getMd5Hash();
if (md5 != null && !md5.isEmpty()) {
try {
final CorrelationAttributeInstance.Type fileAttributeType
= CorrelationAttributeInstance.getDefaultCorrelationTypes()
.stream()
.filter(attrType -> attrType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID)
.findAny()
.get();
//The Central Repository is not enabled
ret.add(new CorrelationAttributeInstance(fileAttributeType, md5, null, null, "", "", TskData.FileKnown.UNKNOWN, file.getId()));
} catch (CentralRepoException ex) {
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
}
}
}
return ret;
}
/**
* Get the associated BlackboardArtifact from a node, if it exists.
*
* @param node The node
*
* @return The associated BlackboardArtifact, or null
*/
public static BlackboardArtifact getBlackboardArtifactFromNode(Node node) {
BlackboardArtifactTag nodeBbArtifactTag = node.getLookup().lookup(BlackboardArtifactTag.class);
BlackboardArtifact nodeBbArtifact = node.getLookup().lookup(BlackboardArtifact.class);
if (nodeBbArtifactTag != null) {
return nodeBbArtifactTag.getArtifact();
} else if (nodeBbArtifact != null) {
return nodeBbArtifact;
}
return null;
}
/**
* Get the associated AbstractFile from a node, if it exists.
*
* @param node The node
*
* @return The associated AbstractFile, or null
*/
public static AbstractFile getAbstractFileFromNode(Node node) {
BlackboardArtifactTag nodeBbArtifactTag = node.getLookup().lookup(BlackboardArtifactTag.class);
ContentTag nodeContentTag = node.getLookup().lookup(ContentTag.class);
BlackboardArtifact nodeBbArtifact = node.getLookup().lookup(BlackboardArtifact.class);
AbstractFile nodeAbstractFile = node.getLookup().lookup(AbstractFile.class);
if (nodeBbArtifactTag != null) {
Content content = nodeBbArtifactTag.getContent();
if (content instanceof AbstractFile) {
return (AbstractFile) content;
}
} else if (nodeContentTag != null) {
Content content = nodeContentTag.getContent();
if (content instanceof AbstractFile) {
return (AbstractFile) content;
}
} else if (nodeBbArtifact != null) {
Content content;
try {
content = nodeBbArtifact.getSleuthkitCase().getContentById(nodeBbArtifact.getObjectID());
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error retrieving blackboard artifact", ex); // NON-NLS
return null;
}
if (content instanceof AbstractFile) {
return (AbstractFile) content;
}
} else if (nodeAbstractFile != null) {
return nodeAbstractFile;
}
return null;
}
/**
* Query the central repo database (if enabled) and the case database to
* find all artifact instances correlated to the given central repository
* artifact. If the central repo is not enabled, this will only return files
* from the current case with matching MD5 hashes.
*
* @param corAttr CorrelationAttribute to query for
*
* @return A collection of correlated artifact instances
*/
public static Map<UniquePathKey, NodeData> getCorrelatedInstances(AbstractFile file, String deviceId, String dataSourceName, CorrelationAttributeInstance corAttr) {
// @@@ Check exception
try {
final Case openCase = Case.getCurrentCaseThrows();
String caseUUID = openCase.getName();
HashMap<UniquePathKey, NodeData> nodeDataMap = new HashMap<>();
if (CentralRepository.isEnabled()) {
List<CorrelationAttributeInstance> instances = CentralRepository.getInstance().getArtifactInstancesByTypeValue(corAttr.getCorrelationType(), corAttr.getCorrelationValue());
for (CorrelationAttributeInstance artifactInstance : instances) {
// Only add the attribute if it isn't the object the user selected.
// We consider it to be a different object if at least one of the following is true:
// - the case UUID is different
// - the data source name is different
// - the data source device ID is different
// - the file path is different
if (artifactInstance.getCorrelationCase().getCaseUUID().equals(caseUUID)
&& (!StringUtils.isBlank(dataSourceName) && artifactInstance.getCorrelationDataSource().getName().equals(dataSourceName))
&& (!StringUtils.isBlank(deviceId) && artifactInstance.getCorrelationDataSource().getDeviceID().equals(deviceId))
&& (file != null && artifactInstance.getFilePath().equalsIgnoreCase(file.getParentPath() + file.getName()))) {
continue;
}
NodeData newNode = new NodeData(artifactInstance, corAttr.getCorrelationType(), corAttr.getCorrelationValue());
UniquePathKey uniquePathKey = new UniquePathKey(newNode);
nodeDataMap.put(uniquePathKey, newNode);
}
if (file != null && corAttr.getCorrelationType().getDisplayName().equals("Files")) {
List<AbstractFile> caseDbFiles = getCaseDbMatches(corAttr, openCase, file);
for (AbstractFile caseDbFile : caseDbFiles) {
addOrUpdateNodeData(openCase, nodeDataMap, caseDbFile);
}
}
}
return nodeDataMap;
} catch (CentralRepoException ex) {
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
} catch (TskCoreException ex) {
// do nothing.
// @@@ Review this behavior
logger.log(Level.SEVERE, "Exception while querying open case.", ex); // NON-NLS
}
return new HashMap<>(
0);
}
/**
* Get all other abstract files in the current case with the same MD5 as the
* selected node.
*
* @param corAttr The CorrelationAttribute containing the MD5 to search for
* @param openCase The current case
* @param file The current file.
*
* @return List of matching AbstractFile objects
*
* @throws NoCurrentCaseException
* @throws TskCoreException
* @throws CentralRepoException
*/
public static List<AbstractFile> getCaseDbMatches(CorrelationAttributeInstance corAttr, Case openCase, AbstractFile file) throws NoCurrentCaseException, TskCoreException, CentralRepoException {
List<AbstractFile> caseDbArtifactInstances = new ArrayList<>();
if (file != null) {
String md5 = corAttr.getCorrelationValue();
SleuthkitCase tsk = openCase.getSleuthkitCase();
List<AbstractFile> matches = tsk.findAllFilesWhere(String.format("md5 = '%s'", new Object[]{md5}));
for (AbstractFile fileMatch : matches) {
if (file.equals(fileMatch)) {
continue; // If this is the file the user clicked on
}
caseDbArtifactInstances.add(fileMatch);
}
}
return caseDbArtifactInstances;
}
/**
* Adds the file to the nodeDataMap map if it does not already exist
*
* @param autopsyCase
* @param nodeDataMap
* @param newFile
*
* @throws TskCoreException
* @throws CentralRepoException
*/
public static void addOrUpdateNodeData(final Case autopsyCase, Map<UniquePathKey, NodeData> nodeDataMap, AbstractFile newFile) throws TskCoreException, CentralRepoException {
NodeData newNode = new NodeData(newFile, autopsyCase);
// If the caseDB object has a notable tag associated with it, update
// the known status to BAD
if (newNode.getKnown() != TskData.FileKnown.BAD) {
List<ContentTag> fileMatchTags = autopsyCase.getServices().getTagsManager().getContentTagsByContent(newFile);
for (ContentTag tag : fileMatchTags) {
TskData.FileKnown tagKnownStatus = tag.getName().getKnownStatus();
if (tagKnownStatus.equals(TskData.FileKnown.BAD)) {
newNode.updateKnown(TskData.FileKnown.BAD);
break;
}
}
}
// Make a key to see if the file is already in the map
UniquePathKey uniquePathKey = new UniquePathKey(newNode);
// If this node is already in the list, the only thing we need to do is
// update the known status to BAD if the caseDB version had known status BAD.
// Otherwise this is a new node so add the new node to the map.
if (nodeDataMap.containsKey(uniquePathKey)) {
if (newNode.getKnown() == TskData.FileKnown.BAD) {
NodeData prevInstance = nodeDataMap.get(uniquePathKey);
prevInstance.updateKnown(newNode.getKnown());
}
} else {
nodeDataMap.put(uniquePathKey, newNode);
}
}
/**
* Create a unique string to be used as a key for deduping data sources as
* best as possible
*/
public static String makeDataSourceString(String caseUUID, String deviceId, String dataSourceName) {
return caseUUID + deviceId + dataSourceName;
}
/**
* Gets the list of Eam Cases and determines the earliest case creation
* date. Sets the label to display the earliest date string to the user.
*/
public static String getEarliestCaseDate() throws CentralRepoException {
String dateStringDisplay = "";
if (CentralRepository.isEnabled()) {
LocalDateTime earliestDate = LocalDateTime.now(DateTimeZone.UTC);
DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.US);
CentralRepository dbManager = CentralRepository.getInstance();
List<CorrelationCase> cases = dbManager.getCases();
for (CorrelationCase aCase : cases) {
LocalDateTime caseDate;
try {
caseDate = LocalDateTime.fromDateFields(datetimeFormat.parse(aCase.getCreationDate()));
if (caseDate.isBefore(earliestDate)) {
earliestDate = caseDate;
dateStringDisplay = aCase.getCreationDate();
}
} catch (ParseException ex) {
throw new CentralRepoException("Failed to format case creation date " + aCase.getCreationDate(), ex);
}
}
}
return dateStringDisplay;
}
@NbBundle.Messages({
"OtherOccurrences.csvHeader.case=Case",
"OtherOccurrences.csvHeader.device=Device",
"OtherOccurrences.csvHeader.dataSource=Data Source",
"OtherOccurrences.csvHeader.attribute=Matched Attribute",
"OtherOccurrences.csvHeader.value=Attribute Value",
"OtherOccurrences.csvHeader.known=Known",
"OtherOccurrences.csvHeader.path=Path",
"OtherOccurrences.csvHeader.comment=Comment"
})
/**
* Create a cvs file of occurrences for the given parameters.
*
* @param destFile Output file for the csv data.
* @param abstractFile Source file.
* @param correlationAttList List of correclationAttributeInstances, should
* not be null.
* @param dataSourceName Name of the data source.
* @param deviceId Device id.
*
* @throws IOException
*/
public static void writeOtherOccurrencesToFileAsCSV(File destFile, AbstractFile abstractFile, Collection<CorrelationAttributeInstance> correlationAttList, String dataSourceName, String deviceId) throws IOException {
try (BufferedWriter writer = Files.newBufferedWriter(destFile.toPath())) {
//write headers
StringBuilder headers = new StringBuilder("\"");
headers.append(Bundle.OtherOccurrences_csvHeader_case())
.append(NodeData.getCsvItemSeparator()).append(Bundle.OtherOccurrences_csvHeader_dataSource())
.append(NodeData.getCsvItemSeparator()).append(Bundle.OtherOccurrences_csvHeader_attribute())
.append(NodeData.getCsvItemSeparator()).append(Bundle.OtherOccurrences_csvHeader_value())
.append(NodeData.getCsvItemSeparator()).append(Bundle.OtherOccurrences_csvHeader_known())
.append(NodeData.getCsvItemSeparator()).append(Bundle.OtherOccurrences_csvHeader_path())
.append(NodeData.getCsvItemSeparator()).append(Bundle.OtherOccurrences_csvHeader_comment())
.append('"').append(System.getProperty("line.separator"));
writer.write(headers.toString());
//write content
for (CorrelationAttributeInstance corAttr : correlationAttList) {
Map<UniquePathKey, NodeData> correlatedNodeDataMap = new HashMap<>(0);
// get correlation and reference set instances from DB
correlatedNodeDataMap.putAll(getCorrelatedInstances(abstractFile, deviceId, dataSourceName, corAttr));
for (NodeData nodeData : correlatedNodeDataMap.values()) {
writer.write(nodeData.toCsvString());
}
}
}
}
/**
* Get a placeholder string to use in place of case uuid when it isn't
* available
*
* @return UUID_PLACEHOLDER_STRING
*/
public static String getPlaceholderUUID() {
return UUID_PLACEHOLDER_STRING;
}
}

View File

@ -16,12 +16,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.centralrepository.contentviewer;
package org.sleuthkit.autopsy.centralrepository.application;
import java.util.Objects;
import java.util.logging.Level;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.contentviewer.OtherOccurrencesPanel;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.coreutils.Logger;
@ -29,7 +30,7 @@ import org.sleuthkit.autopsy.coreutils.Logger;
* Used as a key to ensure we eliminate duplicates from the result set by not
* overwriting CR correlation instances.
*/
final class UniquePathKey {
public final class UniquePathKey {
private static final Logger logger = Logger.getLogger(UniquePathKey.class.getName());
private final String dataSourceID;
@ -37,7 +38,7 @@ final class UniquePathKey {
private final String type;
private final String caseUUID;
UniquePathKey(OtherOccurrenceNodeInstanceData nodeData) {
public UniquePathKey(NodeData nodeData) {
super();
dataSourceID = nodeData.getDeviceID();
if (nodeData.getFilePath() != null) {
@ -56,7 +57,7 @@ final class UniquePathKey {
//place holder value will be used since correlation attribute was unavailble
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Unable to get current case", ex);
tempCaseUUID = OtherOccurrencesPanel.getPlaceholderUUID();
tempCaseUUID = OtherOccurrences.getPlaceholderUUID();
}
}
caseUUID = tempCaseUUID;

View File

@ -31,18 +31,9 @@ OtherOccurrencesPanel.correlatedArtifacts.byType={0}% of data sources have {2} (
OtherOccurrencesPanel.correlatedArtifacts.failed=Failed to get frequency details.
OtherOccurrencesPanel.correlatedArtifacts.isEmpty=There are no files or artifacts to correlate.
OtherOccurrencesPanel.correlatedArtifacts.title=Attribute Frequency
OtherOccurrencesPanel.csvHeader.attribute=Matched Attribute
OtherOccurrencesPanel.csvHeader.case=Case
OtherOccurrencesPanel.csvHeader.comment=Comment
OtherOccurrencesPanel.csvHeader.dataSource=Data Source
OtherOccurrencesPanel.csvHeader.device=Device
OtherOccurrencesPanel.csvHeader.known=Known
OtherOccurrencesPanel.csvHeader.path=Path
OtherOccurrencesPanel.csvHeader.value=Attribute Value
OtherOccurrencesPanel.earliestCaseLabel.toolTipText=
OtherOccurrencesPanel.earliestCaseLabel.text=Central Repository Starting Date:
OtherOccurrencesPanel.earliestCaseDate.text=Earliest Case Date
OtherOccurrencesPanel.earliestCaseNotAvailable=\ Not Enabled.
OtherOccurrencesPanel.foundIn.text=Found %d instances in %d cases and %d data sources.
OtherOccurrencesPanel.foundInLabel.text=
OtherOccurrencesPanel.filesTable.toolTipText=Click column name to sort. Right-click on the table for more options.
@ -52,3 +43,5 @@ OtherOccurrencesPanel.showCommonalityMenuItem.text=Show Frequency
OtherOccurrencesPanel.showCaseDetailsMenuItem.text=Show Case Details
OtherOccurrencesPanel.table.noArtifacts=Item has no attributes with which to search.
OtherOccurrencesPanel.table.noResultsFound=No results found.
OtherOccurrencesPanel_earliestCaseNotAvailable=Not Availble.
OtherOccurrencesPanel_table_loadingResults=Loading results

View File

@ -20,6 +20,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,44,0,0,1,-112"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>

View File

@ -19,33 +19,18 @@
package org.sleuthkit.autopsy.centralrepository.contentviewer;
import java.awt.Component;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.awt.Cursor;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.JPanel;
import org.openide.nodes.Node;
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.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.centralrepository.application.OtherOccurrences;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationDataSource;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
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.TskCoreException;
import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.datamodel.TskException;
/**
* View correlation results from other cases
@ -57,13 +42,10 @@ import org.sleuthkit.datamodel.TskException;
public final 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 final OtherOccurrencesPanel otherOccurrencesPanel = new OtherOccurrencesPanel();
/**
* Could be null.
*/
private AbstractFile file; //the file which the content viewer is being populated for
private OtherOccurrencesNodeWorker worker = null;
/**
* Creates new form DataContentViewerOtherCases
@ -104,146 +86,6 @@ public final class DataContentViewerOtherCases extends JPanel implements DataCon
}
/**
* Get the associated BlackboardArtifact from a node, if it exists.
*
* @param node The node
*
* @return The associated BlackboardArtifact, or null
*/
private BlackboardArtifact
getBlackboardArtifactFromNode(Node node) {
BlackboardArtifactTag nodeBbArtifactTag = node.getLookup().lookup(BlackboardArtifactTag.class
);
BlackboardArtifact nodeBbArtifact = node.getLookup().lookup(BlackboardArtifact.class
);
if (nodeBbArtifactTag != null) {
return nodeBbArtifactTag.getArtifact();
} else if (nodeBbArtifact != null) {
return nodeBbArtifact;
}
return null;
}
/**
* Get the associated AbstractFile from a node, if it exists.
*
* @param node The node
*
* @return The associated AbstractFile, or null
*/
private AbstractFile getAbstractFileFromNode(Node node) {
BlackboardArtifactTag nodeBbArtifactTag = node.getLookup().lookup(BlackboardArtifactTag.class
);
ContentTag nodeContentTag = node.getLookup().lookup(ContentTag.class
);
BlackboardArtifact nodeBbArtifact = node.getLookup().lookup(BlackboardArtifact.class
);
AbstractFile nodeAbstractFile = node.getLookup().lookup(AbstractFile.class
);
if (nodeBbArtifactTag != null) {
Content content = nodeBbArtifactTag.getContent();
if (content instanceof AbstractFile) {
return (AbstractFile) content;
}
} else if (nodeContentTag != null) {
Content content = nodeContentTag.getContent();
if (content instanceof AbstractFile) {
return (AbstractFile) content;
}
} else if (nodeBbArtifact != null) {
Content content;
try {
content = nodeBbArtifact.getSleuthkitCase().getContentById(nodeBbArtifact.getObjectID());
} catch (TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Error retrieving blackboard artifact", ex); // NON-NLS
return null;
}
if (content instanceof AbstractFile) {
return (AbstractFile) content;
}
} else if (nodeAbstractFile != null) {
return nodeAbstractFile;
}
return null;
}
/**
* Determine what attributes can be used for correlation based on the node.
* If EamDB is not enabled, get the default Files correlation.
*
* @param node The node to correlate
*
* @return A list of attributes that can be used for correlation
*/
private Collection<CorrelationAttributeInstance> getCorrelationAttributesFromNode(Node node) {
Collection<CorrelationAttributeInstance> ret = new ArrayList<>();
// correlate on blackboard artifact attributes if they exist and supported
BlackboardArtifact bbArtifact = getBlackboardArtifactFromNode(node);
if (bbArtifact != null && CentralRepository.isEnabled()) {
ret.addAll(CorrelationAttributeUtil.makeCorrAttrsForCorrelation(bbArtifact));
}
// we can correlate based on the MD5 if it is enabled
if (this.file != null && CentralRepository.isEnabled() && this.file.getSize() > 0) {
try {
List<CorrelationAttributeInstance.Type> artifactTypes = CentralRepository.getInstance().getDefinedCorrelationTypes();
String md5 = this.file.getMd5Hash();
if (md5 != null && !md5.isEmpty() && null != artifactTypes && !artifactTypes.isEmpty()) {
for (CorrelationAttributeInstance.Type aType : artifactTypes) {
if (aType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID) {
CorrelationCase corCase = CentralRepository.getInstance().getCase(Case.getCurrentCase());
try {
ret.add(new CorrelationAttributeInstance(
aType,
md5,
corCase,
CorrelationDataSource.fromTSKDataSource(corCase, file.getDataSource()),
file.getParentPath() + file.getName(),
"",
file.getKnown(),
file.getId()));
} catch (CorrelationAttributeNormalizationException ex) {
LOGGER.log(Level.INFO, String.format("Unable to check create CorrelationAttribtueInstance for value %s and type %s.", md5, aType.toString()), ex);
}
break;
}
}
}
} catch (CentralRepoException | TskCoreException ex) {
LOGGER.log(Level.SEVERE, "Error connecting to DB", ex); // NON-NLS
}
// If EamDb not enabled, get the Files default correlation type to allow Other Occurances to be enabled.
} else if (this.file != null && this.file.getSize() > 0) {
String md5 = this.file.getMd5Hash();
if (md5 != null && !md5.isEmpty()) {
try {
final CorrelationAttributeInstance.Type fileAttributeType
= CorrelationAttributeInstance.getDefaultCorrelationTypes()
.stream()
.filter(attrType -> attrType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID)
.findAny()
.get();
//The Central Repository is not enabled
ret.add(new CorrelationAttributeInstance(fileAttributeType, md5, null, null, "", "", TskData.FileKnown.UNKNOWN, this.file.getId()));
} catch (CentralRepoException ex) {
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
}
}
}
return ret;
}
@Override
public boolean isSupported(Node node) {
@ -251,39 +93,44 @@ public final class DataContentViewerOtherCases extends JPanel implements DataCon
// - The central repo is enabled and the node has correlatable content
// (either through the MD5 hash of the associated file or through a BlackboardArtifact)
// - The central repo is disabled and the backing file has a valid MD5 hash
this.file = this.getAbstractFileFromNode(node);
AbstractFile file = OtherOccurrences.getAbstractFileFromNode(node);
if (CentralRepository.isEnabled()) {
return !getCorrelationAttributesFromNode(node).isEmpty();
return !OtherOccurrences.getCorrelationAttributesFromNode(node, file).isEmpty();
} else {
return this.file != null
&& this.file.getSize() > 0
&& ((this.file.getMd5Hash() != null) && (!this.file.getMd5Hash().isEmpty()));
return file != null
&& file.getSize() > 0
&& ((file.getMd5Hash() != null) && (!file.getMd5Hash().isEmpty()));
}
}
@Override
public void setNode(Node node) {
otherOccurrencesPanel.reset(); // reset the table to empty.
otherOccurrencesPanel.showPanelLoadingMessage();
if (node == null) {
return;
}
//could be null
this.file = this.getAbstractFileFromNode(node);
String dataSourceName = "";
String deviceId = "";
try {
if (this.file != null) {
Content dataSource = this.file.getDataSource();
dataSourceName = dataSource.getName();
deviceId = Case.getCurrentCaseThrows().getSleuthkitCase().getDataSource(dataSource.getId()).getDeviceId();
}
} catch (TskException | NoCurrentCaseException ex) {
// do nothing.
// @@@ Review this behavior
}
otherOccurrencesPanel.populateTable(getCorrelationAttributesFromNode(node), dataSourceName, deviceId, file);
if (worker != null) {
worker.cancel(true);
}
worker = new OtherOccurrencesNodeWorker(node) {
@Override
public void done() {
try {
if (!isCancelled()) {
OtherOccurrencesData data = get();
otherOccurrencesPanel.populateTable(data);
otherOccurrencesPanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
} catch (InterruptedException | ExecutionException ex) {
DataContentViewerOtherCases.logger.log(Level.SEVERE, "Failed to update OtherOccurrencesPanel", ex);
}
}
};
otherOccurrencesPanel.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
worker.execute();
}
/**

View File

@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.centralrepository.contentviewer;
import org.sleuthkit.autopsy.centralrepository.application.NodeData;
import java.awt.Color;
import java.awt.Font;
import java.util.ArrayList;
@ -50,7 +51,7 @@ final class OccurrencePanel extends javax.swing.JPanel {
private static final long serialVersionUID = 1L;
private int gridY = 0;
private final List<OtherOccurrenceNodeData> nodeDataList;
private final List<NodeData> nodeDataList;
private final Map<String, String> caseNamesAndDates = new HashMap<>();
private final Set<String> dataSourceNames = new HashSet<>();
private final Set<String> filePaths = new HashSet<>();
@ -97,7 +98,7 @@ final class OccurrencePanel extends javax.swing.JPanel {
* @param nodeDataList the list of OtherOccurrenceNodeData representing
* common properties for the file
*/
OccurrencePanel(List<OtherOccurrenceNodeData> nodeDataList) {
OccurrencePanel(List<NodeData> nodeDataList) {
this.nodeDataList = nodeDataList;
customizeComponents();
}
@ -148,9 +149,9 @@ final class OccurrencePanel extends javax.swing.JPanel {
addItemToBag(gridY, 0, TOP_INSET, 0, commonPropertiesLabel);
gridY++;
//for each other occurrence
for (OtherOccurrenceNodeData occurrence : nodeDataList) {
if (occurrence instanceof OtherOccurrenceNodeInstanceData) {
String type = ((OtherOccurrenceNodeInstanceData) occurrence).getType();
for (NodeData occurrence : nodeDataList) {
if (occurrence instanceof NodeData) {
String type = occurrence.getType();
if (!type.isEmpty()) {
javax.swing.JLabel typeLabel = new javax.swing.JLabel();
org.openide.awt.Mnemonics.setLocalizedText(typeLabel, Bundle.OccurrencePanel_commonPropertyTypeLabel_text());
@ -160,7 +161,7 @@ final class OccurrencePanel extends javax.swing.JPanel {
addItemToBag(gridY, 1, VERTICAL_GAP, 0, typeFieldValue);
gridY++;
}
String value = ((OtherOccurrenceNodeInstanceData) occurrence).getValue();
String value = occurrence.getValue();
if (!value.isEmpty()) {
javax.swing.JLabel valueLabel = new javax.swing.JLabel();
org.openide.awt.Mnemonics.setLocalizedText(valueLabel, Bundle.OccurrencePanel_commonPropertyValueLabel_text());
@ -170,7 +171,7 @@ final class OccurrencePanel extends javax.swing.JPanel {
addItemToBag(gridY, 1, 0, 0, valueFieldValue);
gridY++;
}
TskData.FileKnown knownStatus = ((OtherOccurrenceNodeInstanceData) occurrence).getKnown();
TskData.FileKnown knownStatus = occurrence.getKnown();
javax.swing.JLabel knownStatusLabel = new javax.swing.JLabel();
org.openide.awt.Mnemonics.setLocalizedText(knownStatusLabel, Bundle.OccurrencePanel_commonPropertyKnownStatusLabel_text());
addItemToBag(gridY, 0, 0, 0, knownStatusLabel);
@ -181,7 +182,7 @@ final class OccurrencePanel extends javax.swing.JPanel {
}
addItemToBag(gridY, 1, 0, 0, knownStatusValue);
gridY++;
String comment = ((OtherOccurrenceNodeInstanceData) occurrence).getComment();
String comment = occurrence.getComment();
if (!comment.isEmpty()) {
javax.swing.JLabel commentLabel = new javax.swing.JLabel();
org.openide.awt.Mnemonics.setLocalizedText(commentLabel, Bundle.OccurrencePanel_commonPropertyCommentLabel_text());
@ -201,10 +202,9 @@ final class OccurrencePanel extends javax.swing.JPanel {
}
String caseDate = "";
try {
OtherOccurrenceNodeInstanceData nodeData = ((OtherOccurrenceNodeInstanceData) occurrence);
if (nodeData.isCentralRepoNode()) {
if (occurrence.isCentralRepoNode()) {
if (CentralRepository.isEnabled()) {
CorrelationCase partialCase = nodeData.getCorrelationAttributeInstance().getCorrelationCase();
CorrelationCase partialCase = occurrence.getCorrelationAttributeInstance().getCorrelationCase();
caseDate = CentralRepository.getInstance().getCaseByUUID(partialCase.getCaseUUID()).getCreationDate();
}
} else {
@ -214,9 +214,9 @@ final class OccurrencePanel extends javax.swing.JPanel {
LOGGER.log(Level.WARNING, "Error getting case created date for other occurrence content viewer", ex);
}
//Collect the data that is necessary for the other sections
caseNamesAndDates.put(((OtherOccurrenceNodeInstanceData) occurrence).getCaseName(), caseDate);
dataSourceNames.add(((OtherOccurrenceNodeInstanceData) occurrence).getDataSourceName());
filePaths.add(((OtherOccurrenceNodeInstanceData) occurrence).getFilePath());
caseNamesAndDates.put(occurrence.getCaseName(), caseDate);
dataSourceNames.add(occurrence.getDataSourceName());
filePaths.add(occurrence.getFilePath());
}
}
//end for each

View File

@ -1,25 +0,0 @@
/*
* 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.contentviewer;
/**
* Marker interface for Other Occurrences nodes.
*/
interface OtherOccurrenceNodeData {
}

View File

@ -1,34 +0,0 @@
/*
* 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.contentviewer;
/**
* Class for populating the Other Occurrences tab with a single message.
*/
final class OtherOccurrenceNodeMessageData implements OtherOccurrenceNodeData {
private final String displayMessage;
OtherOccurrenceNodeMessageData(String displayMessage) {
this.displayMessage = displayMessage;
}
String getDisplayMessage() {
return displayMessage;
}
}

View File

@ -0,0 +1,196 @@
/*
* Central Repository
*
* Copyright 2021 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.centralrepository.contentviewer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import javax.swing.SwingWorker;
import org.apache.commons.lang3.StringUtils;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.application.NodeData;
import org.sleuthkit.autopsy.centralrepository.application.OtherOccurrences;
import org.sleuthkit.autopsy.centralrepository.application.UniquePathKey;
import org.sleuthkit.autopsy.centralrepository.contentviewer.OtherOccurrenceOneTypeWorker.OneTypeData;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile;
/**
* Swing worker for getting the Other Occurrence data for the Domain Discovery
* window.
*
* This logic differs a bit from the OtherOcurrencesNodeWorker.
*/
class OtherOccurrenceOneTypeWorker extends SwingWorker<OneTypeData, Void> {
private static final Logger logger = Logger.getLogger(OtherOccurrenceOneTypeWorker.class.getName());
private final CorrelationAttributeInstance.Type aType;
private final String value;
private final AbstractFile file;
private final String deviceId;
private final String dataSourceName;
/**
* Construct the worker.
*
* @param aType
* @param value
* @param file Source file, this maybe null.
* @param deviceId DeviceID string, this maybe an empty string.
* @param dataSourceName DataSourceName, this maybe an empty string.
*/
OtherOccurrenceOneTypeWorker(CorrelationAttributeInstance.Type aType, String value, AbstractFile file, String deviceId, String dataSourceName) {
this.aType = aType;
this.value = value;
this.file = file;
this.deviceId = deviceId;
this.dataSourceName = dataSourceName;
}
@Override
protected OneTypeData doInBackground() throws Exception {
Map<String, CorrelationCase> caseNames = new HashMap<>();
int totalCount = 0;
Set<String> dataSources = new HashSet<>();
Collection<CorrelationAttributeInstance> correlationAttributesToAdd = new ArrayList<>();
String earliestDate = OtherOccurrences.getEarliestCaseDate();
OneTypeData results = null;
if (CentralRepository.isEnabled()) {
List<CorrelationAttributeInstance> instances;
instances = CentralRepository.getInstance().getArtifactInstancesByTypeValue(aType, value);
HashMap<UniquePathKey, NodeData> nodeDataMap = new HashMap<>();
String caseUUID = Case.getCurrentCase().getName();
for (CorrelationAttributeInstance artifactInstance : instances) {
if (isCancelled()) {
break;
}
// Only add the attribute if it isn't the object the user selected.
// We consider it to be a different object if at least one of the following is true:
// - the case UUID is different
// - the data source name is different
// - the data source device ID is different
// - the file path is different
if (artifactInstance.getCorrelationCase().getCaseUUID().equals(caseUUID)
&& (!StringUtils.isBlank(dataSourceName) && artifactInstance.getCorrelationDataSource().getName().equals(dataSourceName))
&& (!StringUtils.isBlank(deviceId) && artifactInstance.getCorrelationDataSource().getDeviceID().equals(deviceId))
&& (file != null && artifactInstance.getFilePath().equalsIgnoreCase(file.getParentPath() + file.getName()))) {
continue;
}
correlationAttributesToAdd.add(artifactInstance);
NodeData newNode = new NodeData(artifactInstance, aType, value);
UniquePathKey uniquePathKey = new UniquePathKey(newNode);
nodeDataMap.put(uniquePathKey, newNode);
}
for (NodeData nodeData : nodeDataMap.values()) {
if (isCancelled()) {
break;
}
if (nodeData.isCentralRepoNode()) {
try {
dataSources.add(OtherOccurrences.makeDataSourceString(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getDeviceID(), nodeData.getDataSourceName()));
caseNames.put(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getCorrelationAttributeInstance().getCorrelationCase());
} catch (CentralRepoException ex) {
logger.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName(), ex);
}
} else {
try {
dataSources.add(OtherOccurrences.makeDataSourceString(Case.getCurrentCaseThrows().getName(), nodeData.getDeviceID(), nodeData.getDataSourceName()));
caseNames.put(Case.getCurrentCaseThrows().getName(), new CorrelationCase(Case.getCurrentCaseThrows().getName(), Case.getCurrentCaseThrows().getDisplayName()));
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "No current case open for other occurrences", ex);
}
}
totalCount++;
}
}
if (!isCancelled()) {
results = new OneTypeData(caseNames, totalCount, dataSources.size(), earliestDate, correlationAttributesToAdd);
}
return results;
}
/**
* Class to store the results of the worker thread.
*/
static final class OneTypeData {
private final Map<String, CorrelationCase> caseNames;
private final int totalCount;
private final int dataSourceCount;
private final Collection<CorrelationAttributeInstance> correlationAttributesToAdd;
private final String earliestCaseDate;
/**
* Construct the results.
*
* @param caseNames Map of correlation cases.
* @param totalCount Total count of instances.
* @param dataSourceCount Data source count.
* @param earliestCaseDate Formatted string which contains the
* earliest case date.
* @param correlationAttributesToAdd The attributes to add to the main
* panel list.
*/
OneTypeData(Map<String, CorrelationCase> caseNames, int totalCount, int dataSourceCount, String earliestCaseDate, Collection<CorrelationAttributeInstance> correlationAttributesToAdd) {
this.caseNames = caseNames;
this.totalCount = totalCount;
this.dataSourceCount = dataSourceCount;
this.correlationAttributesToAdd = correlationAttributesToAdd;
this.earliestCaseDate = earliestCaseDate;
}
public Map<String, CorrelationCase> getCaseNames() {
return caseNames;
}
public int getTotalCount() {
return totalCount;
}
public int getDataSourceCount() {
return dataSourceCount;
}
public Collection<CorrelationAttributeInstance> getCorrelationAttributesToAdd() {
return correlationAttributesToAdd;
}
public String getEarliestCaseDate() {
return earliestCaseDate;
}
}
}

View File

@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.centralrepository.contentviewer;
import org.sleuthkit.autopsy.centralrepository.application.NodeData;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
@ -26,6 +27,7 @@ import javax.swing.table.AbstractTableModel;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.application.OtherOccurrences;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.coreutils.Logger;
@ -139,11 +141,10 @@ final class OtherOccurrencesDataSourcesTableModel extends AbstractTableModel {
*
* @param newNodeData data to add to the table
*/
void addNodeData(OtherOccurrenceNodeData newNodeData) {
OtherOccurrenceNodeInstanceData nodeData = (OtherOccurrenceNodeInstanceData) newNodeData;
void addNodeData(NodeData newNodeData) {
String caseUUID;
try {
caseUUID = nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID();
caseUUID = newNodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID();
} catch (CentralRepoException ignored) {
//non central repo nodeData won't have a correlation case
try {
@ -151,10 +152,10 @@ final class OtherOccurrencesDataSourcesTableModel extends AbstractTableModel {
//place holder value will be used since correlation attribute was unavailble
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Unable to get current case", ex);
caseUUID = OtherOccurrencesPanel.getPlaceholderUUID();
caseUUID = OtherOccurrences.getPlaceholderUUID();
}
}
dataSourceSet.add(new DataSourceColumnItem(nodeData.getCaseName(), nodeData.getDeviceID(), nodeData.getDataSourceName(), caseUUID));
dataSourceSet.add(new DataSourceColumnItem(newNodeData.getCaseName(), newNodeData.getDeviceID(), newNodeData.getDataSourceName(), caseUUID));
fireTableDataChanged();
}

View File

@ -18,6 +18,7 @@
*/
package org.sleuthkit.autopsy.centralrepository.contentviewer;
import org.sleuthkit.autopsy.centralrepository.application.NodeData;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -28,6 +29,7 @@ import org.openide.util.NbBundle.Messages;
import org.apache.commons.io.FilenameUtils;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.application.OtherOccurrences;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.coreutils.Logger;
@ -40,7 +42,7 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(OtherOccurrencesFilesTableModel.class.getName());
private final List<String> nodeKeys = new ArrayList<>();
private final Map<String, List<OtherOccurrenceNodeData>> nodeMap = new HashMap<>();
private final Map<String, List<NodeData>> nodeMap = new HashMap<>();
/**
* Create a table model for displaying file names
@ -75,7 +77,7 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel {
|| nodeMap.get(nodeKeys.get(rowIdx)).isEmpty()) {
return Bundle.OtherOccurrencesFilesTableModel_noData();
}
return FilenameUtils.getName(((OtherOccurrenceNodeInstanceData) nodeMap.get(nodeKeys.get(rowIdx)).get(0)).getFilePath());
return FilenameUtils.getName( nodeMap.get(nodeKeys.get(rowIdx)).get(0).getFilePath());
}
/**
@ -87,7 +89,7 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel {
* @return a list of OtherOccurrenceNodeData for the specified index or an
* empty list if no data was found
*/
List<OtherOccurrenceNodeData> getListOfNodesForFile(int rowIdx) {
List<NodeData> getListOfNodesForFile(int rowIdx) {
//if anything would prevent this from working return an empty list
if (nodeMap.isEmpty() || nodeKeys.isEmpty() || rowIdx < 0
|| rowIdx >= nodeKeys.size() || nodeKeys.get(rowIdx) == null
@ -107,9 +109,9 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel {
*
* @param newNodeData data to add to the table
*/
void addNodeData(OtherOccurrenceNodeData newNodeData) {
String newNodeKey = createNodeKey((OtherOccurrenceNodeInstanceData) newNodeData);//FilenameUtils.getName(((OtherOccurrenceNodeInstanceData)newNodeData).getFilePath());
List<OtherOccurrenceNodeData> nodeList = nodeMap.get(newNodeKey);
void addNodeData(NodeData newNodeData) {
String newNodeKey = createNodeKey(newNodeData);
List<NodeData> nodeList = nodeMap.get(newNodeKey);
if (nodeList == null) {
nodeKeys.add(newNodeKey);
nodeList = new ArrayList<>();
@ -119,7 +121,7 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel {
fireTableDataChanged();
}
private String createNodeKey(OtherOccurrenceNodeInstanceData nodeData) {
private String createNodeKey(NodeData nodeData) {
String caseUUID;
try {
caseUUID = nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID();
@ -130,7 +132,7 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel {
//place holder value will be used since correlation attribute was unavailble
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Unable to get current case", ex);
caseUUID = OtherOccurrencesPanel.getPlaceholderUUID();
caseUUID = OtherOccurrences.getPlaceholderUUID();
}
}
return nodeData.getCaseName() + nodeData.getDataSourceName() + nodeData.getDeviceID() + nodeData.getFilePath() + caseUUID;

View File

@ -0,0 +1,179 @@
/*
* Central Repository
*
* Copyright 2021 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.centralrepository.contentviewer;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import javax.swing.SwingWorker;
import org.openide.nodes.Node;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.application.NodeData;
import org.sleuthkit.autopsy.centralrepository.application.OtherOccurrences;
import org.sleuthkit.autopsy.centralrepository.contentviewer.OtherOccurrencesNodeWorker.OtherOccurrencesData;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskException;
/**
* A SwingWorker that gathers data for the OtherOccurencesPanel which appears in
* the dataContentViewerOtherCases panel.
*/
class OtherOccurrencesNodeWorker extends SwingWorker<OtherOccurrencesData, Void> {
private static final Logger logger = Logger.getLogger(OtherOccurrencesNodeWorker.class.getName());
private final Node node;
/**
* Constructs a new instance for the given node.
*
* @param node
*/
OtherOccurrencesNodeWorker(Node node) {
this.node = node;
}
@Override
protected OtherOccurrencesData doInBackground() throws Exception {
AbstractFile file = OtherOccurrences.getAbstractFileFromNode(node);
String deviceId = "";
String dataSourceName = "";
Map<String, CorrelationCase> caseNames = new HashMap<>();
Case currentCase = Case.getCurrentCaseThrows();
OtherOccurrencesData data = null;
try {
if (file != null) {
Content dataSource = file.getDataSource();
deviceId = currentCase.getSleuthkitCase().getDataSource(dataSource.getId()).getDeviceId();
dataSourceName = dataSource.getName();
}
} catch (TskException ex) {
// do nothing.
// @@@ Review this behavior
return null;
}
Collection<CorrelationAttributeInstance> correlationAttributes = OtherOccurrences.getCorrelationAttributesFromNode(node, file);
int totalCount = 0;
Set<String> dataSources = new HashSet<>();
for (CorrelationAttributeInstance corAttr : correlationAttributes) {
for (NodeData nodeData : OtherOccurrences.getCorrelatedInstances(file, deviceId, dataSourceName, corAttr).values()) {
if (nodeData.isCentralRepoNode()) {
try {
dataSources.add(OtherOccurrences.makeDataSourceString(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getDeviceID(), nodeData.getDataSourceName()));
caseNames.put(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getCorrelationAttributeInstance().getCorrelationCase());
} catch (CentralRepoException ex) {
logger.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName(), ex);
}
} else {
try {
dataSources.add(OtherOccurrences.makeDataSourceString(Case.getCurrentCaseThrows().getName(), nodeData.getDeviceID(), nodeData.getDataSourceName()));
caseNames.put(Case.getCurrentCaseThrows().getName(), new CorrelationCase(Case.getCurrentCaseThrows().getName(), Case.getCurrentCaseThrows().getDisplayName()));
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "No current case open for other occurrences", ex);
}
}
totalCount++;
if (isCancelled()) {
break;
}
}
}
if (!isCancelled()) {
data = new OtherOccurrencesData(correlationAttributes, file, dataSourceName, deviceId, caseNames, totalCount, dataSources.size(), OtherOccurrences.getEarliestCaseDate());
}
return data;
}
/**
* Object to store all of the data gathered in the OtherOccurrencesWorker
* doInBackground method.
*/
static class OtherOccurrencesData {
private final String deviceId;
private final AbstractFile file;
private final String dataSourceName;
private final Map<String, CorrelationCase> caseMap;
private final int instanceDataCount;
private final int dataSourceCount;
private final String earliestCaseDate;
private final Collection<CorrelationAttributeInstance> correlationAttributes;
private OtherOccurrencesData(Collection<CorrelationAttributeInstance> correlationAttributes, AbstractFile file, String dataSourceName, String deviceId, Map<String, CorrelationCase> caseMap, int instanceCount, int dataSourceCount, String earliestCaseDate) {
this.file = file;
this.deviceId = deviceId;
this.dataSourceName = dataSourceName;
this.caseMap = caseMap;
this.instanceDataCount = instanceCount;
this.dataSourceCount = dataSourceCount;
this.earliestCaseDate = earliestCaseDate;
this.correlationAttributes = correlationAttributes;
}
public String getDeviceId() {
return deviceId;
}
public AbstractFile getFile() {
return file;
}
public String getDataSourceName() {
return dataSourceName;
}
public Map<String, CorrelationCase> getCaseMap() {
return caseMap;
}
public int getInstanceDataCount() {
return instanceDataCount;
}
public int getDataSourceCount() {
return dataSourceCount;
}
/**
* Returns the earliest date in the case.
*
* @return Formatted date string, or message that one was not found.
*/
public String getEarliestCaseDate() {
return earliestCaseDate;
}
public Collection<CorrelationAttributeInstance> getCorrelationAttributes() {
return correlationAttributes;
}
}
}

View File

@ -18,26 +18,23 @@
*/
package org.sleuthkit.autopsy.centralrepository.contentviewer;
import org.sleuthkit.autopsy.centralrepository.application.NodeData;
import org.sleuthkit.autopsy.centralrepository.application.UniquePathKey;
import org.sleuthkit.autopsy.centralrepository.application.OtherOccurrences;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javax.swing.JFileChooser;
import javax.swing.JMenuItem;
@ -45,15 +42,15 @@ import javax.swing.JOptionPane;
import static javax.swing.JOptionPane.DEFAULT_OPTION;
import static javax.swing.JOptionPane.ERROR_MESSAGE;
import static javax.swing.JOptionPane.PLAIN_MESSAGE;
import javax.swing.SwingWorker;
import javax.swing.filechooser.FileNameExtensionFilter;
import javax.swing.table.TableModel;
import javax.swing.table.TableRowSorter;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDateTime;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.contentviewer.OtherOccurrencesNodeWorker.OtherOccurrencesData;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
@ -61,22 +58,20 @@ import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNor
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationCase;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.ContentTag;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData;
/**
* Panel for displaying other occurrences results.
*/
@NbBundle.Messages({
"OtherOccurrencesPanel.table.noArtifacts=Item has no attributes with which to search.",
"OtherOccurrencesPanel.table.noResultsFound=No results found."})
"OtherOccurrencesPanel.table.noResultsFound=No results found.",
"OtherOccurrencesPanel_table_loadingResults=Loading results"
})
public final class OtherOccurrencesPanel extends javax.swing.JPanel {
private static final CorrelationCaseWrapper NO_ARTIFACTS_CASE = new CorrelationCaseWrapper(Bundle.OtherOccurrencesPanel_table_noArtifacts());
private static final CorrelationCaseWrapper NO_RESULTS_CASE = new CorrelationCaseWrapper(Bundle.OtherOccurrencesPanel_table_noResultsFound());
private static final String UUID_PLACEHOLDER_STRING = "NoCorrelationAttributeInstance";
private static final CorrelationCaseWrapper LOADING_CASE = new CorrelationCaseWrapper(Bundle.OtherOccurrencesPanel_table_loadingResults());
private static final Logger logger = Logger.getLogger(OtherOccurrencesPanel.class.getName());
private static final long serialVersionUID = 1L;
private final OtherOccurrencesFilesTableModel filesTableModel;
@ -88,6 +83,8 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
private String deviceId = ""; //the device id of the data source for the file which the content viewer is being populated for
private AbstractFile file = null;
private SwingWorker<?, ?> worker;
/**
* Creates new form OtherOccurrencesPanel
*/
@ -101,16 +98,6 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
customizeComponents();
}
/**
* Get a placeholder string to use in place of case uuid when it isn't
* available
*
* @return UUID_PLACEHOLDER_STRING
*/
static String getPlaceholderUUID() {
return UUID_PLACEHOLDER_STRING;
}
private void customizeComponents() {
ActionListener actList = (ActionEvent e) -> {
JMenuItem jmi = (JMenuItem) e.getSource();
@ -182,14 +169,16 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
*/
private void showCommonalityDetails() {
if (correlationAttributes.isEmpty()) {
JOptionPane.showConfirmDialog(showCommonalityMenuItem,
JOptionPane.showConfirmDialog(OtherOccurrencesPanel.this,
Bundle.OtherOccurrencesPanel_correlatedArtifacts_isEmpty(),
Bundle.OtherOccurrencesPanel_correlatedArtifacts_title(),
DEFAULT_OPTION, PLAIN_MESSAGE);
} else {
StringBuilder msg = new StringBuilder(correlationAttributes.size());
int percentage;
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
// Leaving these calls on the EDT but adding wait cursor
CentralRepository dbManager = CentralRepository.getInstance();
for (CorrelationAttributeInstance eamArtifact : correlationAttributes) {
try {
@ -201,13 +190,15 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
logger.log(Level.WARNING, String.format("Error getting commonality details for artifact with ID: %s.", eamArtifact.getID()), ex);
}
}
JOptionPane.showConfirmDialog(showCommonalityMenuItem,
this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
JOptionPane.showConfirmDialog(OtherOccurrencesPanel.this,
msg.toString(),
Bundle.OtherOccurrencesPanel_correlatedArtifacts_title(),
DEFAULT_OPTION, PLAIN_MESSAGE);
} catch (CentralRepoException ex) {
this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
logger.log(Level.SEVERE, "Error getting commonality details.", ex);
JOptionPane.showConfirmDialog(showCommonalityMenuItem,
JOptionPane.showConfirmDialog(OtherOccurrencesPanel.this,
Bundle.OtherOccurrencesPanel_correlatedArtifacts_failed(),
Bundle.OtherOccurrencesPanel_correlatedArtifacts_title(),
DEFAULT_OPTION, ERROR_MESSAGE);
@ -227,20 +218,16 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
if (-1 != selectedRowViewIdx) {
CentralRepository dbManager = CentralRepository.getInstance();
int selectedRowModelIdx = filesTable.convertRowIndexToModel(selectedRowViewIdx);
List<OtherOccurrenceNodeData> rowList = filesTableModel.getListOfNodesForFile(selectedRowModelIdx);
List<NodeData> rowList = filesTableModel.getListOfNodesForFile(selectedRowModelIdx);
if (!rowList.isEmpty()) {
if (rowList.get(0) instanceof OtherOccurrenceNodeInstanceData) {
CorrelationCase eamCasePartial = ((OtherOccurrenceNodeInstanceData) rowList.get(0)).getCorrelationAttributeInstance().getCorrelationCase();
caseDisplayName = eamCasePartial.getDisplayName();
// query case details
CorrelationCase eamCase = dbManager.getCaseByUUID(eamCasePartial.getCaseUUID());
if (eamCase != null) {
details = eamCase.getCaseDetailsOptionsPaneDialog();
} else {
details = Bundle.OtherOccurrencesPanel_caseDetailsDialog_noDetails();
}
CorrelationCase eamCasePartial = rowList.get(0).getCorrelationAttributeInstance().getCorrelationCase();
caseDisplayName = eamCasePartial.getDisplayName();
// query case details
CorrelationCase eamCase = dbManager.getCaseByUUID(eamCasePartial.getCaseUUID());
if (eamCase != null) {
details = eamCase.getCaseDetailsOptionsPaneDialog();
} else {
details = Bundle.OtherOccurrencesPanel_caseDetailsDialog_notSelected();
details = Bundle.OtherOccurrencesPanel_caseDetailsDialog_noDetails();
}
} else {
details = Bundle.OtherOccurrencesPanel_caseDetailsDialog_noDetailsReference();
@ -249,7 +236,7 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, "Error loading case details", ex);
} finally {
JOptionPane.showConfirmDialog(showCaseDetailsMenuItem,
JOptionPane.showConfirmDialog(OtherOccurrencesPanel.this,
details,
caseDisplayName,
DEFAULT_OPTION, PLAIN_MESSAGE);
@ -271,85 +258,13 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
if (!selectedFile.getName().endsWith(".csv")) { // NON-NLS
selectedFile = new File(selectedFile.toString() + ".csv"); // NON-NLS
}
writeOtherOccurrencesToFileAsCSV(selectedFile);
CSVWorker worker = new CSVWorker(selectedFile, file, dataSourceName, deviceId, Collections.unmodifiableCollection(correlationAttributes));
worker.execute();
}
}
}
@NbBundle.Messages({
"OtherOccurrencesPanel.csvHeader.case=Case",
"OtherOccurrencesPanel.csvHeader.device=Device",
"OtherOccurrencesPanel.csvHeader.dataSource=Data Source",
"OtherOccurrencesPanel.csvHeader.attribute=Matched Attribute",
"OtherOccurrencesPanel.csvHeader.value=Attribute Value",
"OtherOccurrencesPanel.csvHeader.known=Known",
"OtherOccurrencesPanel.csvHeader.path=Path",
"OtherOccurrencesPanel.csvHeader.comment=Comment"
})
/**
* Write data for all cases in the content viewer to a CSV file
*/
private void writeOtherOccurrencesToFileAsCSV(File destFile) {
try (BufferedWriter writer = Files.newBufferedWriter(destFile.toPath())) {
//write headers
StringBuilder headers = new StringBuilder("\"");
headers.append(Bundle.OtherOccurrencesPanel_csvHeader_case())
.append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.OtherOccurrencesPanel_csvHeader_dataSource())
.append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.OtherOccurrencesPanel_csvHeader_attribute())
.append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.OtherOccurrencesPanel_csvHeader_value())
.append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.OtherOccurrencesPanel_csvHeader_known())
.append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.OtherOccurrencesPanel_csvHeader_path())
.append(OtherOccurrenceNodeInstanceData.getCsvItemSeparator()).append(Bundle.OtherOccurrencesPanel_csvHeader_comment())
.append('"').append(System.getProperty("line.separator"));
writer.write(headers.toString());
//write content
for (CorrelationAttributeInstance corAttr : correlationAttributes) {
Map<UniquePathKey, OtherOccurrenceNodeInstanceData> correlatedNodeDataMap = new HashMap<>(0);
// get correlation and reference set instances from DB
correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr));
for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) {
writer.write(nodeData.toCsvString());
}
}
} catch (IOException ex) {
logger.log(Level.SEVERE, "Error writing selected rows to CSV.", ex);
}
}
@NbBundle.Messages({"OtherOccurrencesPanel.earliestCaseNotAvailable= Not Enabled."})
/**
* Gets the list of Eam Cases and determines the earliest case creation
* date. Sets the label to display the earliest date string to the user.
*/
private void setEarliestCaseDate() {
String dateStringDisplay = Bundle.OtherOccurrencesPanel_earliestCaseNotAvailable();
if (CentralRepository.isEnabled()) {
LocalDateTime earliestDate = LocalDateTime.now(DateTimeZone.UTC);
DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.US);
try {
CentralRepository dbManager = CentralRepository.getInstance();
List<CorrelationCase> cases = dbManager.getCases();
for (CorrelationCase aCase : cases) {
LocalDateTime caseDate = LocalDateTime.fromDateFields(datetimeFormat.parse(aCase.getCreationDate()));
if (caseDate.isBefore(earliestDate)) {
earliestDate = caseDate;
dateStringDisplay = aCase.getCreationDate();
}
}
} catch (CentralRepoException ex) {
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
}
}
earliestCaseDate.setText(dateStringDisplay);
}
@NbBundle.Messages({"OtherOccurrencesPanel_earliestCaseNotAvailable=Not Availble."})
/**
* Reset the UI and clear cached data.
*/
@ -371,123 +286,82 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
* Populate the other occurrences table for one Correlation Attribute type
* and value.
*
* This method contains its own SwingWorker togather data.
*
* @param aType The correlation attribute type to display other occurrences
* for.
* @param value The value being correlated on.
*/
public void populateTableForOneType(CorrelationAttributeInstance.Type aType, String value) {
Map<String, CorrelationCase> caseNames = new HashMap<>();
int totalCount = 0;
Set<String> dataSources = new HashSet<>();
if (CentralRepository.isEnabled()) {
try {
List<CorrelationAttributeInstance> instances;
instances = CentralRepository.getInstance().getArtifactInstancesByTypeValue(aType, value);
HashMap<UniquePathKey, OtherOccurrenceNodeInstanceData> nodeDataMap = new HashMap<>();
String caseUUID = Case.getCurrentCase().getName();
for (CorrelationAttributeInstance artifactInstance : instances) {
public void populateTableForOneType(CorrelationAttributeInstance.Type aType, String value) throws CentralRepoException {
if (worker != null) {
worker.cancel(true);
worker = null;
}
// Only add the attribute if it isn't the object the user selected.
// We consider it to be a different object if at least one of the following is true:
// - the case UUID is different
// - the data source name is different
// - the data source device ID is different
// - the file path is different
if (artifactInstance.getCorrelationCase().getCaseUUID().equals(caseUUID)
&& (!StringUtils.isBlank(dataSourceName) && artifactInstance.getCorrelationDataSource().getName().equals(dataSourceName))
&& (!StringUtils.isBlank(deviceId) && artifactInstance.getCorrelationDataSource().getDeviceID().equals(deviceId))
&& (file != null && artifactInstance.getFilePath().equalsIgnoreCase(file.getParentPath() + file.getName()))) {
continue;
casesTableModel.addCorrelationCase(NO_ARTIFACTS_CASE);
worker = new OtherOccurrenceOneTypeWorker(aType, value, file, deviceId, dataSourceName) {
@Override
public void done() {
try {
if (isCancelled()) {
return;
}
correlationAttributes.add(artifactInstance);
OtherOccurrenceNodeInstanceData newNode = new OtherOccurrenceNodeInstanceData(artifactInstance, aType, value);
UniquePathKey uniquePathKey = new UniquePathKey(newNode);
nodeDataMap.put(uniquePathKey, newNode);
}
for (OtherOccurrenceNodeInstanceData nodeData : nodeDataMap.values()) {
if (nodeData.isCentralRepoNode()) {
try {
dataSources.add(makeDataSourceString(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getDeviceID(), nodeData.getDataSourceName()));
caseNames.put(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getCorrelationAttributeInstance().getCorrelationCase());
} catch (CentralRepoException ex) {
logger.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName(), ex);
}
} else {
try {
dataSources.add(makeDataSourceString(Case.getCurrentCaseThrows().getName(), nodeData.getDeviceID(), nodeData.getDataSourceName()));
caseNames.put(Case.getCurrentCaseThrows().getName(), new CorrelationCase(Case.getCurrentCaseThrows().getName(), Case.getCurrentCaseThrows().getDisplayName()));
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "No current case open for other occurrences", ex);
}
casesTableModel.clearTable();
OtherOccurrenceOneTypeWorker.OneTypeData data = get();
for (CorrelationCase corCase : data.getCaseNames().values()) {
casesTableModel.addCorrelationCase(new CorrelationCaseWrapper(corCase));
}
totalCount++;
int caseCount = casesTableModel.getRowCount();
if (correlationAttributes.isEmpty()) {
casesTableModel.addCorrelationCase(NO_ARTIFACTS_CASE);
} else if (caseCount == 0) {
casesTableModel.addCorrelationCase(NO_RESULTS_CASE);
}
String earliestDate = data.getEarliestCaseDate();
earliestCaseDate.setText(earliestDate.isEmpty() ? Bundle.OtherOccurrencesPanel_earliestCaseNotAvailable() : earliestDate);
foundInLabel.setText(String.format(Bundle.OtherOccurrencesPanel_foundIn_text(), data.getTotalCount(), caseCount, data.getDataSourceCount()));
if (caseCount > 0) {
casesTable.setRowSelectionInterval(0, 0);
}
} catch (InterruptedException | ExecutionException ex) {
logger.log(Level.SEVERE, "Failed to update OtherOccurrence panel", ex);
}
} catch (CorrelationAttributeNormalizationException | CentralRepoException ex) {
logger.log(Level.WARNING, "Error retrieving other occurrences for " + aType.getDisplayName() + ": " + value, ex);
}
}
for (CorrelationCase corCase : caseNames.values()) {
casesTableModel.addCorrelationCase(new CorrelationCaseWrapper(corCase));
}
int caseCount = casesTableModel.getRowCount();
if (correlationAttributes.isEmpty()) {
casesTableModel.addCorrelationCase(NO_ARTIFACTS_CASE);
} else if (caseCount == 0) {
casesTableModel.addCorrelationCase(NO_RESULTS_CASE);
}
setEarliestCaseDate();
foundInLabel.setText(String.format(Bundle.OtherOccurrencesPanel_foundIn_text(), totalCount, caseCount, dataSources.size()));
if (caseCount > 0) {
casesTable.setRowSelectionInterval(0, 0);
}
};
worker.execute();
}
/**
* Makes a loading message appear in the case table.
*/
void showPanelLoadingMessage() {
casesTableModel.addCorrelationCase(NO_ARTIFACTS_CASE);
}
/**
* Load the correlatable data into the table model. If there is no data
* available display the message on the status panel.
*
* @param correlationAttrs The correlationAttributes to correlate on.
* @param dataSourceName The name of the dataSource to ignore results
* from.
* @param deviceId The deviceId of the device to ignore results
* from.
* @param abstractFile The abstract file to ignore files with the same
* location as.
* @param data A data wrapper object.
*/
@NbBundle.Messages({
"OtherOccurrencesPanel.foundIn.text=Found %d instances in %d cases and %d data sources."
})
void populateTable(Collection<CorrelationAttributeInstance> correlationAttrs, String dataSourceName, String deviceId, AbstractFile abstractFile) {
this.file = abstractFile;
this.dataSourceName = dataSourceName;
this.deviceId = deviceId;
void populateTable(OtherOccurrencesData data) {
this.file = data.getFile();
this.dataSourceName = data.getDataSourceName();
this.deviceId = data.getDeviceId();
// get the attributes we can correlate on
correlationAttributes.addAll(correlationAttrs);
Map<String, CorrelationCase> caseNames = new HashMap<>();
int totalCount = 0;
Set<String> dataSources = new HashSet<>();
for (CorrelationAttributeInstance corAttr : correlationAttributes) {
for (OtherOccurrenceNodeInstanceData nodeData : getCorrelatedInstances(corAttr).values()) {
if (nodeData.isCentralRepoNode()) {
try {
dataSources.add(makeDataSourceString(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getDeviceID(), nodeData.getDataSourceName()));
caseNames.put(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getCorrelationAttributeInstance().getCorrelationCase());
} catch (CentralRepoException ex) {
logger.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName(), ex);
}
} else {
try {
dataSources.add(makeDataSourceString(Case.getCurrentCaseThrows().getName(), nodeData.getDeviceID(), nodeData.getDataSourceName()));
caseNames.put(Case.getCurrentCaseThrows().getName(), new CorrelationCase(Case.getCurrentCaseThrows().getName(), Case.getCurrentCaseThrows().getDisplayName()));
} catch (NoCurrentCaseException ex) {
logger.log(Level.WARNING, "No current case open for other occurrences", ex);
}
}
totalCount++;
}
}
for (CorrelationCase corCase : caseNames.values()) {
casesTableModel.clearTable();
correlationAttributes.addAll(data.getCorrelationAttributes());
for (CorrelationCase corCase : data.getCaseMap().values()) {
casesTableModel.addCorrelationCase(new CorrelationCaseWrapper(corCase));
}
int caseCount = casesTableModel.getRowCount();
@ -496,241 +370,142 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
} else if (caseCount == 0) {
casesTableModel.addCorrelationCase(NO_RESULTS_CASE);
}
setEarliestCaseDate();
foundInLabel.setText(String.format(Bundle.OtherOccurrencesPanel_foundIn_text(), totalCount, caseCount, dataSources.size()));
String earliestDate = data.getEarliestCaseDate();
earliestCaseDate.setText(earliestDate.isEmpty() ? Bundle.OtherOccurrencesPanel_earliestCaseNotAvailable() : earliestDate);
foundInLabel.setText(String.format(Bundle.OtherOccurrencesPanel_foundIn_text(), data.getInstanceDataCount(), caseCount, data.getDataSourceCount()));
if (caseCount > 0) {
casesTable.setRowSelectionInterval(0, 0);
}
}
/**
* Query the central repo database (if enabled) and the case database to
* find all artifact instances correlated to the given central repository
* artifact. If the central repo is not enabled, this will only return files
* from the current case with matching MD5 hashes.
*
* @param corAttr CorrelationAttribute to query for
*
* @return A collection of correlated artifact instances
*/
private Map<UniquePathKey, OtherOccurrenceNodeInstanceData> getCorrelatedInstances(CorrelationAttributeInstance corAttr) {
// @@@ Check exception
try {
final Case openCase = Case.getCurrentCaseThrows();
String caseUUID = openCase.getName();
HashMap<UniquePathKey, OtherOccurrenceNodeInstanceData> nodeDataMap = new HashMap<>();
if (CentralRepository.isEnabled()) {
List<CorrelationAttributeInstance> instances = CentralRepository.getInstance().getArtifactInstancesByTypeValue(corAttr.getCorrelationType(), corAttr.getCorrelationValue());
for (CorrelationAttributeInstance artifactInstance : instances) {
// Only add the attribute if it isn't the object the user selected.
// We consider it to be a different object if at least one of the following is true:
// - the case UUID is different
// - the data source name is different
// - the data source device ID is different
// - the file path is different
if (artifactInstance.getCorrelationCase().getCaseUUID().equals(caseUUID)
&& (!StringUtils.isBlank(dataSourceName) && artifactInstance.getCorrelationDataSource().getName().equals(dataSourceName))
&& (!StringUtils.isBlank(deviceId) && artifactInstance.getCorrelationDataSource().getDeviceID().equals(deviceId))
&& (file != null && artifactInstance.getFilePath().equalsIgnoreCase(file.getParentPath() + file.getName()))) {
continue;
}
OtherOccurrenceNodeInstanceData newNode = new OtherOccurrenceNodeInstanceData(artifactInstance, corAttr.getCorrelationType(), corAttr.getCorrelationValue());
UniquePathKey uniquePathKey = new UniquePathKey(newNode);
nodeDataMap.put(uniquePathKey, newNode);
}
if (file != null && corAttr.getCorrelationType().getDisplayName().equals("Files")) {
List<AbstractFile> caseDbFiles = getCaseDbMatches(corAttr, openCase, file);
for (AbstractFile caseDbFile : caseDbFiles) {
addOrUpdateNodeData(openCase, nodeDataMap, caseDbFile);
}
}
}
return nodeDataMap;
} catch (CentralRepoException ex) {
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
} catch (TskCoreException ex) {
// do nothing.
// @@@ Review this behavior
logger.log(Level.SEVERE, "Exception while querying open case.", ex); // NON-NLS
}
return new HashMap<>(
0);
}
/**
* Adds the file to the nodeDataMap map if it does not already exist
*
* @param autopsyCase
* @param nodeDataMap
* @param newFile
*
* @throws TskCoreException
* @throws CentralRepoException
*/
private void addOrUpdateNodeData(final Case autopsyCase, Map<UniquePathKey, OtherOccurrenceNodeInstanceData> nodeDataMap, AbstractFile newFile) throws TskCoreException, CentralRepoException {
OtherOccurrenceNodeInstanceData newNode = new OtherOccurrenceNodeInstanceData(newFile, autopsyCase);
// If the caseDB object has a notable tag associated with it, update
// the known status to BAD
if (newNode.getKnown() != TskData.FileKnown.BAD) {
List<ContentTag> fileMatchTags = autopsyCase.getServices().getTagsManager().getContentTagsByContent(newFile);
for (ContentTag tag : fileMatchTags) {
TskData.FileKnown tagKnownStatus = tag.getName().getKnownStatus();
if (tagKnownStatus.equals(TskData.FileKnown.BAD)) {
newNode.updateKnown(TskData.FileKnown.BAD);
break;
}
}
}
// Make a key to see if the file is already in the map
UniquePathKey uniquePathKey = new UniquePathKey(newNode);
// If this node is already in the list, the only thing we need to do is
// update the known status to BAD if the caseDB version had known status BAD.
// Otherwise this is a new node so add the new node to the map.
if (nodeDataMap.containsKey(uniquePathKey)) {
if (newNode.getKnown() == TskData.FileKnown.BAD) {
OtherOccurrenceNodeInstanceData prevInstance = nodeDataMap.get(uniquePathKey);
prevInstance.updateKnown(newNode.getKnown());
}
} else {
nodeDataMap.put(uniquePathKey, newNode);
}
}
/**
* Get all other abstract files in the current case with the same MD5 as the
* selected node.
*
* @param corAttr The CorrelationAttribute containing the MD5 to search for
* @param openCase The current case
* @param file The current file.
*
* @return List of matching AbstractFile objects
*
* @throws NoCurrentCaseException
* @throws TskCoreException
* @throws CentralRepoException
*/
private List<AbstractFile> getCaseDbMatches(CorrelationAttributeInstance corAttr, Case openCase, AbstractFile file) throws NoCurrentCaseException, TskCoreException, CentralRepoException {
List<AbstractFile> caseDbArtifactInstances = new ArrayList<>();
if (file != null) {
String md5 = corAttr.getCorrelationValue();
SleuthkitCase tsk = openCase.getSleuthkitCase();
List<AbstractFile> matches = tsk.findAllFilesWhere(String.format("md5 = '%s'", new Object[]{md5}));
for (AbstractFile fileMatch : matches) {
if (file.equals(fileMatch)) {
continue; // If this is the file the user clicked on
}
caseDbArtifactInstances.add(fileMatch);
}
}
return caseDbArtifactInstances;
}
/**
* Create a unique string to be used as a key for deduping data sources as
* best as possible
*/
private String makeDataSourceString(String caseUUID, String deviceId, String dataSourceName) {
return caseUUID + deviceId + dataSourceName;
}
/**
* Updates diplayed information to be correct for the current case selection
* Updates displayed information to be correct for the current case
* selection
*/
private void updateOnCaseSelection() {
int[] selectedCaseIndexes = casesTable.getSelectedRows();
if (worker != null) {
worker.cancel(true);
worker = null;
}
final int[] selectedCaseIndexes = casesTable.getSelectedRows();
dataSourcesTableModel.clearTable();
filesTableModel.clearTable();
if (selectedCaseIndexes.length == 0) {
//special case when no cases are selected
occurrencePanel = new OccurrencePanel();
occurrencePanel.getPreferredSize();
detailsPanelScrollPane.setViewportView(occurrencePanel);
} else {
String currentCaseName;
try {
currentCaseName = Case.getCurrentCaseThrows().getName();
} catch (NoCurrentCaseException ex) {
currentCaseName = null;
logger.log(Level.WARNING, "Unable to get current case for other occurrences content viewer", ex);
}
for (CorrelationAttributeInstance corAttr : correlationAttributes) {
Map<UniquePathKey, OtherOccurrenceNodeInstanceData> correlatedNodeDataMap = new HashMap<>(0);
// get correlation and reference set instances from DB
correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr));
for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) {
for (int selectedRow : selectedCaseIndexes) {
try {
if (nodeData.isCentralRepoNode()) {
if (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)) != null
&& casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())) {
return;
}
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
worker = new SelectionWorker(correlationAttributes, file, deviceId, dataSourceName) {
@Override
public void done() {
if (isCancelled()) {
return;
}
try {
Map<UniquePathKey, NodeData> correlatedNodeDataMap = get();
String currentCaseName;
try {
currentCaseName = Case.getCurrentCaseThrows().getName();
} catch (NoCurrentCaseException ex) {
currentCaseName = null;
logger.log(Level.WARNING, "Unable to get current case for other occurrences content viewer", ex);
}
for (NodeData nodeData : correlatedNodeDataMap.values()) {
for (int selectedRow : selectedCaseIndexes) {
try {
if (nodeData.isCentralRepoNode()) {
if (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)) != null
&& casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)).getCaseUUID().equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())) {
dataSourcesTableModel.addNodeData(nodeData);
}
} else if (currentCaseName != null && (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)).getCaseUUID().equals(currentCaseName))) {
dataSourcesTableModel.addNodeData(nodeData);
}
} else if (currentCaseName != null && (casesTableModel.getCorrelationCase(casesTable.convertRowIndexToModel(selectedRow)).getCaseUUID().equals(currentCaseName))) {
dataSourcesTableModel.addNodeData(nodeData);
} catch (CentralRepoException ex) {
logger.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex);
}
} catch (CentralRepoException ex) {
logger.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex);
}
}
if (dataSourcesTable.getRowCount() > 0) {
dataSourcesTable.setRowSelectionInterval(0, 0);
}
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
} catch (InterruptedException | ExecutionException ex) {
logger.log(Level.SEVERE, "Failed to update OtherOccurrencesPanel on data source selection", ex);
}
}
if (dataSourcesTable.getRowCount() > 0) {
dataSourcesTable.setRowSelectionInterval(0, 0);
}
}
};
worker.execute();
}
/**
* Updates diplayed information to be correct for the current data source
* Updates displayed information to be correct for the current data source
* selection
*/
private void updateOnDataSourceSelection() {
int[] selectedDataSources = dataSourcesTable.getSelectedRows();
filesTableModel.clearTable();
for (CorrelationAttributeInstance corAttr : correlationAttributes) {
Map<UniquePathKey, OtherOccurrenceNodeInstanceData> correlatedNodeDataMap = new HashMap<>(0);
if (worker != null) {
worker.cancel(true);
worker = null;
}
// get correlation and reference set instances from DB
correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr));
for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) {
for (int selectedDataSourceRow : selectedDataSources) {
try {
if (nodeData.isCentralRepoNode()) {
if (dataSourcesTableModel.getCaseUUIDForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())
&& dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) {
filesTableModel.addNodeData(nodeData);
}
} else {
if (dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) {
filesTableModel.addNodeData(nodeData);
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
final int[] selectedDataSources = dataSourcesTable.getSelectedRows();
filesTableModel.clearTable();
worker = new SelectionWorker(correlationAttributes, file, deviceId, dataSourceName) {
@Override
public void done() {
if (isCancelled()) {
return;
}
try {
Map<UniquePathKey, NodeData> correlatedNodeDataMap = get();
for (NodeData nodeData : correlatedNodeDataMap.values()) {
for (int selectedDataSourceRow : selectedDataSources) {
try {
if (nodeData.isCentralRepoNode()) {
if (dataSourcesTableModel.getCaseUUIDForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID())
&& dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) {
filesTableModel.addNodeData(nodeData);
}
} else {
if (dataSourcesTableModel.getDeviceIdForRow(dataSourcesTable.convertRowIndexToModel(selectedDataSourceRow)).equals(nodeData.getDeviceID())) {
filesTableModel.addNodeData(nodeData);
}
}
} catch (CentralRepoException ex) {
logger.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex);
}
}
} catch (CentralRepoException ex) {
logger.log(Level.WARNING, "Unable to get correlation attribute instance from OtherOccurrenceNodeInstanceData for case " + nodeData.getCaseName(), ex);
}
if (filesTable.getRowCount() > 0) {
filesTable.setRowSelectionInterval(0, 0);
}
} catch (InterruptedException | ExecutionException ex) {
logger.log(Level.SEVERE, "Failed to update OtherOccurrencesPanel on case selection", ex);
} finally {
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
}
}
if (filesTable.getRowCount() > 0) {
filesTable.setRowSelectionInterval(0, 0);
}
};
worker.execute();
}
/**
@ -738,42 +513,47 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
* currently selected File
*/
private void updateOnFileSelection() {
if (filesTable.getSelectedRowCount() == 1) {
//if there is one file selected update the deatils to show the data for that file
occurrencePanel = new OccurrencePanel(filesTableModel.getListOfNodesForFile(filesTable.convertRowIndexToModel(filesTable.getSelectedRow())));
} else if (dataSourcesTable.getSelectedRowCount() == 1) {
//if no files were selected and only one data source is selected update the information to reflect the data source
String caseName = dataSourcesTableModel.getCaseNameForRow(dataSourcesTable.convertRowIndexToModel(dataSourcesTable.getSelectedRow()));
String dsName = dataSourcesTableModel.getValueAt(dataSourcesTable.convertRowIndexToModel(dataSourcesTable.getSelectedRow()), 0).toString();
String caseCreatedDate = "";
for (int row : casesTable.getSelectedRows()) {
if (casesTableModel.getValueAt(casesTable.convertRowIndexToModel(row), 0).toString().equals(caseName)) {
caseCreatedDate = getCaseCreatedDate(row);
break;
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
if (filesTable.getSelectedRowCount() == 1) {
//if there is one file selected update the deatils to show the data for that file
occurrencePanel = new OccurrencePanel(filesTableModel.getListOfNodesForFile(filesTable.convertRowIndexToModel(filesTable.getSelectedRow())));
} else if (dataSourcesTable.getSelectedRowCount() == 1) {
//if no files were selected and only one data source is selected update the information to reflect the data source
String caseName = dataSourcesTableModel.getCaseNameForRow(dataSourcesTable.convertRowIndexToModel(dataSourcesTable.getSelectedRow()));
String dsName = dataSourcesTableModel.getValueAt(dataSourcesTable.convertRowIndexToModel(dataSourcesTable.getSelectedRow()), 0).toString();
String caseCreatedDate = "";
for (int row : casesTable.getSelectedRows()) {
if (casesTableModel.getValueAt(casesTable.convertRowIndexToModel(row), 0).toString().equals(caseName)) {
caseCreatedDate = getCaseCreatedDate(row);
break;
}
}
occurrencePanel = new OccurrencePanel(caseName, caseCreatedDate, dsName);
} else if (casesTable.getSelectedRowCount() == 1) {
//if no files were selected and a number of data source other than 1 are selected
//update the information to reflect the case
String createdDate;
String caseName = "";
if (casesTable.getRowCount() > 0) {
caseName = casesTableModel.getValueAt(casesTable.convertRowIndexToModel(casesTable.getSelectedRow()), 0).toString();
}
if (caseName.isEmpty()) {
occurrencePanel = new OccurrencePanel();
} else {
createdDate = getCaseCreatedDate(casesTable.getSelectedRow());
occurrencePanel = new OccurrencePanel(caseName, createdDate);
}
}
occurrencePanel = new OccurrencePanel(caseName, caseCreatedDate, dsName);
} else if (casesTable.getSelectedRowCount() == 1) {
//if no files were selected and a number of data source other than 1 are selected
//update the information to reflect the case
String createdDate;
String caseName = "";
if (casesTable.getRowCount() > 0) {
caseName = casesTableModel.getValueAt(casesTable.convertRowIndexToModel(casesTable.getSelectedRow()), 0).toString();
}
if (caseName.isEmpty()) {
occurrencePanel = new OccurrencePanel();
} else {
createdDate = getCaseCreatedDate(casesTable.getSelectedRow());
occurrencePanel = new OccurrencePanel(caseName, createdDate);
//else display an empty details area
occurrencePanel = new OccurrencePanel();
}
} else {
//else display an empty details area
occurrencePanel = new OccurrencePanel();
//calling getPreferredSize has a side effect of ensuring it has a preferred size which reflects the contents which are visible
occurrencePanel.getPreferredSize();
detailsPanelScrollPane.setViewportView(occurrencePanel);
} finally {
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
//calling getPreferredSize has a side effect of ensuring it has a preferred size which reflects the contents which are visible
occurrencePanel.getPreferredSize();
detailsPanelScrollPane.setViewportView(occurrencePanel);
}
/**
@ -802,6 +582,95 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
return "";
}
/**
* SwingWorker used by the case and data source selection handler.
*/
private class SelectionWorker extends SwingWorker<Map<UniquePathKey, NodeData>, Void> {
private final Collection<CorrelationAttributeInstance> coAtInstances;
private final AbstractFile abstractFile;
private final String deviceIdStr;
private final String dataSourceNameStr;
/**
* Construct a new SelectionWorker.
*
* @param coAtInstances
* @param abstractFile
* @param deviceIdStr
* @param dataSourceNameStr
*/
SelectionWorker(Collection<CorrelationAttributeInstance> coAtInstances, AbstractFile abstractFile, String deviceIdStr, String dataSourceNameStr) {
this.coAtInstances = coAtInstances;
this.abstractFile = abstractFile;
this.dataSourceNameStr = dataSourceNameStr;
this.deviceIdStr = deviceIdStr;
}
@Override
protected Map<UniquePathKey, NodeData> doInBackground() throws Exception {
Map<UniquePathKey, NodeData> correlatedNodeDataMap = new HashMap<>();
for (CorrelationAttributeInstance corAttr : coAtInstances) {
correlatedNodeDataMap.putAll(OtherOccurrences.getCorrelatedInstances(abstractFile, deviceIdStr, dataSourceNameStr, corAttr));
if(isCancelled()) {
return new HashMap<>();
}
}
return correlatedNodeDataMap;
}
}
/**
* SwingWorker for creating the CSV dump file.
*/
private class CSVWorker extends SwingWorker<Void, Void> {
private final Collection<CorrelationAttributeInstance> correlationAttList;
private final String dataSourceName;
private final String deviceId;
private final File destFile;
private final AbstractFile abstractFile;
/**
* Construct a CSVWorker
*
* @param destFile Output file.
* @param sourceFile Input file.
* @param dataSourceName Name of current dataSource.
* @param deviceId Id of the selected device.
* @param correlationAttList
*/
CSVWorker(File destFile, AbstractFile sourceFile, String dataSourceName, String deviceId, Collection<CorrelationAttributeInstance> correlationAttList) {
this.destFile = destFile;
this.abstractFile = sourceFile;
this.dataSourceName = dataSourceName;
this.deviceId = deviceId;
this.correlationAttList = correlationAttList;
}
@Override
protected Void doInBackground() throws Exception {
OtherOccurrences.writeOtherOccurrencesToFileAsCSV(this.destFile, this.abstractFile, this.correlationAttList, this.dataSourceName, this.deviceId);
return null;
}
@Override
public void done() {
try {
get();
} catch (InterruptedException | ExecutionException ex) {
JOptionPane.showMessageDialog(OtherOccurrencesPanel.this,
"Failed to create csv file for Other Occurrences at\n" + destFile.getAbsolutePath(),
"Error Creating CSV",
JOptionPane.ERROR_MESSAGE);
logger.log(Level.SEVERE, "Error writing selected rows to from OtherOccurrencePanel to " + destFile.getAbsolutePath(), ex);
}
}
}
/**
* 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
@ -976,9 +845,9 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
boolean enableCentralRepoActions = false;
if (CentralRepository.isEnabled() && filesTable.getSelectedRowCount() == 1) {
int rowIndex = filesTable.getSelectedRow();
List<OtherOccurrenceNodeData> selectedFile = filesTableModel.getListOfNodesForFile(rowIndex);
if (!selectedFile.isEmpty() && selectedFile.get(0) instanceof OtherOccurrenceNodeInstanceData) {
OtherOccurrenceNodeInstanceData instanceData = (OtherOccurrenceNodeInstanceData) selectedFile.get(0);
List<NodeData> selectedFile = filesTableModel.getListOfNodesForFile(rowIndex);
if (!selectedFile.isEmpty() && selectedFile.get(0) instanceof NodeData) {
NodeData instanceData = selectedFile.get(0);
enableCentralRepoActions = instanceData.isCentralRepoNode();
}
}

View File

@ -21,21 +21,25 @@
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="jScrollPane5" alignment="0" pref="907" max="32767" attributes="0"/>
<Component id="scrollPane" 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"/>
<Component id="scrollPane" alignment="1" pref="435" max="32767" attributes="0"/>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Container class="javax.swing.JScrollPane" name="jScrollPane5">
<Container class="javax.swing.JScrollPane" name="scrollPane">
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTextPane" name="jTextPane1">
<Component class="javax.swing.JTextPane" name="textPanel">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="name" type="java.lang.String" value="" noResource="true"/>

View File

@ -19,43 +19,23 @@
package org.sleuthkit.autopsy.contentviewers;
import java.awt.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.swing.JLabel;
import javax.swing.SwingWorker;
import javax.swing.text.EditorKit;
import javax.swing.text.html.HTMLEditorKit;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import static org.openide.util.NbBundle.Messages;
import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
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.CorrelationAttributeUtil;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
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;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.jsoup.Jsoup;
import org.sleuthkit.autopsy.contentviewers.application.Annotations;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
/**
* Annotations view of file contents.
@ -65,90 +45,10 @@ import org.jsoup.nodes.Element;
@Messages({
"AnnotationsContentViewer.title=Annotations",
"AnnotationsContentViewer.toolTip=Displays tags and comments associated with the selected content.",
"AnnotationsContentViewer.centralRepositoryEntry.title=Central Repository Comments",
"AnnotationsContentViewer.centralRepositoryEntryDataLabel.case=Case:",
"AnnotationsContentViewer.centralRepositoryEntryDataLabel.type=Type:",
"AnnotationsContentViewer.centralRepositoryEntryDataLabel.comment=Comment:",
"AnnotationsContentViewer.centralRepositoryEntryDataLabel.path=Path:",
"AnnotationsContentViewer.tagEntry.title=Tags",
"AnnotationsContentViewer.tagEntryDataLabel.tag=Tag:",
"AnnotationsContentViewer.tagEntryDataLabel.tagUser=Examiner:",
"AnnotationsContentViewer.tagEntryDataLabel.comment=Comment:",
"AnnotationsContentViewer.fileHitEntry.artifactCommentTitle=Artifact Comment",
"AnnotationsContentViewer.fileHitEntry.hashSetHitTitle=Hash Set Hit Comments",
"AnnotationsContentViewer.fileHitEntry.interestingFileHitTitle=Interesting File Hit Comments",
"AnnotationsContentViewer.fileHitEntry.setName=Set Name:",
"AnnotationsContentViewer.fileHitEntry.comment=Comment:",
"AnnotationsContentViewer.sourceFile.title=Source File",
"AnnotationsContentViewer.onEmpty=No annotations were found for this particular item."
})
public class AnnotationsContentViewer extends javax.swing.JPanel implements DataContentViewer {
/**
* Describes a key value pair for an item of type T where the key is the
* field name to display and the value is retrieved from item of type T
* using a provided Function<T, string>.
*
* @param <T> The item type.
*/
private static class ItemEntry<T> {
private final String itemName;
private final Function<T, String> valueRetriever;
ItemEntry(String itemName, Function<T, String> valueRetriever) {
this.itemName = itemName;
this.valueRetriever = valueRetriever;
}
String getItemName() {
return itemName;
}
Function<T, String> getValueRetriever() {
return valueRetriever;
}
String retrieveValue(T object) {
return valueRetriever.apply(object);
}
}
/**
* Describes a section that will be appended to the annotations view panel.
*
* @param <T> The item type for items to display.
*/
private static class SectionConfig<T> {
private final String title;
private final List<ItemEntry<T>> attributes;
SectionConfig(String title, List<ItemEntry<T>> attributes) {
this.title = title;
this.attributes = attributes;
}
/**
* @return The title for the section.
*/
String getTitle() {
return title;
}
/**
* @return Describes key-value pairs on the object to display to the
* user.
*/
List<ItemEntry<T>> getAttributes() {
return attributes;
}
}
private static final Logger logger = Logger.getLogger(AnnotationsContentViewer.class.getName());
private static final String EMPTY_HTML = "<html><head></head><body></body></html>";
private static final int DEFAULT_FONT_SIZE = new JLabel().getFont().getSize();
// how big the subheader should be
@ -161,77 +61,34 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
private static final int DEFAULT_SUBSECTION_LEFT_PAD = DEFAULT_FONT_SIZE;
// spacing occurring after an item
private static final int DEFAULT_TABLE_SPACING = DEFAULT_FONT_SIZE;
private static final int DEFAULT_SECTION_SPACING = DEFAULT_FONT_SIZE * 2;
private static final int DEFAULT_SUBSECTION_SPACING = DEFAULT_FONT_SIZE / 2;
private static final int CELL_SPACING = DEFAULT_FONT_SIZE / 2;
// html stylesheet classnames for components
private static final String MESSAGE_CLASSNAME = "message";
private static final String SUBSECTION_CLASSNAME = "subsection";
private static final String SUBHEADER_CLASSNAME = "subheader";
private static final String SECTION_CLASSNAME = "section";
private static final String HEADER_CLASSNAME = "header";
private static final String VERTICAL_TABLE_CLASSNAME = "vertical-table";
// additional styling for components
private static final String STYLE_SHEET_RULE
= String.format(" .%s { font-size: %dpx;font-style:italic; margin: 0px; padding: 0px; } ", MESSAGE_CLASSNAME, DEFAULT_FONT_SIZE)
= String.format(" .%s { font-size: %dpx;font-style:italic; margin: 0px; padding: 0px; } ", Annotations.MESSAGE_CLASSNAME, DEFAULT_FONT_SIZE)
+ String.format(" .%s {font-size:%dpx;font-weight:bold; margin: 0px; margin-top: %dpx; padding: 0px; } ",
SUBHEADER_CLASSNAME, SUBHEADER_FONT_SIZE, DEFAULT_SUBSECTION_SPACING)
+ String.format(" .%s { font-size:%dpx;font-weight:bold; margin: 0px; padding: 0px; } ", HEADER_CLASSNAME, HEADER_FONT_SIZE)
Annotations.SUBHEADER_CLASSNAME, SUBHEADER_FONT_SIZE, DEFAULT_SUBSECTION_SPACING)
+ String.format(" .%s { font-size:%dpx;font-weight:bold; margin: 0px; padding: 0px; } ", Annotations.HEADER_CLASSNAME, HEADER_FONT_SIZE)
+ String.format(" td { vertical-align: top; font-size:%dpx; text-align: left; margin: 0px; padding: 0px %dpx 0px 0px;} ", DEFAULT_FONT_SIZE, CELL_SPACING)
+ String.format(" th { vertical-align: top; text-align: left; margin: 0px; padding: 0px %dpx 0px 0px} ", DEFAULT_FONT_SIZE, CELL_SPACING)
+ String.format(" .%s { margin: %dpx 0px; padding-left: %dpx; } ", SUBSECTION_CLASSNAME, DEFAULT_SUBSECTION_SPACING, DEFAULT_SUBSECTION_LEFT_PAD)
+ String.format(" .%s { margin-bottom: %dpx; } ", SECTION_CLASSNAME, DEFAULT_SECTION_SPACING);
// describing table values for a tag
private static final List<ItemEntry<Tag>> TAG_ENTRIES = Arrays.asList(
new ItemEntry<>(Bundle.AnnotationsContentViewer_tagEntryDataLabel_tag(),
(tag) -> (tag.getName() != null) ? tag.getName().getDisplayName() : null),
new ItemEntry<>(Bundle.AnnotationsContentViewer_tagEntryDataLabel_tagUser(), (tag) -> tag.getUserName()),
new ItemEntry<>(Bundle.AnnotationsContentViewer_tagEntryDataLabel_comment(), (tag) -> tag.getComment())
);
private static final SectionConfig<Tag> TAG_CONFIG
= new SectionConfig<>(Bundle.AnnotationsContentViewer_tagEntry_title(), TAG_ENTRIES);
// file set attributes and table configurations
private static final List<ItemEntry<BlackboardArtifact>> FILESET_HIT_ENTRIES = Arrays.asList(
new ItemEntry<>(Bundle.AnnotationsContentViewer_fileHitEntry_setName(),
(bba) -> tryGetAttribute(bba, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME)),
new ItemEntry<>(Bundle.AnnotationsContentViewer_fileHitEntry_comment(),
(bba) -> tryGetAttribute(bba, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT))
);
private static final SectionConfig<BlackboardArtifact> INTERESTING_FILE_CONFIG
= new SectionConfig<>(Bundle.AnnotationsContentViewer_fileHitEntry_interestingFileHitTitle(), FILESET_HIT_ENTRIES);
private static final SectionConfig<BlackboardArtifact> HASHSET_CONFIG
= new SectionConfig<>(Bundle.AnnotationsContentViewer_fileHitEntry_hashSetHitTitle(), FILESET_HIT_ENTRIES);
private static final SectionConfig<BlackboardArtifact> ARTIFACT_COMMENT_CONFIG
= new SectionConfig<>(Bundle.AnnotationsContentViewer_fileHitEntry_artifactCommentTitle(), FILESET_HIT_ENTRIES);
// central repository attributes and table configuration
private static final List<ItemEntry<CorrelationAttributeInstance>> CR_COMMENTS_ENTRIES = Arrays.asList(
new ItemEntry<>(Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_case(),
cai -> (cai.getCorrelationCase() != null) ? cai.getCorrelationCase().getDisplayName() : null),
new ItemEntry<>(Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_comment(), cai -> cai.getComment()),
new ItemEntry<>(Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_path(), cai -> cai.getFilePath())
);
private static final SectionConfig<CorrelationAttributeInstance> CR_COMMENTS_CONFIG
= new SectionConfig<>(Bundle.AnnotationsContentViewer_centralRepositoryEntry_title(), CR_COMMENTS_ENTRIES);
+ String.format(" .%s { margin: %dpx 0px; padding-left: %dpx; } ", Annotations.SUBSECTION_CLASSNAME, DEFAULT_SUBSECTION_SPACING, DEFAULT_SUBSECTION_LEFT_PAD)
+ String.format(" .%s { margin-bottom: %dpx; } ", Annotations.SECTION_CLASSNAME, DEFAULT_SECTION_SPACING);
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(AnnotationsContentViewer.class.getName());
private AnnotationWorker worker;
/**
* Creates an instance of AnnotationsContentViewer.
*/
public AnnotationsContentViewer() {
initComponents();
Utilities.configureTextPaneAsHtml(jTextPane1);
Utilities.configureTextPaneAsHtml(textPanel);
// get html editor kit and apply additional style rules
EditorKit editorKit = jTextPane1.getEditorKit();
EditorKit editorKit = textPanel.getEditorKit();
if (editorKit instanceof HTMLEditorKit) {
HTMLEditorKit htmlKit = (HTMLEditorKit) editorKit;
htmlKit.getStyleSheet().addRule(STYLE_SHEET_RULE);
@ -240,472 +97,21 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
@Override
public void setNode(Node node) {
if ((node == null) || (!isSupported(node))) {
resetComponent();
resetComponent();
if(worker != null) {
worker.cancel(true);
worker = null;
}
if(node == null) {
return;
}
Document html = Jsoup.parse(EMPTY_HTML);
Element body = html.getElementsByTag("body").first();
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);
}
boolean somethingWasRendered = false;
if (artifact != null) {
somethingWasRendered = renderArtifact(body, artifact, sourceFile);
} else {
somethingWasRendered = renderContent(body, sourceFile, false);
}
if (!somethingWasRendered) {
appendMessage(body, Bundle.AnnotationsContentViewer_onEmpty());
}
jTextPane1.setText(html.html());
jTextPane1.setCaretPosition(0);
worker = new AnnotationWorker(node);
worker.execute();
}
/**
* Renders annotations for an artifact.
*
* @param parent The html element to render content int.
* @param bba The blackboard artifact to render.
* @param sourceContent The content from which the blackboard artifact
* comes.
*
* @return If any content was actually rendered.
*/
private static boolean renderArtifact(Element parent, BlackboardArtifact bba, Content sourceContent) {
boolean contentRendered = appendEntries(parent, TAG_CONFIG, getTags(bba), false);
if (CentralRepository.isEnabled()) {
List<CorrelationAttributeInstance> centralRepoComments = getCentralRepositoryData(bba);
boolean crRendered = appendEntries(parent, CR_COMMENTS_CONFIG, centralRepoComments, false);
contentRendered = contentRendered || crRendered;
}
// if artifact is a hashset hit or interesting file and has a non-blank comment
if ((ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID() == bba.getArtifactTypeID()
|| ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID() == bba.getArtifactTypeID())
&& (hasTskComment(bba))) {
boolean filesetRendered = appendEntries(parent, ARTIFACT_COMMENT_CONFIG, Arrays.asList(bba), false);
contentRendered = contentRendered || filesetRendered;
}
Element sourceFileSection = appendSection(parent, Bundle.AnnotationsContentViewer_sourceFile_title());
boolean sourceFileRendered = renderContent(sourceFileSection, sourceContent, true);
if (!sourceFileRendered) {
sourceFileSection.remove();
}
return contentRendered || sourceFileRendered;
}
/**
* Renders annotations for a content item.
*
* @param parent The parent within which to render.
* @param sourceContent The content for which annotations will be gathered.
* @param isSubheader True if this section should be rendered as a
* subheader as opposed to a top-level header.
*
* @return If any content was actually rendered.
*/
private static boolean renderContent(Element parent, Content sourceContent, boolean isSubheader) {
boolean contentRendered = appendEntries(parent, TAG_CONFIG, getTags(sourceContent), isSubheader);
if (sourceContent instanceof AbstractFile) {
AbstractFile sourceFile = (AbstractFile) sourceContent;
if (CentralRepository.isEnabled()) {
List<CorrelationAttributeInstance> centralRepoComments = getCentralRepositoryData(sourceFile);
boolean crRendered = appendEntries(parent, CR_COMMENTS_CONFIG, centralRepoComments, isSubheader);
contentRendered = contentRendered || crRendered;
}
boolean hashsetRendered = appendEntries(parent, HASHSET_CONFIG,
getFileSetHits(sourceFile, ARTIFACT_TYPE.TSK_HASHSET_HIT),
isSubheader);
boolean interestingFileRendered = appendEntries(parent, INTERESTING_FILE_CONFIG,
getFileSetHits(sourceFile, ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT),
isSubheader);
contentRendered = contentRendered || hashsetRendered || interestingFileRendered;
}
return contentRendered;
}
/**
* Retrieves tags associated with a content item.
*
* @param sourceContent The content for which to gather content.
*
* @return The Tags associated with this item.
*/
private static List<ContentTag> getTags(Content sourceContent) {
try {
SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
return tskCase.getContentTagsByContent(sourceContent);
} 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
}
return new ArrayList<>();
}
/**
* Retrieves tags for blackboard artifact tags.
*
* @param bba The blackboard artifact for which to retrieve tags.
*
* @return The found tags.
*/
private static List<BlackboardArtifactTag> getTags(BlackboardArtifact bba) {
try {
SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
return tskCase.getBlackboardArtifactTagsByArtifact(bba);
} 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
}
return new ArrayList<>();
}
/**
* Retrieves the blackboard artifacts for a source file matching a certain
* type that have a non-blank TSK_COMMENT.
*
* @param sourceFile The source file for which to fetch artifacts.
* @param type The type of blackboard artifact to fetch.
*
* @return The artifacts found matching this type.
*/
private static List<BlackboardArtifact> getFileSetHits(AbstractFile sourceFile, ARTIFACT_TYPE type) {
try {
SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
return tskCase.getBlackboardArtifacts(type, sourceFile.getId()).stream()
.filter((bba) -> hasTskComment(bba))
.collect(Collectors.toList());
} 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 file set hits from the case database.", ex); //NON-NLS
}
return new ArrayList<>();
}
/**
* Returns true if the artifact contains a non-blank TSK_COMMENT attribute.
*
* @param artifact The artifact to check.
*
* @return True if it has a non-blank TSK_COMMENT.
*/
private static boolean hasTskComment(BlackboardArtifact artifact) {
return StringUtils.isNotBlank(tryGetAttribute(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT));
}
/**
* Attempts to retrieve the attribute of a particular type from a blackboard
* artifact.
*
* @param artifact The artifact from which to retrieve the information.
* @param attributeType The attribute type to retrieve from the artifact.
*
* @return The string value of the attribute or null if not found.
*/
private static String tryGetAttribute(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) {
if (artifact == null) {
return null;
}
BlackboardAttribute attr = null;
try {
attr = artifact.getAttribute(new BlackboardAttribute.Type(attributeType));
} catch (TskCoreException ex) {
logger.log(Level.WARNING, String.format("Unable to fetch attribute of type %s for artifact %s", attributeType, artifact), ex);
}
if (attr == null) {
return null;
}
return attr.getValueString();
}
/**
* Gets the "Central Repository Comments" section with data for the
* blackboard artifact.
*
* @param artifact The selected artifact.
*
* @return The Correlation Attribute Instances associated with the artifact
* that have comments.
*/
private static List<CorrelationAttributeInstance> getCentralRepositoryData(BlackboardArtifact artifact) {
if (artifact == null) {
return new ArrayList<>();
}
List<Pair<CorrelationAttributeInstance.Type, String>> lookupKeys = CorrelationAttributeUtil.makeCorrAttrsForCorrelation(artifact)
.stream()
.map(cai -> Pair.of(cai.getCorrelationType(), cai.getCorrelationValue()))
.collect(Collectors.toList());
return getCorrelationAttributeComments(lookupKeys);
}
/**
* Gets the "Central Repository Comments" section with data.
*
* @param sourceFile A selected file, or a source file of the selected
* artifact.
*
* @return The Correlation Attribute Instances associated with the
* sourcefile that have comments.
*/
private static List<CorrelationAttributeInstance> getCentralRepositoryData(AbstractFile sourceFile) {
if (sourceFile == null || StringUtils.isEmpty(sourceFile.getMd5Hash())) {
return new ArrayList<>();
}
List<CorrelationAttributeInstance.Type> artifactTypes = null;
try {
artifactTypes = CentralRepository.getInstance().getDefinedCorrelationTypes();
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, "Error connecting to the Central Repository database.", ex); // NON-NLS
}
if (artifactTypes == null || artifactTypes.isEmpty()) {
return new ArrayList<>();
}
String md5 = sourceFile.getMd5Hash();
// get key lookups for a file attribute types and the md5 hash
List<Pair<CorrelationAttributeInstance.Type, String>> lookupKeys = artifactTypes.stream()
.filter((attributeType) -> attributeType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID)
.map((attributeType) -> Pair.of(attributeType, md5))
.collect(Collectors.toList());
return getCorrelationAttributeComments(lookupKeys);
}
/**
* Given a type and a value for that type, does a lookup in the Central
* Repository for matching values that have comments.
*
* @param lookupKeys The type and value to lookup.
*
* @return The found correlation attribute instances.
*/
private static List<CorrelationAttributeInstance> getCorrelationAttributeComments(List<Pair<CorrelationAttributeInstance.Type, String>> lookupKeys) {
List<CorrelationAttributeInstance> instancesToRet = new ArrayList<>();
try {
// use lookup instances to find the actual correlation attributes for the items selected
for (Pair<CorrelationAttributeInstance.Type, String> typeVal : lookupKeys) {
instancesToRet.addAll(CentralRepository.getInstance()
.getArtifactInstancesByTypeValue(typeVal.getKey(), typeVal.getValue())
.stream()
// for each one found, if it has a comment, return
.filter((cai) -> StringUtils.isNotBlank(cai.getComment()))
.collect(Collectors.toList()));
}
} catch (CentralRepoException 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 Central Repository database.", ex); // NON-NLS
}
return instancesToRet;
}
/**
* Append entries to the parent element in the annotations viewer. Entries
* will be formatted as a table in the format specified in the
* SectionConfig.
*
* @param parent The parent element for which the entries will be
* attached.
* @param config The display configuration for this entry type (i.e.
* table type, name, if data is not present).
* @param items The items to display.
* @param isSubsection Whether or not this should be displayed as a
* subsection. If not displayed as a top-level section.
*
* @return If there was actual content rendered for this set of entries.
*/
private static <T> boolean appendEntries(Element parent, SectionConfig<T> config, List<? extends T> items,
boolean isSubsection) {
if (items == null || items.isEmpty()) {
return false;
}
Element sectionDiv = (isSubsection) ? appendSubsection(parent, config.getTitle()) : appendSection(parent, config.getTitle());
appendVerticalEntryTables(sectionDiv, items, config.getAttributes());
return true;
}
/**
* Appends a table where items are displayed in rows of key-value pairs.
*
* @param parent The parent to append the table.
* @param items The items to process into a series of tables.
* @param rowHeaders The keys and the means to process items in order to get
* key-value pairs.
*
* @return The parent element provided as parameter.
*/
private static <T> Element appendVerticalEntryTables(Element parent, List<? extends T> items, List<ItemEntry<T>> rowHeaders) {
boolean isFirst = true;
for (T item : items) {
if (item == null) {
continue;
}
List<List<String>> tableData = rowHeaders.stream()
.map(row -> Arrays.asList(row.getItemName(), row.retrieveValue(item)))
.collect(Collectors.toList());
Element childTable = appendTable(parent, 2, tableData, null);
childTable.attr("class", VERTICAL_TABLE_CLASSNAME);
if (isFirst) {
isFirst = false;
} else {
childTable.attr("style", String.format("margin-top: %dpx;", DEFAULT_TABLE_SPACING));
}
}
return parent;
}
/**
* Appends a generic table to the parent element.
*
* @param parent The parent element that will have a table appended
* to it.
* @param columnNumber The number of columns to append.
* @param content The content in content.get(row).get(column) format.
* @param columnHeaders The column headers or null if no column headers
* should be created.
*
* @return The created table.
*/
private static Element appendTable(Element parent, int columnNumber, List<List<String>> content, List<String> columnHeaders) {
Element table = parent.appendElement("table");
if (columnHeaders != null && !columnHeaders.isEmpty()) {
Element header = table.appendElement("thead");
appendRow(header, columnHeaders, columnNumber, true);
}
Element tableBody = table.appendElement("tbody");
content.forEach((rowData) -> appendRow(tableBody, rowData, columnNumber, false));
return table;
}
/**
* Appends a row to the parent element (should be thead or tbody).
*
* @param rowParent The parent table element.
* @param data The data to place in columns within the table.
* @param columnNumber The number of columns to append.
* @param isHeader Whether or not this should have header cells ('th')
* instead of regular cells ('td').
*
* @return The row created.
*/
private static Element appendRow(Element rowParent, List<String> data, int columnNumber, boolean isHeader) {
String cellType = isHeader ? "th" : "td";
Element row = rowParent.appendElement("tr");
for (int i = 0; i < columnNumber; i++) {
Element cell = row.appendElement(cellType);
if (data != null && i < data.size()) {
cell.text(StringUtils.isEmpty(data.get(i)) ? "" : data.get(i));
}
}
return row;
}
/**
* Appends a new section with a section header to the parent element.
*
* @param parent The element to append this section to.
* @param headerText The text for the section.
*
* @return The div for the new section.
*/
private static Element appendSection(Element parent, String headerText) {
Element sectionDiv = parent.appendElement("div");
sectionDiv.attr("class", SECTION_CLASSNAME);
Element header = sectionDiv.appendElement("h1");
header.text(headerText);
header.attr("class", HEADER_CLASSNAME);
return sectionDiv;
}
/**
* Appends a new subsection with a subsection header to the parent element.
*
* @param parent The element to append this subsection to.
* @param headerText The text for the subsection.
*
* @return The div for the new subsection.
*/
private static Element appendSubsection(Element parent, String headerText) {
Element subsectionDiv = parent.appendElement("div");
subsectionDiv.attr("class", SUBSECTION_CLASSNAME);
Element header = subsectionDiv.appendElement("h2");
header.text(headerText);
header.attr("class", SUBHEADER_CLASSNAME);
return subsectionDiv;
}
/**
* Appends a message to the parent element. This is typically used in the
* event that no data exists for a certain type.
*
* @param parent The parent element that will have this message appended to
* it.
* @param message The message to append.
*
* @return The paragraph element for the new message.
*/
private static Element appendMessage(Element parent, String message) {
Element messageEl = parent.appendElement("p");
messageEl.text(message);
messageEl.attr("class", MESSAGE_CLASSNAME);
return messageEl;
}
/**
* This method is called from within the constructor to initialize the form.
@ -716,31 +122,30 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
jScrollPane5 = new javax.swing.JScrollPane();
jTextPane1 = new javax.swing.JTextPane();
javax.swing.JScrollPane scrollPane = new javax.swing.JScrollPane();
textPanel = 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);
textPanel.setEditable(false);
textPanel.setName(""); // NOI18N
textPanel.setPreferredSize(new java.awt.Dimension(600, 52));
scrollPane.setViewportView(textPanel);
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)
.addComponent(scrollPane, 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)
.addComponent(scrollPane, 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;
private javax.swing.JTextPane textPanel;
// End of variables declaration//GEN-END:variables
@Override
@ -793,6 +198,49 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
@Override
public void resetComponent() {
jTextPane1.setText(EMPTY_HTML);
textPanel.setText("");
}
/**
* A SwingWorker that will fetch the annotation information for the given
* node.
*/
private class AnnotationWorker extends SwingWorker<String, Void> {
private final Node node;
AnnotationWorker(Node node) {
this.node = node;
}
@Override
protected String doInBackground() throws Exception {
Document doc = Annotations.buildDocument(node);
if(isCancelled()) {
return null;
}
if(doc != null) {
return doc.html();
} else {
return Bundle.AnnotationsContentViewer_onEmpty();
}
}
@Override
public void done() {
if (isCancelled()) {
return;
}
try {
String text = get();
textPanel.setText(text);
textPanel.setCaretPosition(0);
} catch (InterruptedException | ExecutionException ex) {
logger.log(Level.SEVERE, "Failed to get annotation information for node", ex);
}
}
}
}

View File

@ -15,22 +15,7 @@
# governing permissions and limitations under the License.
#
AnnotationsContentViewer.centralRepositoryEntry.title=Central Repository Comments
AnnotationsContentViewer.centralRepositoryEntryDataLabel.case=Case:
AnnotationsContentViewer.centralRepositoryEntryDataLabel.comment=Comment:
AnnotationsContentViewer.centralRepositoryEntryDataLabel.path=Path:
AnnotationsContentViewer.centralRepositoryEntryDataLabel.type=Type:
AnnotationsContentViewer.fileHitEntry.artifactCommentTitle=Artifact Comment
AnnotationsContentViewer.fileHitEntry.comment=Comment:
AnnotationsContentViewer.fileHitEntry.hashSetHitTitle=Hash Set Hit Comments
AnnotationsContentViewer.fileHitEntry.interestingFileHitTitle=Interesting File Hit Comments
AnnotationsContentViewer.fileHitEntry.setName=Set Name:
AnnotationsContentViewer.onEmpty=No annotations were found for this particular item.
AnnotationsContentViewer.sourceFile.title=Source File
AnnotationsContentViewer.tagEntry.title=Tags
AnnotationsContentViewer.tagEntryDataLabel.comment=Comment:
AnnotationsContentViewer.tagEntryDataLabel.tag=Tag:
AnnotationsContentViewer.tagEntryDataLabel.tagUser=Examiner:
AnnotationsContentViewer.title=Annotations
AnnotationsContentViewer.toolTip=Displays tags and comments associated with the selected content.
ApplicationContentViewer.title=Application
@ -85,6 +70,7 @@ MediaViewVideoPanel.progressLabel.text=00:00
MediaViewVideoPanel.infoLabel.text=info
MediaViewImagePanel.imgFileTooLarge.msg=Could not load image file (too large): {0}
Metadata.nodeText.loading=Metadata loading...
Metadata.nodeText.none=None
Metadata.nodeText.truncated=(results truncated)
Metadata.nodeText.unknown=Unknown

View File

@ -19,10 +19,14 @@
package org.sleuthkit.autopsy.contentviewers;
import java.awt.Component;
import java.awt.Cursor;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import javax.swing.SwingWorker;
import org.apache.commons.lang3.StringUtils;
import org.openide.nodes.Node;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.ServiceProvider;
@ -50,7 +54,9 @@ import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
public class Metadata extends javax.swing.JPanel implements DataContentViewer {
private static final Logger LOGGER = Logger.getLogger(Metadata.class.getName());
private MetaDataWorker worker;
/**
* Creates new form Metadata
*/
@ -145,183 +151,64 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
"Metadata.tableRowTitle.acquisitionDetails=Acquisition Details",
"Metadata.tableRowTitle.downloadSource=Downloaded From",
"Metadata.nodeText.unknown=Unknown",
"Metadata.nodeText.none=None"})
"Metadata.nodeText.none=None",
"Metadata.nodeText.loading=Metadata loading..."})
@Override
public void setNode(Node node) {
AbstractFile file = node.getLookup().lookup(AbstractFile.class);
Image image = node.getLookup().lookup(Image.class);
DataSource dataSource = node.getLookup().lookup(DataSource.class);
if (file == null && image == null) {
setText(NbBundle.getMessage(this.getClass(), "Metadata.nodeText.nonFilePassedIn"));
return;
if (worker != null) {
worker.cancel(true);
worker = null;
}
StringBuilder sb = new StringBuilder();
startTable(sb);
if (file != null) {
try {
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.name"), file.getUniquePath());
} catch (TskCoreException ex) {
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.name"), file.getParentPath() + "/" + file.getName());
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.type"), file.getType().getName());
addRow(sb, Bundle.Metadata_tableRowTitle_mimeType(), file.getMIMEType());
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.size"), Long.toString(file.getSize()));
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.fileNameAlloc"), file.getDirFlagAsString());
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.metadataAlloc"), file.getMetaFlagsAsString());
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.modified"), ContentUtils.getStringTime(file.getMtime(), file));
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.accessed"), ContentUtils.getStringTime(file.getAtime(), file));
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.created"), ContentUtils.getStringTime(file.getCrtime(), file));
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.changed"), ContentUtils.getStringTime(file.getCtime(), file));
String md5 = file.getMd5Hash();
if (md5 == null) {
md5 = NbBundle.getMessage(this.getClass(), "Metadata.tableRowContent.md5notCalc");
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.md5"), md5);
String sha256 = file.getSha256Hash();
if (sha256 == null) {
sha256 = NbBundle.getMessage(this.getClass(), "Metadata.tableRowContent.md5notCalc");
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.sha256"), sha256);
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.hashLookupResults"), file.getKnown().toString());
addAcquisitionDetails(sb, dataSource);
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.internalid"), Long.toString(file.getId()));
if (file.getType().compareTo(TSK_DB_FILES_TYPE_ENUM.LOCAL) == 0) {
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.localPath"), file.getLocalAbsPath());
}
try {
List<BlackboardArtifact> associatedObjectArtifacts = file.getArtifacts(ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT);
if (!associatedObjectArtifacts.isEmpty()) {
BlackboardArtifact artifact = associatedObjectArtifacts.get(0);
BlackboardAttribute associatedArtifactAttribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
if (associatedArtifactAttribute != null) {
long artifactId = associatedArtifactAttribute.getValueLong();
BlackboardArtifact associatedArtifact = artifact.getSleuthkitCase().getBlackboardArtifact(artifactId);
addDownloadSourceRow(sb, associatedArtifact);
}
}
} catch (TskCoreException ex) {
sb.append(NbBundle.getMessage(this.getClass(), "Metadata.nodeText.exceptionNotice.text")).append(ex.getLocalizedMessage());
}
endTable(sb);
/*
* If we have a file system file, grab the more detailed metadata text
* too
*/
try {
if (file instanceof FsContent) {
FsContent fsFile = (FsContent) file;
sb.append("<hr /><pre>\n"); //NON-NLS
sb.append(NbBundle.getMessage(this.getClass(), "Metadata.nodeText.text"));
sb.append(" <br /><br />"); // NON-NLS
for (String str : fsFile.getMetaDataText()) {
sb.append(str).append("<br />"); //NON-NLS
/*
* Very long results can cause the UI to hang before displaying,
* so truncate the results if necessary.
*/
if(sb.length() > 50000){
sb.append(NbBundle.getMessage(this.getClass(), "Metadata.nodeText.truncated"));
break;
if (node != null) {
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
setText(Bundle.Metadata_nodeText_loading());
worker = new MetaDataWorker(node) {
@Override
public void done() {
try {
if (!isCancelled()) {
setText(get());
jTextPane1.setCaretPosition(0);
}
} catch (InterruptedException | ExecutionException ex) {
LOGGER.log(Level.SEVERE, "Failed to get metaData for node " + node.getName(), ex);
}
sb.append("</pre>\n"); //NON-NLS
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
} catch (TskCoreException ex) {
sb.append(NbBundle.getMessage(this.getClass(), "Metadata.nodeText.exceptionNotice.text")).append(ex.getLocalizedMessage());
}
};
worker.execute();
} else {
try {
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.name"), image.getUniquePath());
} catch (TskCoreException ex) {
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.name"), image.getName());
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.imageType"), image.getType().getName());
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.size"), Long.toString(image.getSize()));
try {
String md5 = image.getMd5();
if (md5 == null || md5.isEmpty()) {
md5 = NbBundle.getMessage(this.getClass(), "Metadata.tableRowContent.md5notCalc");
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.md5"), md5);
String sha1 = image.getSha1();
if (sha1 == null || sha1.isEmpty()) {
sha1 = NbBundle.getMessage(this.getClass(), "Metadata.tableRowContent.md5notCalc");
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.sha1"), sha1);
String sha256 = image.getSha256();
if (sha256 == null || sha256.isEmpty()) {
sha256 = NbBundle.getMessage(this.getClass(), "Metadata.tableRowContent.md5notCalc");
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.sha256"), sha256);
} catch (TskCoreException ex) {
sb.append(NbBundle.getMessage(this.getClass(), "Metadata.nodeText.exceptionNotice.text")).append(ex.getLocalizedMessage());
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.sectorSize"), Long.toString(image.getSsize()));
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.timezone"), image.getTimeZone());
addAcquisitionDetails(sb, dataSource);
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.deviceId"), image.getDeviceId());
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.internalid"), Long.toString(image.getId()));
// Add all the data source paths to the "Local Path" value cell.
String[] imagePaths = image.getPaths();
if (imagePaths.length > 0) {
StringBuilder pathValues = new StringBuilder("<div>");
pathValues.append(imagePaths[0]);
pathValues.append("</div>");
for (int i=1; i < imagePaths.length; i++) {
pathValues.append("<div>");
pathValues.append(imagePaths[i]);
pathValues.append("</div>");
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.localPath"), pathValues.toString());
} else {
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.localPath"),
NbBundle.getMessage(this.getClass(), "Metadata.nodeText.none"));
}
setText("");
}
setText(sb.toString());
jTextPane1.setCaretPosition(0);
this.setCursor(null);
}
/**
* Adds a row for download source from the given associated artifact,
* if the associated artifacts specifies a source.
*
* @param sb string builder.
* Adds a row for download source from the given associated artifact, if the
* associated artifacts specifies a source.
*
* @param sb string builder.
* @param associatedArtifact
*
*
* @throws TskCoreException if there is an error
*/
private void addDownloadSourceRow(StringBuilder sb, BlackboardArtifact associatedArtifact ) throws TskCoreException {
if (associatedArtifact != null &&
((associatedArtifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID()) ||
(associatedArtifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID())) ) {
private void addDownloadSourceRow(StringBuilder sb, BlackboardArtifact associatedArtifact) throws TskCoreException {
if (associatedArtifact != null
&& ((associatedArtifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID())
|| (associatedArtifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID()))) {
BlackboardAttribute urlAttr = associatedArtifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_URL));
if (urlAttr != null) {
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.downloadSource"), urlAttr.getValueString());
}
}
}
/**
* Add the acquisition details to the results (if applicable)
*
*
* @param sb The output StringBuilder object
* @param dataSource The data source (may be null)
*/
@ -376,4 +263,169 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
public int isPreferred(Node node) {
return 1;
}
/**
* SwingWorker for gathering the file metadata.
*/
private class MetaDataWorker extends SwingWorker<String, Void> {
private final Node node;
MetaDataWorker(Node node) {
this.node = node;
}
@Override
protected String doInBackground() throws Exception {
AbstractFile file = node.getLookup().lookup(AbstractFile.class);
Image image = node.getLookup().lookup(Image.class);
DataSource dataSource = node.getLookup().lookup(DataSource.class);
if (file == null && image == null) {
return NbBundle.getMessage(this.getClass(), "Metadata.nodeText.nonFilePassedIn");
}
StringBuilder sb = new StringBuilder();
startTable(sb);
if (file != null) {
try {
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.name"), file.getUniquePath());
} catch (TskCoreException ex) {
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.name"), file.getParentPath() + "/" + file.getName());
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.type"), file.getType().getName());
addRow(sb, Bundle.Metadata_tableRowTitle_mimeType(), file.getMIMEType());
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.size"), Long.toString(file.getSize()));
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.fileNameAlloc"), file.getDirFlagAsString());
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.metadataAlloc"), file.getMetaFlagsAsString());
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.modified"), ContentUtils.getStringTime(file.getMtime(), file));
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.accessed"), ContentUtils.getStringTime(file.getAtime(), file));
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.created"), ContentUtils.getStringTime(file.getCrtime(), file));
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.changed"), ContentUtils.getStringTime(file.getCtime(), file));
String md5 = file.getMd5Hash();
if (md5 == null) {
md5 = NbBundle.getMessage(this.getClass(), "Metadata.tableRowContent.md5notCalc");
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.md5"), md5);
String sha256 = file.getSha256Hash();
if (sha256 == null) {
sha256 = NbBundle.getMessage(this.getClass(), "Metadata.tableRowContent.md5notCalc");
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.sha256"), sha256);
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.hashLookupResults"), file.getKnown().toString());
addAcquisitionDetails(sb, dataSource);
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.internalid"), Long.toString(file.getId()));
if (file.getType().compareTo(TSK_DB_FILES_TYPE_ENUM.LOCAL) == 0) {
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.localPath"), file.getLocalAbsPath());
}
try {
List<BlackboardArtifact> associatedObjectArtifacts = file.getArtifacts(ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT);
if (!associatedObjectArtifacts.isEmpty()) {
BlackboardArtifact artifact = associatedObjectArtifacts.get(0);
BlackboardAttribute associatedArtifactAttribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT));
if (associatedArtifactAttribute != null) {
long artifactId = associatedArtifactAttribute.getValueLong();
BlackboardArtifact associatedArtifact = artifact.getSleuthkitCase().getBlackboardArtifact(artifactId);
addDownloadSourceRow(sb, associatedArtifact);
}
}
} catch (TskCoreException ex) {
sb.append(NbBundle.getMessage(this.getClass(), "Metadata.nodeText.exceptionNotice.text")).append(ex.getLocalizedMessage());
}
endTable(sb);
/*
* If we have a file system file, grab the more detailed
* metadata text too
*/
try {
if (file instanceof FsContent) {
FsContent fsFile = (FsContent) file;
sb.append("<hr /><pre>\n"); //NON-NLS
sb.append(NbBundle.getMessage(this.getClass(), "Metadata.nodeText.text"));
sb.append(" <br /><br />"); // NON-NLS
for (String str : fsFile.getMetaDataText()) {
sb.append(str).append("<br />"); //NON-NLS
/*
* Very long results can cause the UI to hang before
* displaying, so truncate the results if necessary.
*/
if (sb.length() > 50000) {
sb.append(NbBundle.getMessage(this.getClass(), "Metadata.nodeText.truncated"));
break;
}
}
sb.append("</pre>\n"); //NON-NLS
}
} catch (TskCoreException ex) {
sb.append(NbBundle.getMessage(this.getClass(), "Metadata.nodeText.exceptionNotice.text")).append(ex.getLocalizedMessage());
}
} else {
try {
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.name"), image.getUniquePath());
} catch (TskCoreException ex) {
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.name"), image.getName());
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.imageType"), image.getType().getName());
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.size"), Long.toString(image.getSize()));
try {
String md5 = image.getMd5();
if (md5 == null || md5.isEmpty()) {
md5 = NbBundle.getMessage(this.getClass(), "Metadata.tableRowContent.md5notCalc");
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.md5"), md5);
String sha1 = image.getSha1();
if (sha1 == null || sha1.isEmpty()) {
sha1 = NbBundle.getMessage(this.getClass(), "Metadata.tableRowContent.md5notCalc");
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.sha1"), sha1);
String sha256 = image.getSha256();
if (sha256 == null || sha256.isEmpty()) {
sha256 = NbBundle.getMessage(this.getClass(), "Metadata.tableRowContent.md5notCalc");
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.sha256"), sha256);
} catch (TskCoreException ex) {
sb.append(NbBundle.getMessage(this.getClass(), "Metadata.nodeText.exceptionNotice.text")).append(ex.getLocalizedMessage());
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.sectorSize"), Long.toString(image.getSsize()));
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.timezone"), image.getTimeZone());
addAcquisitionDetails(sb, dataSource);
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.deviceId"), image.getDeviceId());
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.internalid"), Long.toString(image.getId()));
// Add all the data source paths to the "Local Path" value cell.
String[] imagePaths = image.getPaths();
if (imagePaths.length > 0) {
StringBuilder pathValues = new StringBuilder("<div>");
pathValues.append(imagePaths[0]);
pathValues.append("</div>");
for (int i = 1; i < imagePaths.length; i++) {
pathValues.append("<div>");
pathValues.append(imagePaths[i]);
pathValues.append("</div>");
}
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.localPath"), pathValues.toString());
} else {
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.localPath"),
NbBundle.getMessage(this.getClass(), "Metadata.nodeText.none"));
}
}
if (isCancelled()) {
return "";
}
return sb.toString();
}
}
}

View File

@ -0,0 +1,673 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2021 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.application;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.swing.JLabel;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.openide.nodes.Node;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeUtil;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifactTag;
import org.sleuthkit.datamodel.BlackboardAttribute;
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;
/**
* The business logic for the Annotations content panel.
*/
public class Annotations {
@NbBundle.Messages({
"Annotations.title=Annotations",
"Annotations.toolTip=Displays tags and comments associated with the selected content.",
"Annotations.centralRepositoryEntry.title=Central Repository Comments",
"Annotations.centralRepositoryEntryDataLabel.case=Case:",
"Annotations.centralRepositoryEntryDataLabel.type=Type:",
"Annotations.centralRepositoryEntryDataLabel.comment=Comment:",
"Annotations.centralRepositoryEntryDataLabel.path=Path:",
"Annotations.tagEntry.title=Tags",
"Annotations.tagEntryDataLabel.tag=Tag:",
"Annotations.tagEntryDataLabel.tagUser=Examiner:",
"Annotations.tagEntryDataLabel.comment=Comment:",
"Annotations.fileHitEntry.artifactCommentTitle=Artifact Comment",
"Annotations.fileHitEntry.hashSetHitTitle=Hash Set Hit Comments",
"Annotations.fileHitEntry.interestingFileHitTitle=Interesting File Hit Comments",
"Annotations.fileHitEntry.setName=Set Name:",
"Annotations.fileHitEntry.comment=Comment:",
"Annotations.sourceFile.title=Source File",
"Annotations.onEmpty=No annotations were found for this particular item."
})
private static final Logger logger = Logger.getLogger(Annotations.class.getName());
private static final String EMPTY_HTML = "<html><head></head><body></body></html>";
private static final int DEFAULT_FONT_SIZE = new JLabel().getFont().getSize();
// spacing occurring after an item
private static final int DEFAULT_TABLE_SPACING = DEFAULT_FONT_SIZE;
// html stylesheet classnames for components
public static final String MESSAGE_CLASSNAME = "message";
public static final String SUBSECTION_CLASSNAME = "subsection";
public static final String SUBHEADER_CLASSNAME = "subheader";
public static final String SECTION_CLASSNAME = "section";
public static final String HEADER_CLASSNAME = "header";
public static final String VERTICAL_TABLE_CLASSNAME = "vertical-table";
// describing table values for a tag
private static final List<ItemEntry<Tag>> TAG_ENTRIES = Arrays.asList(
new ItemEntry<>(Bundle.Annotations_tagEntryDataLabel_tag(),
(tag) -> (tag.getName() != null) ? tag.getName().getDisplayName() : null),
new ItemEntry<>(Bundle.Annotations_tagEntryDataLabel_tagUser(), (tag) -> tag.getUserName()),
new ItemEntry<>(Bundle.Annotations_tagEntryDataLabel_comment(), (tag) -> tag.getComment())
);
private static final SectionConfig<Tag> TAG_CONFIG
= new SectionConfig<>(Bundle.Annotations_tagEntry_title(), TAG_ENTRIES);
// file set attributes and table configurations
private static final List<ItemEntry<BlackboardArtifact>> FILESET_HIT_ENTRIES = Arrays.asList(
new ItemEntry<>(Bundle.Annotations_fileHitEntry_setName(),
(bba) -> tryGetAttribute(bba, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME)),
new ItemEntry<>(Bundle.Annotations_fileHitEntry_comment(),
(bba) -> tryGetAttribute(bba, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT))
);
private static final SectionConfig<BlackboardArtifact> INTERESTING_FILE_CONFIG
= new SectionConfig<>(Bundle.Annotations_fileHitEntry_interestingFileHitTitle(), FILESET_HIT_ENTRIES);
private static final SectionConfig<BlackboardArtifact> HASHSET_CONFIG
= new SectionConfig<>(Bundle.Annotations_fileHitEntry_hashSetHitTitle(), FILESET_HIT_ENTRIES);
private static final SectionConfig<BlackboardArtifact> ARTIFACT_COMMENT_CONFIG
= new SectionConfig<>(Bundle.Annotations_fileHitEntry_artifactCommentTitle(), FILESET_HIT_ENTRIES);
// central repository attributes and table configuration
private static final List<ItemEntry<CorrelationAttributeInstance>> CR_COMMENTS_ENTRIES = Arrays.asList(
new ItemEntry<>(Bundle.Annotations_centralRepositoryEntryDataLabel_case(),
cai -> (cai.getCorrelationCase() != null) ? cai.getCorrelationCase().getDisplayName() : null),
new ItemEntry<>(Bundle.Annotations_centralRepositoryEntryDataLabel_comment(), cai -> cai.getComment()),
new ItemEntry<>(Bundle.Annotations_centralRepositoryEntryDataLabel_path(), cai -> cai.getFilePath())
);
private static final SectionConfig<CorrelationAttributeInstance> CR_COMMENTS_CONFIG
= new SectionConfig<>(Bundle.Annotations_centralRepositoryEntry_title(), CR_COMMENTS_ENTRIES);
/*
* Private constructor for this utility class.
*/
private Annotations() {
}
/**
* Returns the formatted Annotation information for the given node. If no
* data was found the method will return null;
*
* @param node Node to get data for.
*
* @return A formatted document of annotation information for the given node
* or null.
*/
public static Document buildDocument(Node node) {
Document html = Jsoup.parse(EMPTY_HTML);
Element body = html.getElementsByTag("body").first();
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);
}
boolean somethingWasRendered = false;
if (artifact != null) {
somethingWasRendered = renderArtifact(body, artifact, sourceFile);
} else {
somethingWasRendered = renderContent(body, sourceFile, false);
}
if (!somethingWasRendered) {
return null;
}
return html;
}
/**
* Renders annotations for an artifact.
*
* @param parent The html element to render content int.
* @param bba The blackboard artifact to render.
* @param sourceContent The content from which the blackboard artifact
* comes.
*
* @return If any content was actually rendered.
*/
private static boolean renderArtifact(Element parent, BlackboardArtifact bba, Content sourceContent) {
boolean contentRendered = appendEntries(parent, TAG_CONFIG, getTags(bba), false);
if (CentralRepository.isEnabled()) {
List<CorrelationAttributeInstance> centralRepoComments = getCentralRepositoryData(bba);
boolean crRendered = appendEntries(parent, CR_COMMENTS_CONFIG, centralRepoComments, false);
contentRendered = contentRendered || crRendered;
}
// if artifact is a hashset hit or interesting file and has a non-blank comment
if ((BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID() == bba.getArtifactTypeID()
|| BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID() == bba.getArtifactTypeID())
&& (hasTskComment(bba))) {
boolean filesetRendered = appendEntries(parent, ARTIFACT_COMMENT_CONFIG, Arrays.asList(bba), false);
contentRendered = contentRendered || filesetRendered;
}
Element sourceFileSection = appendSection(parent, Bundle.Annotations_sourceFile_title());
boolean sourceFileRendered = renderContent(sourceFileSection, sourceContent, true);
if (!sourceFileRendered) {
sourceFileSection.remove();
}
return contentRendered || sourceFileRendered;
}
/**
* Renders annotations for a content item.
*
* @param parent The parent within which to render.
* @param sourceContent The content for which annotations will be gathered.
* @param isSubheader True if this section should be rendered as a
* subheader as opposed to a top-level header.
*
* @return If any content was actually rendered.
*/
private static boolean renderContent(Element parent, Content sourceContent, boolean isSubheader) {
boolean contentRendered = appendEntries(parent, TAG_CONFIG, getTags(sourceContent), isSubheader);
if (sourceContent instanceof AbstractFile) {
AbstractFile sourceFile = (AbstractFile) sourceContent;
if (CentralRepository.isEnabled()) {
List<CorrelationAttributeInstance> centralRepoComments = getCentralRepositoryData(sourceFile);
boolean crRendered = appendEntries(parent, CR_COMMENTS_CONFIG, centralRepoComments, isSubheader);
contentRendered = contentRendered || crRendered;
}
boolean hashsetRendered = appendEntries(parent, HASHSET_CONFIG,
getFileSetHits(sourceFile, BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT),
isSubheader);
boolean interestingFileRendered = appendEntries(parent, INTERESTING_FILE_CONFIG,
getFileSetHits(sourceFile, BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT),
isSubheader);
contentRendered = contentRendered || hashsetRendered || interestingFileRendered;
}
return contentRendered;
}
/**
* Retrieves tags associated with a content item.
*
* @param sourceContent The content for which to gather content.
*
* @return The Tags associated with this item.
*/
private static List<ContentTag> getTags(Content sourceContent) {
try {
SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
return tskCase.getContentTagsByContent(sourceContent);
} 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
}
return new ArrayList<>();
}
/**
* Retrieves tags for blackboard artifact tags.
*
* @param bba The blackboard artifact for which to retrieve tags.
*
* @return The found tags.
*/
private static List<BlackboardArtifactTag> getTags(BlackboardArtifact bba) {
try {
SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
return tskCase.getBlackboardArtifactTagsByArtifact(bba);
} 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
}
return new ArrayList<>();
}
/**
* Retrieves the blackboard artifacts for a source file matching a certain
* type that have a non-blank TSK_COMMENT.
*
* @param sourceFile The source file for which to fetch artifacts.
* @param type The type of blackboard artifact to fetch.
*
* @return The artifacts found matching this type.
*/
private static List<BlackboardArtifact> getFileSetHits(AbstractFile sourceFile, BlackboardArtifact.ARTIFACT_TYPE type) {
try {
SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
return tskCase.getBlackboardArtifacts(type, sourceFile.getId()).stream()
.filter((bba) -> hasTskComment(bba))
.collect(Collectors.toList());
} 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 file set hits from the case database.", ex); //NON-NLS
}
return new ArrayList<>();
}
/**
* Returns true if the artifact contains a non-blank TSK_COMMENT attribute.
*
* @param artifact The artifact to check.
*
* @return True if it has a non-blank TSK_COMMENT.
*/
private static boolean hasTskComment(BlackboardArtifact artifact) {
return StringUtils.isNotBlank(tryGetAttribute(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT));
}
/**
* Attempts to retrieve the attribute of a particular type from a blackboard
* artifact.
*
* @param artifact The artifact from which to retrieve the information.
* @param attributeType The attribute type to retrieve from the artifact.
*
* @return The string value of the attribute or null if not found.
*/
private static String tryGetAttribute(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) {
if (artifact == null) {
return null;
}
BlackboardAttribute attr = null;
try {
attr = artifact.getAttribute(new BlackboardAttribute.Type(attributeType));
} catch (TskCoreException ex) {
logger.log(Level.WARNING, String.format("Unable to fetch attribute of type %s for artifact %s", attributeType, artifact), ex);
}
if (attr == null) {
return null;
}
return attr.getValueString();
}
/**
* Gets the "Central Repository Comments" section with data for the
* blackboard artifact.
*
* @param artifact The selected artifact.
*
* @return The Correlation Attribute Instances associated with the artifact
* that have comments.
*/
private static List<CorrelationAttributeInstance> getCentralRepositoryData(BlackboardArtifact artifact) {
if (artifact == null) {
return new ArrayList<>();
}
List<Pair<CorrelationAttributeInstance.Type, String>> lookupKeys = CorrelationAttributeUtil.makeCorrAttrsForCorrelation(artifact)
.stream()
.map(cai -> Pair.of(cai.getCorrelationType(), cai.getCorrelationValue()))
.collect(Collectors.toList());
return getCorrelationAttributeComments(lookupKeys);
}
/**
* Gets the "Central Repository Comments" section with data.
*
* @param sourceFile A selected file, or a source file of the selected
* artifact.
*
* @return The Correlation Attribute Instances associated with the
* sourcefile that have comments.
*/
private static List<CorrelationAttributeInstance> getCentralRepositoryData(AbstractFile sourceFile) {
if (sourceFile == null || StringUtils.isEmpty(sourceFile.getMd5Hash())) {
return new ArrayList<>();
}
List<CorrelationAttributeInstance.Type> artifactTypes = null;
try {
artifactTypes = CentralRepository.getInstance().getDefinedCorrelationTypes();
} catch (CentralRepoException ex) {
logger.log(Level.SEVERE, "Error connecting to the Central Repository database.", ex); // NON-NLS
}
if (artifactTypes == null || artifactTypes.isEmpty()) {
return new ArrayList<>();
}
String md5 = sourceFile.getMd5Hash();
// get key lookups for a file attribute types and the md5 hash
List<Pair<CorrelationAttributeInstance.Type, String>> lookupKeys = artifactTypes.stream()
.filter((attributeType) -> attributeType.getId() == CorrelationAttributeInstance.FILES_TYPE_ID)
.map((attributeType) -> Pair.of(attributeType, md5))
.collect(Collectors.toList());
return getCorrelationAttributeComments(lookupKeys);
}
/**
* Given a type and a value for that type, does a lookup in the Central
* Repository for matching values that have comments.
*
* @param lookupKeys The type and value to lookup.
*
* @return The found correlation attribute instances.
*/
private static List<CorrelationAttributeInstance> getCorrelationAttributeComments(List<Pair<CorrelationAttributeInstance.Type, String>> lookupKeys) {
List<CorrelationAttributeInstance> instancesToRet = new ArrayList<>();
try {
// use lookup instances to find the actual correlation attributes for the items selected
for (Pair<CorrelationAttributeInstance.Type, String> typeVal : lookupKeys) {
instancesToRet.addAll(CentralRepository.getInstance()
.getArtifactInstancesByTypeValue(typeVal.getKey(), typeVal.getValue())
.stream()
// for each one found, if it has a comment, return
.filter((cai) -> StringUtils.isNotBlank(cai.getComment()))
.collect(Collectors.toList()));
}
} catch (CentralRepoException 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 Central Repository database.", ex); // NON-NLS
}
return instancesToRet;
}
/**
* Append entries to the parent element in the annotations viewer. Entries
* will be formatted as a table in the format specified in the
* SectionConfig.
*
* @param parent The parent element for which the entries will be
* attached.
* @param config The display configuration for this entry type (i.e.
* table type, name, if data is not present).
* @param items The items to display.
* @param isSubsection Whether or not this should be displayed as a
* subsection. If not displayed as a top-level section.
*
* @return If there was actual content rendered for this set of entries.
*/
private static <T> boolean appendEntries(Element parent, Annotations.SectionConfig<T> config, List<? extends T> items,
boolean isSubsection) {
if (items == null || items.isEmpty()) {
return false;
}
Element sectionDiv = (isSubsection) ? appendSubsection(parent, config.getTitle()) : appendSection(parent, config.getTitle());
appendVerticalEntryTables(sectionDiv, items, config.getAttributes());
return true;
}
/**
* Appends a table where items are displayed in rows of key-value pairs.
*
* @param parent The parent to append the table.
* @param items The items to process into a series of tables.
* @param rowHeaders The keys and the means to process items in order to get
* key-value pairs.
*
* @return The parent element provided as parameter.
*/
private static <T> Element appendVerticalEntryTables(Element parent, List<? extends T> items, List<ItemEntry<T>> rowHeaders) {
boolean isFirst = true;
for (T item : items) {
if (item == null) {
continue;
}
List<List<String>> tableData = rowHeaders.stream()
.map(row -> Arrays.asList(row.getItemName(), row.retrieveValue(item)))
.collect(Collectors.toList());
Element childTable = appendTable(parent, 2, tableData, null);
childTable.attr("class", VERTICAL_TABLE_CLASSNAME);
if (isFirst) {
isFirst = false;
} else {
childTable.attr("style", String.format("margin-top: %dpx;", DEFAULT_TABLE_SPACING));
}
}
return parent;
}
/**
* Appends a generic table to the parent element.
*
* @param parent The parent element that will have a table appended
* to it.
* @param columnNumber The number of columns to append.
* @param content The content in content.get(row).get(column) format.
* @param columnHeaders The column headers or null if no column headers
* should be created.
*
* @return The created table.
*/
private static Element appendTable(Element parent, int columnNumber, List<List<String>> content, List<String> columnHeaders) {
Element table = parent.appendElement("table");
if (columnHeaders != null && !columnHeaders.isEmpty()) {
Element header = table.appendElement("thead");
appendRow(header, columnHeaders, columnNumber, true);
}
Element tableBody = table.appendElement("tbody");
content.forEach((rowData) -> appendRow(tableBody, rowData, columnNumber, false));
return table;
}
/**
* Appends a row to the parent element (should be thead or tbody).
*
* @param rowParent The parent table element.
* @param data The data to place in columns within the table.
* @param columnNumber The number of columns to append.
* @param isHeader Whether or not this should have header cells ('th')
* instead of regular cells ('td').
*
* @return The row created.
*/
private static Element appendRow(Element rowParent, List<String> data, int columnNumber, boolean isHeader) {
String cellType = isHeader ? "th" : "td";
Element row = rowParent.appendElement("tr");
for (int i = 0; i < columnNumber; i++) {
Element cell = row.appendElement(cellType);
if (data != null && i < data.size()) {
cell.text(StringUtils.isEmpty(data.get(i)) ? "" : data.get(i));
}
}
return row;
}
/**
* Appends a new section with a section header to the parent element.
*
* @param parent The element to append this section to.
* @param headerText The text for the section.
*
* @return The div for the new section.
*/
private static Element appendSection(Element parent, String headerText) {
Element sectionDiv = parent.appendElement("div");
sectionDiv.attr("class", SECTION_CLASSNAME);
Element header = sectionDiv.appendElement("h1");
header.text(headerText);
header.attr("class", HEADER_CLASSNAME);
return sectionDiv;
}
/**
* Appends a new subsection with a subsection header to the parent element.
*
* @param parent The element to append this subsection to.
* @param headerText The text for the subsection.
*
* @return The div for the new subsection.
*/
private static Element appendSubsection(Element parent, String headerText) {
Element subsectionDiv = parent.appendElement("div");
subsectionDiv.attr("class", SUBSECTION_CLASSNAME);
Element header = subsectionDiv.appendElement("h2");
header.text(headerText);
header.attr("class", SUBHEADER_CLASSNAME);
return subsectionDiv;
}
/**
* Appends a message to the parent element. This is typically used in the
* event that no data exists for a certain type.
*
* @param parent The parent element that will have this message appended to
* it.
* @param message The message to append.
*
* @return The paragraph element for the new message.
*/
private static Element appendMessage(Element parent, String message) {
Element messageEl = parent.appendElement("p");
messageEl.text(message);
messageEl.attr("class", MESSAGE_CLASSNAME);
return messageEl;
}
/**
* Describes a key value pair for an item of type T where the key is the
* field name to display and the value is retrieved from item of type T
* using a provided Function<T, string>.
*
* @param <T> The item type.
*/
static class ItemEntry<T> {
private final String itemName;
private final Function<T, String> valueRetriever;
ItemEntry(String itemName, Function<T, String> valueRetriever) {
this.itemName = itemName;
this.valueRetriever = valueRetriever;
}
String getItemName() {
return itemName;
}
Function<T, String> getValueRetriever() {
return valueRetriever;
}
String retrieveValue(T object) {
return valueRetriever.apply(object);
}
}
/**
* Describes a section that will be appended to the annotations view panel.
*
* @param <T> The item type for items to display.
*/
static class SectionConfig<T> {
private final String title;
private final List<ItemEntry<T>> attributes;
SectionConfig(String title, List<ItemEntry<T>> attributes) {
this.title = title;
this.attributes = attributes;
}
/**
* @return The title for the section.
*/
String getTitle() {
return title;
}
/**
* @return Describes key-value pairs on the object to display to the
* user.
*/
List<ItemEntry<T>> getAttributes() {
return attributes;
}
}
}

View File

@ -0,0 +1,18 @@
Annotations.centralRepositoryEntry.title=Central Repository Comments
Annotations.centralRepositoryEntryDataLabel.case=Case:
Annotations.centralRepositoryEntryDataLabel.comment=Comment:
Annotations.centralRepositoryEntryDataLabel.path=Path:
Annotations.centralRepositoryEntryDataLabel.type=Type:
Annotations.fileHitEntry.artifactCommentTitle=Artifact Comment
Annotations.fileHitEntry.comment=Comment:
Annotations.fileHitEntry.hashSetHitTitle=Hash Set Hit Comments
Annotations.fileHitEntry.interestingFileHitTitle=Interesting File Hit Comments
Annotations.fileHitEntry.setName=Set Name:
Annotations.onEmpty=No annotations were found for this particular item.
Annotations.sourceFile.title=Source File
Annotations.tagEntry.title=Tags
Annotations.tagEntryDataLabel.comment=Comment:
Annotations.tagEntryDataLabel.tag=Tag:
Annotations.tagEntryDataLabel.tagUser=Examiner:
Annotations.title=Annotations
Annotations.toolTip=Displays tags and comments associated with the selected content.

View File

@ -1,3 +1,4 @@
StringContentPanel_Loading_String=Loading text...
StringsTextViewer.goToPageTextField.msgDlg=Please enter a valid page number between 1 and {0}
StringsTextViewer.goToPageTextField.err=Invalid page number
StringsTextViewer.setDataView.errorText=(offset {0}-{1} could not be read)

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2019 Basis Technology Corp.
* Copyright 2011-2021 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -23,12 +23,15 @@ import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.SwingWorker;
import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.coreutils.StringExtract;
import org.sleuthkit.autopsy.coreutils.StringExtract.StringExtractResult;
import org.sleuthkit.autopsy.coreutils.StringExtract.StringExtractUnicodeTable.SCRIPT;
@ -49,10 +52,10 @@ public class StringsContentPanel extends javax.swing.JPanel {
private final byte[] data = new byte[(int) PAGE_LENGTH];
private static int currentPage = 1;
private Content dataSource;
//string extract utility
private final StringExtract stringExtract = new StringExtract();
private static final Logger logger = Logger.getLogger(StringsContentPanel.class.getName());
private SwingWorker<String, Void> worker;
/**
* Creates new form StringsTextViewer
*/
@ -81,10 +84,10 @@ public class StringsContentPanel extends javax.swing.JPanel {
});
// use wrap layout for better component wrapping
WrapLayout layout = new WrapLayout(0,5);
WrapLayout layout = new WrapLayout(0, 5);
layout.setOppositeAligned(Arrays.asList(panelScriptSelect));
controlPanel.setLayout(layout);
}
final void resetDisplay() {
@ -363,6 +366,10 @@ public class StringsContentPanel extends javax.swing.JPanel {
private javax.swing.JLabel totalPageLabel;
// End of variables declaration//GEN-END:variables
@Messages({
"StringContentPanel_Loading_String=Loading text..."
})
/**
* Sets the DataView (The tabbed panel)
*
@ -370,83 +377,36 @@ public class StringsContentPanel extends javax.swing.JPanel {
* @param offset the starting offset
*/
void setDataView(Content dataSource, long offset) {
if (worker != null) {
worker.cancel(true);
worker = null;
}
if (dataSource == null) {
return;
}
// change the cursor to "waiting cursor" for this operation
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
this.dataSource = dataSource;
int bytesRead = 0;
// set the data on the bottom and show it
if (dataSource.getSize() > 0) {
try {
bytesRead = dataSource.read(data, offset, PAGE_LENGTH); // read the data
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Error while trying to show the String content.", ex); //NON-NLS
}
}
String text;
if (bytesRead > 0) {
//text = DataConversion.getString(data, bytesRead, 4);
final SCRIPT selScript = (SCRIPT) languageCombo.getSelectedItem();
stringExtract.setEnabledScript(selScript);
StringExtractResult res = stringExtract.extract(data, bytesRead, 0);
text = res.getText();
if (StringUtils.isBlank(text)) {
text = NbBundle.getMessage(this.getClass(),
"StringsTextViewer.setDataView.errorNoText", currentOffset,
currentOffset + PAGE_LENGTH);
}
} else {
text = NbBundle.getMessage(this.getClass(), "StringsTextViewer.setDataView.errorText", currentOffset,
currentOffset + PAGE_LENGTH);
}
// disable or enable the next button
if (offset + PAGE_LENGTH < dataSource.getSize()) {
nextPageButton.setEnabled(true);
} else {
nextPageButton.setEnabled(false);
}
if (offset == 0) {
prevPageButton.setEnabled(false);
currentPage = 1; // reset the page number
} else {
prevPageButton.setEnabled(true);
}
int totalPage = Math.round((dataSource.getSize() - 1) / PAGE_LENGTH) + 1;
totalPageLabel.setText(Integer.toString(totalPage));
currentPageLabel.setText(Integer.toString(currentPage));
outputViewPane.setText(text); // set the output view
setComponentsVisibility(true); // shows the components that not needed
outputViewPane.moveCaretPosition(0);
this.setCursor(null);
worker = new ContentWorker(dataSource, offset);
outputViewPane.setText(Bundle.StringContentPanel_Loading_String());
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
worker.execute();
}
void setDataView(StringContent dataSource) {
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
this.dataSource = null;
// set the data on the bottom and show it
String text = dataSource.getString();
nextPageButton.setEnabled(false);
prevPageButton.setEnabled(false);
currentPage = 1;
int totalPage = 1;
totalPageLabel.setText(Integer.toString(totalPage));
currentPageLabel.setText(Integer.toString(currentPage));
outputViewPane.setText(text); // set the output view
setComponentsVisibility(true); // shows the components that not needed
outputViewPane.moveCaretPosition(0);
} finally {
this.setCursor(null);
if (worker != null) {
worker.cancel(true);
worker = null;
}
if (dataSource == null) {
return;
}
worker = new StringContentWorker(dataSource);
outputViewPane.setText(Bundle.StringContentPanel_Loading_String());
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
worker.execute();
}
/**
@ -468,4 +428,145 @@ public class StringsContentPanel extends javax.swing.JPanel {
languageLabel.setVisible(isVisible);
}
/**
* Swingworker for getting the text from a content object.
*/
private final class ContentWorker extends SwingWorker<String, Void> {
private final Content content;
private final long offset;
/**
* ContentWorker constructor
*
* @param content Content to get text from.
* @param offset The starting offset.
*/
ContentWorker(Content content, long offset) {
this.content = content;
this.offset = offset;
}
@Override
protected String doInBackground() throws Exception {
int bytesRead = 0;
// set the data on the bottom and show it
if (content.getSize() > 0) {
try {
bytesRead = content.read(data, offset, PAGE_LENGTH); // read the data
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Error while trying to show the String content.", ex); //NON-NLS
}
}
String text;
if (bytesRead > 0) {
//text = DataConversion.getString(data, bytesRead, 4);
final SCRIPT selScript = (SCRIPT) languageCombo.getSelectedItem();
StringExtract stringExtract = new StringExtract();
stringExtract.setEnabledScript(selScript);
StringExtractResult res = stringExtract.extract(data, bytesRead, 0);
text = res.getText();
if (StringUtils.isBlank(text)) {
text = NbBundle.getMessage(this.getClass(),
"StringsTextViewer.setDataView.errorNoText", currentOffset,
currentOffset + PAGE_LENGTH);
}
} else {
text = NbBundle.getMessage(this.getClass(), "StringsTextViewer.setDataView.errorText", currentOffset,
currentOffset + PAGE_LENGTH);
}
return text;
}
@Override
public void done() {
if (isCancelled()) {
return;
}
try {
if (isCancelled()) {
return;
}
String text = get();
dataSource = content;
// disable or enable the next button
if (offset + PAGE_LENGTH < dataSource.getSize()) {
nextPageButton.setEnabled(true);
} else {
nextPageButton.setEnabled(false);
}
if (offset == 0) {
prevPageButton.setEnabled(false);
currentPage = 1; // reset the page number
} else {
prevPageButton.setEnabled(true);
}
int totalPage = Math.round((dataSource.getSize() - 1) / PAGE_LENGTH) + 1;
totalPageLabel.setText(Integer.toString(totalPage));
currentPageLabel.setText("1");
outputViewPane.setText(text); // set the output view
setComponentsVisibility(true); // shows the components that not needed
outputViewPane.moveCaretPosition(0);
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
} catch (InterruptedException | ExecutionException ex) {
logger.log(Level.SEVERE, String.format("Failed to get text from content (id=%d)", content.getId()), ex);
}
}
}
/**
* SwingWorker for getting the text from a StringContent object.
*/
private final class StringContentWorker extends SwingWorker<String, Void> {
private final StringContent content;
/**
* Constructor to pulling the text out of a string content object.
*
* @param content
*/
StringContentWorker(StringContent content) {
this.content = content;
}
@Override
protected String doInBackground() throws Exception {
return content.getString();
}
@Override
public void done() {
if (isCancelled()) {
return;
}
try {
String text = get();
dataSource = null;
nextPageButton.setEnabled(false);
prevPageButton.setEnabled(false);
currentPage = 1;
totalPageLabel.setText("1");
currentPageLabel.setText("1");
outputViewPane.setText(text); // set the output view
setComponentsVisibility(true); // shows the components that not needed
outputViewPane.moveCaretPosition(0);
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
} catch (InterruptedException | ExecutionException ex) {
logger.log(Level.SEVERE, String.format("Failed to get text from StringContent"), ex);
}
}
}
}

View File

@ -34,6 +34,7 @@ DataContentViewerArtifact.failedToGetAttributes.message=Failed to get some or al
DataContentViewerArtifact.failedToGetSourcePath.message=Failed to get source file path from case database
DataContentViewerHex.copyingFile=Copying file to open in HxD...
DataContentViewerHex.launchError=Unable to launch HxD Editor. Please specify the HxD install location in Tools -> Options -> External Viewer
DataContentViewerHex_loading_text=Loading hex from file...
DataResultViewerTable.commentRender.name=C
DataResultViewerTable.commentRender.toolTip=C(omments) indicates whether the item has a comment
DataResultViewerTable.commentRenderer.crAndTagComment.toolTip=Comments exist both in Central Repository and on associated tag(s)

View File

@ -33,7 +33,6 @@ import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.Content;
import org.sleuthkit.datamodel.TskCoreException;
@ -64,6 +63,11 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
private final static String WAIT_TEXT = NbBundle.getMessage(DataContentViewerArtifact.class, "DataContentViewerArtifact.waitText");
private final static String ERROR_TEXT = NbBundle.getMessage(DataContentViewerArtifact.class, "DataContentViewerArtifact.errorText");
// Value to return in isPreferred if this viewer is less preferred.
private static final int LESS_PREFERRED = 3;
// Value to return in isPreferred if this viewer is more preferred.
private static final int MORE_PREFERRED = 6;
private Node currentNode; // @@@ Remove this when the redundant setNode() calls problem is fixed.
private int currentPage = 1;
private final Object lock = new Object();
@ -347,22 +351,38 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
@Override
public int isPreferred(Node node) {
// get the artifact from the lookup
BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class);
// low priority if node doesn't have an artifact (meaning it was found from normal directory
// browsing, or if the artifact is something that means the user really wants to see the original
// file and not more details about the artifact
if ((artifact == null)
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID())
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID())
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID())
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_OBJECT_DETECTED.getTypeID())
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID())
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED.getTypeID())
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID())
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID())) {
return 3;
} else {
return 6;
if (artifact == null) {
return LESS_PREFERRED;
}
// get the type of the artifact
BlackboardArtifact.Type artifactType;
try {
artifactType = artifact.getType();
} catch (TskCoreException ex) {
logger.log(Level.SEVERE,
String.format("There was an error getting the artifact type for artifact with id: %d", artifact.getId()),
ex);
return LESS_PREFERRED;
}
// if web download or web cache, less preferred since the content is important and not the artifact itself.
if (artifactType.getTypeID() == BlackboardArtifact.Type.TSK_WEB_DOWNLOAD.getTypeID()
|| artifactType.getTypeID() == BlackboardArtifact.Type.TSK_WEB_CACHE.getTypeID()) {
return LESS_PREFERRED;
}
switch (artifactType.getCategory()) {
// data artifacts should be more preferred
case DATA_ARTIFACT:
return MORE_PREFERRED;
// everything else is less preferred
case ANALYSIS_RESULT:
default:
return LESS_PREFERRED;
}
}

View File

@ -26,6 +26,7 @@ import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.openide.util.NbBundle;
@ -37,6 +38,7 @@ import javax.swing.text.BadLocationException;
import javax.swing.text.Utilities;
import org.netbeans.api.progress.ProgressHandle;
import org.openide.nodes.Node;
import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
@ -63,6 +65,8 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
private int totalPages;
private Content dataSource;
private HexWorker worker;
private static final Logger logger = Logger.getLogger(DataContentViewerHex.class.getName());
/**
@ -455,16 +459,11 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
* @param page Page to display (1-based counting)
*/
private void setDataViewByPageNumber(int page) {
if (this.dataSource == null) {
return;
}
if (page == 0) {
return;
}
currentPage = page;
long offset = (currentPage - 1) * PAGE_LENGTH;
setDataView(offset);
goToOffsetTextField.setText(Long.toString(offset));
launchWorker(dataSource, (page - 1) * PAGE_LENGTH, page);
}
/**
@ -473,75 +472,46 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
* @param offset Page to display (1-based counting)
*/
private void setDataViewByOffset(long offset) {
launchWorker(dataSource, offset, (int) (offset / PAGE_LENGTH) + 1);
}
@Messages({
"DataContentViewerHex_loading_text=Loading hex from file..."
})
/**
* Launches the worker thread to read the hex from the given source.
*
* @param source
* @param offset
* @param page
*/
private void launchWorker(Content source, long offset, int page) {
if (this.dataSource == null) {
return;
}
currentPage = (int) (offset / PAGE_LENGTH) + 1;
setDataView(offset);
goToPageTextField.setText(Integer.toString(currentPage));
}
private void setDataView(long offset) {
// change the cursor to "waiting cursor" for this operation
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
String errorText = null;
int bytesRead = 0;
if (dataSource.getSize() > 0) {
try {
bytesRead = dataSource.read(data, offset, PAGE_LENGTH); // read the data
} catch (TskCoreException ex) {
errorText = NbBundle.getMessage(this.getClass(), "DataContentViewerHex.setDataView.errorText", offset,
offset + PAGE_LENGTH);
logger.log(Level.WARNING, "Error while trying to show the hex content.", ex); //NON-NLS
}
}
// set the data on the bottom and show it
if (bytesRead <= 0) {
errorText = NbBundle.getMessage(this.getClass(), "DataContentViewerHex.setDataView.errorText", offset,
offset + PAGE_LENGTH);
}
// disable or enable the next button
if ((errorText == null) && (currentPage < totalPages)) {
nextPageButton.setEnabled(true);
} else {
nextPageButton.setEnabled(false);
}
if ((errorText == null) && (currentPage > 1)) {
prevPageButton.setEnabled(true);
} else {
prevPageButton.setEnabled(false);
}
currentPageLabel.setText(Integer.toString(currentPage));
setComponentsVisibility(true); // shows the components that not needed
// set the output view
if (errorText == null) {
int showLength = bytesRead < PAGE_LENGTH ? bytesRead : (int) PAGE_LENGTH;
outputTextArea.setText(DataConversion.byteArrayToHex(data, showLength, offset));
} else {
outputTextArea.setText(errorText);
}
outputTextArea.setCaretPosition(0);
this.setCursor(null);
worker = new HexWorker(source, offset, page);
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
outputTextArea.setText(Bundle.DataContentViewerHex_loading_text());
worker.execute();
}
@Override
public void setNode(Node selectedNode) {
if ((selectedNode == null) || (!isSupported(selectedNode))) {
resetComponent();
if (worker != null) {
worker.cancel(true);
worker = null;
}
resetComponent();
if ((selectedNode == null)) {
return;
}
Content content = DataContentViewerUtility.getDefaultContent(selectedNode);
if (content == null) {
resetComponent();
return;
}
@ -607,7 +577,7 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
return false;
}
Content content = DataContentViewerUtility.getDefaultContent(node);
return content != null && !(content instanceof BlackboardArtifact) && content.getSize() > 0;
return content != null && !(content instanceof BlackboardArtifact) && content.getSize() > 0;
}
@Override
@ -619,4 +589,84 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
public Component getComponent() {
return this;
}
/**
* SwingWorker to fetch hex from the given data source.
*/
private class HexWorker extends SwingWorker<String, Void> {
private final byte[] data = new byte[(int) PAGE_LENGTH];
private final long offset;
private final Content content;
private final int newCurrentPage;
private String errorText = "";
HexWorker(Content content, long offset, int newCurrentPage) {
this.content = content;
this.offset = offset;
this.newCurrentPage = newCurrentPage;
}
@Override
protected String doInBackground() throws Exception {
int bytesRead = 0;
if (content.getSize() > 0) {
try {
bytesRead = content.read(data, offset, PAGE_LENGTH); // read the data
} catch (TskCoreException ex) {
errorText = NbBundle.getMessage(this.getClass(), "DataContentViewerHex.setDataView.errorText", offset,
offset + PAGE_LENGTH);
logger.log(Level.WARNING, "Error while trying to show the hex content.", ex); //NON-NLS
}
}
// set the data on the bottom and show it
if (bytesRead <= 0) {
errorText = NbBundle.getMessage(this.getClass(), "DataContentViewerHex.setDataView.errorText", offset,
offset + PAGE_LENGTH);
}
if (errorText.isEmpty()) {
int showLength = bytesRead < PAGE_LENGTH ? bytesRead : (int) PAGE_LENGTH;
return DataConversion.byteArrayToHex(data, showLength, offset);
} else {
return errorText;
}
}
@Override
public void done() {
if (isCancelled()) {
return;
}
try {
String text = get();
outputTextArea.setText(text);
// disable or enable the next button
if ((errorText.isEmpty()) && (newCurrentPage < totalPages)) {
nextPageButton.setEnabled(true);
} else {
nextPageButton.setEnabled(false);
}
if ((errorText.isEmpty()) && (newCurrentPage > 1)) {
prevPageButton.setEnabled(true);
} else {
prevPageButton.setEnabled(false);
}
currentPageLabel.setText(Integer.toString(newCurrentPage));
setComponentsVisibility(true); // shows the components that not needed
outputTextArea.setCaretPosition(0);
goToPageTextField.setText(Integer.toString(newCurrentPage));
currentPage = newCurrentPage;
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
} catch (InterruptedException | ExecutionException ex) {
logger.log(Level.SEVERE, String.format("Failed to get hex data from content (%d)", content.getId()), ex);
}
}
}
}

View File

@ -22,6 +22,7 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
@ -96,6 +97,7 @@ public final class AutopsyTreeChildFactory extends ChildFactory.Detachable<Objec
*/
@Override
protected boolean createKeys(List<Object> list) {
List<Object> nodes = Collections.emptyList();
try {
SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
@ -103,27 +105,28 @@ public final class AutopsyTreeChildFactory extends ChildFactory.Detachable<Objec
List<Person> persons = personManager.getPersons();
// show persons level if there are persons to be shown
if (!CollectionUtils.isEmpty(persons)) {
persons.stream()
nodes = persons.stream()
.map(PersonGrouping::new)
.sorted()
.forEach(list::add);
.collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(personManager.getHostsForPerson(null))) {
list.add(new PersonGrouping(null));
nodes.add(new PersonGrouping(null));
}
} else {
// otherwise, just show host level
tskCase.getHostManager().getAllHosts().stream()
nodes = tskCase.getHostManager().getAllHosts().stream()
.map(HostGrouping::new)
.sorted()
.forEach(list::add);
.collect(Collectors.toList());
}
list.add(new Reports());
return true;
// either way, add in reports node
nodes.add(new Reports());
} else {
// data source by type view
List<AutopsyVisitableItem> keys = new ArrayList<>(Arrays.asList(
nodes = Arrays.asList(
new DataSourcesByType(),
new Views(Case.getCurrentCaseThrows().getSleuthkitCase()),
new DataArtifacts(),
@ -131,15 +134,16 @@ public final class AutopsyTreeChildFactory extends ChildFactory.Detachable<Objec
new OsAccounts(Case.getCurrentCaseThrows().getSleuthkitCase()),
new Tags(),
new Reports()
));
list.addAll(keys);
);
}
} 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 data from case.", ex); //NON-NLS
}
// add all nodes to the netbeans node list
list.addAll(nodes);
return true;
}

View File

@ -26,6 +26,7 @@ import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.stream.Collectors;
import javax.swing.Action;
import org.openide.nodes.ChildFactory;
@ -119,11 +120,11 @@ public class HostNode extends DisplayableItemNode {
}
if (dataSources != null) {
dataSources.stream()
toPopulate.addAll(dataSources.stream()
.filter(ds -> ds != null)
.map(DataSourceGrouping::new)
.sorted((a, b) -> getNameOrEmpty(a).compareToIgnoreCase(getNameOrEmpty(b)))
.forEach(toPopulate::add);
.collect(Collectors.toList()));
}
return true;

View File

@ -126,10 +126,10 @@ public class PersonGroupingNode extends DisplayableItemNode {
logger.log(Level.WARNING, String.format("Unable to get data sources for host: %s", personName), ex);
}
hosts.stream()
toPopulate.addAll(hosts.stream()
.map(HostGrouping::new)
.sorted()
.forEach(toPopulate::add);
.collect(Collectors.toList()));
return true;
}

View File

@ -675,7 +675,7 @@
<Component class="javax.swing.JComboBox" name="equalitySignComboBox">
<Properties>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="new javax.swing.DefaultComboBoxModel&lt;String&gt;(new String[] { &quot;=&quot;, &quot;&gt;&quot;, &quot;&#x2265;&quot;, &quot;&lt;&quot;, &quot;&#x2264;&quot; })" type="code"/>
<Connection code="new javax.swing.DefaultComboBoxModel&lt;String&gt;(new String[] { &quot;&gt;&quot;, &quot;&lt;&quot; })" type="code"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>

View File

@ -1,7 +1,7 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011-2020 Basis Technology Corp.
* Copyright 2011-2021 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -126,12 +126,8 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
this.exportSetButton.setVisible(false);
this.mimeTypeComboBox.setVisible(false);
this.mimeTypeLabel.setVisible(false);
this.fileSizeUnitComboBox.setVisible(false);
this.fileSizeSpinner.setVisible(false);
this.filterDialogTitle = "FilesSetPanel.filter.title";
this.ruleDialogTitle = "FilesSetPanel.rule.title";
this.fileSizeLabel.setVisible(false);
this.equalitySignComboBox.setVisible(false);
this.ignoreKnownFilesCheckbox.setVisible(false);
this.fileTypeLabel.setVisible(false);
this.filesRadioButton.setVisible(false);
@ -192,7 +188,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
}
this.fileSizeUnitComboBox.setSelectedIndex(1);
this.equalitySignComboBox.setSelectedIndex(2);
this.equalitySignComboBox.setSelectedIndex(0);
}
@Override
@ -217,7 +213,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
boolean ruleSelected = (FilesSetDefsPanel.this.rulesList.getSelectedValue() != null);
newRuleButton.setEnabled(canBeEnabled && !isStandardSet);
newRuleButton.setEnabled(canBeEnabled && setSelected && !isStandardSet);
copySetButton.setEnabled(canBeEnabled && setSelected);
newSetButton.setEnabled(canBeEnabled);
editRuleButton.setEnabled(canBeEnabled && ruleSelected && !isStandardSet);
@ -292,7 +288,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
this.daysIncludedTextField.setText("");
this.rulePathConditionRegexCheckBox.setSelected(false);
this.mimeTypeComboBox.setSelectedIndex(0);
this.equalitySignComboBox.setSelectedIndex(2);
this.equalitySignComboBox.setSelectedIndex(0);
this.fileSizeUnitComboBox.setSelectedIndex(1);
this.fileSizeSpinner.setValue(0);
enableButtons();
@ -405,7 +401,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
FilesSetDefsPanel.this.fileSizeSpinner.setValue(fileSizeCondition.getSizeValue());
} else {
FilesSetDefsPanel.this.fileSizeUnitComboBox.setSelectedIndex(1);
FilesSetDefsPanel.this.equalitySignComboBox.setSelectedIndex(2);
FilesSetDefsPanel.this.equalitySignComboBox.setSelectedIndex(0);
FilesSetDefsPanel.this.fileSizeSpinner.setValue(0);
}
if (dateCondition != null) {
@ -810,7 +806,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
org.openide.awt.Mnemonics.setLocalizedText(fileSizeLabel, org.openide.util.NbBundle.getMessage(FilesSetDefsPanel.class, "FilesSetDefsPanel.fileSizeLabel.text")); // NOI18N
equalitySignComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] { "=", ">", "", "<", "" }));
equalitySignComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] { ">", "<" }));
equalitySignComboBox.setEnabled(false);
fileSizeSpinner.setEnabled(false);

View File

@ -282,7 +282,7 @@
<Component class="javax.swing.JComboBox" name="equalitySymbolComboBox">
<Properties>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="new javax.swing.DefaultComboBoxModel&lt;String&gt;(new String[] { &quot;=&quot;, &quot;&gt;&quot;, &quot;&#x2265;&quot;, &quot;&lt;&quot;, &quot;&#x2264;&quot; })" type="code"/>
<Connection code="new javax.swing.DefaultComboBoxModel&lt;String&gt;(new String[] { &quot;&gt;&quot;, &quot;&lt;&quot; })" type="code"/>
</Property>
<Property name="enabled" type="boolean" value="false"/>
</Properties>

View File

@ -79,10 +79,6 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
if (panelType == FilesSetDefsPanel.PANEL_TYPE.FILE_INGEST_FILTERS) { //Hide the mimetype settings when this is displaying a FileSet rule instead of a interesting item rule
mimeTypeComboBox.setVisible(false);
mimeCheck.setVisible(false);
fileSizeComboBox.setVisible(false);
fileSizeCheck.setVisible(false);
equalitySymbolComboBox.setVisible(false);
fileSizeSpinner.setVisible(false);
jLabel1.setVisible(false);
filesRadioButton.setVisible(false);
dirsRadioButton.setVisible(false);
@ -109,10 +105,6 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
if (panelType == FilesSetDefsPanel.PANEL_TYPE.FILE_INGEST_FILTERS) { //Hide the mimetype settings when this is displaying a FileSet rule instead of a interesting item rule
mimeTypeComboBox.setVisible(false);
mimeCheck.setVisible(false);
fileSizeComboBox.setVisible(false);
fileSizeCheck.setVisible(false);
equalitySymbolComboBox.setVisible(false);
fileSizeSpinner.setVisible(false);
jLabel1.setVisible(false);
filesRadioButton.setVisible(false);
dirsRadioButton.setVisible(false);
@ -120,8 +112,6 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
} else {
populateMimeTypesComboBox();
populateMimeConditionComponents(rule);
populateSizeConditionComponents(rule);
}
populateMimeTypesComboBox();
populateRuleNameComponent(rule);
@ -129,6 +119,7 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
populateNameConditionComponents(rule);
populatePathConditionComponents(rule);
populateDateConditionComponents(rule);
populateSizeConditionComponents(rule);
this.setButtons(okButton, cancelButton);
updateNameTextFieldPrompt();
@ -745,7 +736,7 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
mimeTypeComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] {""}));
mimeTypeComboBox.setEnabled(false);
equalitySymbolComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] { "=", ">", "", "<", "" }));
equalitySymbolComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] { ">", "<" }));
equalitySymbolComboBox.setEnabled(false);
fileSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] { Bundle.FilesSetRulePanel_bytes(), Bundle.FilesSetRulePanel_kiloBytes(), Bundle.FilesSetRulePanel_megaBytes(), Bundle.FilesSetRulePanel_gigaBytes() }));

View File

@ -33,6 +33,8 @@ import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -565,8 +567,13 @@ final class ChromeCacheExtractor {
List<AbstractFile> effFiles = fileManager.findFiles(dataSource, "f_%", cachePath); //NON-NLS
for (AbstractFile abstractFile : effFiles ) {
String cacheKey = cachePath + abstractFile.getName();
if (cachePath.equals(abstractFile.getParentPath()) && abstractFile.isFile()) {
this.externalFilesTable.put(cachePath + abstractFile.getName(), abstractFile);
// Don't overwrite an allocated version with an unallocated version
if (abstractFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)
|| !externalFilesTable.containsKey(cacheKey)) {
this.externalFilesTable.put(cacheKey, abstractFile);
}
}
}
}
@ -590,20 +597,49 @@ final class ChromeCacheExtractor {
return Optional.of(fileCopyCache.get(fileTableKey).getAbstractFile());
}
List<AbstractFile> cacheFiles = fileManager.findFiles(dataSource, cacheFileName, cacheFolderName); //NON-NLS
if (!cacheFiles.isEmpty()) {
for (AbstractFile abstractFile: cacheFiles ) {
if (abstractFile.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)) {
return Optional.of(abstractFile);
// Sort the list for consistency. Preference is:
// - In correct subfolder and allocated
// - In correct subfolder and unallocated
// - In incorrect subfolder and allocated
Collections.sort(cacheFiles, new Comparator<AbstractFile>() {
@Override
public int compare(AbstractFile file1, AbstractFile file2) {
try {
if (file1.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)
&& ! file2.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)) {
return -1;
}
if (file2.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)
&& ! file1.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)) {
return 1;
}
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Error getting unique path for file with ID " + file1.getId() + " or " + file2.getId(), ex);
}
if (file1.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)
&& ! file2.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
return -1;
}
if (file2.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)
&& ! file1.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
return 1;
}
return Long.compare(file1.getId(), file2.getId());
}
}
});
// The best match will be the first element
return Optional.of(cacheFiles.get(0));
}
return Optional.empty();
}
/**
* Finds the "index" file that exists in each user's cache. This is used to
* enumerate all of the caches on the system.