mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 16:06:15 +00:00
Done with changes, now commenting and making it more readable, on the verge of opening a PR
This commit is contained in:
parent
5d2a52d86f
commit
c5b43a9355
@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.datamodel;
|
|||||||
|
|
||||||
import java.beans.PropertyChangeEvent;
|
import java.beans.PropertyChangeEvent;
|
||||||
import java.beans.PropertyChangeListener;
|
import java.beans.PropertyChangeListener;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -29,7 +30,6 @@ import java.util.concurrent.ExecutorService;
|
|||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.apache.commons.io.FilenameUtils;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.tuple.Pair;
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
import org.openide.nodes.Children;
|
import org.openide.nodes.Children;
|
||||||
@ -42,29 +42,18 @@ import org.sleuthkit.autopsy.casemodule.events.CommentChangedEvent;
|
|||||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent;
|
||||||
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent;
|
||||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
||||||
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
|
|
||||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil;
|
|
||||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
|
|
||||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
|
|
||||||
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil;
|
|
||||||
import org.sleuthkit.autopsy.core.UserPreferences;
|
import org.sleuthkit.autopsy.core.UserPreferences;
|
||||||
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score;
|
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score;
|
||||||
import org.sleuthkit.autopsy.coreutils.Logger;
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
import static org.sleuthkit.autopsy.datamodel.Bundle.*;
|
import static org.sleuthkit.autopsy.datamodel.Bundle.*;
|
||||||
import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType.*;
|
import static org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType.*;
|
||||||
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.HasCommentStatus;
|
import org.sleuthkit.autopsy.datamodel.SCOAndTranslationTask.SCOResults;
|
||||||
import org.sleuthkit.autopsy.events.AutopsyEvent;
|
|
||||||
import org.sleuthkit.autopsy.ingest.IngestManager;
|
import org.sleuthkit.autopsy.ingest.IngestManager;
|
||||||
import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
|
import org.sleuthkit.autopsy.ingest.ModuleContentEvent;
|
||||||
import org.sleuthkit.autopsy.texttranslation.NoServiceProviderException;
|
|
||||||
import org.sleuthkit.autopsy.texttranslation.TextTranslationService;
|
|
||||||
import org.sleuthkit.autopsy.texttranslation.TranslationException;
|
|
||||||
import org.sleuthkit.datamodel.AbstractFile;
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
import org.sleuthkit.datamodel.BlackboardArtifact;
|
|
||||||
import org.sleuthkit.datamodel.Content;
|
import org.sleuthkit.datamodel.Content;
|
||||||
import org.sleuthkit.datamodel.ContentTag;
|
import org.sleuthkit.datamodel.ContentTag;
|
||||||
import org.sleuthkit.datamodel.TskCoreException;
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
import org.sleuthkit.datamodel.TskData;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An abstract node that encapsulates AbstractFile data
|
* An abstract node that encapsulates AbstractFile data
|
||||||
@ -77,14 +66,9 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
|||||||
@NbBundle.Messages("AbstractAbstractFileNode.addFileProperty.desc=no description")
|
@NbBundle.Messages("AbstractAbstractFileNode.addFileProperty.desc=no description")
|
||||||
private static final String NO_DESCR = AbstractAbstractFileNode_addFileProperty_desc();
|
private static final String NO_DESCR = AbstractAbstractFileNode_addFileProperty_desc();
|
||||||
|
|
||||||
private static final String TRANSLATION_AVAILABLE_EVENT = "TRANSLATION_AVAILABLE";
|
|
||||||
private static final String NO_TRANSLATION = "";
|
|
||||||
|
|
||||||
private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE,
|
private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE,
|
||||||
Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_DELETED, Case.Events.CR_COMMENT_CHANGED);
|
Case.Events.CONTENT_TAG_ADDED, Case.Events.CONTENT_TAG_DELETED, Case.Events.CR_COMMENT_CHANGED);
|
||||||
|
|
||||||
private volatile List<FileProperty> currentProperties;
|
|
||||||
|
|
||||||
private static final ExecutorService pool;
|
private static final ExecutorService pool;
|
||||||
private static final Integer MAX_POOL_SIZE = 10;
|
private static final Integer MAX_POOL_SIZE = 10;
|
||||||
|
|
||||||
@ -108,6 +92,8 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
|||||||
}
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
//Initialize this pool only once! This will be used by every instance of AAFN
|
||||||
|
//to do their heavy duty SCO column and translation updates.
|
||||||
pool = Executors.newFixedThreadPool(MAX_POOL_SIZE);
|
pool = Executors.newFixedThreadPool(MAX_POOL_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,12 +117,16 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
|||||||
Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl);
|
Case.removeEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, weakPcl);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Events signals to indicate the background tasks have completed processing.
|
/**
|
||||||
private enum NodeSpecificEvents {
|
* Event signals to indicate the background tasks have completed processing.
|
||||||
TRANSLATION_AVAILABLE_EVENT,
|
* Currently, we have two property tasks in the background:
|
||||||
SCORE_AVAILABLE_EVENT,
|
*
|
||||||
COMMENT_AVAILABLE_EVENT,
|
* 1) Retreiving the translation of the file name 2) Getting the SCO column
|
||||||
OCCURRENCES_AVAILABLE_EVENT;
|
* properties from the databases
|
||||||
|
*/
|
||||||
|
enum NodeSpecificEvents {
|
||||||
|
TRANSLATION_AVAILABLE,
|
||||||
|
DABABASE_CONTENT_AVAILABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
|
private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
|
||||||
@ -178,56 +168,111 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
|||||||
} else if (eventType.equals(Case.Events.CONTENT_TAG_ADDED.toString())) {
|
} else if (eventType.equals(Case.Events.CONTENT_TAG_ADDED.toString())) {
|
||||||
ContentTagAddedEvent event = (ContentTagAddedEvent) evt;
|
ContentTagAddedEvent event = (ContentTagAddedEvent) evt;
|
||||||
if (event.getAddedTag().getContent().equals(content)) {
|
if (event.getAddedTag().getContent().equals(content)) {
|
||||||
updateSheet();
|
//No need to do any asynchrony around these events, they are so infrequent
|
||||||
|
//and user driven that we can just keep a simple blocking approach, where we
|
||||||
|
//go out to the database ourselves!
|
||||||
|
List<ContentTag> tags = PropertyUtil.getContentTagsFromDatabase(content);
|
||||||
|
|
||||||
|
updateProperty(new FileProperty(SCORE.toString()) {
|
||||||
|
Pair<Score, String> scorePropertyAndDescription
|
||||||
|
= PropertyUtil.getScorePropertyAndDescription(content, tags);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPropertyValue() {
|
||||||
|
return scorePropertyAndDescription.getLeft();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return scorePropertyAndDescription.getRight();
|
||||||
|
}
|
||||||
|
}, new FileProperty(COMMENT.toString()) {
|
||||||
|
@Override
|
||||||
|
public Object getPropertyValue() {
|
||||||
|
//Null out the correlation attribute because we are only
|
||||||
|
//concerned with changes to the content tag, not the CR!
|
||||||
|
return PropertyUtil.getCommentProperty(tags, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else if (eventType.equals(Case.Events.CONTENT_TAG_DELETED.toString())) {
|
} else if (eventType.equals(Case.Events.CONTENT_TAG_DELETED.toString())) {
|
||||||
ContentTagDeletedEvent event = (ContentTagDeletedEvent) evt;
|
ContentTagDeletedEvent event = (ContentTagDeletedEvent) evt;
|
||||||
if (event.getDeletedTagInfo().getContentID() == content.getId()) {
|
if (event.getDeletedTagInfo().getContentID() == content.getId()) {
|
||||||
updateSheet();
|
//No need to do any asynchrony around these events, they are so infrequent
|
||||||
|
//and user driven that we can just keep a simple blocking approach, where we
|
||||||
|
//go out to the database ourselves!
|
||||||
|
List<ContentTag> tags = PropertyUtil.getContentTagsFromDatabase(content);
|
||||||
|
|
||||||
|
updateProperty(new FileProperty(SCORE.toString()) {
|
||||||
|
Pair<Score, String> scorePropertyAndDescription
|
||||||
|
= PropertyUtil.getScorePropertyAndDescription(content, tags);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPropertyValue() {
|
||||||
|
return scorePropertyAndDescription.getLeft();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription() {
|
||||||
|
return scorePropertyAndDescription.getRight();
|
||||||
|
}
|
||||||
|
}, new FileProperty(COMMENT.toString()) {
|
||||||
|
@Override
|
||||||
|
public Object getPropertyValue() {
|
||||||
|
//Null out the correlation attribute because we are only
|
||||||
|
//concerned with changes to the content tag, not the CR!
|
||||||
|
return PropertyUtil.getCommentProperty(tags, null);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else if (eventType.equals(Case.Events.CR_COMMENT_CHANGED.toString())) {
|
} else if (eventType.equals(Case.Events.CR_COMMENT_CHANGED.toString())) {
|
||||||
CommentChangedEvent event = (CommentChangedEvent) evt;
|
CommentChangedEvent event = (CommentChangedEvent) evt;
|
||||||
if (event.getContentID() == content.getId()) {
|
if (event.getContentID() == content.getId()) {
|
||||||
updateSheet();
|
//No need to do any asynchrony around these events, they are so infrequent
|
||||||
|
//and user driven that we can just keep a simple blocking approach, where we
|
||||||
|
//go out to the database ourselves!
|
||||||
|
updateProperty(new FileProperty(COMMENT.toString()) {
|
||||||
|
@Override
|
||||||
|
public Object getPropertyValue() {
|
||||||
|
List<ContentTag> tags = PropertyUtil.getContentTagsFromDatabase(content);
|
||||||
|
CorrelationAttributeInstance attribute = PropertyUtil.getCorrelationAttributeInstance(content);
|
||||||
|
return PropertyUtil.getCommentProperty(tags, attribute);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else if (eventType.equals(NodeSpecificEvents.TRANSLATION_AVAILABLE_EVENT.toString())) {
|
} else if (eventType.equals(NodeSpecificEvents.TRANSLATION_AVAILABLE.toString())) {
|
||||||
updateProperty(TRANSLATION.toString(), new FileProperty(TRANSLATION.toString()) {
|
updateProperty(new FileProperty(TRANSLATION.toString()) {
|
||||||
@Override
|
@Override
|
||||||
public Object getPropertyValue() {
|
public Object getPropertyValue() {
|
||||||
return evt.getNewValue();
|
return evt.getNewValue();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if(eventType.equals(NodeSpecificEvents.SCORE_AVAILABLE_EVENT.toString())) {
|
} else if (eventType.equals(NodeSpecificEvents.DABABASE_CONTENT_AVAILABLE.toString())) {
|
||||||
Pair<Score, String> scoreAndDescription = (Pair) evt.getNewValue();
|
SCOResults results = (SCOResults) evt.getNewValue();
|
||||||
updateProperty(SCORE.toString(), new FileProperty(SCORE.toString()) {
|
updateProperty(new FileProperty(SCORE.toString()) {
|
||||||
@Override
|
@Override
|
||||||
public Object getPropertyValue() {
|
public Object getPropertyValue() {
|
||||||
return scoreAndDescription.getLeft();
|
return results.getScore();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return scoreAndDescription.getRight();
|
return results.getScoreDescription();
|
||||||
}
|
}
|
||||||
});
|
}, new FileProperty(COMMENT.toString()) {
|
||||||
} else if(eventType.equals(NodeSpecificEvents.COMMENT_AVAILABLE_EVENT.toString())) {
|
|
||||||
updateProperty(COMMENT.toString(), new FileProperty(COMMENT.toString()) {
|
|
||||||
@Override
|
@Override
|
||||||
public Object getPropertyValue() {
|
public Object getPropertyValue() {
|
||||||
return evt.getNewValue();
|
return results.getComment();
|
||||||
}
|
}
|
||||||
});
|
}, new FileProperty(OCCURRENCES.toString()) {
|
||||||
} else if(eventType.equals(NodeSpecificEvents.OCCURRENCES_AVAILABLE_EVENT.toString())) {
|
|
||||||
Pair<Long, String> countAndDescription = (Pair) evt.getNewValue();
|
|
||||||
updateProperty(OCCURRENCES.toString(), new FileProperty(OCCURRENCES.toString()) {
|
|
||||||
@Override
|
@Override
|
||||||
public Object getPropertyValue() {
|
public Object getPropertyValue() {
|
||||||
return countAndDescription.getLeft();
|
return results.getCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return countAndDescription.getRight();
|
return results.getCountDescription();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -243,54 +288,70 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
|||||||
*/
|
*/
|
||||||
private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
|
private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
|
||||||
|
|
||||||
Sheet getBlankSheet() {
|
/**
|
||||||
|
* Returns a blank sheet to the caller, useful for giving subclasses the
|
||||||
|
* ability to override createSheet() with their own implementation.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected Sheet getBlankSheet() {
|
||||||
return super.createSheet();
|
return super.createSheet();
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void updateProperty(String propName, FileProperty newProp) {
|
/**
|
||||||
for(int i = 0; i < currentProperties.size(); i++) {
|
* Updates the values of the properties in the current property sheet with
|
||||||
FileProperty property = currentProperties.get(i);
|
* the new properties being passed in! Only if that property exists in the
|
||||||
if(property.getPropertyName().equals(propName)) {
|
* current sheet will it be applied. That way, we allow for subclasses to
|
||||||
currentProperties.set(i, newProp);
|
* add their own (or omit some!) properties and we will not accidentally
|
||||||
|
* disrupt their UI.
|
||||||
|
*
|
||||||
|
* Race condition if not synchronized. Only one update should be applied at a time.
|
||||||
|
* The timing of currSheetSet.getProperties() could result in wrong/stale data
|
||||||
|
* being shown!
|
||||||
|
*
|
||||||
|
* @param newProps New file property instances to be updated in the current
|
||||||
|
* sheet.
|
||||||
|
*/
|
||||||
|
private synchronized void updateProperty(FileProperty... newProps) {
|
||||||
|
|
||||||
|
//Refresh ONLY those properties in the sheet currently. Subclasses may have
|
||||||
|
//only added a subset of our properties or their own props! Let's keep their UI correct.
|
||||||
|
Sheet currSheet = this.getSheet();
|
||||||
|
Sheet.Set currSheetSet = currSheet.get(Sheet.PROPERTIES);
|
||||||
|
Property<?>[] currProps = currSheetSet.getProperties();
|
||||||
|
|
||||||
|
for (int i = 0; i < currProps.length; i++) {
|
||||||
|
for (FileProperty property : newProps) {
|
||||||
|
if (currProps[i].getName().equals(property.getPropertyName())) {
|
||||||
|
currProps[i] = new NodeProperty<>(
|
||||||
|
property.getPropertyName(),
|
||||||
|
property.getPropertyName(),
|
||||||
|
property.getDescription(),
|
||||||
|
property.getPropertyValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Sheet sheet = super.createSheet();
|
currSheetSet.put(currProps);
|
||||||
Sheet.Set sheetSet = Sheet.createPropertiesSet();
|
currSheet.put(currSheetSet);
|
||||||
sheet.put(sheetSet);
|
|
||||||
|
|
||||||
for (FileProperty property : currentProperties) {
|
//setSheet() will notify Netbeans to update this node in the UI!
|
||||||
if (property.isEnabled()) {
|
this.setSheet(currSheet);
|
||||||
sheetSet.put(new NodeProperty<>(
|
|
||||||
property.getPropertyName(),
|
|
||||||
property.getPropertyName(),
|
|
||||||
property.getDescription(),
|
|
||||||
property.getPropertyValue()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setSheet(sheet);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Race condition if not synchronized. The main thread could be updating the
|
|
||||||
//sheet with blank properties, if a background task is complete, it may finish
|
|
||||||
//the updateSheet() call before the main thread. If that's the case, main thread's
|
|
||||||
//sheet will be the one to 'win' the UI and we will see stale data. Problem
|
|
||||||
//for multi-user cases if both the CR comment is updated from elsewhere and the background
|
|
||||||
//task is completed and also trying to update.
|
|
||||||
private synchronized void updateSheet() {
|
|
||||||
this.setSheet(createSheet());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is called when the node is first initialized. Any new updates or changes
|
||||||
|
* happen by directly manipulating the sheet. That means we can fire off background
|
||||||
|
* events everytime this method is called and not worry about duplicated jobs!
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected Sheet createSheet() {
|
protected synchronized Sheet createSheet() {
|
||||||
Sheet sheet = super.createSheet();
|
Sheet sheet = getBlankSheet();
|
||||||
Sheet.Set sheetSet = Sheet.createPropertiesSet();
|
Sheet.Set sheetSet = Sheet.createPropertiesSet();
|
||||||
sheet.put(sheetSet);
|
sheet.put(sheetSet);
|
||||||
|
|
||||||
//This will fire off fresh background tasks.
|
//This will fire off fresh background tasks.
|
||||||
List<FileProperty> newProperties = getProperties();
|
List<FileProperty> newProperties = getProperties();
|
||||||
currentProperties = newProperties;
|
|
||||||
|
|
||||||
//Add only the enabled properties to the sheet!
|
//Add only the enabled properties to the sheet!
|
||||||
for (FileProperty property : newProperties) {
|
for (FileProperty property : newProperties) {
|
||||||
@ -372,63 +433,17 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts translation on the file name by kicking off a background task to
|
* Creates a list of properties for this file node. Each property has its
|
||||||
* do the translation. Once the background task is done, it will fire a
|
* own strategy for producing a value, its own description, name, and
|
||||||
* PropertyChangeEvent, which will force this node to refresh itself, thus
|
* ability to be disabled. The FileProperty abstract class provides a
|
||||||
* updating its translated name column.
|
* wrapper for all of these characteristics. Additionally, with a return
|
||||||
|
* value of a list, any children classes of this node may reorder or omit
|
||||||
|
* any of these properties as they see fit for their use case.
|
||||||
*
|
*
|
||||||
* @return The file names translation.
|
* @return List of file properties associated with this file node's content.
|
||||||
*/
|
|
||||||
private String getTranslatedFileName() {
|
|
||||||
//If already in complete English, don't translate.
|
|
||||||
if (content.getName().matches("^\\p{ASCII}+$")) {
|
|
||||||
return NO_TRANSLATION;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Lets fire off a background translation that will update the UI
|
|
||||||
//when it is done.
|
|
||||||
TextTranslationService tts = TextTranslationService.getInstance();
|
|
||||||
if (tts.hasProvider()) {
|
|
||||||
//Seperate out the base and ext from the contents file name.
|
|
||||||
String base = FilenameUtils.getBaseName(content.getName());
|
|
||||||
|
|
||||||
try {
|
|
||||||
String translation = tts.translate(base);
|
|
||||||
String ext = FilenameUtils.getExtension(content.getName());
|
|
||||||
|
|
||||||
//If we have no extension, then we shouldn't add the .
|
|
||||||
String extensionDelimiter = (ext.isEmpty()) ? "" : ".";
|
|
||||||
|
|
||||||
//Talk directly to this nodes pcl, fire an update when the translation
|
|
||||||
//is complete.
|
|
||||||
if (!translation.isEmpty()) {
|
|
||||||
return translation + extensionDelimiter + ext;
|
|
||||||
}
|
|
||||||
} catch (NoServiceProviderException noServiceEx) {
|
|
||||||
logger.log(Level.WARNING, "Translate unsuccessful because no TextTranslator "
|
|
||||||
+ "implementation was provided.", noServiceEx);
|
|
||||||
} catch (TranslationException noTranslationEx) {
|
|
||||||
logger.log(Level.WARNING, "Could not successfully translate file name "
|
|
||||||
+ content.getName(), noTranslationEx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//In the mean time, return a blank translation.
|
|
||||||
return NO_TRANSLATION;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a list of properties for this file node. Each property has it's own
|
|
||||||
* strategy for getting it's value, it's own description, name, and ability to be
|
|
||||||
* disabled. The FileProperty abstract class provides a wrapper for all
|
|
||||||
* of these characteristics. Additionally, with a return value of a list, any
|
|
||||||
* children classes of this node may reorder or omit any of these properties as
|
|
||||||
* they see fit for their use case.
|
|
||||||
*
|
|
||||||
* @return List of file properties associated with this file nodes content.
|
|
||||||
*/
|
*/
|
||||||
List<FileProperty> getProperties() {
|
List<FileProperty> getProperties() {
|
||||||
ArrayList<FileProperty> properties = new ArrayList<>();
|
List<FileProperty> properties = new ArrayList<>();
|
||||||
|
|
||||||
properties.add(new FileProperty(NAME.toString()) {
|
properties.add(new FileProperty(NAME.toString()) {
|
||||||
@Override
|
@Override
|
||||||
@ -436,17 +451,15 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
|||||||
return getContentDisplayName(content);
|
return getContentDisplayName(content);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Initialize dummy place holder properties! These obviously do no work
|
||||||
|
//to get their property values, but at the bottom we kick off a background
|
||||||
|
//task that promises to update these values.
|
||||||
|
final String NO_OP = "";
|
||||||
properties.add(new FileProperty(TRANSLATION.toString()) {
|
properties.add(new FileProperty(TRANSLATION.toString()) {
|
||||||
@Override
|
@Override
|
||||||
public Object getPropertyValue() {
|
public Object getPropertyValue() {
|
||||||
pool.submit(() -> {
|
return NO_OP;
|
||||||
weakPcl.propertyChange(new PropertyChangeEvent(
|
|
||||||
AutopsyEvent.SourceType.LOCAL.toString(),
|
|
||||||
NodeSpecificEvents.TRANSLATION_AVAILABLE_EVENT.toString(),
|
|
||||||
null,
|
|
||||||
getTranslatedFileName()));
|
|
||||||
});
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -455,35 +468,16 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
List<ContentTag> tags = getContentTagsFromDatabase(content);
|
|
||||||
CorrelationAttributeInstance correlationAttribute = getCorrelationAttributeInstance();
|
|
||||||
properties.add(new FileProperty(SCORE.toString()) {
|
properties.add(new FileProperty(SCORE.toString()) {
|
||||||
@Override
|
@Override
|
||||||
public Object getPropertyValue() {
|
public Object getPropertyValue() {
|
||||||
pool.submit(() -> {
|
return NO_OP;
|
||||||
List<ContentTag> tags = getContentTagsFromDatabase(content);
|
|
||||||
weakPcl.propertyChange(new PropertyChangeEvent(
|
|
||||||
AutopsyEvent.SourceType.LOCAL.toString(),
|
|
||||||
NodeSpecificEvents.SCORE_AVAILABLE_EVENT.toString(),
|
|
||||||
null,
|
|
||||||
getScorePropertyAndDescription(tags)));
|
|
||||||
});
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
properties.add(new FileProperty(COMMENT.toString()) {
|
properties.add(new FileProperty(COMMENT.toString()) {
|
||||||
@Override
|
@Override
|
||||||
public Object getPropertyValue() {
|
public Object getPropertyValue() {
|
||||||
pool.submit(() -> {
|
return NO_OP;
|
||||||
List<ContentTag> tags = getContentTagsFromDatabase(content);
|
|
||||||
CorrelationAttributeInstance correlationAttribute = getCorrelationAttributeInstance();
|
|
||||||
weakPcl.propertyChange(new PropertyChangeEvent(
|
|
||||||
AutopsyEvent.SourceType.LOCAL.toString(),
|
|
||||||
NodeSpecificEvents.COMMENT_AVAILABLE_EVENT.toString(),
|
|
||||||
null,
|
|
||||||
getCommentProperty(tags, correlationAttribute)));
|
|
||||||
});
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -494,15 +488,7 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
|||||||
properties.add(new FileProperty(OCCURRENCES.toString()) {
|
properties.add(new FileProperty(OCCURRENCES.toString()) {
|
||||||
@Override
|
@Override
|
||||||
public Object getPropertyValue() {
|
public Object getPropertyValue() {
|
||||||
pool.submit(() -> {
|
return NO_OP;
|
||||||
CorrelationAttributeInstance correlationAttribute = getCorrelationAttributeInstance();
|
|
||||||
weakPcl.propertyChange(new PropertyChangeEvent(
|
|
||||||
AutopsyEvent.SourceType.LOCAL.toString(),
|
|
||||||
NodeSpecificEvents.OCCURRENCES_AVAILABLE_EVENT.toString(),
|
|
||||||
null,
|
|
||||||
getCountPropertyAndDescription(correlationAttribute)));
|
|
||||||
});
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -631,135 +617,14 @@ public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends A
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Submit the database queries ASAP! We want updated SCO columns
|
||||||
|
//without blocking the UI as soon as we can get it! Keep all weak references
|
||||||
|
//so this task doesn't block the ability of this node to be GC'd. Handle potentially
|
||||||
|
//null reference values in the Task!
|
||||||
|
pool.submit(new SCOAndTranslationTask(new WeakReference<>(content), weakPcl));
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all tags from the case database that are associated with the file
|
|
||||||
*
|
|
||||||
* @return a list of tags that are associated with the file
|
|
||||||
*/
|
|
||||||
private List<ContentTag> getContentTagsFromDatabase(AbstractFile content) {
|
|
||||||
List<ContentTag> tags = new ArrayList<>();
|
|
||||||
try {
|
|
||||||
tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(content));
|
|
||||||
} catch (TskCoreException | NoCurrentCaseException ex) {
|
|
||||||
logger.log(Level.SEVERE, "Failed to get tags for content " + content.getName(), ex);
|
|
||||||
}
|
|
||||||
return tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
private CorrelationAttributeInstance getCorrelationAttributeInstance() {
|
|
||||||
CorrelationAttributeInstance correlationAttribute = null;
|
|
||||||
if (EamDbUtil.useCentralRepo()) {
|
|
||||||
correlationAttribute = EamArtifactUtil.getInstanceFromContent(content);
|
|
||||||
}
|
|
||||||
return correlationAttribute;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used by subclasses of AbstractAbstractFileNode to add the comment
|
|
||||||
* property to their sheets.
|
|
||||||
*
|
|
||||||
* @param sheetSet the modifiable Sheet.Set returned by
|
|
||||||
* Sheet.get(Sheet.PROPERTIES)
|
|
||||||
* @param tags the list of tags associated with the file
|
|
||||||
* @param attribute the correlation attribute associated with this file,
|
|
||||||
* null if central repo is not enabled
|
|
||||||
*/
|
|
||||||
@NbBundle.Messages({
|
|
||||||
"AbstractAbstractFileNode.createSheet.comment.displayName=C"})
|
|
||||||
private HasCommentStatus getCommentProperty(List<ContentTag> tags, CorrelationAttributeInstance attribute) {
|
|
||||||
|
|
||||||
HasCommentStatus status = tags.size() > 0 ? HasCommentStatus.TAG_NO_COMMENT : HasCommentStatus.NO_COMMENT;
|
|
||||||
|
|
||||||
for (ContentTag tag : tags) {
|
|
||||||
if (!StringUtils.isBlank(tag.getComment())) {
|
|
||||||
//if the tag is null or empty or contains just white space it will indicate there is not a comment
|
|
||||||
status = HasCommentStatus.TAG_COMMENT;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (attribute != null && !StringUtils.isBlank(attribute.getComment())) {
|
|
||||||
if (status == HasCommentStatus.TAG_COMMENT) {
|
|
||||||
status = HasCommentStatus.CR_AND_TAG_COMMENTS;
|
|
||||||
} else {
|
|
||||||
status = HasCommentStatus.CR_COMMENT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used by subclasses of AbstractAbstractFileNode to add the Score property
|
|
||||||
* to their sheets.
|
|
||||||
*
|
|
||||||
* @param sheetSet the modifiable Sheet.Set returned by
|
|
||||||
* Sheet.get(Sheet.PROPERTIES)
|
|
||||||
* @param tags the list of tags associated with the file
|
|
||||||
*/
|
|
||||||
@NbBundle.Messages({
|
|
||||||
"AbstractAbstractFileNode.createSheet.score.displayName=S",
|
|
||||||
"AbstractAbstractFileNode.createSheet.notableFile.description=File recognized as notable.",
|
|
||||||
"AbstractAbstractFileNode.createSheet.interestingResult.description=File has interesting result associated with it.",
|
|
||||||
"AbstractAbstractFileNode.createSheet.taggedFile.description=File has been tagged.",
|
|
||||||
"AbstractAbstractFileNode.createSheet.notableTaggedFile.description=File tagged with notable tag.",
|
|
||||||
"AbstractAbstractFileNode.createSheet.noScore.description=No score"})
|
|
||||||
private Pair<Score, String> getScorePropertyAndDescription(List<ContentTag> tags) {
|
|
||||||
Score score = Score.NO_SCORE;
|
|
||||||
String description = "";
|
|
||||||
if (content.getKnown() == TskData.FileKnown.BAD) {
|
|
||||||
score = Score.NOTABLE_SCORE;
|
|
||||||
description = Bundle.AbstractAbstractFileNode_createSheet_notableFile_description();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (score == Score.NO_SCORE && !content.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT).isEmpty()) {
|
|
||||||
score = Score.INTERESTING_SCORE;
|
|
||||||
description = Bundle.AbstractAbstractFileNode_createSheet_interestingResult_description();
|
|
||||||
}
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
logger.log(Level.WARNING, "Error getting artifacts for file: " + content.getName(), ex);
|
|
||||||
}
|
|
||||||
if (tags.size() > 0 && (score == Score.NO_SCORE || score == Score.INTERESTING_SCORE)) {
|
|
||||||
score = Score.INTERESTING_SCORE;
|
|
||||||
description = Bundle.AbstractAbstractFileNode_createSheet_taggedFile_description();
|
|
||||||
for (ContentTag tag : tags) {
|
|
||||||
if (tag.getName().getKnownStatus() == TskData.FileKnown.BAD) {
|
|
||||||
score = Score.NOTABLE_SCORE;
|
|
||||||
description = Bundle.AbstractAbstractFileNode_createSheet_notableTaggedFile_description();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Pair.of(score, description);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NbBundle.Messages({
|
|
||||||
"AbstractAbstractFileNode.createSheet.count.displayName=O",
|
|
||||||
"AbstractAbstractFileNode.createSheet.count.noCentralRepo.description=Central repository was not enabled when this column was populated",
|
|
||||||
"AbstractAbstractFileNode.createSheet.count.hashLookupNotRun.description=Hash lookup had not been run on this file when the column was populated",
|
|
||||||
"# {0} - occuranceCount",
|
|
||||||
"AbstractAbstractFileNode.createSheet.count.description=There were {0} datasource(s) found with occurances of the correlation value"})
|
|
||||||
private static Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance attribute) {
|
|
||||||
Long count = -1L; //The column renderer will not display negative values, negative value used when count unavailble to preserve sorting
|
|
||||||
String description = Bundle.AbstractAbstractFileNode_createSheet_count_noCentralRepo_description();
|
|
||||||
try {
|
|
||||||
//don't perform the query if there is no correlation value
|
|
||||||
if (attribute != null && StringUtils.isNotBlank(attribute.getCorrelationValue())) {
|
|
||||||
count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(attribute.getCorrelationType(), attribute.getCorrelationValue());
|
|
||||||
description = Bundle.AbstractAbstractFileNode_createSheet_count_description(count);
|
|
||||||
} else if (attribute != null) {
|
|
||||||
description = Bundle.AbstractAbstractFileNode_createSheet_count_hashLookupNotRun_description();
|
|
||||||
}
|
|
||||||
} catch (EamDbException ex) {
|
|
||||||
logger.log(Level.WARNING, "Error getting count of datasources with correlation attribute", ex);
|
|
||||||
} catch (CorrelationAttributeNormalizationException ex) {
|
|
||||||
logger.log(Level.WARNING, "Unable to normalize data to get count of datasources with correlation attribute", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Pair.of(count, description);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used by subclasses of AbstractAbstractFileNode to add the tags property
|
* Used by subclasses of AbstractAbstractFileNode to add the tags property
|
||||||
* to their sheets.
|
* to their sheets.
|
||||||
|
@ -1,7 +1,20 @@
|
|||||||
/*
|
/*
|
||||||
* To change this license header, choose License Headers in Project Properties.
|
* Autopsy Forensic Browser
|
||||||
* To change this template file, choose Tools | Templates
|
*
|
||||||
* and open the template in the editor.
|
* Copyright 2018-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.datamodel;
|
package org.sleuthkit.autopsy.datamodel;
|
||||||
|
|
||||||
|
222
Core/src/org/sleuthkit/autopsy/datamodel/PropertyUtil.java
Executable file
222
Core/src/org/sleuthkit/autopsy/datamodel/PropertyUtil.java
Executable file
@ -0,0 +1,222 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2018-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.datamodel;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import org.apache.commons.io.FilenameUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
import org.openide.util.NbBundle;
|
||||||
|
import org.sleuthkit.autopsy.casemodule.Case;
|
||||||
|
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
|
||||||
|
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
||||||
|
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeNormalizationException;
|
||||||
|
import org.sleuthkit.autopsy.centralrepository.datamodel.EamArtifactUtil;
|
||||||
|
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDb;
|
||||||
|
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbException;
|
||||||
|
import org.sleuthkit.autopsy.centralrepository.datamodel.EamDbUtil;
|
||||||
|
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
|
||||||
|
import org.sleuthkit.autopsy.coreutils.Logger;
|
||||||
|
import org.sleuthkit.autopsy.texttranslation.NoServiceProviderException;
|
||||||
|
import org.sleuthkit.autopsy.texttranslation.TextTranslationService;
|
||||||
|
import org.sleuthkit.autopsy.texttranslation.TranslationException;
|
||||||
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
|
import org.sleuthkit.datamodel.BlackboardArtifact;
|
||||||
|
import org.sleuthkit.datamodel.ContentTag;
|
||||||
|
import org.sleuthkit.datamodel.TskCoreException;
|
||||||
|
import org.sleuthkit.datamodel.TskData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author dsmyda
|
||||||
|
*/
|
||||||
|
class PropertyUtil {
|
||||||
|
|
||||||
|
private static final String NO_TRANSLATION = "";
|
||||||
|
private static final Logger logger = Logger.getLogger(PropertyUtil.class.getName());
|
||||||
|
|
||||||
|
@NbBundle.Messages({
|
||||||
|
"AbstractAbstractFileNode.createSheet.count.displayName=O",
|
||||||
|
"AbstractAbstractFileNode.createSheet.count.noCentralRepo.description=Central repository was not enabled when this column was populated",
|
||||||
|
"AbstractAbstractFileNode.createSheet.count.hashLookupNotRun.description=Hash lookup had not been run on this file when the column was populated",
|
||||||
|
"# {0} - occuranceCount",
|
||||||
|
"AbstractAbstractFileNode.createSheet.count.description=There were {0} datasource(s) found with occurances of the correlation value"})
|
||||||
|
static Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance attribute) {
|
||||||
|
Long count = -1L; //The column renderer will not display negative values, negative value used when count unavailble to preserve sorting
|
||||||
|
String description = Bundle.AbstractAbstractFileNode_createSheet_count_noCentralRepo_description();
|
||||||
|
try {
|
||||||
|
//don't perform the query if there is no correlation value
|
||||||
|
if (attribute != null && StringUtils.isNotBlank(attribute.getCorrelationValue())) {
|
||||||
|
count = EamDb.getInstance().getCountUniqueCaseDataSourceTuplesHavingTypeValue(attribute.getCorrelationType(), attribute.getCorrelationValue());
|
||||||
|
description = Bundle.AbstractAbstractFileNode_createSheet_count_description(count);
|
||||||
|
} else if (attribute != null) {
|
||||||
|
description = Bundle.AbstractAbstractFileNode_createSheet_count_hashLookupNotRun_description();
|
||||||
|
}
|
||||||
|
} catch (EamDbException ex) {
|
||||||
|
logger.log(Level.WARNING, "Error getting count of datasources with correlation attribute", ex);
|
||||||
|
} catch (CorrelationAttributeNormalizationException ex) {
|
||||||
|
logger.log(Level.WARNING, "Unable to normalize data to get count of datasources with correlation attribute", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pair.of(count, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by subclasses of AbstractAbstractFileNode to add the Score property
|
||||||
|
* to their sheets.
|
||||||
|
*
|
||||||
|
* @param sheetSet the modifiable Sheet.Set returned by
|
||||||
|
* Sheet.get(Sheet.PROPERTIES)
|
||||||
|
* @param tags the list of tags associated with the file
|
||||||
|
*/
|
||||||
|
@NbBundle.Messages({
|
||||||
|
"AbstractAbstractFileNode.createSheet.score.displayName=S",
|
||||||
|
"AbstractAbstractFileNode.createSheet.notableFile.description=File recognized as notable.",
|
||||||
|
"AbstractAbstractFileNode.createSheet.interestingResult.description=File has interesting result associated with it.",
|
||||||
|
"AbstractAbstractFileNode.createSheet.taggedFile.description=File has been tagged.",
|
||||||
|
"AbstractAbstractFileNode.createSheet.notableTaggedFile.description=File tagged with notable tag.",
|
||||||
|
"AbstractAbstractFileNode.createSheet.noScore.description=No score"})
|
||||||
|
static Pair<DataResultViewerTable.Score, String> getScorePropertyAndDescription(AbstractFile content, List<ContentTag> tags) {
|
||||||
|
DataResultViewerTable.Score score = DataResultViewerTable.Score.NO_SCORE;
|
||||||
|
String description = "";
|
||||||
|
if (content.getKnown() == TskData.FileKnown.BAD) {
|
||||||
|
score = DataResultViewerTable.Score.NOTABLE_SCORE;
|
||||||
|
description = Bundle.AbstractAbstractFileNode_createSheet_notableFile_description();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (score == DataResultViewerTable.Score.NO_SCORE && !content.getArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT).isEmpty()) {
|
||||||
|
score = DataResultViewerTable.Score.INTERESTING_SCORE;
|
||||||
|
description = Bundle.AbstractAbstractFileNode_createSheet_interestingResult_description();
|
||||||
|
}
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
logger.log(Level.WARNING, "Error getting artifacts for file: " + content.getName(), ex);
|
||||||
|
}
|
||||||
|
if (tags.size() > 0 && (score == DataResultViewerTable.Score.NO_SCORE || score == DataResultViewerTable.Score.INTERESTING_SCORE)) {
|
||||||
|
score = DataResultViewerTable.Score.INTERESTING_SCORE;
|
||||||
|
description = Bundle.AbstractAbstractFileNode_createSheet_taggedFile_description();
|
||||||
|
for (ContentTag tag : tags) {
|
||||||
|
if (tag.getName().getKnownStatus() == TskData.FileKnown.BAD) {
|
||||||
|
score = DataResultViewerTable.Score.NOTABLE_SCORE;
|
||||||
|
description = Bundle.AbstractAbstractFileNode_createSheet_notableTaggedFile_description();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Pair.of(score, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by subclasses of AbstractAbstractFileNode to add the comment
|
||||||
|
* property to their sheets.
|
||||||
|
*
|
||||||
|
* @param sheetSet the modifiable Sheet.Set returned by
|
||||||
|
* Sheet.get(Sheet.PROPERTIES)
|
||||||
|
* @param tags the list of tags associated with the file
|
||||||
|
* @param attribute the correlation attribute associated with this file,
|
||||||
|
* null if central repo is not enabled
|
||||||
|
*/
|
||||||
|
@NbBundle.Messages({
|
||||||
|
"AbstractAbstractFileNode.createSheet.comment.displayName=C"})
|
||||||
|
static DataResultViewerTable.HasCommentStatus getCommentProperty(List<ContentTag> tags, CorrelationAttributeInstance attribute) {
|
||||||
|
|
||||||
|
DataResultViewerTable.HasCommentStatus status = tags.size() > 0 ? DataResultViewerTable.HasCommentStatus.TAG_NO_COMMENT : DataResultViewerTable.HasCommentStatus.NO_COMMENT;
|
||||||
|
|
||||||
|
for (ContentTag tag : tags) {
|
||||||
|
if (!StringUtils.isBlank(tag.getComment())) {
|
||||||
|
//if the tag is null or empty or contains just white space it will indicate there is not a comment
|
||||||
|
status = DataResultViewerTable.HasCommentStatus.TAG_COMMENT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (attribute != null && !StringUtils.isBlank(attribute.getComment())) {
|
||||||
|
if (status == DataResultViewerTable.HasCommentStatus.TAG_COMMENT) {
|
||||||
|
status = DataResultViewerTable.HasCommentStatus.CR_AND_TAG_COMMENTS;
|
||||||
|
} else {
|
||||||
|
status = DataResultViewerTable.HasCommentStatus.CR_COMMENT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts translation of the content name being passed in.
|
||||||
|
*
|
||||||
|
* @return The file names translation.
|
||||||
|
*/
|
||||||
|
static String getTranslatedFileName(AbstractFile content) {
|
||||||
|
//If already in complete English, don't translate.
|
||||||
|
if (content.getName().matches("^\\p{ASCII}+$")) {
|
||||||
|
return NO_TRANSLATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextTranslationService tts = TextTranslationService.getInstance();
|
||||||
|
if (tts.hasProvider()) {
|
||||||
|
//Seperate out the base and ext from the contents file name.
|
||||||
|
String base = FilenameUtils.getBaseName(content.getName());
|
||||||
|
|
||||||
|
try {
|
||||||
|
String translation = tts.translate(base);
|
||||||
|
String ext = FilenameUtils.getExtension(content.getName());
|
||||||
|
|
||||||
|
//If we have no extension, then we shouldn't add the .
|
||||||
|
String extensionDelimiter = (ext.isEmpty()) ? "" : ".";
|
||||||
|
|
||||||
|
//Talk directly to this nodes pcl, fire an update when the translation
|
||||||
|
//is complete.
|
||||||
|
if (!translation.isEmpty()) {
|
||||||
|
return translation + extensionDelimiter + ext;
|
||||||
|
}
|
||||||
|
} catch (NoServiceProviderException noServiceEx) {
|
||||||
|
logger.log(Level.WARNING, "Translate unsuccessful because no TextTranslator "
|
||||||
|
+ "implementation was provided.", noServiceEx);
|
||||||
|
} catch (TranslationException noTranslationEx) {
|
||||||
|
logger.log(Level.WARNING, "Could not successfully translate file name "
|
||||||
|
+ content.getName(), noTranslationEx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO_TRANSLATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all tags from the case database that are associated with the file
|
||||||
|
*
|
||||||
|
* @return a list of tags that are associated with the file
|
||||||
|
*/
|
||||||
|
static List<ContentTag> getContentTagsFromDatabase(AbstractFile content) {
|
||||||
|
List<ContentTag> tags = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(content));
|
||||||
|
} catch (TskCoreException | NoCurrentCaseException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Failed to get tags for content " + content.getName(), ex);
|
||||||
|
}
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
static CorrelationAttributeInstance getCorrelationAttributeInstance(AbstractFile content) {
|
||||||
|
CorrelationAttributeInstance attribute = null;
|
||||||
|
if (EamDbUtil.useCentralRepo()) {
|
||||||
|
attribute = EamArtifactUtil.getInstanceFromContent(content);
|
||||||
|
}
|
||||||
|
return attribute;
|
||||||
|
}
|
||||||
|
}
|
144
Core/src/org/sleuthkit/autopsy/datamodel/SCOAndTranslationTask.java
Executable file
144
Core/src/org/sleuthkit/autopsy/datamodel/SCOAndTranslationTask.java
Executable file
@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
* Autopsy Forensic Browser
|
||||||
|
*
|
||||||
|
* Copyright 2018-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.datamodel;
|
||||||
|
|
||||||
|
import java.beans.PropertyChangeEvent;
|
||||||
|
import java.beans.PropertyChangeListener;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
import org.sleuthkit.autopsy.centralrepository.datamodel.CorrelationAttributeInstance;
|
||||||
|
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable;
|
||||||
|
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.HasCommentStatus;
|
||||||
|
import org.sleuthkit.autopsy.corecomponents.DataResultViewerTable.Score;
|
||||||
|
import org.sleuthkit.autopsy.events.AutopsyEvent;
|
||||||
|
import org.sleuthkit.datamodel.AbstractFile;
|
||||||
|
import org.sleuthkit.datamodel.ContentTag;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Completes the tasks needed to populate the Score, Comment, Occurrences and Translation
|
||||||
|
* columns in the background so that the UI is not blocked while waiting for responses from the database or
|
||||||
|
* translation service. Once these events are done, it fires a PropertyChangeEvent
|
||||||
|
* to let the AbstractAbstractFileNode know it's time to update!
|
||||||
|
*/
|
||||||
|
class SCOAndTranslationTask implements Runnable {
|
||||||
|
|
||||||
|
private final WeakReference<AbstractFile> weakContentRef;
|
||||||
|
private final PropertyChangeListener listener;
|
||||||
|
|
||||||
|
public SCOAndTranslationTask(WeakReference<AbstractFile> weakContentRef, PropertyChangeListener listener) {
|
||||||
|
this.weakContentRef = weakContentRef;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
AbstractFile content = weakContentRef.get();
|
||||||
|
|
||||||
|
//Long DB queries
|
||||||
|
List<ContentTag> tags = PropertyUtil.getContentTagsFromDatabase(content);
|
||||||
|
CorrelationAttributeInstance attribute =
|
||||||
|
PropertyUtil.getCorrelationAttributeInstance(content);
|
||||||
|
|
||||||
|
Pair<DataResultViewerTable.Score, String> scoreAndDescription =
|
||||||
|
PropertyUtil.getScorePropertyAndDescription(content, tags);
|
||||||
|
DataResultViewerTable.HasCommentStatus comment =
|
||||||
|
PropertyUtil.getCommentProperty(tags, attribute);
|
||||||
|
Pair<Long, String> countAndDescription =
|
||||||
|
PropertyUtil.getCountPropertyAndDescription(attribute);
|
||||||
|
|
||||||
|
//Load the results from the SCO column operations into a wrapper object to be passed
|
||||||
|
//back to the listener so that the node can internally update it's propertySheet.
|
||||||
|
SCOResults results = new SCOResults(
|
||||||
|
scoreAndDescription.getLeft(),
|
||||||
|
scoreAndDescription.getRight(),
|
||||||
|
comment,
|
||||||
|
countAndDescription.getLeft(),
|
||||||
|
countAndDescription.getRight()
|
||||||
|
);
|
||||||
|
|
||||||
|
listener.propertyChange(new PropertyChangeEvent(
|
||||||
|
AutopsyEvent.SourceType.LOCAL.toString(),
|
||||||
|
AbstractAbstractFileNode.NodeSpecificEvents.DABABASE_CONTENT_AVAILABLE.toString(),
|
||||||
|
null,
|
||||||
|
results));
|
||||||
|
|
||||||
|
//Once we've got the SCO columns, then lets fire the translation result.
|
||||||
|
//Updating of this column is significantly lower priority than
|
||||||
|
//getting results to the SCO columns!
|
||||||
|
listener.propertyChange(new PropertyChangeEvent(
|
||||||
|
AutopsyEvent.SourceType.LOCAL.toString(),
|
||||||
|
AbstractAbstractFileNode.NodeSpecificEvents.TRANSLATION_AVAILABLE.toString(),
|
||||||
|
null,
|
||||||
|
PropertyUtil.getTranslatedFileName(content)));
|
||||||
|
} catch (NullPointerException ex) {
|
||||||
|
//If we are here, that means our weakPcl or content pointer has gone stale (aka
|
||||||
|
//has been garbage collected). There's no recovery. Netbeans has
|
||||||
|
//GC'd the node because its not useful to the user anymore. No need
|
||||||
|
//to log. Fail out fast to keep the thread pool rollin!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper around data obtained from doing the Score, Comment, and Occurrences
|
||||||
|
* tasks. This object will be accessed by the AAFN to update it's state.
|
||||||
|
*/
|
||||||
|
final class SCOResults {
|
||||||
|
|
||||||
|
private final Score score;
|
||||||
|
private final String scoreDescription;
|
||||||
|
|
||||||
|
private final HasCommentStatus comment;
|
||||||
|
|
||||||
|
private final Long count;
|
||||||
|
private final String countDescription;
|
||||||
|
|
||||||
|
public SCOResults(Score score, String scoreDescription,
|
||||||
|
HasCommentStatus comment, Long count,
|
||||||
|
String countDescription) {
|
||||||
|
this.score = score;
|
||||||
|
this.scoreDescription = scoreDescription;
|
||||||
|
this.comment = comment;
|
||||||
|
this.count = count;
|
||||||
|
this.countDescription = countDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Score getScore() {
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScoreDescription() {
|
||||||
|
return scoreDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HasCommentStatus getComment() {
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getCount() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCountDescription() {
|
||||||
|
return countDescription;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -115,7 +115,7 @@ public class VirtualDirectoryNode extends SpecialDirectoryNode {
|
|||||||
} catch (SQLException | TskCoreException | NoCurrentCaseException ex) {
|
} catch (SQLException | TskCoreException | NoCurrentCaseException ex) {
|
||||||
logger.log(Level.SEVERE, "Failed to get device id for the following image: " + this.content.getId(), ex);
|
logger.log(Level.SEVERE, "Failed to get device id for the following image: " + this.content.getId(), ex);
|
||||||
}
|
}
|
||||||
|
return sheet;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Otherwise default to the AAFN createSheet method.
|
//Otherwise default to the AAFN createSheet method.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user