mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-13 08:26: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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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.casemodule.Case;
|
||||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
||||||
@ -30,7 +30,7 @@ import org.sleuthkit.datamodel.TskDataException;
|
|||||||
/**
|
/**
|
||||||
* Class for populating the Other Occurrences tab
|
* 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
|
// For now hard code the string for the central repo files type, since
|
||||||
// getting it dynamically can fail.
|
// getting it dynamically can fail.
|
||||||
@ -56,7 +56,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
|||||||
* @param type The type of the instance
|
* @param type The type of the instance
|
||||||
* @param value The value 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();
|
caseName = instance.getCorrelationCase().getDisplayName();
|
||||||
deviceID = instance.getCorrelationDataSource().getDeviceID();
|
deviceID = instance.getCorrelationDataSource().getDeviceID();
|
||||||
dataSourceName = instance.getCorrelationDataSource().getName();
|
dataSourceName = instance.getCorrelationDataSource().getName();
|
||||||
@ -77,7 +77,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
|||||||
*
|
*
|
||||||
* @throws CentralRepoException
|
* @throws CentralRepoException
|
||||||
*/
|
*/
|
||||||
OtherOccurrenceNodeInstanceData(AbstractFile newFile, Case autopsyCase) throws CentralRepoException {
|
NodeData(AbstractFile newFile, Case autopsyCase) throws CentralRepoException {
|
||||||
caseName = autopsyCase.getDisplayName();
|
caseName = autopsyCase.getDisplayName();
|
||||||
try {
|
try {
|
||||||
DataSource dataSource = autopsyCase.getSleuthkitCase().getDataSource(newFile.getDataSource().getId());
|
DataSource dataSource = autopsyCase.getSleuthkitCase().getDataSource(newFile.getDataSource().getId());
|
||||||
@ -119,7 +119,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
|||||||
*
|
*
|
||||||
* @param newComment The new comment
|
* @param newComment The new comment
|
||||||
*/
|
*/
|
||||||
void updateComment(String newComment) {
|
public void updateComment(String newComment) {
|
||||||
comment = newComment;
|
comment = newComment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +129,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
|||||||
* @return true if this node was created from a central repo instance, false
|
* @return true if this node was created from a central repo instance, false
|
||||||
* otherwise
|
* otherwise
|
||||||
*/
|
*/
|
||||||
boolean isCentralRepoNode() {
|
public boolean isCentralRepoNode() {
|
||||||
return (originalCorrelationInstance != null);
|
return (originalCorrelationInstance != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +138,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
|||||||
*
|
*
|
||||||
* @return the case name
|
* @return the case name
|
||||||
*/
|
*/
|
||||||
String getCaseName() {
|
public String getCaseName() {
|
||||||
return caseName;
|
return caseName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +147,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
|||||||
*
|
*
|
||||||
* @return the device ID
|
* @return the device ID
|
||||||
*/
|
*/
|
||||||
String getDeviceID() {
|
public String getDeviceID() {
|
||||||
return deviceID;
|
return deviceID;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +156,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
|||||||
*
|
*
|
||||||
* @return the data source name
|
* @return the data source name
|
||||||
*/
|
*/
|
||||||
String getDataSourceName() {
|
public String getDataSourceName() {
|
||||||
return dataSourceName;
|
return dataSourceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,7 +165,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
|||||||
*
|
*
|
||||||
* @return the file path
|
* @return the file path
|
||||||
*/
|
*/
|
||||||
String getFilePath() {
|
public String getFilePath() {
|
||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,7 +174,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
|||||||
*
|
*
|
||||||
* @return the type
|
* @return the type
|
||||||
*/
|
*/
|
||||||
String getType() {
|
public String getType() {
|
||||||
return typeStr;
|
return typeStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +183,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
|||||||
*
|
*
|
||||||
* @return the value
|
* @return the value
|
||||||
*/
|
*/
|
||||||
String getValue() {
|
public String getValue() {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +192,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
|||||||
*
|
*
|
||||||
* @return the known status
|
* @return the known status
|
||||||
*/
|
*/
|
||||||
TskData.FileKnown getKnown() {
|
public TskData.FileKnown getKnown() {
|
||||||
return known;
|
return known;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,7 +201,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
|||||||
*
|
*
|
||||||
* @return the comment
|
* @return the comment
|
||||||
*/
|
*/
|
||||||
String getComment() {
|
public String getComment() {
|
||||||
return comment;
|
return comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,7 +211,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
|||||||
*
|
*
|
||||||
* @return the original abstract file
|
* @return the original abstract file
|
||||||
*/
|
*/
|
||||||
AbstractFile getAbstractFile() throws CentralRepoException {
|
public AbstractFile getAbstractFile() throws CentralRepoException {
|
||||||
if (originalAbstractFile == null) {
|
if (originalAbstractFile == null) {
|
||||||
throw new CentralRepoException("AbstractFile is null");
|
throw new CentralRepoException("AbstractFile is null");
|
||||||
}
|
}
|
||||||
@ -226,7 +226,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
|||||||
*
|
*
|
||||||
* @throws CentralRepoException
|
* @throws CentralRepoException
|
||||||
*/
|
*/
|
||||||
CorrelationAttributeInstance getCorrelationAttributeInstance() throws CentralRepoException {
|
public CorrelationAttributeInstance getCorrelationAttributeInstance() throws CentralRepoException {
|
||||||
if (originalCorrelationInstance == null) {
|
if (originalCorrelationInstance == null) {
|
||||||
throw new CentralRepoException("CorrelationAttributeInstance is null");
|
throw new CentralRepoException("CorrelationAttributeInstance is null");
|
||||||
}
|
}
|
||||||
@ -239,7 +239,7 @@ class OtherOccurrenceNodeInstanceData implements OtherOccurrenceNodeData {
|
|||||||
*
|
*
|
||||||
* @return the CSV_ITEM_SEPARATOR string
|
* @return the CSV_ITEM_SEPARATOR string
|
||||||
*/
|
*/
|
||||||
static String getCsvItemSeparator() {
|
public static String getCsvItemSeparator() {
|
||||||
return CSV_ITEM_SEPARATOR;
|
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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.centralrepository.contentviewer;
|
package org.sleuthkit.autopsy.centralrepository.application;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||||
|
import org.sleuthkit.autopsy.centralrepository.contentviewer.OtherOccurrencesPanel;
|
||||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
|
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
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
|
* Used as a key to ensure we eliminate duplicates from the result set by not
|
||||||
* overwriting CR correlation instances.
|
* overwriting CR correlation instances.
|
||||||
*/
|
*/
|
||||||
final class UniquePathKey {
|
public final class UniquePathKey {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(UniquePathKey.class.getName());
|
private static final Logger logger = Logger.getLogger(UniquePathKey.class.getName());
|
||||||
private final String dataSourceID;
|
private final String dataSourceID;
|
||||||
@ -37,7 +38,7 @@ final class UniquePathKey {
|
|||||||
private final String type;
|
private final String type;
|
||||||
private final String caseUUID;
|
private final String caseUUID;
|
||||||
|
|
||||||
UniquePathKey(OtherOccurrenceNodeInstanceData nodeData) {
|
public UniquePathKey(NodeData nodeData) {
|
||||||
super();
|
super();
|
||||||
dataSourceID = nodeData.getDeviceID();
|
dataSourceID = nodeData.getDeviceID();
|
||||||
if (nodeData.getFilePath() != null) {
|
if (nodeData.getFilePath() != null) {
|
||||||
@ -56,7 +57,7 @@ final class UniquePathKey {
|
|||||||
//place holder value will be used since correlation attribute was unavailble
|
//place holder value will be used since correlation attribute was unavailble
|
||||||
} catch (NoCurrentCaseException ex) {
|
} catch (NoCurrentCaseException ex) {
|
||||||
logger.log(Level.WARNING, "Unable to get current case", ex);
|
logger.log(Level.WARNING, "Unable to get current case", ex);
|
||||||
tempCaseUUID = OtherOccurrencesPanel.getPlaceholderUUID();
|
tempCaseUUID = OtherOccurrences.getPlaceholderUUID();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
caseUUID = tempCaseUUID;
|
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.failed=Failed to get frequency details.
|
||||||
OtherOccurrencesPanel.correlatedArtifacts.isEmpty=There are no files or artifacts to correlate.
|
OtherOccurrencesPanel.correlatedArtifacts.isEmpty=There are no files or artifacts to correlate.
|
||||||
OtherOccurrencesPanel.correlatedArtifacts.title=Attribute Frequency
|
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.toolTipText=
|
||||||
OtherOccurrencesPanel.earliestCaseLabel.text=Central Repository Starting Date:
|
OtherOccurrencesPanel.earliestCaseLabel.text=Central Repository Starting Date:
|
||||||
OtherOccurrencesPanel.earliestCaseDate.text=Earliest Case 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.foundIn.text=Found %d instances in %d cases and %d data sources.
|
||||||
OtherOccurrencesPanel.foundInLabel.text=
|
OtherOccurrencesPanel.foundInLabel.text=
|
||||||
OtherOccurrencesPanel.filesTable.toolTipText=Click column name to sort. Right-click on the table for more options.
|
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.showCaseDetailsMenuItem.text=Show Case Details
|
||||||
OtherOccurrencesPanel.table.noArtifacts=Item has no attributes with which to search.
|
OtherOccurrencesPanel.table.noArtifacts=Item has no attributes with which to search.
|
||||||
OtherOccurrencesPanel.table.noResultsFound=No results found.
|
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_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
<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>
|
</AuxValues>
|
||||||
|
|
||||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
|
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignBorderLayout"/>
|
||||||
|
@ -19,33 +19,18 @@
|
|||||||
package org.sleuthkit.autopsy.centralrepository.contentviewer;
|
package org.sleuthkit.autopsy.centralrepository.contentviewer;
|
||||||
|
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
import java.util.ArrayList;
|
import java.awt.Cursor;
|
||||||
import java.util.Collection;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.List;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import org.openide.nodes.Node;
|
import org.openide.nodes.Node;
|
||||||
import org.openide.util.NbBundle.Messages;
|
import org.openide.util.NbBundle.Messages;
|
||||||
import org.openide.util.lookup.ServiceProvider;
|
import org.openide.util.lookup.ServiceProvider;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.centralrepository.application.OtherOccurrences;
|
||||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
|
||||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
|
|
||||||
import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer;
|
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.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.autopsy.centralrepository.datamodel.CentralRepository;
|
||||||
import org.sleuthkit.datamodel.TskException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View correlation results from other cases
|
* View correlation results from other cases
|
||||||
@ -57,13 +42,10 @@ import org.sleuthkit.datamodel.TskException;
|
|||||||
public final class DataContentViewerOtherCases extends JPanel implements DataContentViewer {
|
public final class DataContentViewerOtherCases extends JPanel implements DataContentViewer {
|
||||||
|
|
||||||
private static final long serialVersionUID = -1L;
|
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();
|
private final OtherOccurrencesPanel otherOccurrencesPanel = new OtherOccurrencesPanel();
|
||||||
|
|
||||||
/**
|
private OtherOccurrencesNodeWorker worker = null;
|
||||||
* Could be null.
|
|
||||||
*/
|
|
||||||
private AbstractFile file; //the file which the content viewer is being populated for
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new form DataContentViewerOtherCases
|
* 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
|
@Override
|
||||||
public boolean isSupported(Node node) {
|
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
|
// - The central repo is enabled and the node has correlatable content
|
||||||
// (either through the MD5 hash of the associated file or through a BlackboardArtifact)
|
// (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
|
// - 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()) {
|
if (CentralRepository.isEnabled()) {
|
||||||
return !getCorrelationAttributesFromNode(node).isEmpty();
|
return !OtherOccurrences.getCorrelationAttributesFromNode(node, file).isEmpty();
|
||||||
} else {
|
} else {
|
||||||
return this.file != null
|
return file != null
|
||||||
&& this.file.getSize() > 0
|
&& file.getSize() > 0
|
||||||
&& ((this.file.getMd5Hash() != null) && (!this.file.getMd5Hash().isEmpty()));
|
&& ((file.getMd5Hash() != null) && (!file.getMd5Hash().isEmpty()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setNode(Node node) {
|
public void setNode(Node node) {
|
||||||
|
|
||||||
otherOccurrencesPanel.reset(); // reset the table to empty.
|
otherOccurrencesPanel.reset(); // reset the table to empty.
|
||||||
|
otherOccurrencesPanel.showPanelLoadingMessage();
|
||||||
|
|
||||||
if (node == null) {
|
if (node == null) {
|
||||||
return;
|
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;
|
package org.sleuthkit.autopsy.centralrepository.contentviewer;
|
||||||
|
|
||||||
|
import org.sleuthkit.autopsy.centralrepository.application.NodeData;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Font;
|
import java.awt.Font;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -50,7 +51,7 @@ final class OccurrencePanel extends javax.swing.JPanel {
|
|||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private int gridY = 0;
|
private int gridY = 0;
|
||||||
private final List<OtherOccurrenceNodeData> nodeDataList;
|
private final List<NodeData> nodeDataList;
|
||||||
private final Map<String, String> caseNamesAndDates = new HashMap<>();
|
private final Map<String, String> caseNamesAndDates = new HashMap<>();
|
||||||
private final Set<String> dataSourceNames = new HashSet<>();
|
private final Set<String> dataSourceNames = new HashSet<>();
|
||||||
private final Set<String> filePaths = 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
|
* @param nodeDataList the list of OtherOccurrenceNodeData representing
|
||||||
* common properties for the file
|
* common properties for the file
|
||||||
*/
|
*/
|
||||||
OccurrencePanel(List<OtherOccurrenceNodeData> nodeDataList) {
|
OccurrencePanel(List<NodeData> nodeDataList) {
|
||||||
this.nodeDataList = nodeDataList;
|
this.nodeDataList = nodeDataList;
|
||||||
customizeComponents();
|
customizeComponents();
|
||||||
}
|
}
|
||||||
@ -148,9 +149,9 @@ final class OccurrencePanel extends javax.swing.JPanel {
|
|||||||
addItemToBag(gridY, 0, TOP_INSET, 0, commonPropertiesLabel);
|
addItemToBag(gridY, 0, TOP_INSET, 0, commonPropertiesLabel);
|
||||||
gridY++;
|
gridY++;
|
||||||
//for each other occurrence
|
//for each other occurrence
|
||||||
for (OtherOccurrenceNodeData occurrence : nodeDataList) {
|
for (NodeData occurrence : nodeDataList) {
|
||||||
if (occurrence instanceof OtherOccurrenceNodeInstanceData) {
|
if (occurrence instanceof NodeData) {
|
||||||
String type = ((OtherOccurrenceNodeInstanceData) occurrence).getType();
|
String type = occurrence.getType();
|
||||||
if (!type.isEmpty()) {
|
if (!type.isEmpty()) {
|
||||||
javax.swing.JLabel typeLabel = new javax.swing.JLabel();
|
javax.swing.JLabel typeLabel = new javax.swing.JLabel();
|
||||||
org.openide.awt.Mnemonics.setLocalizedText(typeLabel, Bundle.OccurrencePanel_commonPropertyTypeLabel_text());
|
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);
|
addItemToBag(gridY, 1, VERTICAL_GAP, 0, typeFieldValue);
|
||||||
gridY++;
|
gridY++;
|
||||||
}
|
}
|
||||||
String value = ((OtherOccurrenceNodeInstanceData) occurrence).getValue();
|
String value = occurrence.getValue();
|
||||||
if (!value.isEmpty()) {
|
if (!value.isEmpty()) {
|
||||||
javax.swing.JLabel valueLabel = new javax.swing.JLabel();
|
javax.swing.JLabel valueLabel = new javax.swing.JLabel();
|
||||||
org.openide.awt.Mnemonics.setLocalizedText(valueLabel, Bundle.OccurrencePanel_commonPropertyValueLabel_text());
|
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);
|
addItemToBag(gridY, 1, 0, 0, valueFieldValue);
|
||||||
gridY++;
|
gridY++;
|
||||||
}
|
}
|
||||||
TskData.FileKnown knownStatus = ((OtherOccurrenceNodeInstanceData) occurrence).getKnown();
|
TskData.FileKnown knownStatus = occurrence.getKnown();
|
||||||
javax.swing.JLabel knownStatusLabel = new javax.swing.JLabel();
|
javax.swing.JLabel knownStatusLabel = new javax.swing.JLabel();
|
||||||
org.openide.awt.Mnemonics.setLocalizedText(knownStatusLabel, Bundle.OccurrencePanel_commonPropertyKnownStatusLabel_text());
|
org.openide.awt.Mnemonics.setLocalizedText(knownStatusLabel, Bundle.OccurrencePanel_commonPropertyKnownStatusLabel_text());
|
||||||
addItemToBag(gridY, 0, 0, 0, knownStatusLabel);
|
addItemToBag(gridY, 0, 0, 0, knownStatusLabel);
|
||||||
@ -181,7 +182,7 @@ final class OccurrencePanel extends javax.swing.JPanel {
|
|||||||
}
|
}
|
||||||
addItemToBag(gridY, 1, 0, 0, knownStatusValue);
|
addItemToBag(gridY, 1, 0, 0, knownStatusValue);
|
||||||
gridY++;
|
gridY++;
|
||||||
String comment = ((OtherOccurrenceNodeInstanceData) occurrence).getComment();
|
String comment = occurrence.getComment();
|
||||||
if (!comment.isEmpty()) {
|
if (!comment.isEmpty()) {
|
||||||
javax.swing.JLabel commentLabel = new javax.swing.JLabel();
|
javax.swing.JLabel commentLabel = new javax.swing.JLabel();
|
||||||
org.openide.awt.Mnemonics.setLocalizedText(commentLabel, Bundle.OccurrencePanel_commonPropertyCommentLabel_text());
|
org.openide.awt.Mnemonics.setLocalizedText(commentLabel, Bundle.OccurrencePanel_commonPropertyCommentLabel_text());
|
||||||
@ -201,10 +202,9 @@ final class OccurrencePanel extends javax.swing.JPanel {
|
|||||||
}
|
}
|
||||||
String caseDate = "";
|
String caseDate = "";
|
||||||
try {
|
try {
|
||||||
OtherOccurrenceNodeInstanceData nodeData = ((OtherOccurrenceNodeInstanceData) occurrence);
|
if (occurrence.isCentralRepoNode()) {
|
||||||
if (nodeData.isCentralRepoNode()) {
|
|
||||||
if (CentralRepository.isEnabled()) {
|
if (CentralRepository.isEnabled()) {
|
||||||
CorrelationCase partialCase = nodeData.getCorrelationAttributeInstance().getCorrelationCase();
|
CorrelationCase partialCase = occurrence.getCorrelationAttributeInstance().getCorrelationCase();
|
||||||
caseDate = CentralRepository.getInstance().getCaseByUUID(partialCase.getCaseUUID()).getCreationDate();
|
caseDate = CentralRepository.getInstance().getCaseByUUID(partialCase.getCaseUUID()).getCreationDate();
|
||||||
}
|
}
|
||||||
} else {
|
} 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);
|
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
|
//Collect the data that is necessary for the other sections
|
||||||
caseNamesAndDates.put(((OtherOccurrenceNodeInstanceData) occurrence).getCaseName(), caseDate);
|
caseNamesAndDates.put(occurrence.getCaseName(), caseDate);
|
||||||
dataSourceNames.add(((OtherOccurrenceNodeInstanceData) occurrence).getDataSourceName());
|
dataSourceNames.add(occurrence.getDataSourceName());
|
||||||
filePaths.add(((OtherOccurrenceNodeInstanceData) occurrence).getFilePath());
|
filePaths.add(occurrence.getFilePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//end for each
|
//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;
|
package org.sleuthkit.autopsy.centralrepository.contentviewer;
|
||||||
|
|
||||||
|
import org.sleuthkit.autopsy.centralrepository.application.NodeData;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -26,6 +27,7 @@ import javax.swing.table.AbstractTableModel;
|
|||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||||
|
import org.sleuthkit.autopsy.centralrepository.application.OtherOccurrences;
|
||||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
|
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
|
||||||
@ -139,11 +141,10 @@ final class OtherOccurrencesDataSourcesTableModel extends AbstractTableModel {
|
|||||||
*
|
*
|
||||||
* @param newNodeData data to add to the table
|
* @param newNodeData data to add to the table
|
||||||
*/
|
*/
|
||||||
void addNodeData(OtherOccurrenceNodeData newNodeData) {
|
void addNodeData(NodeData newNodeData) {
|
||||||
OtherOccurrenceNodeInstanceData nodeData = (OtherOccurrenceNodeInstanceData) newNodeData;
|
|
||||||
String caseUUID;
|
String caseUUID;
|
||||||
try {
|
try {
|
||||||
caseUUID = nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID();
|
caseUUID = newNodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID();
|
||||||
} catch (CentralRepoException ignored) {
|
} catch (CentralRepoException ignored) {
|
||||||
//non central repo nodeData won't have a correlation case
|
//non central repo nodeData won't have a correlation case
|
||||||
try {
|
try {
|
||||||
@ -151,10 +152,10 @@ final class OtherOccurrencesDataSourcesTableModel extends AbstractTableModel {
|
|||||||
//place holder value will be used since correlation attribute was unavailble
|
//place holder value will be used since correlation attribute was unavailble
|
||||||
} catch (NoCurrentCaseException ex) {
|
} catch (NoCurrentCaseException ex) {
|
||||||
logger.log(Level.WARNING, "Unable to get current case", 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();
|
fireTableDataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.sleuthkit.autopsy.centralrepository.contentviewer;
|
package org.sleuthkit.autopsy.centralrepository.contentviewer;
|
||||||
|
|
||||||
|
import org.sleuthkit.autopsy.centralrepository.application.NodeData;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -28,6 +29,7 @@ import org.openide.util.NbBundle.Messages;
|
|||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||||
|
import org.sleuthkit.autopsy.centralrepository.application.OtherOccurrences;
|
||||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
|
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepoException;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
|
||||||
@ -40,7 +42,7 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel {
|
|||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private static final Logger logger = Logger.getLogger(OtherOccurrencesFilesTableModel.class.getName());
|
private static final Logger logger = Logger.getLogger(OtherOccurrencesFilesTableModel.class.getName());
|
||||||
private final List<String> nodeKeys = new ArrayList<>();
|
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
|
* Create a table model for displaying file names
|
||||||
@ -75,7 +77,7 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel {
|
|||||||
|| nodeMap.get(nodeKeys.get(rowIdx)).isEmpty()) {
|
|| nodeMap.get(nodeKeys.get(rowIdx)).isEmpty()) {
|
||||||
return Bundle.OtherOccurrencesFilesTableModel_noData();
|
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
|
* @return a list of OtherOccurrenceNodeData for the specified index or an
|
||||||
* empty list if no data was found
|
* 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 anything would prevent this from working return an empty list
|
||||||
if (nodeMap.isEmpty() || nodeKeys.isEmpty() || rowIdx < 0
|
if (nodeMap.isEmpty() || nodeKeys.isEmpty() || rowIdx < 0
|
||||||
|| rowIdx >= nodeKeys.size() || nodeKeys.get(rowIdx) == null
|
|| rowIdx >= nodeKeys.size() || nodeKeys.get(rowIdx) == null
|
||||||
@ -107,9 +109,9 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel {
|
|||||||
*
|
*
|
||||||
* @param newNodeData data to add to the table
|
* @param newNodeData data to add to the table
|
||||||
*/
|
*/
|
||||||
void addNodeData(OtherOccurrenceNodeData newNodeData) {
|
void addNodeData(NodeData newNodeData) {
|
||||||
String newNodeKey = createNodeKey((OtherOccurrenceNodeInstanceData) newNodeData);//FilenameUtils.getName(((OtherOccurrenceNodeInstanceData)newNodeData).getFilePath());
|
String newNodeKey = createNodeKey(newNodeData);
|
||||||
List<OtherOccurrenceNodeData> nodeList = nodeMap.get(newNodeKey);
|
List<NodeData> nodeList = nodeMap.get(newNodeKey);
|
||||||
if (nodeList == null) {
|
if (nodeList == null) {
|
||||||
nodeKeys.add(newNodeKey);
|
nodeKeys.add(newNodeKey);
|
||||||
nodeList = new ArrayList<>();
|
nodeList = new ArrayList<>();
|
||||||
@ -119,7 +121,7 @@ public class OtherOccurrencesFilesTableModel extends AbstractTableModel {
|
|||||||
fireTableDataChanged();
|
fireTableDataChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createNodeKey(OtherOccurrenceNodeInstanceData nodeData) {
|
private String createNodeKey(NodeData nodeData) {
|
||||||
String caseUUID;
|
String caseUUID;
|
||||||
try {
|
try {
|
||||||
caseUUID = nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID();
|
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
|
//place holder value will be used since correlation attribute was unavailble
|
||||||
} catch (NoCurrentCaseException ex) {
|
} catch (NoCurrentCaseException ex) {
|
||||||
logger.log(Level.WARNING, "Unable to get current case", 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;
|
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;
|
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.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.awt.event.ComponentAdapter;
|
import java.awt.event.ComponentAdapter;
|
||||||
import java.awt.event.ComponentEvent;
|
import java.awt.event.ComponentEvent;
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.File;
|
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.ArrayList;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import javax.swing.JFileChooser;
|
import javax.swing.JFileChooser;
|
||||||
import javax.swing.JMenuItem;
|
import javax.swing.JMenuItem;
|
||||||
@ -45,15 +42,15 @@ import javax.swing.JOptionPane;
|
|||||||
import static javax.swing.JOptionPane.DEFAULT_OPTION;
|
import static javax.swing.JOptionPane.DEFAULT_OPTION;
|
||||||
import static javax.swing.JOptionPane.ERROR_MESSAGE;
|
import static javax.swing.JOptionPane.ERROR_MESSAGE;
|
||||||
import static javax.swing.JOptionPane.PLAIN_MESSAGE;
|
import static javax.swing.JOptionPane.PLAIN_MESSAGE;
|
||||||
|
import javax.swing.SwingWorker;
|
||||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||||
import javax.swing.table.TableModel;
|
import javax.swing.table.TableModel;
|
||||||
import javax.swing.table.TableRowSorter;
|
import javax.swing.table.TableRowSorter;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.openide.util.Exceptions;
|
||||||
import org.joda.time.DateTimeZone;
|
|
||||||
import org.joda.time.LocalDateTime;
|
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
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.CentralRepoException;
|
||||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
|
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
|
||||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
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.centralrepository.datamodel.CorrelationCase;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
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.
|
* Panel for displaying other occurrences results.
|
||||||
*/
|
*/
|
||||||
@NbBundle.Messages({
|
@NbBundle.Messages({
|
||||||
"OtherOccurrencesPanel.table.noArtifacts=Item has no attributes with which to search.",
|
"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 {
|
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_ARTIFACTS_CASE = new CorrelationCaseWrapper(Bundle.OtherOccurrencesPanel_table_noArtifacts());
|
||||||
private static final CorrelationCaseWrapper NO_RESULTS_CASE = new CorrelationCaseWrapper(Bundle.OtherOccurrencesPanel_table_noResultsFound());
|
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 Logger logger = Logger.getLogger(OtherOccurrencesPanel.class.getName());
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private final OtherOccurrencesFilesTableModel filesTableModel;
|
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 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 AbstractFile file = null;
|
||||||
|
|
||||||
|
private SwingWorker<?, ?> worker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new form OtherOccurrencesPanel
|
* Creates new form OtherOccurrencesPanel
|
||||||
*/
|
*/
|
||||||
@ -101,16 +98,6 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
|||||||
customizeComponents();
|
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() {
|
private void customizeComponents() {
|
||||||
ActionListener actList = (ActionEvent e) -> {
|
ActionListener actList = (ActionEvent e) -> {
|
||||||
JMenuItem jmi = (JMenuItem) e.getSource();
|
JMenuItem jmi = (JMenuItem) e.getSource();
|
||||||
@ -182,14 +169,16 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
|||||||
*/
|
*/
|
||||||
private void showCommonalityDetails() {
|
private void showCommonalityDetails() {
|
||||||
if (correlationAttributes.isEmpty()) {
|
if (correlationAttributes.isEmpty()) {
|
||||||
JOptionPane.showConfirmDialog(showCommonalityMenuItem,
|
JOptionPane.showConfirmDialog(OtherOccurrencesPanel.this,
|
||||||
Bundle.OtherOccurrencesPanel_correlatedArtifacts_isEmpty(),
|
Bundle.OtherOccurrencesPanel_correlatedArtifacts_isEmpty(),
|
||||||
Bundle.OtherOccurrencesPanel_correlatedArtifacts_title(),
|
Bundle.OtherOccurrencesPanel_correlatedArtifacts_title(),
|
||||||
DEFAULT_OPTION, PLAIN_MESSAGE);
|
DEFAULT_OPTION, PLAIN_MESSAGE);
|
||||||
} else {
|
} else {
|
||||||
StringBuilder msg = new StringBuilder(correlationAttributes.size());
|
StringBuilder msg = new StringBuilder(correlationAttributes.size());
|
||||||
int percentage;
|
int percentage;
|
||||||
|
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||||
try {
|
try {
|
||||||
|
// Leaving these calls on the EDT but adding wait cursor
|
||||||
CentralRepository dbManager = CentralRepository.getInstance();
|
CentralRepository dbManager = CentralRepository.getInstance();
|
||||||
for (CorrelationAttributeInstance eamArtifact : correlationAttributes) {
|
for (CorrelationAttributeInstance eamArtifact : correlationAttributes) {
|
||||||
try {
|
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);
|
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(),
|
msg.toString(),
|
||||||
Bundle.OtherOccurrencesPanel_correlatedArtifacts_title(),
|
Bundle.OtherOccurrencesPanel_correlatedArtifacts_title(),
|
||||||
DEFAULT_OPTION, PLAIN_MESSAGE);
|
DEFAULT_OPTION, PLAIN_MESSAGE);
|
||||||
} catch (CentralRepoException ex) {
|
} catch (CentralRepoException ex) {
|
||||||
|
this.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||||
logger.log(Level.SEVERE, "Error getting commonality details.", ex);
|
logger.log(Level.SEVERE, "Error getting commonality details.", ex);
|
||||||
JOptionPane.showConfirmDialog(showCommonalityMenuItem,
|
JOptionPane.showConfirmDialog(OtherOccurrencesPanel.this,
|
||||||
Bundle.OtherOccurrencesPanel_correlatedArtifacts_failed(),
|
Bundle.OtherOccurrencesPanel_correlatedArtifacts_failed(),
|
||||||
Bundle.OtherOccurrencesPanel_correlatedArtifacts_title(),
|
Bundle.OtherOccurrencesPanel_correlatedArtifacts_title(),
|
||||||
DEFAULT_OPTION, ERROR_MESSAGE);
|
DEFAULT_OPTION, ERROR_MESSAGE);
|
||||||
@ -227,10 +218,9 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
|||||||
if (-1 != selectedRowViewIdx) {
|
if (-1 != selectedRowViewIdx) {
|
||||||
CentralRepository dbManager = CentralRepository.getInstance();
|
CentralRepository dbManager = CentralRepository.getInstance();
|
||||||
int selectedRowModelIdx = filesTable.convertRowIndexToModel(selectedRowViewIdx);
|
int selectedRowModelIdx = filesTable.convertRowIndexToModel(selectedRowViewIdx);
|
||||||
List<OtherOccurrenceNodeData> rowList = filesTableModel.getListOfNodesForFile(selectedRowModelIdx);
|
List<NodeData> rowList = filesTableModel.getListOfNodesForFile(selectedRowModelIdx);
|
||||||
if (!rowList.isEmpty()) {
|
if (!rowList.isEmpty()) {
|
||||||
if (rowList.get(0) instanceof OtherOccurrenceNodeInstanceData) {
|
CorrelationCase eamCasePartial = rowList.get(0).getCorrelationAttributeInstance().getCorrelationCase();
|
||||||
CorrelationCase eamCasePartial = ((OtherOccurrenceNodeInstanceData) rowList.get(0)).getCorrelationAttributeInstance().getCorrelationCase();
|
|
||||||
caseDisplayName = eamCasePartial.getDisplayName();
|
caseDisplayName = eamCasePartial.getDisplayName();
|
||||||
// query case details
|
// query case details
|
||||||
CorrelationCase eamCase = dbManager.getCaseByUUID(eamCasePartial.getCaseUUID());
|
CorrelationCase eamCase = dbManager.getCaseByUUID(eamCasePartial.getCaseUUID());
|
||||||
@ -239,9 +229,6 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
|||||||
} else {
|
} else {
|
||||||
details = Bundle.OtherOccurrencesPanel_caseDetailsDialog_noDetails();
|
details = Bundle.OtherOccurrencesPanel_caseDetailsDialog_noDetails();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
details = Bundle.OtherOccurrencesPanel_caseDetailsDialog_notSelected();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
details = Bundle.OtherOccurrencesPanel_caseDetailsDialog_noDetailsReference();
|
details = Bundle.OtherOccurrencesPanel_caseDetailsDialog_noDetailsReference();
|
||||||
}
|
}
|
||||||
@ -249,7 +236,7 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
|||||||
} catch (CentralRepoException ex) {
|
} catch (CentralRepoException ex) {
|
||||||
logger.log(Level.SEVERE, "Error loading case details", ex);
|
logger.log(Level.SEVERE, "Error loading case details", ex);
|
||||||
} finally {
|
} finally {
|
||||||
JOptionPane.showConfirmDialog(showCaseDetailsMenuItem,
|
JOptionPane.showConfirmDialog(OtherOccurrencesPanel.this,
|
||||||
details,
|
details,
|
||||||
caseDisplayName,
|
caseDisplayName,
|
||||||
DEFAULT_OPTION, PLAIN_MESSAGE);
|
DEFAULT_OPTION, PLAIN_MESSAGE);
|
||||||
@ -271,85 +258,13 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
|||||||
if (!selectedFile.getName().endsWith(".csv")) { // NON-NLS
|
if (!selectedFile.getName().endsWith(".csv")) { // NON-NLS
|
||||||
selectedFile = new File(selectedFile.toString() + ".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({
|
@NbBundle.Messages({"OtherOccurrencesPanel_earliestCaseNotAvailable=Not Availble."})
|
||||||
"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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the UI and clear cached data.
|
* Reset the UI and clear cached data.
|
||||||
*/
|
*/
|
||||||
@ -371,62 +286,32 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
|||||||
* Populate the other occurrences table for one Correlation Attribute type
|
* Populate the other occurrences table for one Correlation Attribute type
|
||||||
* and value.
|
* and value.
|
||||||
*
|
*
|
||||||
|
* This method contains its own SwingWorker togather data.
|
||||||
|
*
|
||||||
* @param aType The correlation attribute type to display other occurrences
|
* @param aType The correlation attribute type to display other occurrences
|
||||||
* for.
|
* for.
|
||||||
* @param value The value being correlated on.
|
* @param value The value being correlated on.
|
||||||
*/
|
*/
|
||||||
public void populateTableForOneType(CorrelationAttributeInstance.Type aType, String value) {
|
public void populateTableForOneType(CorrelationAttributeInstance.Type aType, String value) throws CentralRepoException {
|
||||||
Map<String, CorrelationCase> caseNames = new HashMap<>();
|
if (worker != null) {
|
||||||
int totalCount = 0;
|
worker.cancel(true);
|
||||||
Set<String> dataSources = new HashSet<>();
|
worker = null;
|
||||||
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) {
|
|
||||||
|
|
||||||
// Only add the attribute if it isn't the object the user selected.
|
casesTableModel.addCorrelationCase(NO_ARTIFACTS_CASE);
|
||||||
// We consider it to be a different object if at least one of the following is true:
|
|
||||||
// - the case UUID is different
|
worker = new OtherOccurrenceOneTypeWorker(aType, value, file, deviceId, dataSourceName) {
|
||||||
// - the data source name is different
|
@Override
|
||||||
// - the data source device ID is different
|
public void done() {
|
||||||
// - 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;
|
|
||||||
}
|
|
||||||
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 {
|
try {
|
||||||
dataSources.add(makeDataSourceString(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getDeviceID(), nodeData.getDataSourceName()));
|
if (isCancelled()) {
|
||||||
caseNames.put(nodeData.getCorrelationAttributeInstance().getCorrelationCase().getCaseUUID(), nodeData.getCorrelationAttributeInstance().getCorrelationCase());
|
return;
|
||||||
} catch (CentralRepoException ex) {
|
|
||||||
logger.log(Level.WARNING, "Unable to get correlation case for displaying other occurrence for case: " + nodeData.getCaseName(), ex);
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
try {
|
casesTableModel.clearTable();
|
||||||
dataSources.add(makeDataSourceString(Case.getCurrentCaseThrows().getName(), nodeData.getDeviceID(), nodeData.getDataSourceName()));
|
|
||||||
caseNames.put(Case.getCurrentCaseThrows().getName(), new CorrelationCase(Case.getCurrentCaseThrows().getName(), Case.getCurrentCaseThrows().getDisplayName()));
|
OtherOccurrenceOneTypeWorker.OneTypeData data = get();
|
||||||
} catch (NoCurrentCaseException ex) {
|
for (CorrelationCase corCase : data.getCaseNames().values()) {
|
||||||
logger.log(Level.WARNING, "No current case open for other occurrences", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
totalCount++;
|
|
||||||
}
|
|
||||||
} 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));
|
casesTableModel.addCorrelationCase(new CorrelationCaseWrapper(corCase));
|
||||||
}
|
}
|
||||||
int caseCount = casesTableModel.getRowCount();
|
int caseCount = casesTableModel.getRowCount();
|
||||||
@ -435,59 +320,48 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
|||||||
} else if (caseCount == 0) {
|
} else if (caseCount == 0) {
|
||||||
casesTableModel.addCorrelationCase(NO_RESULTS_CASE);
|
casesTableModel.addCorrelationCase(NO_RESULTS_CASE);
|
||||||
}
|
}
|
||||||
setEarliestCaseDate();
|
String earliestDate = data.getEarliestCaseDate();
|
||||||
foundInLabel.setText(String.format(Bundle.OtherOccurrencesPanel_foundIn_text(), totalCount, caseCount, dataSources.size()));
|
earliestCaseDate.setText(earliestDate.isEmpty() ? Bundle.OtherOccurrencesPanel_earliestCaseNotAvailable() : earliestDate);
|
||||||
|
foundInLabel.setText(String.format(Bundle.OtherOccurrencesPanel_foundIn_text(), data.getTotalCount(), caseCount, data.getDataSourceCount()));
|
||||||
if (caseCount > 0) {
|
if (caseCount > 0) {
|
||||||
casesTable.setRowSelectionInterval(0, 0);
|
casesTable.setRowSelectionInterval(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} catch (InterruptedException | ExecutionException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Failed to update OtherOccurrence panel", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
* Load the correlatable data into the table model. If there is no data
|
||||||
* available display the message on the status panel.
|
* available display the message on the status panel.
|
||||||
*
|
*
|
||||||
* @param correlationAttrs The correlationAttributes to correlate on.
|
* @param data A data wrapper object.
|
||||||
* @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.
|
|
||||||
*/
|
*/
|
||||||
@NbBundle.Messages({
|
@NbBundle.Messages({
|
||||||
"OtherOccurrencesPanel.foundIn.text=Found %d instances in %d cases and %d data sources."
|
"OtherOccurrencesPanel.foundIn.text=Found %d instances in %d cases and %d data sources."
|
||||||
})
|
})
|
||||||
void populateTable(Collection<CorrelationAttributeInstance> correlationAttrs, String dataSourceName, String deviceId, AbstractFile abstractFile) {
|
void populateTable(OtherOccurrencesData data) {
|
||||||
this.file = abstractFile;
|
this.file = data.getFile();
|
||||||
this.dataSourceName = dataSourceName;
|
this.dataSourceName = data.getDataSourceName();
|
||||||
this.deviceId = deviceId;
|
this.deviceId = data.getDeviceId();
|
||||||
|
|
||||||
// get the attributes we can correlate on
|
casesTableModel.clearTable();
|
||||||
correlationAttributes.addAll(correlationAttrs);
|
|
||||||
Map<String, CorrelationCase> caseNames = new HashMap<>();
|
correlationAttributes.addAll(data.getCorrelationAttributes());
|
||||||
int totalCount = 0;
|
|
||||||
Set<String> dataSources = new HashSet<>();
|
for (CorrelationCase corCase : data.getCaseMap().values()) {
|
||||||
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.addCorrelationCase(new CorrelationCaseWrapper(corCase));
|
casesTableModel.addCorrelationCase(new CorrelationCaseWrapper(corCase));
|
||||||
}
|
}
|
||||||
int caseCount = casesTableModel.getRowCount();
|
int caseCount = casesTableModel.getRowCount();
|
||||||
@ -496,172 +370,49 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
|||||||
} else if (caseCount == 0) {
|
} else if (caseCount == 0) {
|
||||||
casesTableModel.addCorrelationCase(NO_RESULTS_CASE);
|
casesTableModel.addCorrelationCase(NO_RESULTS_CASE);
|
||||||
}
|
}
|
||||||
setEarliestCaseDate();
|
String earliestDate = data.getEarliestCaseDate();
|
||||||
foundInLabel.setText(String.format(Bundle.OtherOccurrencesPanel_foundIn_text(), totalCount, caseCount, dataSources.size()));
|
earliestCaseDate.setText(earliestDate.isEmpty() ? Bundle.OtherOccurrencesPanel_earliestCaseNotAvailable() : earliestDate);
|
||||||
|
foundInLabel.setText(String.format(Bundle.OtherOccurrencesPanel_foundIn_text(), data.getInstanceDataCount(), caseCount, data.getDataSourceCount()));
|
||||||
if (caseCount > 0) {
|
if (caseCount > 0) {
|
||||||
casesTable.setRowSelectionInterval(0, 0);
|
casesTable.setRowSelectionInterval(0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query the central repo database (if enabled) and the case database to
|
* Updates displayed information to be correct for the current case
|
||||||
* find all artifact instances correlated to the given central repository
|
* selection
|
||||||
* 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
|
|
||||||
*/
|
*/
|
||||||
private void updateOnCaseSelection() {
|
private void updateOnCaseSelection() {
|
||||||
int[] selectedCaseIndexes = casesTable.getSelectedRows();
|
if (worker != null) {
|
||||||
|
worker.cancel(true);
|
||||||
|
worker = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int[] selectedCaseIndexes = casesTable.getSelectedRows();
|
||||||
dataSourcesTableModel.clearTable();
|
dataSourcesTableModel.clearTable();
|
||||||
filesTableModel.clearTable();
|
filesTableModel.clearTable();
|
||||||
|
|
||||||
if (selectedCaseIndexes.length == 0) {
|
if (selectedCaseIndexes.length == 0) {
|
||||||
//special case when no cases are selected
|
//special case when no cases are selected
|
||||||
occurrencePanel = new OccurrencePanel();
|
occurrencePanel = new OccurrencePanel();
|
||||||
occurrencePanel.getPreferredSize();
|
occurrencePanel.getPreferredSize();
|
||||||
detailsPanelScrollPane.setViewportView(occurrencePanel);
|
detailsPanelScrollPane.setViewportView(occurrencePanel);
|
||||||
} else {
|
|
||||||
|
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;
|
String currentCaseName;
|
||||||
try {
|
try {
|
||||||
currentCaseName = Case.getCurrentCaseThrows().getName();
|
currentCaseName = Case.getCurrentCaseThrows().getName();
|
||||||
@ -669,12 +420,8 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
|||||||
currentCaseName = null;
|
currentCaseName = null;
|
||||||
logger.log(Level.WARNING, "Unable to get current case for other occurrences content viewer", ex);
|
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
|
for (NodeData nodeData : correlatedNodeDataMap.values()) {
|
||||||
correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr));
|
|
||||||
for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) {
|
|
||||||
for (int selectedRow : selectedCaseIndexes) {
|
for (int selectedRow : selectedCaseIndexes) {
|
||||||
try {
|
try {
|
||||||
if (nodeData.isCentralRepoNode()) {
|
if (nodeData.isCentralRepoNode()) {
|
||||||
@ -690,26 +437,46 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (dataSourcesTable.getRowCount() > 0) {
|
if (dataSourcesTable.getRowCount() > 0) {
|
||||||
dataSourcesTable.setRowSelectionInterval(0, 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
* selection
|
||||||
*/
|
*/
|
||||||
private void updateOnDataSourceSelection() {
|
private void updateOnDataSourceSelection() {
|
||||||
int[] selectedDataSources = dataSourcesTable.getSelectedRows();
|
if (worker != null) {
|
||||||
filesTableModel.clearTable();
|
worker.cancel(true);
|
||||||
for (CorrelationAttributeInstance corAttr : correlationAttributes) {
|
worker = null;
|
||||||
Map<UniquePathKey, OtherOccurrenceNodeInstanceData> correlatedNodeDataMap = new HashMap<>(0);
|
}
|
||||||
|
|
||||||
// get correlation and reference set instances from DB
|
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||||
correlatedNodeDataMap.putAll(getCorrelatedInstances(corAttr));
|
|
||||||
for (OtherOccurrenceNodeInstanceData nodeData : correlatedNodeDataMap.values()) {
|
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) {
|
for (int selectedDataSourceRow : selectedDataSources) {
|
||||||
try {
|
try {
|
||||||
if (nodeData.isCentralRepoNode()) {
|
if (nodeData.isCentralRepoNode()) {
|
||||||
@ -727,10 +494,18 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (filesTable.getRowCount() > 0) {
|
if (filesTable.getRowCount() > 0) {
|
||||||
filesTable.setRowSelectionInterval(0, 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
worker.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -738,6 +513,8 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
|||||||
* currently selected File
|
* currently selected File
|
||||||
*/
|
*/
|
||||||
private void updateOnFileSelection() {
|
private void updateOnFileSelection() {
|
||||||
|
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||||
|
try {
|
||||||
if (filesTable.getSelectedRowCount() == 1) {
|
if (filesTable.getSelectedRowCount() == 1) {
|
||||||
//if there is one file selected update the deatils to show the data for that file
|
//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())));
|
occurrencePanel = new OccurrencePanel(filesTableModel.getListOfNodesForFile(filesTable.convertRowIndexToModel(filesTable.getSelectedRow())));
|
||||||
@ -774,6 +551,9 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
|||||||
//calling getPreferredSize has a side effect of ensuring it has a preferred size which reflects the contents which are visible
|
//calling getPreferredSize has a side effect of ensuring it has a preferred size which reflects the contents which are visible
|
||||||
occurrencePanel.getPreferredSize();
|
occurrencePanel.getPreferredSize();
|
||||||
detailsPanelScrollPane.setViewportView(occurrencePanel);
|
detailsPanelScrollPane.setViewportView(occurrencePanel);
|
||||||
|
} finally {
|
||||||
|
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -802,6 +582,95 @@ public final class OtherOccurrencesPanel extends javax.swing.JPanel {
|
|||||||
return "";
|
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.
|
* 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
|
* 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;
|
boolean enableCentralRepoActions = false;
|
||||||
if (CentralRepository.isEnabled() && filesTable.getSelectedRowCount() == 1) {
|
if (CentralRepository.isEnabled() && filesTable.getSelectedRowCount() == 1) {
|
||||||
int rowIndex = filesTable.getSelectedRow();
|
int rowIndex = filesTable.getSelectedRow();
|
||||||
List<OtherOccurrenceNodeData> selectedFile = filesTableModel.getListOfNodesForFile(rowIndex);
|
List<NodeData> selectedFile = filesTableModel.getListOfNodesForFile(rowIndex);
|
||||||
if (!selectedFile.isEmpty() && selectedFile.get(0) instanceof OtherOccurrenceNodeInstanceData) {
|
if (!selectedFile.isEmpty() && selectedFile.get(0) instanceof NodeData) {
|
||||||
OtherOccurrenceNodeInstanceData instanceData = (OtherOccurrenceNodeInstanceData) selectedFile.get(0);
|
NodeData instanceData = selectedFile.get(0);
|
||||||
enableCentralRepoActions = instanceData.isCentralRepoNode();
|
enableCentralRepoActions = instanceData.isCentralRepoNode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,21 +21,25 @@
|
|||||||
<Layout>
|
<Layout>
|
||||||
<DimensionLayout dim="0">
|
<DimensionLayout dim="0">
|
||||||
<Group type="103" groupAlignment="0" attributes="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>
|
</Group>
|
||||||
</DimensionLayout>
|
</DimensionLayout>
|
||||||
<DimensionLayout dim="1">
|
<DimensionLayout dim="1">
|
||||||
<Group type="103" groupAlignment="0" attributes="0">
|
<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>
|
</Group>
|
||||||
</DimensionLayout>
|
</DimensionLayout>
|
||||||
</Layout>
|
</Layout>
|
||||||
<SubComponents>
|
<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"/>
|
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||||
<SubComponents>
|
<SubComponents>
|
||||||
<Component class="javax.swing.JTextPane" name="jTextPane1">
|
<Component class="javax.swing.JTextPane" name="textPanel">
|
||||||
<Properties>
|
<Properties>
|
||||||
<Property name="editable" type="boolean" value="false"/>
|
<Property name="editable" type="boolean" value="false"/>
|
||||||
<Property name="name" type="java.lang.String" value="" noResource="true"/>
|
<Property name="name" type="java.lang.String" value="" noResource="true"/>
|
||||||
|
@ -19,43 +19,23 @@
|
|||||||
package org.sleuthkit.autopsy.contentviewers;
|
package org.sleuthkit.autopsy.contentviewers;
|
||||||
|
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
import java.util.ArrayList;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
|
import javax.swing.SwingWorker;
|
||||||
import javax.swing.text.EditorKit;
|
import javax.swing.text.EditorKit;
|
||||||
import javax.swing.text.html.HTMLEditorKit;
|
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.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.nodes.Node;
|
||||||
import org.openide.util.lookup.ServiceProvider;
|
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.autopsy.corecomponentinterfaces.DataContentViewer;
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
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.datamodel.TskCoreException;
|
||||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
|
import org.sleuthkit.autopsy.contentviewers.application.Annotations;
|
||||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.jsoup.Jsoup;
|
|
||||||
import org.jsoup.nodes.Document;
|
import org.jsoup.nodes.Document;
|
||||||
import org.jsoup.nodes.Element;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotations view of file contents.
|
* Annotations view of file contents.
|
||||||
@ -65,90 +45,10 @@ import org.jsoup.nodes.Element;
|
|||||||
@Messages({
|
@Messages({
|
||||||
"AnnotationsContentViewer.title=Annotations",
|
"AnnotationsContentViewer.title=Annotations",
|
||||||
"AnnotationsContentViewer.toolTip=Displays tags and comments associated with the selected content.",
|
"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."
|
"AnnotationsContentViewer.onEmpty=No annotations were found for this particular item."
|
||||||
})
|
})
|
||||||
public class AnnotationsContentViewer extends javax.swing.JPanel implements DataContentViewer {
|
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();
|
private static final int DEFAULT_FONT_SIZE = new JLabel().getFont().getSize();
|
||||||
|
|
||||||
// how big the subheader should be
|
// 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;
|
private static final int DEFAULT_SUBSECTION_LEFT_PAD = DEFAULT_FONT_SIZE;
|
||||||
|
|
||||||
// spacing occurring after an item
|
// 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_SECTION_SPACING = DEFAULT_FONT_SIZE * 2;
|
||||||
private static final int DEFAULT_SUBSECTION_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;
|
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
|
// additional styling for components
|
||||||
private static final String STYLE_SHEET_RULE
|
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; } ",
|
+ String.format(" .%s {font-size:%dpx;font-weight:bold; margin: 0px; margin-top: %dpx; padding: 0px; } ",
|
||||||
SUBHEADER_CLASSNAME, SUBHEADER_FONT_SIZE, DEFAULT_SUBSECTION_SPACING)
|
Annotations.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)
|
+ 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(" 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(" 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: %dpx 0px; padding-left: %dpx; } ", Annotations.SUBSECTION_CLASSNAME, DEFAULT_SUBSECTION_SPACING, DEFAULT_SUBSECTION_LEFT_PAD)
|
||||||
+ String.format(" .%s { margin-bottom: %dpx; } ", SECTION_CLASSNAME, DEFAULT_SECTION_SPACING);
|
+ String.format(" .%s { margin-bottom: %dpx; } ", Annotations.SECTION_CLASSNAME, DEFAULT_SECTION_SPACING);
|
||||||
|
|
||||||
// describing table values for a tag
|
private static final long serialVersionUID = 1L;
|
||||||
private static final List<ItemEntry<Tag>> TAG_ENTRIES = Arrays.asList(
|
private static final Logger logger = Logger.getLogger(AnnotationsContentViewer.class.getName());
|
||||||
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
|
private AnnotationWorker worker;
|
||||||
= 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);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of AnnotationsContentViewer.
|
* Creates an instance of AnnotationsContentViewer.
|
||||||
*/
|
*/
|
||||||
public AnnotationsContentViewer() {
|
public AnnotationsContentViewer() {
|
||||||
initComponents();
|
initComponents();
|
||||||
Utilities.configureTextPaneAsHtml(jTextPane1);
|
Utilities.configureTextPaneAsHtml(textPanel);
|
||||||
// get html editor kit and apply additional style rules
|
// get html editor kit and apply additional style rules
|
||||||
EditorKit editorKit = jTextPane1.getEditorKit();
|
EditorKit editorKit = textPanel.getEditorKit();
|
||||||
if (editorKit instanceof HTMLEditorKit) {
|
if (editorKit instanceof HTMLEditorKit) {
|
||||||
HTMLEditorKit htmlKit = (HTMLEditorKit) editorKit;
|
HTMLEditorKit htmlKit = (HTMLEditorKit) editorKit;
|
||||||
htmlKit.getStyleSheet().addRule(STYLE_SHEET_RULE);
|
htmlKit.getStyleSheet().addRule(STYLE_SHEET_RULE);
|
||||||
@ -240,472 +97,21 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setNode(Node node) {
|
public void setNode(Node node) {
|
||||||
if ((node == null) || (!isSupported(node))) {
|
|
||||||
resetComponent();
|
resetComponent();
|
||||||
|
|
||||||
|
if(worker != null) {
|
||||||
|
worker.cancel(true);
|
||||||
|
worker = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(node == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Document html = Jsoup.parse(EMPTY_HTML);
|
worker = new AnnotationWorker(node);
|
||||||
Element body = html.getElementsByTag("body").first();
|
worker.execute();
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
* 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
|
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||||
private void initComponents() {
|
private void initComponents() {
|
||||||
|
|
||||||
jScrollPane5 = new javax.swing.JScrollPane();
|
javax.swing.JScrollPane scrollPane = new javax.swing.JScrollPane();
|
||||||
jTextPane1 = new javax.swing.JTextPane();
|
textPanel = new javax.swing.JTextPane();
|
||||||
|
|
||||||
setPreferredSize(new java.awt.Dimension(100, 58));
|
setPreferredSize(new java.awt.Dimension(100, 58));
|
||||||
|
|
||||||
jTextPane1.setEditable(false);
|
textPanel.setEditable(false);
|
||||||
jTextPane1.setName(""); // NOI18N
|
textPanel.setName(""); // NOI18N
|
||||||
jTextPane1.setPreferredSize(new java.awt.Dimension(600, 52));
|
textPanel.setPreferredSize(new java.awt.Dimension(600, 52));
|
||||||
jScrollPane5.setViewportView(jTextPane1);
|
scrollPane.setViewportView(textPanel);
|
||||||
|
|
||||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||||
this.setLayout(layout);
|
this.setLayout(layout);
|
||||||
layout.setHorizontalGroup(
|
layout.setHorizontalGroup(
|
||||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
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.setVerticalGroup(
|
||||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
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
|
}// </editor-fold>//GEN-END:initComponents
|
||||||
|
|
||||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||||
private javax.swing.JScrollPane jScrollPane5;
|
private javax.swing.JTextPane textPanel;
|
||||||
private javax.swing.JTextPane jTextPane1;
|
|
||||||
// End of variables declaration//GEN-END:variables
|
// End of variables declaration//GEN-END:variables
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -793,6 +198,49 @@ public class AnnotationsContentViewer extends javax.swing.JPanel implements Data
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resetComponent() {
|
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.
|
# 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.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.title=Annotations
|
||||||
AnnotationsContentViewer.toolTip=Displays tags and comments associated with the selected content.
|
AnnotationsContentViewer.toolTip=Displays tags and comments associated with the selected content.
|
||||||
ApplicationContentViewer.title=Application
|
ApplicationContentViewer.title=Application
|
||||||
@ -85,6 +70,7 @@ MediaViewVideoPanel.progressLabel.text=00:00
|
|||||||
MediaViewVideoPanel.infoLabel.text=info
|
MediaViewVideoPanel.infoLabel.text=info
|
||||||
MediaViewImagePanel.imgFileTooLarge.msg=Could not load image file (too large): {0}
|
MediaViewImagePanel.imgFileTooLarge.msg=Could not load image file (too large): {0}
|
||||||
|
|
||||||
|
Metadata.nodeText.loading=Metadata loading...
|
||||||
Metadata.nodeText.none=None
|
Metadata.nodeText.none=None
|
||||||
Metadata.nodeText.truncated=(results truncated)
|
Metadata.nodeText.truncated=(results truncated)
|
||||||
Metadata.nodeText.unknown=Unknown
|
Metadata.nodeText.unknown=Unknown
|
||||||
|
@ -19,10 +19,14 @@
|
|||||||
package org.sleuthkit.autopsy.contentviewers;
|
package org.sleuthkit.autopsy.contentviewers;
|
||||||
|
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
|
import java.awt.Cursor;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
import javax.swing.SwingWorker;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.openide.nodes.Node;
|
import org.openide.nodes.Node;
|
||||||
|
import org.openide.util.Exceptions;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.openide.util.NbBundle.Messages;
|
import org.openide.util.NbBundle.Messages;
|
||||||
import org.openide.util.lookup.ServiceProvider;
|
import org.openide.util.lookup.ServiceProvider;
|
||||||
@ -51,6 +55,8 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
|
|||||||
|
|
||||||
private static final Logger LOGGER = Logger.getLogger(Metadata.class.getName());
|
private static final Logger LOGGER = Logger.getLogger(Metadata.class.getName());
|
||||||
|
|
||||||
|
private MetaDataWorker worker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new form Metadata
|
* Creates new form Metadata
|
||||||
*/
|
*/
|
||||||
@ -145,173 +151,54 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
|
|||||||
"Metadata.tableRowTitle.acquisitionDetails=Acquisition Details",
|
"Metadata.tableRowTitle.acquisitionDetails=Acquisition Details",
|
||||||
"Metadata.tableRowTitle.downloadSource=Downloaded From",
|
"Metadata.tableRowTitle.downloadSource=Downloaded From",
|
||||||
"Metadata.nodeText.unknown=Unknown",
|
"Metadata.nodeText.unknown=Unknown",
|
||||||
"Metadata.nodeText.none=None"})
|
"Metadata.nodeText.none=None",
|
||||||
|
"Metadata.nodeText.loading=Metadata loading..."})
|
||||||
@Override
|
@Override
|
||||||
public void setNode(Node node) {
|
public void setNode(Node node) {
|
||||||
AbstractFile file = node.getLookup().lookup(AbstractFile.class);
|
|
||||||
Image image = node.getLookup().lookup(Image.class);
|
if (worker != null) {
|
||||||
DataSource dataSource = node.getLookup().lookup(DataSource.class);
|
worker.cancel(true);
|
||||||
if (file == null && image == null) {
|
worker = null;
|
||||||
setText(NbBundle.getMessage(this.getClass(), "Metadata.nodeText.nonFilePassedIn"));
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
if (node != null) {
|
||||||
startTable(sb);
|
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||||
|
setText(Bundle.Metadata_nodeText_loading());
|
||||||
if (file != null) {
|
worker = new MetaDataWorker(node) {
|
||||||
|
@Override
|
||||||
|
public void done() {
|
||||||
try {
|
try {
|
||||||
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.name"), file.getUniquePath());
|
if (!isCancelled()) {
|
||||||
} catch (TskCoreException ex) {
|
setText(get());
|
||||||
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"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setText(sb.toString());
|
|
||||||
jTextPane1.setCaretPosition(0);
|
jTextPane1.setCaretPosition(0);
|
||||||
this.setCursor(null);
|
}
|
||||||
|
} catch (InterruptedException | ExecutionException ex) {
|
||||||
|
LOGGER.log(Level.SEVERE, "Failed to get metaData for node " + node.getName(), ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
worker.execute();
|
||||||
|
} else {
|
||||||
|
setText("");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a row for download source from the given associated artifact,
|
* Adds a row for download source from the given associated artifact, if the
|
||||||
* if the associated artifacts specifies a source.
|
* associated artifacts specifies a source.
|
||||||
*
|
*
|
||||||
* @param sb string builder.
|
* @param sb string builder.
|
||||||
* @param associatedArtifact
|
* @param associatedArtifact
|
||||||
*
|
*
|
||||||
* @throws TskCoreException if there is an error
|
* @throws TskCoreException if there is an error
|
||||||
*/
|
*/
|
||||||
private void addDownloadSourceRow(StringBuilder sb, BlackboardArtifact associatedArtifact ) throws TskCoreException {
|
private void addDownloadSourceRow(StringBuilder sb, BlackboardArtifact associatedArtifact) throws TskCoreException {
|
||||||
if (associatedArtifact != null &&
|
if (associatedArtifact != null
|
||||||
((associatedArtifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID()) ||
|
&& ((associatedArtifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID())
|
||||||
(associatedArtifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID())) ) {
|
|| (associatedArtifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID()))) {
|
||||||
BlackboardAttribute urlAttr = associatedArtifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_URL));
|
BlackboardAttribute urlAttr = associatedArtifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_URL));
|
||||||
if (urlAttr != null) {
|
if (urlAttr != null) {
|
||||||
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.downloadSource"), urlAttr.getValueString());
|
addRow(sb, NbBundle.getMessage(this.getClass(), "Metadata.tableRowTitle.downloadSource"), urlAttr.getValueString());
|
||||||
@ -376,4 +263,169 @@ public class Metadata extends javax.swing.JPanel implements DataContentViewer {
|
|||||||
public int isPreferred(Node node) {
|
public int isPreferred(Node node) {
|
||||||
return 1;
|
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.msgDlg=Please enter a valid page number between 1 and {0}
|
||||||
StringsTextViewer.goToPageTextField.err=Invalid page number
|
StringsTextViewer.goToPageTextField.err=Invalid page number
|
||||||
StringsTextViewer.setDataView.errorText=(offset {0}-{1} could not be read)
|
StringsTextViewer.setDataView.errorText=(offset {0}-{1} could not be read)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2011-2019 Basis Technology Corp.
|
* Copyright 2011-2021 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* 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.awt.event.ActionListener;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import javax.swing.JMenuItem;
|
import javax.swing.JMenuItem;
|
||||||
import javax.swing.JOptionPane;
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.SwingWorker;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.openide.util.NbBundle.Messages;
|
||||||
import org.sleuthkit.autopsy.coreutils.StringExtract;
|
import org.sleuthkit.autopsy.coreutils.StringExtract;
|
||||||
import org.sleuthkit.autopsy.coreutils.StringExtract.StringExtractResult;
|
import org.sleuthkit.autopsy.coreutils.StringExtract.StringExtractResult;
|
||||||
import org.sleuthkit.autopsy.coreutils.StringExtract.StringExtractUnicodeTable.SCRIPT;
|
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 final byte[] data = new byte[(int) PAGE_LENGTH];
|
||||||
private static int currentPage = 1;
|
private static int currentPage = 1;
|
||||||
private Content dataSource;
|
private Content dataSource;
|
||||||
//string extract utility
|
|
||||||
private final StringExtract stringExtract = new StringExtract();
|
|
||||||
private static final Logger logger = Logger.getLogger(StringsContentPanel.class.getName());
|
private static final Logger logger = Logger.getLogger(StringsContentPanel.class.getName());
|
||||||
|
|
||||||
|
private SwingWorker<String, Void> worker;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new form StringsTextViewer
|
* Creates new form StringsTextViewer
|
||||||
*/
|
*/
|
||||||
@ -81,7 +84,7 @@ public class StringsContentPanel extends javax.swing.JPanel {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// use wrap layout for better component wrapping
|
// use wrap layout for better component wrapping
|
||||||
WrapLayout layout = new WrapLayout(0,5);
|
WrapLayout layout = new WrapLayout(0, 5);
|
||||||
layout.setOppositeAligned(Arrays.asList(panelScriptSelect));
|
layout.setOppositeAligned(Arrays.asList(panelScriptSelect));
|
||||||
controlPanel.setLayout(layout);
|
controlPanel.setLayout(layout);
|
||||||
|
|
||||||
@ -363,6 +366,10 @@ public class StringsContentPanel extends javax.swing.JPanel {
|
|||||||
private javax.swing.JLabel totalPageLabel;
|
private javax.swing.JLabel totalPageLabel;
|
||||||
// End of variables declaration//GEN-END:variables
|
// End of variables declaration//GEN-END:variables
|
||||||
|
|
||||||
|
@Messages({
|
||||||
|
"StringContentPanel_Loading_String=Loading text..."
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the DataView (The tabbed panel)
|
* Sets the DataView (The tabbed panel)
|
||||||
*
|
*
|
||||||
@ -370,83 +377,36 @@ public class StringsContentPanel extends javax.swing.JPanel {
|
|||||||
* @param offset the starting offset
|
* @param offset the starting offset
|
||||||
*/
|
*/
|
||||||
void setDataView(Content dataSource, long offset) {
|
void setDataView(Content dataSource, long offset) {
|
||||||
|
|
||||||
|
if (worker != null) {
|
||||||
|
worker.cancel(true);
|
||||||
|
worker = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (dataSource == null) {
|
if (dataSource == null) {
|
||||||
return;
|
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) {
|
worker = new ContentWorker(dataSource, offset);
|
||||||
try {
|
outputViewPane.setText(Bundle.StringContentPanel_Loading_String());
|
||||||
bytesRead = dataSource.read(data, offset, PAGE_LENGTH); // read the data
|
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||||
} catch (TskCoreException ex) {
|
worker.execute();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setDataView(StringContent dataSource) {
|
void setDataView(StringContent dataSource) {
|
||||||
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
if (worker != null) {
|
||||||
try {
|
worker.cancel(true);
|
||||||
this.dataSource = null;
|
worker = 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 (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);
|
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
|
DataContentViewerArtifact.failedToGetSourcePath.message=Failed to get source file path from case database
|
||||||
DataContentViewerHex.copyingFile=Copying file to open in HxD...
|
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.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.name=C
|
||||||
DataResultViewerTable.commentRender.toolTip=C(omments) indicates whether the item has a comment
|
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)
|
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.corecomponentinterfaces.DataContentViewer;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
|
|
||||||
import org.sleuthkit.datamodel.BlackboardAttribute;
|
import org.sleuthkit.datamodel.BlackboardAttribute;
|
||||||
import org.sleuthkit.datamodel.Content;
|
import org.sleuthkit.datamodel.Content;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
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 WAIT_TEXT = NbBundle.getMessage(DataContentViewerArtifact.class, "DataContentViewerArtifact.waitText");
|
||||||
private final static String ERROR_TEXT = NbBundle.getMessage(DataContentViewerArtifact.class, "DataContentViewerArtifact.errorText");
|
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 Node currentNode; // @@@ Remove this when the redundant setNode() calls problem is fixed.
|
||||||
private int currentPage = 1;
|
private int currentPage = 1;
|
||||||
private final Object lock = new Object();
|
private final Object lock = new Object();
|
||||||
@ -347,22 +351,38 @@ public class DataContentViewerArtifact extends javax.swing.JPanel implements Dat
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int isPreferred(Node node) {
|
public int isPreferred(Node node) {
|
||||||
|
// get the artifact from the lookup
|
||||||
BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class);
|
BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class);
|
||||||
// low priority if node doesn't have an artifact (meaning it was found from normal directory
|
if (artifact == null) {
|
||||||
// browsing, or if the artifact is something that means the user really wants to see the original
|
return LESS_PREFERRED;
|
||||||
// file and not more details about the artifact
|
}
|
||||||
if ((artifact == null)
|
|
||||||
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID())
|
// get the type of the artifact
|
||||||
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID())
|
BlackboardArtifact.Type artifactType;
|
||||||
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID())
|
try {
|
||||||
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_OBJECT_DETECTED.getTypeID())
|
artifactType = artifact.getType();
|
||||||
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID())
|
} catch (TskCoreException ex) {
|
||||||
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED.getTypeID())
|
logger.log(Level.SEVERE,
|
||||||
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID())
|
String.format("There was an error getting the artifact type for artifact with id: %d", artifact.getId()),
|
||||||
|| (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_WEB_CACHE.getTypeID())) {
|
ex);
|
||||||
return 3;
|
return LESS_PREFERRED;
|
||||||
} else {
|
}
|
||||||
return 6;
|
|
||||||
|
// 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.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
|
||||||
import org.openide.util.NbBundle;
|
import org.openide.util.NbBundle;
|
||||||
@ -37,6 +38,7 @@ import javax.swing.text.BadLocationException;
|
|||||||
import javax.swing.text.Utilities;
|
import javax.swing.text.Utilities;
|
||||||
import org.netbeans.api.progress.ProgressHandle;
|
import org.netbeans.api.progress.ProgressHandle;
|
||||||
import org.openide.nodes.Node;
|
import org.openide.nodes.Node;
|
||||||
|
import org.openide.util.NbBundle.Messages;
|
||||||
import org.openide.util.lookup.ServiceProvider;
|
import org.openide.util.lookup.ServiceProvider;
|
||||||
import org.sleuthkit.autopsy.casemodule.Case;
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||||
@ -63,6 +65,8 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
|
|||||||
private int totalPages;
|
private int totalPages;
|
||||||
private Content dataSource;
|
private Content dataSource;
|
||||||
|
|
||||||
|
private HexWorker worker;
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(DataContentViewerHex.class.getName());
|
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)
|
* @param page Page to display (1-based counting)
|
||||||
*/
|
*/
|
||||||
private void setDataViewByPageNumber(int page) {
|
private void setDataViewByPageNumber(int page) {
|
||||||
if (this.dataSource == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (page == 0) {
|
if (page == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentPage = page;
|
|
||||||
long offset = (currentPage - 1) * PAGE_LENGTH;
|
launchWorker(dataSource, (page - 1) * PAGE_LENGTH, page);
|
||||||
setDataView(offset);
|
|
||||||
goToOffsetTextField.setText(Long.toString(offset));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -473,75 +472,46 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
|
|||||||
* @param offset Page to display (1-based counting)
|
* @param offset Page to display (1-based counting)
|
||||||
*/
|
*/
|
||||||
private void setDataViewByOffset(long offset) {
|
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) {
|
if (this.dataSource == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentPage = (int) (offset / PAGE_LENGTH) + 1;
|
|
||||||
setDataView(offset);
|
|
||||||
goToPageTextField.setText(Integer.toString(currentPage));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setDataView(long offset) {
|
worker = new HexWorker(source, offset, page);
|
||||||
// change the cursor to "waiting cursor" for this operation
|
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
||||||
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
|
outputTextArea.setText(Bundle.DataContentViewerHex_loading_text());
|
||||||
|
worker.execute();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setNode(Node selectedNode) {
|
public void setNode(Node selectedNode) {
|
||||||
if ((selectedNode == null) || (!isSupported(selectedNode))) {
|
if (worker != null) {
|
||||||
|
worker.cancel(true);
|
||||||
|
worker = null;
|
||||||
|
}
|
||||||
|
|
||||||
resetComponent();
|
resetComponent();
|
||||||
|
|
||||||
|
if ((selectedNode == null)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Content content = DataContentViewerUtility.getDefaultContent(selectedNode);
|
Content content = DataContentViewerUtility.getDefaultContent(selectedNode);
|
||||||
if (content == null) {
|
if (content == null) {
|
||||||
resetComponent();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -619,4 +589,84 @@ public class DataContentViewerHex extends javax.swing.JPanel implements DataCont
|
|||||||
public Component getComponent() {
|
public Component getComponent() {
|
||||||
return this;
|
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.beans.PropertyChangeListener;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@ -96,6 +97,7 @@ public final class AutopsyTreeChildFactory extends ChildFactory.Detachable<Objec
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean createKeys(List<Object> list) {
|
protected boolean createKeys(List<Object> list) {
|
||||||
|
List<Object> nodes = Collections.emptyList();
|
||||||
try {
|
try {
|
||||||
SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
|
SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
|
||||||
if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
|
if (Objects.equals(CasePreferences.getGroupItemsInTreeByDataSource(), true)) {
|
||||||
@ -103,27 +105,28 @@ public final class AutopsyTreeChildFactory extends ChildFactory.Detachable<Objec
|
|||||||
List<Person> persons = personManager.getPersons();
|
List<Person> persons = personManager.getPersons();
|
||||||
// show persons level if there are persons to be shown
|
// show persons level if there are persons to be shown
|
||||||
if (!CollectionUtils.isEmpty(persons)) {
|
if (!CollectionUtils.isEmpty(persons)) {
|
||||||
persons.stream()
|
nodes = persons.stream()
|
||||||
.map(PersonGrouping::new)
|
.map(PersonGrouping::new)
|
||||||
.sorted()
|
.sorted()
|
||||||
.forEach(list::add);
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
if (CollectionUtils.isNotEmpty(personManager.getHostsForPerson(null))) {
|
if (CollectionUtils.isNotEmpty(personManager.getHostsForPerson(null))) {
|
||||||
list.add(new PersonGrouping(null));
|
nodes.add(new PersonGrouping(null));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// otherwise, just show host level
|
// otherwise, just show host level
|
||||||
tskCase.getHostManager().getAllHosts().stream()
|
nodes = tskCase.getHostManager().getAllHosts().stream()
|
||||||
.map(HostGrouping::new)
|
.map(HostGrouping::new)
|
||||||
.sorted()
|
.sorted()
|
||||||
.forEach(list::add);
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
}
|
}
|
||||||
list.add(new Reports());
|
|
||||||
return true;
|
// either way, add in reports node
|
||||||
|
nodes.add(new Reports());
|
||||||
} else {
|
} else {
|
||||||
// data source by type view
|
// data source by type view
|
||||||
List<AutopsyVisitableItem> keys = new ArrayList<>(Arrays.asList(
|
nodes = Arrays.asList(
|
||||||
new DataSourcesByType(),
|
new DataSourcesByType(),
|
||||||
new Views(Case.getCurrentCaseThrows().getSleuthkitCase()),
|
new Views(Case.getCurrentCaseThrows().getSleuthkitCase()),
|
||||||
new DataArtifacts(),
|
new DataArtifacts(),
|
||||||
@ -131,15 +134,16 @@ public final class AutopsyTreeChildFactory extends ChildFactory.Detachable<Objec
|
|||||||
new OsAccounts(Case.getCurrentCaseThrows().getSleuthkitCase()),
|
new OsAccounts(Case.getCurrentCaseThrows().getSleuthkitCase()),
|
||||||
new Tags(),
|
new Tags(),
|
||||||
new Reports()
|
new Reports()
|
||||||
));
|
);
|
||||||
|
|
||||||
list.addAll(keys);
|
|
||||||
}
|
}
|
||||||
} catch (NoCurrentCaseException ex) {
|
} catch (NoCurrentCaseException ex) {
|
||||||
logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
|
logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
|
||||||
} catch (TskCoreException ex) {
|
} catch (TskCoreException ex) {
|
||||||
logger.log(Level.SEVERE, "Exception while getting data from case.", ex); //NON-NLS
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ import java.util.List;
|
|||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import javax.swing.Action;
|
import javax.swing.Action;
|
||||||
import org.openide.nodes.ChildFactory;
|
import org.openide.nodes.ChildFactory;
|
||||||
|
|
||||||
@ -119,11 +120,11 @@ public class HostNode extends DisplayableItemNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dataSources != null) {
|
if (dataSources != null) {
|
||||||
dataSources.stream()
|
toPopulate.addAll(dataSources.stream()
|
||||||
.filter(ds -> ds != null)
|
.filter(ds -> ds != null)
|
||||||
.map(DataSourceGrouping::new)
|
.map(DataSourceGrouping::new)
|
||||||
.sorted((a, b) -> getNameOrEmpty(a).compareToIgnoreCase(getNameOrEmpty(b)))
|
.sorted((a, b) -> getNameOrEmpty(a).compareToIgnoreCase(getNameOrEmpty(b)))
|
||||||
.forEach(toPopulate::add);
|
.collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
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);
|
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)
|
.map(HostGrouping::new)
|
||||||
.sorted()
|
.sorted()
|
||||||
.forEach(toPopulate::add);
|
.collect(Collectors.toList()));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -675,7 +675,7 @@
|
|||||||
<Component class="javax.swing.JComboBox" name="equalitySignComboBox">
|
<Component class="javax.swing.JComboBox" name="equalitySignComboBox">
|
||||||
<Properties>
|
<Properties>
|
||||||
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
<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>
|
||||||
<Property name="enabled" type="boolean" value="false"/>
|
<Property name="enabled" type="boolean" value="false"/>
|
||||||
</Properties>
|
</Properties>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* Autopsy Forensic Browser
|
* Autopsy Forensic Browser
|
||||||
*
|
*
|
||||||
* Copyright 2011-2020 Basis Technology Corp.
|
* Copyright 2011-2021 Basis Technology Corp.
|
||||||
* Contact: carrier <at> sleuthkit <dot> org
|
* Contact: carrier <at> sleuthkit <dot> org
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* 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.exportSetButton.setVisible(false);
|
||||||
this.mimeTypeComboBox.setVisible(false);
|
this.mimeTypeComboBox.setVisible(false);
|
||||||
this.mimeTypeLabel.setVisible(false);
|
this.mimeTypeLabel.setVisible(false);
|
||||||
this.fileSizeUnitComboBox.setVisible(false);
|
|
||||||
this.fileSizeSpinner.setVisible(false);
|
|
||||||
this.filterDialogTitle = "FilesSetPanel.filter.title";
|
this.filterDialogTitle = "FilesSetPanel.filter.title";
|
||||||
this.ruleDialogTitle = "FilesSetPanel.rule.title";
|
this.ruleDialogTitle = "FilesSetPanel.rule.title";
|
||||||
this.fileSizeLabel.setVisible(false);
|
|
||||||
this.equalitySignComboBox.setVisible(false);
|
|
||||||
this.ignoreKnownFilesCheckbox.setVisible(false);
|
this.ignoreKnownFilesCheckbox.setVisible(false);
|
||||||
this.fileTypeLabel.setVisible(false);
|
this.fileTypeLabel.setVisible(false);
|
||||||
this.filesRadioButton.setVisible(false);
|
this.filesRadioButton.setVisible(false);
|
||||||
@ -192,7 +188,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.fileSizeUnitComboBox.setSelectedIndex(1);
|
this.fileSizeUnitComboBox.setSelectedIndex(1);
|
||||||
this.equalitySignComboBox.setSelectedIndex(2);
|
this.equalitySignComboBox.setSelectedIndex(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -217,7 +213,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
|
|||||||
|
|
||||||
boolean ruleSelected = (FilesSetDefsPanel.this.rulesList.getSelectedValue() != null);
|
boolean ruleSelected = (FilesSetDefsPanel.this.rulesList.getSelectedValue() != null);
|
||||||
|
|
||||||
newRuleButton.setEnabled(canBeEnabled && !isStandardSet);
|
newRuleButton.setEnabled(canBeEnabled && setSelected && !isStandardSet);
|
||||||
copySetButton.setEnabled(canBeEnabled && setSelected);
|
copySetButton.setEnabled(canBeEnabled && setSelected);
|
||||||
newSetButton.setEnabled(canBeEnabled);
|
newSetButton.setEnabled(canBeEnabled);
|
||||||
editRuleButton.setEnabled(canBeEnabled && ruleSelected && !isStandardSet);
|
editRuleButton.setEnabled(canBeEnabled && ruleSelected && !isStandardSet);
|
||||||
@ -292,7 +288,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
|
|||||||
this.daysIncludedTextField.setText("");
|
this.daysIncludedTextField.setText("");
|
||||||
this.rulePathConditionRegexCheckBox.setSelected(false);
|
this.rulePathConditionRegexCheckBox.setSelected(false);
|
||||||
this.mimeTypeComboBox.setSelectedIndex(0);
|
this.mimeTypeComboBox.setSelectedIndex(0);
|
||||||
this.equalitySignComboBox.setSelectedIndex(2);
|
this.equalitySignComboBox.setSelectedIndex(0);
|
||||||
this.fileSizeUnitComboBox.setSelectedIndex(1);
|
this.fileSizeUnitComboBox.setSelectedIndex(1);
|
||||||
this.fileSizeSpinner.setValue(0);
|
this.fileSizeSpinner.setValue(0);
|
||||||
enableButtons();
|
enableButtons();
|
||||||
@ -405,7 +401,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp
|
|||||||
FilesSetDefsPanel.this.fileSizeSpinner.setValue(fileSizeCondition.getSizeValue());
|
FilesSetDefsPanel.this.fileSizeSpinner.setValue(fileSizeCondition.getSizeValue());
|
||||||
} else {
|
} else {
|
||||||
FilesSetDefsPanel.this.fileSizeUnitComboBox.setSelectedIndex(1);
|
FilesSetDefsPanel.this.fileSizeUnitComboBox.setSelectedIndex(1);
|
||||||
FilesSetDefsPanel.this.equalitySignComboBox.setSelectedIndex(2);
|
FilesSetDefsPanel.this.equalitySignComboBox.setSelectedIndex(0);
|
||||||
FilesSetDefsPanel.this.fileSizeSpinner.setValue(0);
|
FilesSetDefsPanel.this.fileSizeSpinner.setValue(0);
|
||||||
}
|
}
|
||||||
if (dateCondition != null) {
|
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
|
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);
|
equalitySignComboBox.setEnabled(false);
|
||||||
|
|
||||||
fileSizeSpinner.setEnabled(false);
|
fileSizeSpinner.setEnabled(false);
|
||||||
|
@ -282,7 +282,7 @@
|
|||||||
<Component class="javax.swing.JComboBox" name="equalitySymbolComboBox">
|
<Component class="javax.swing.JComboBox" name="equalitySymbolComboBox">
|
||||||
<Properties>
|
<Properties>
|
||||||
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
<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>
|
||||||
<Property name="enabled" type="boolean" value="false"/>
|
<Property name="enabled" type="boolean" value="false"/>
|
||||||
</Properties>
|
</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
|
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);
|
mimeTypeComboBox.setVisible(false);
|
||||||
mimeCheck.setVisible(false);
|
mimeCheck.setVisible(false);
|
||||||
fileSizeComboBox.setVisible(false);
|
|
||||||
fileSizeCheck.setVisible(false);
|
|
||||||
equalitySymbolComboBox.setVisible(false);
|
|
||||||
fileSizeSpinner.setVisible(false);
|
|
||||||
jLabel1.setVisible(false);
|
jLabel1.setVisible(false);
|
||||||
filesRadioButton.setVisible(false);
|
filesRadioButton.setVisible(false);
|
||||||
dirsRadioButton.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
|
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);
|
mimeTypeComboBox.setVisible(false);
|
||||||
mimeCheck.setVisible(false);
|
mimeCheck.setVisible(false);
|
||||||
fileSizeComboBox.setVisible(false);
|
|
||||||
fileSizeCheck.setVisible(false);
|
|
||||||
equalitySymbolComboBox.setVisible(false);
|
|
||||||
fileSizeSpinner.setVisible(false);
|
|
||||||
jLabel1.setVisible(false);
|
jLabel1.setVisible(false);
|
||||||
filesRadioButton.setVisible(false);
|
filesRadioButton.setVisible(false);
|
||||||
dirsRadioButton.setVisible(false);
|
dirsRadioButton.setVisible(false);
|
||||||
@ -120,8 +112,6 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
|||||||
} else {
|
} else {
|
||||||
populateMimeTypesComboBox();
|
populateMimeTypesComboBox();
|
||||||
populateMimeConditionComponents(rule);
|
populateMimeConditionComponents(rule);
|
||||||
populateSizeConditionComponents(rule);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
populateMimeTypesComboBox();
|
populateMimeTypesComboBox();
|
||||||
populateRuleNameComponent(rule);
|
populateRuleNameComponent(rule);
|
||||||
@ -129,6 +119,7 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
|||||||
populateNameConditionComponents(rule);
|
populateNameConditionComponents(rule);
|
||||||
populatePathConditionComponents(rule);
|
populatePathConditionComponents(rule);
|
||||||
populateDateConditionComponents(rule);
|
populateDateConditionComponents(rule);
|
||||||
|
populateSizeConditionComponents(rule);
|
||||||
this.setButtons(okButton, cancelButton);
|
this.setButtons(okButton, cancelButton);
|
||||||
|
|
||||||
updateNameTextFieldPrompt();
|
updateNameTextFieldPrompt();
|
||||||
@ -745,7 +736,7 @@ final class FilesSetRulePanel extends javax.swing.JPanel {
|
|||||||
mimeTypeComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] {""}));
|
mimeTypeComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] {""}));
|
||||||
mimeTypeComboBox.setEnabled(false);
|
mimeTypeComboBox.setEnabled(false);
|
||||||
|
|
||||||
equalitySymbolComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] { "=", ">", "≥", "<", "≤" }));
|
equalitySymbolComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] { ">", "<" }));
|
||||||
equalitySymbolComboBox.setEnabled(false);
|
equalitySymbolComboBox.setEnabled(false);
|
||||||
|
|
||||||
fileSizeComboBox.setModel(new javax.swing.DefaultComboBoxModel<String>(new String[] { Bundle.FilesSetRulePanel_bytes(), Bundle.FilesSetRulePanel_kiloBytes(), Bundle.FilesSetRulePanel_megaBytes(), Bundle.FilesSetRulePanel_gigaBytes() }));
|
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.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -565,8 +567,13 @@ final class ChromeCacheExtractor {
|
|||||||
|
|
||||||
List<AbstractFile> effFiles = fileManager.findFiles(dataSource, "f_%", cachePath); //NON-NLS
|
List<AbstractFile> effFiles = fileManager.findFiles(dataSource, "f_%", cachePath); //NON-NLS
|
||||||
for (AbstractFile abstractFile : effFiles ) {
|
for (AbstractFile abstractFile : effFiles ) {
|
||||||
|
String cacheKey = cachePath + abstractFile.getName();
|
||||||
if (cachePath.equals(abstractFile.getParentPath()) && abstractFile.isFile()) {
|
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,14 +597,43 @@ final class ChromeCacheExtractor {
|
|||||||
return Optional.of(fileCopyCache.get(fileTableKey).getAbstractFile());
|
return Optional.of(fileCopyCache.get(fileTableKey).getAbstractFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
List<AbstractFile> cacheFiles = fileManager.findFiles(dataSource, cacheFileName, cacheFolderName); //NON-NLS
|
List<AbstractFile> cacheFiles = fileManager.findFiles(dataSource, cacheFileName, cacheFolderName); //NON-NLS
|
||||||
if (!cacheFiles.isEmpty()) {
|
if (!cacheFiles.isEmpty()) {
|
||||||
for (AbstractFile abstractFile: cacheFiles ) {
|
// Sort the list for consistency. Preference is:
|
||||||
if (abstractFile.getUniquePath().trim().endsWith(DEFAULT_CACHE_PATH_STR)) {
|
// - In correct subfolder and allocated
|
||||||
return Optional.of(abstractFile);
|
// - 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.of(cacheFiles.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user