mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 16:06:15 +00:00
Merge branch 'develop' of github.com:sleuthkit/autopsy into 7592-analysisResultsScore
This commit is contained in:
commit
718738832e
@ -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
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
@ -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
|
||||
|
@ -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"/>
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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"/>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
673
Core/src/org/sleuthkit/autopsy/contentviewers/application/Annotations.java
Executable file
673
Core/src/org/sleuthkit/autopsy/contentviewers/application/Annotations.java
Executable 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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.
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<String>(new String[] { "=", ">", "≥", "<", "≤" })" type="code"/>
|
||||
<Connection code="new javax.swing.DefaultComboBoxModel<String>(new String[] { ">", "<" })" type="code"/>
|
||||
</Property>
|
||||
<Property name="enabled" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
|
@ -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);
|
||||
|
@ -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<String>(new String[] { "=", ">", "≥", "<", "≤" })" type="code"/>
|
||||
<Connection code="new javax.swing.DefaultComboBoxModel<String>(new String[] { ">", "<" })" type="code"/>
|
||||
</Property>
|
||||
<Property name="enabled" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
|
@ -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() }));
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user