mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-15 09:17:42 +00:00
Merge pull request #2631 from eugene7646/substring_search_2384
Substring search 2384
This commit is contained in:
commit
34e7d8a165
@ -91,6 +91,8 @@ public interface DisplayableItemNodeVisitor<T> {
|
|||||||
|
|
||||||
T visit(KeywordHits.TermNode khmln);
|
T visit(KeywordHits.TermNode khmln);
|
||||||
|
|
||||||
|
T visit(KeywordHits.RegExpInstanceNode khmln);
|
||||||
|
|
||||||
T visit(HashsetHits.RootNode hhrn);
|
T visit(HashsetHits.RootNode hhrn);
|
||||||
|
|
||||||
T visit(HashsetHits.HashsetNameNode hhsn);
|
T visit(HashsetHits.HashsetNameNode hhsn);
|
||||||
@ -281,6 +283,11 @@ public interface DisplayableItemNodeVisitor<T> {
|
|||||||
return defaultVisit(khsn);
|
return defaultVisit(khsn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T visit(KeywordHits.RegExpInstanceNode khsn) {
|
||||||
|
return defaultVisit(khsn);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public T visit(KeywordHits.TermNode khmln) {
|
public T visit(KeywordHits.TermNode khmln) {
|
||||||
return defaultVisit(khmln);
|
return defaultVisit(khmln);
|
||||||
|
@ -64,17 +64,24 @@ public class KeywordHits implements AutopsyVisitableItem {
|
|||||||
public static final String SIMPLE_REGEX_SEARCH = NbBundle
|
public static final String SIMPLE_REGEX_SEARCH = NbBundle
|
||||||
.getMessage(KeywordHits.class, "KeywordHits.singleRegexSearch.text");
|
.getMessage(KeywordHits.class, "KeywordHits.singleRegexSearch.text");
|
||||||
private final KeywordResults keywordResults;
|
private final KeywordResults keywordResults;
|
||||||
|
private final String DUMMY_INSTANCE = "DUMMY_EXACT_MATCH_INSTANCE";
|
||||||
|
|
||||||
public KeywordHits(SleuthkitCase skCase) {
|
public KeywordHits(SleuthkitCase skCase) {
|
||||||
this.skCase = skCase;
|
this.skCase = skCase;
|
||||||
keywordResults = new KeywordResults();
|
keywordResults = new KeywordResults();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* All of these maps and code assume the following:
|
||||||
|
* Regexps will have an 'instance' layer that shows the specific words that matched the regexp
|
||||||
|
* Exact match and substring will not have the instance layer and instead will have the specific hits
|
||||||
|
* below their term.
|
||||||
|
*/
|
||||||
|
|
||||||
private final class KeywordResults extends Observable {
|
private final class KeywordResults extends Observable {
|
||||||
|
|
||||||
// Map from listName/Type to Map of keyword to set of artifact Ids
|
// Map from listName/Type to Map of keywords/regexp to Map of instance terms to Set of artifact Ids
|
||||||
// NOTE: the map can be accessed by multiple worker threads and needs to be synchronized
|
// NOTE: the map can be accessed by multiple worker threads and needs to be synchronized
|
||||||
private final Map<String, Map<String, Set<Long>>> topLevelMap = new LinkedHashMap<>();
|
private final Map<String, Map<String, Map<String, Set<Long>>>> topLevelMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
KeywordResults() {
|
KeywordResults() {
|
||||||
update();
|
update();
|
||||||
@ -99,10 +106,50 @@ public class KeywordHits implements AutopsyVisitableItem {
|
|||||||
return keywords;
|
return keywords;
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<Long> getArtifactIds(String listName, String keyword) {
|
List<String> getKeywordInstances(String listName, String keyword) {
|
||||||
|
List<String> instances;
|
||||||
synchronized (topLevelMap) {
|
synchronized (topLevelMap) {
|
||||||
return topLevelMap.get(listName).get(keyword);
|
instances = new ArrayList<>(topLevelMap.get(listName).get(keyword).keySet());
|
||||||
}
|
}
|
||||||
|
Collections.sort(instances);
|
||||||
|
return instances;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<Long> getArtifactIds(String listName, String keyword, String keywordInstance) {
|
||||||
|
synchronized (topLevelMap) {
|
||||||
|
return topLevelMap.get(listName).get(keyword).get(keywordInstance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addRegExpToList(Map<String, Map<String, Set<Long>>> listMap, String regExp, String word, Long id) {
|
||||||
|
if (listMap.containsKey(regExp) == false) {
|
||||||
|
listMap.put(regExp, new LinkedHashMap<>());
|
||||||
|
}
|
||||||
|
Map<String, Set<Long>> instanceMap = listMap.get(regExp);
|
||||||
|
|
||||||
|
// get or create keyword instances entry.
|
||||||
|
if (instanceMap.containsKey(word) == false) {
|
||||||
|
instanceMap.put(word, new HashSet<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// add this ID to the instance
|
||||||
|
instanceMap.get(word).add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addExactMatchToList(Map<String, Map<String, Set<Long>>> listMap, String word, Long id) {
|
||||||
|
if (listMap.containsKey(word) == false) {
|
||||||
|
listMap.put(word, new LinkedHashMap<>());
|
||||||
|
}
|
||||||
|
Map<String, Set<Long>> instanceMap = listMap.get(word);
|
||||||
|
|
||||||
|
// get or create keyword instances entry.
|
||||||
|
// for exact match, use a dummy instance
|
||||||
|
if (instanceMap.containsKey(DUMMY_INSTANCE) == false) {
|
||||||
|
instanceMap.put(DUMMY_INSTANCE, new HashSet<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// add this ID to the instance
|
||||||
|
instanceMap.get(DUMMY_INSTANCE).add(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// populate maps based on artifactIds
|
// populate maps based on artifactIds
|
||||||
@ -111,13 +158,13 @@ public class KeywordHits implements AutopsyVisitableItem {
|
|||||||
topLevelMap.clear();
|
topLevelMap.clear();
|
||||||
|
|
||||||
// map of list name to keword to artifact IDs
|
// map of list name to keword to artifact IDs
|
||||||
Map<String, Map<String, Set<Long>>> listsMap = new LinkedHashMap<>();
|
Map<String, Map<String, Map<String, Set<Long>>>> listsMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
// Map from from literal keyword to artifact IDs
|
// Map from from literal keyword to instances (which will be empty) to artifact IDs
|
||||||
Map<String, Set<Long>> literalMap = new LinkedHashMap<>();
|
Map<String, Map<String, Set<Long>>> literalMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
// Map from regex keyword artifact IDs
|
// Map from regex keyword artifact to instances to artifact IDs
|
||||||
Map<String, Set<Long>> regexMap = new LinkedHashMap<>();
|
Map<String, Map<String, Set<Long>>> regexMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
// top-level nodes
|
// top-level nodes
|
||||||
topLevelMap.put(SIMPLE_LITERAL_SEARCH, literalMap);
|
topLevelMap.put(SIMPLE_LITERAL_SEARCH, literalMap);
|
||||||
@ -131,34 +178,47 @@ public class KeywordHits implements AutopsyVisitableItem {
|
|||||||
String listName = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()));
|
String listName = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()));
|
||||||
String word = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()));
|
String word = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()));
|
||||||
String reg = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID()));
|
String reg = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID()));
|
||||||
|
// new in 4.4
|
||||||
|
String kwType = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE.getTypeID()));
|
||||||
|
|
||||||
// part of a list
|
// part of a list
|
||||||
if (listName != null) {
|
if (listName != null) {
|
||||||
|
// get or create list entry
|
||||||
if (listsMap.containsKey(listName) == false) {
|
if (listsMap.containsKey(listName) == false) {
|
||||||
listsMap.put(listName, new LinkedHashMap<String, Set<Long>>());
|
listsMap.put(listName, new LinkedHashMap<>());
|
||||||
}
|
}
|
||||||
|
Map<String, Map<String, Set<Long>>> listMap = listsMap.get(listName);
|
||||||
|
|
||||||
Map<String, Set<Long>> listMap = listsMap.get(listName);
|
// substring, treated same as exact match
|
||||||
if (listMap.containsKey(word) == false) {
|
// Enum for "1" is defined in KeywordSearch.java
|
||||||
listMap.put(word, new HashSet<Long>());
|
if ((kwType != null) && (kwType.equals("1"))) {
|
||||||
|
// original term should be stored in reg
|
||||||
|
if (reg != null) {
|
||||||
|
addExactMatchToList(listMap, reg, id);
|
||||||
|
} else {
|
||||||
|
addExactMatchToList(listMap, word, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (reg != null) {
|
||||||
|
addRegExpToList(listMap, reg, word, id);
|
||||||
|
} else {
|
||||||
|
addExactMatchToList(listMap, word, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
listMap.get(word).add(id);
|
|
||||||
} // regular expression, single term
|
} // regular expression, single term
|
||||||
else if (reg != null) {
|
else if (reg != null) {
|
||||||
if (regexMap.containsKey(reg) == false) {
|
// substring is treated same as exact
|
||||||
regexMap.put(reg, new HashSet<Long>());
|
if ((kwType != null) && (kwType.equals("1"))) {
|
||||||
|
// original term should be stored in reg
|
||||||
|
addExactMatchToList(literalMap, reg, id);
|
||||||
|
} else {
|
||||||
|
addRegExpToList(regexMap, reg, word, id);
|
||||||
}
|
}
|
||||||
regexMap.get(reg).add(id);
|
|
||||||
} // literal, single term
|
} // literal, single term
|
||||||
else {
|
else {
|
||||||
if (literalMap.containsKey(word) == false) {
|
addExactMatchToList(literalMap, word, id);
|
||||||
literalMap.put(word, new HashSet<Long>());
|
|
||||||
}
|
|
||||||
literalMap.get(word).add(id);
|
|
||||||
}
|
}
|
||||||
topLevelMap.putAll(listsMap);
|
|
||||||
}
|
}
|
||||||
|
topLevelMap.putAll(listsMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
setChanged();
|
setChanged();
|
||||||
@ -167,35 +227,43 @@ public class KeywordHits implements AutopsyVisitableItem {
|
|||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public void update() {
|
public void update() {
|
||||||
|
// maps Artifact ID to map of attribute types to attribute values
|
||||||
Map<Long, Map<Long, String>> artifactIds = new LinkedHashMap<>();
|
Map<Long, Map<Long, String>> artifactIds = new LinkedHashMap<>();
|
||||||
|
|
||||||
if (skCase == null) {
|
if (skCase == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// query attributes table for the ones that we need for the tree
|
||||||
int setId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID();
|
int setId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID();
|
||||||
int wordId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID();
|
int wordId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID();
|
||||||
int regexId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID();
|
int regexId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID();
|
||||||
int artId = BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID();
|
int artId = BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID();
|
||||||
String query = "SELECT blackboard_attributes.value_text,blackboard_attributes.artifact_id," //NON-NLS
|
String query = "SELECT blackboard_attributes.value_text,blackboard_attributes.value_int32,"
|
||||||
|
+ "blackboard_attributes.artifact_id," //NON-NLS
|
||||||
+ "blackboard_attributes.attribute_type_id FROM blackboard_attributes,blackboard_artifacts WHERE " //NON-NLS
|
+ "blackboard_attributes.attribute_type_id FROM blackboard_attributes,blackboard_artifacts WHERE " //NON-NLS
|
||||||
+ "(blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id AND " //NON-NLS
|
+ "(blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id AND " //NON-NLS
|
||||||
+ "blackboard_artifacts.artifact_type_id=" + artId //NON-NLS
|
+ "blackboard_artifacts.artifact_type_id=" + artId //NON-NLS
|
||||||
+ ") AND (attribute_type_id=" + setId + " OR " //NON-NLS
|
+ ") AND (attribute_type_id=" + setId + " OR " //NON-NLS
|
||||||
+ "attribute_type_id=" + wordId + " OR " //NON-NLS
|
+ "attribute_type_id=" + wordId + " OR " //NON-NLS
|
||||||
|
+ "attribute_type_id=" + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE.getTypeID() + " OR " //NON-NLS
|
||||||
+ "attribute_type_id=" + regexId + ")"; //NON-NLS
|
+ "attribute_type_id=" + regexId + ")"; //NON-NLS
|
||||||
|
|
||||||
try (CaseDbQuery dbQuery = skCase.executeQuery(query)) {
|
try (CaseDbQuery dbQuery = skCase.executeQuery(query)) {
|
||||||
ResultSet resultSet = dbQuery.getResultSet();
|
ResultSet resultSet = dbQuery.getResultSet();
|
||||||
while (resultSet.next()) {
|
while (resultSet.next()) {
|
||||||
String value = resultSet.getString("value_text"); //NON-NLS
|
String valueStr = resultSet.getString("value_text"); //NON-NLS
|
||||||
long artifactId = resultSet.getLong("artifact_id"); //NON-NLS
|
long artifactId = resultSet.getLong("artifact_id"); //NON-NLS
|
||||||
long typeId = resultSet.getLong("attribute_type_id"); //NON-NLS
|
long typeId = resultSet.getLong("attribute_type_id"); //NON-NLS
|
||||||
if (!artifactIds.containsKey(artifactId)) {
|
if (!artifactIds.containsKey(artifactId)) {
|
||||||
artifactIds.put(artifactId, new LinkedHashMap<Long, String>());
|
artifactIds.put(artifactId, new LinkedHashMap<Long, String>());
|
||||||
}
|
}
|
||||||
if (!value.equals("")) {
|
if (valueStr != null && !valueStr.equals("")) {
|
||||||
artifactIds.get(artifactId).put(typeId, value);
|
artifactIds.get(artifactId).put(typeId, valueStr);
|
||||||
|
} else {
|
||||||
|
// Keyword Search Type is an int
|
||||||
|
Long valueLong = resultSet.getLong("value_int32");
|
||||||
|
artifactIds.get(artifactId).put(typeId, valueLong.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (TskCoreException | SQLException ex) {
|
} catch (TskCoreException | SQLException ex) {
|
||||||
@ -254,6 +322,9 @@ public class KeywordHits implements AutopsyVisitableItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the list nodes
|
||||||
|
*/
|
||||||
private class ListFactory extends ChildFactory.Detachable<String> implements Observer {
|
private class ListFactory extends ChildFactory.Detachable<String> implements Observer {
|
||||||
|
|
||||||
private final PropertyChangeListener pcl = new PropertyChangeListener() {
|
private final PropertyChangeListener pcl = new PropertyChangeListener() {
|
||||||
@ -344,6 +415,9 @@ public class KeywordHits implements AutopsyVisitableItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the keyword search lists (or default groupings if list was not given)
|
||||||
|
*/
|
||||||
public class ListNode extends DisplayableItemNode implements Observer {
|
public class ListNode extends DisplayableItemNode implements Observer {
|
||||||
|
|
||||||
private final String listName;
|
private final String listName;
|
||||||
@ -360,8 +434,10 @@ public class KeywordHits implements AutopsyVisitableItem {
|
|||||||
private void updateDisplayName() {
|
private void updateDisplayName() {
|
||||||
int totalDescendants = 0;
|
int totalDescendants = 0;
|
||||||
for (String word : keywordResults.getKeywords(listName)) {
|
for (String word : keywordResults.getKeywords(listName)) {
|
||||||
Set<Long> ids = keywordResults.getArtifactIds(listName, word);
|
for (String instance : keywordResults.getKeywordInstances(listName, word)) {
|
||||||
totalDescendants += ids.size();
|
Set<Long> ids = keywordResults.getArtifactIds(listName, word, instance);
|
||||||
|
totalDescendants += ids.size();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
super.setDisplayName(listName + " (" + totalDescendants + ")");
|
super.setDisplayName(listName + " (" + totalDescendants + ")");
|
||||||
}
|
}
|
||||||
@ -409,6 +485,9 @@ public class KeywordHits implements AutopsyVisitableItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the nodes that represent search terms
|
||||||
|
*/
|
||||||
private class TermFactory extends ChildFactory.Detachable<String> implements Observer {
|
private class TermFactory extends ChildFactory.Detachable<String> implements Observer {
|
||||||
|
|
||||||
private final String setName;
|
private final String setName;
|
||||||
@ -445,13 +524,16 @@ public class KeywordHits implements AutopsyVisitableItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the search term or regexp that user searched for
|
||||||
|
*/
|
||||||
public class TermNode extends DisplayableItemNode implements Observer {
|
public class TermNode extends DisplayableItemNode implements Observer {
|
||||||
|
|
||||||
private final String setName;
|
private final String setName;
|
||||||
private final String keyword;
|
private final String keyword;
|
||||||
|
|
||||||
public TermNode(String setName, String keyword) {
|
public TermNode(String setName, String keyword) {
|
||||||
super(Children.create(new HitsFactory(setName, keyword), true), Lookups.singleton(keyword));
|
super(Children.create(new RegExpInstancesFactory(setName, keyword), true), Lookups.singleton(keyword));
|
||||||
super.setName(keyword);
|
super.setName(keyword);
|
||||||
this.setName = setName;
|
this.setName = setName;
|
||||||
this.keyword = keyword;
|
this.keyword = keyword;
|
||||||
@ -461,7 +543,175 @@ public class KeywordHits implements AutopsyVisitableItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateDisplayName() {
|
private void updateDisplayName() {
|
||||||
super.setDisplayName(keyword + " (" + keywordResults.getArtifactIds(setName, keyword).size() + ")");
|
int totalDescendants = 0;
|
||||||
|
|
||||||
|
for (String instance : keywordResults.getKeywordInstances(setName, keyword)) {
|
||||||
|
Set<Long> ids = keywordResults.getArtifactIds(setName, keyword, instance);
|
||||||
|
totalDescendants += ids.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
super.setDisplayName(keyword + " (" + totalDescendants + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(Observable o, Object arg) {
|
||||||
|
updateDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLeafTypeNode() {
|
||||||
|
List<String> instances = keywordResults.getKeywordInstances(setName, keyword);
|
||||||
|
// is this an exact match
|
||||||
|
if (instances.size() == 1 && instances.get(0).equals(DUMMY_INSTANCE)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T accept(DisplayableItemNodeVisitor<T> v) {
|
||||||
|
return v.visit(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Sheet createSheet() {
|
||||||
|
Sheet s = super.createSheet();
|
||||||
|
Sheet.Set ss = s.get(Sheet.PROPERTIES);
|
||||||
|
if (ss == null) {
|
||||||
|
ss = Sheet.createPropertiesSet();
|
||||||
|
s.put(ss);
|
||||||
|
}
|
||||||
|
|
||||||
|
ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.listName.name"),
|
||||||
|
NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.listName.displayName"),
|
||||||
|
NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.listName.desc"),
|
||||||
|
getDisplayName()));
|
||||||
|
|
||||||
|
ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.name"),
|
||||||
|
NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.displayName"),
|
||||||
|
NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.desc"),
|
||||||
|
keywordResults.getKeywordInstances(setName, keyword).size()));
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getItemType() {
|
||||||
|
return getClass().getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allows us to pass in either longs or strings
|
||||||
|
// as they keys for different types of nodes at the
|
||||||
|
// same level. Probably a better way to do this, but
|
||||||
|
// it works.
|
||||||
|
class RegExpInstanceKey {
|
||||||
|
private final boolean isRegExp;
|
||||||
|
private String strKey;
|
||||||
|
private Long longKey;
|
||||||
|
public RegExpInstanceKey(String key) {
|
||||||
|
isRegExp = true;
|
||||||
|
strKey = key;
|
||||||
|
}
|
||||||
|
public RegExpInstanceKey(Long key) {
|
||||||
|
isRegExp = false;
|
||||||
|
longKey = key;
|
||||||
|
}
|
||||||
|
boolean isRegExp() {
|
||||||
|
return isRegExp;
|
||||||
|
}
|
||||||
|
Long getIdKey() {
|
||||||
|
return longKey;
|
||||||
|
}
|
||||||
|
String getRegExpKey() {
|
||||||
|
return strKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the nodes for a given regexp that represent the specific terms that were found
|
||||||
|
*/
|
||||||
|
public class RegExpInstancesFactory extends ChildFactory.Detachable<RegExpInstanceKey> implements Observer {
|
||||||
|
private final String keyword;
|
||||||
|
private final String setName;
|
||||||
|
|
||||||
|
public RegExpInstancesFactory(String setName, String keyword) {
|
||||||
|
super();
|
||||||
|
this.setName = setName;
|
||||||
|
this.keyword = keyword;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void addNotify() {
|
||||||
|
keywordResults.addObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void removeNotify() {
|
||||||
|
keywordResults.deleteObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean createKeys(List<RegExpInstanceKey> list) {
|
||||||
|
List <String>instances = keywordResults.getKeywordInstances(setName, keyword);
|
||||||
|
// The keys are different depending on what we are displaying.
|
||||||
|
// regexp get another layer to show instances.
|
||||||
|
// Exact matches don't.
|
||||||
|
if ((instances.size() == 1) && (instances.get(0).equals(DUMMY_INSTANCE))) {
|
||||||
|
for (Long id : keywordResults.getArtifactIds(setName, keyword, DUMMY_INSTANCE) ) {
|
||||||
|
list.add(new RegExpInstanceKey(id));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (String instance : instances) {
|
||||||
|
list.add(new RegExpInstanceKey(instance));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Node createNodeForKey(RegExpInstanceKey key) {
|
||||||
|
// if it isn't not a regexp, then skip the 'instance' layer of the tree
|
||||||
|
if (key.isRegExp() == false) {
|
||||||
|
return createBlackboardArtifactNode(key.getIdKey());
|
||||||
|
} else {
|
||||||
|
return new RegExpInstanceNode(setName, keyword, key.getRegExpKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(Observable o, Object arg) {
|
||||||
|
refresh(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a specific term that was found from a regexp
|
||||||
|
*/
|
||||||
|
public class RegExpInstanceNode extends DisplayableItemNode implements Observer {
|
||||||
|
|
||||||
|
private final String setName;
|
||||||
|
private final String keyword;
|
||||||
|
private final String instance;
|
||||||
|
|
||||||
|
public RegExpInstanceNode(String setName, String keyword, String instance) {
|
||||||
|
super(Children.create(new HitsFactory(setName, keyword, instance), true), Lookups.singleton(keyword));
|
||||||
|
super.setName(keyword);
|
||||||
|
this.setName = setName;
|
||||||
|
this.keyword = keyword;
|
||||||
|
this.instance = instance;
|
||||||
|
this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS
|
||||||
|
updateDisplayName();
|
||||||
|
keywordResults.addObserver(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateDisplayName() {
|
||||||
|
int totalDescendants = keywordResults.getArtifactIds(setName, keyword, instance).size();
|
||||||
|
super.setDisplayName(instance + " (" + totalDescendants + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -496,7 +746,7 @@ public class KeywordHits implements AutopsyVisitableItem {
|
|||||||
ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.name"),
|
ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.name"),
|
||||||
NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.displayName"),
|
NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.displayName"),
|
||||||
NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.desc"),
|
NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.desc"),
|
||||||
keywordResults.getArtifactIds(setName, keyword).size()));
|
keywordResults.getKeywordInstances(setName, keyword).size()));
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
@ -507,15 +757,76 @@ public class KeywordHits implements AutopsyVisitableItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a blackboard node for the given Keyword Hit artifact
|
||||||
|
* @param artifactId
|
||||||
|
* @return Node or null on error
|
||||||
|
*/
|
||||||
|
private BlackboardArtifactNode createBlackboardArtifactNode (Long artifactId) {
|
||||||
|
if (skCase == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
BlackboardArtifact art = skCase.getBlackboardArtifact(artifactId);
|
||||||
|
BlackboardArtifactNode n = new BlackboardArtifactNode(art);
|
||||||
|
AbstractFile file;
|
||||||
|
try {
|
||||||
|
file = skCase.getAbstractFileById(art.getObjectID());
|
||||||
|
} catch (TskCoreException ex) {
|
||||||
|
logger.log(Level.SEVERE, "TskCoreException while constructing BlackboardArtifact Node from KeywordHitsKeywordChildren"); //NON-NLS
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is possible to get a keyword hit on artifacts generated
|
||||||
|
// for the underlying image in which case MAC times are not
|
||||||
|
// available/applicable/useful.
|
||||||
|
if (file == null) {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
n.addNodeProperty(new NodeProperty<>(
|
||||||
|
NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.modTime.name"),
|
||||||
|
NbBundle.getMessage(this.getClass(),
|
||||||
|
"KeywordHits.createNodeForKey.modTime.displayName"),
|
||||||
|
NbBundle.getMessage(this.getClass(),
|
||||||
|
"KeywordHits.createNodeForKey.modTime.desc"),
|
||||||
|
ContentUtils.getStringTime(file.getMtime(), file)));
|
||||||
|
n.addNodeProperty(new NodeProperty<>(
|
||||||
|
NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.accessTime.name"),
|
||||||
|
NbBundle.getMessage(this.getClass(),
|
||||||
|
"KeywordHits.createNodeForKey.accessTime.displayName"),
|
||||||
|
NbBundle.getMessage(this.getClass(),
|
||||||
|
"KeywordHits.createNodeForKey.accessTime.desc"),
|
||||||
|
ContentUtils.getStringTime(file.getAtime(), file)));
|
||||||
|
n.addNodeProperty(new NodeProperty<>(
|
||||||
|
NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.chgTime.name"),
|
||||||
|
NbBundle.getMessage(this.getClass(),
|
||||||
|
"KeywordHits.createNodeForKey.chgTime.displayName"),
|
||||||
|
NbBundle.getMessage(this.getClass(),
|
||||||
|
"KeywordHits.createNodeForKey.chgTime.desc"),
|
||||||
|
ContentUtils.getStringTime(file.getCtime(), file)));
|
||||||
|
return n;
|
||||||
|
} catch (TskException ex) {
|
||||||
|
logger.log(Level.WARNING, "TSK Exception occurred", ex); //NON-NLS
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates nodes for individual files that had hits
|
||||||
|
*/
|
||||||
public class HitsFactory extends ChildFactory.Detachable<Long> implements Observer {
|
public class HitsFactory extends ChildFactory.Detachable<Long> implements Observer {
|
||||||
|
|
||||||
private final String keyword;
|
private final String keyword;
|
||||||
private final String setName;
|
private final String setName;
|
||||||
|
private final String instance;
|
||||||
|
|
||||||
public HitsFactory(String setName, String keyword) {
|
public HitsFactory(String setName, String keyword, String instance) {
|
||||||
super();
|
super();
|
||||||
this.setName = setName;
|
this.setName = setName;
|
||||||
this.keyword = keyword;
|
this.keyword = keyword;
|
||||||
|
this.instance = instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -530,60 +841,13 @@ public class KeywordHits implements AutopsyVisitableItem {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean createKeys(List<Long> list) {
|
protected boolean createKeys(List<Long> list) {
|
||||||
list.addAll(keywordResults.getArtifactIds(setName, keyword));
|
list.addAll(keywordResults.getArtifactIds(setName, keyword, instance));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Node createNodeForKey(Long artifactId) {
|
protected Node createNodeForKey(Long artifactId) {
|
||||||
if (skCase == null) {
|
return createBlackboardArtifactNode(artifactId);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
BlackboardArtifact art = skCase.getBlackboardArtifact(artifactId);
|
|
||||||
BlackboardArtifactNode n = new BlackboardArtifactNode(art);
|
|
||||||
AbstractFile file;
|
|
||||||
try {
|
|
||||||
file = skCase.getAbstractFileById(art.getObjectID());
|
|
||||||
} catch (TskCoreException ex) {
|
|
||||||
logger.log(Level.SEVERE, "TskCoreException while constructing BlackboardArtifact Node from KeywordHitsKeywordChildren"); //NON-NLS
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
// It is possible to get a keyword hit on artifacts generated
|
|
||||||
// for the underlying image in which case MAC times are not
|
|
||||||
// available/applicable/useful.
|
|
||||||
if (file == null) {
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
n.addNodeProperty(new NodeProperty<>(
|
|
||||||
NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.modTime.name"),
|
|
||||||
NbBundle.getMessage(this.getClass(),
|
|
||||||
"KeywordHits.createNodeForKey.modTime.displayName"),
|
|
||||||
NbBundle.getMessage(this.getClass(),
|
|
||||||
"KeywordHits.createNodeForKey.modTime.desc"),
|
|
||||||
ContentUtils.getStringTime(file.getMtime(), file)));
|
|
||||||
n.addNodeProperty(new NodeProperty<>(
|
|
||||||
NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.accessTime.name"),
|
|
||||||
NbBundle.getMessage(this.getClass(),
|
|
||||||
"KeywordHits.createNodeForKey.accessTime.displayName"),
|
|
||||||
NbBundle.getMessage(this.getClass(),
|
|
||||||
"KeywordHits.createNodeForKey.accessTime.desc"),
|
|
||||||
ContentUtils.getStringTime(file.getAtime(), file)));
|
|
||||||
n.addNodeProperty(new NodeProperty<>(
|
|
||||||
NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.chgTime.name"),
|
|
||||||
NbBundle.getMessage(this.getClass(),
|
|
||||||
"KeywordHits.createNodeForKey.chgTime.displayName"),
|
|
||||||
NbBundle.getMessage(this.getClass(),
|
|
||||||
"KeywordHits.createNodeForKey.chgTime.desc"),
|
|
||||||
ContentUtils.getStringTime(file.getCtime(), file)));
|
|
||||||
return n;
|
|
||||||
} catch (TskException ex) {
|
|
||||||
logger.log(Level.WARNING, "TSK Exception occurred", ex); //NON-NLS
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -42,8 +42,9 @@ public class KeywordSearch {
|
|||||||
private static final Logger TIKA_LOGGER = Logger.getLogger("Tika"); //NON-NLS
|
private static final Logger TIKA_LOGGER = Logger.getLogger("Tika"); //NON-NLS
|
||||||
private static final org.sleuthkit.autopsy.coreutils.Logger logger = org.sleuthkit.autopsy.coreutils.Logger.getLogger(Case.class.getName());
|
private static final org.sleuthkit.autopsy.coreutils.Logger logger = org.sleuthkit.autopsy.coreutils.Logger.getLogger(Case.class.getName());
|
||||||
|
|
||||||
|
// @@@ We should move this into TskData (or somewhere) because we are using
|
||||||
|
// this value in the results tree to display substring differently from regexp (KeywordHit.java)
|
||||||
public enum QueryType {
|
public enum QueryType {
|
||||||
|
|
||||||
LITERAL, SUBSTRING, REGEX
|
LITERAL, SUBSTRING, REGEX
|
||||||
};
|
};
|
||||||
public static final String NUM_FILES_CHANGE_EVT = "NUM_FILES_CHANGE_EVT"; //NON-NLS
|
public static final String NUM_FILES_CHANGE_EVT = "NUM_FILES_CHANGE_EVT"; //NON-NLS
|
||||||
|
@ -55,22 +55,7 @@ class KeywordSearchQueryDelegator {
|
|||||||
|
|
||||||
for (KeywordList keywordList : keywordLists) {
|
for (KeywordList keywordList : keywordLists) {
|
||||||
for (Keyword keyword : keywordList.getKeywords()) {
|
for (Keyword keyword : keywordList.getKeywords()) {
|
||||||
KeywordSearchQuery query;
|
KeywordSearchQuery query = KeywordSearchUtil.getQueryForKeyword(keyword, keywordList);
|
||||||
if (keyword.searchTermIsLiteral()) {
|
|
||||||
// literal, exact match
|
|
||||||
if (keyword.searchTermIsWholeWord()) {
|
|
||||||
query = new LuceneQuery(keywordList, keyword);
|
|
||||||
query.escape();
|
|
||||||
} // literal, substring match
|
|
||||||
else {
|
|
||||||
query = new TermsComponentQuery(keywordList, keyword);
|
|
||||||
query.escape();
|
|
||||||
query.setSubstringQuery();
|
|
||||||
}
|
|
||||||
} // regexp
|
|
||||||
else {
|
|
||||||
query = new RegexQuery(keywordList, keyword);
|
|
||||||
}
|
|
||||||
queryDelegates.add(query);
|
queryDelegates.add(query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,6 +127,26 @@ class KeywordSearchUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static KeywordSearchQuery getQueryForKeyword(Keyword keyword, KeywordList keywordList) {
|
||||||
|
KeywordSearchQuery query = null;
|
||||||
|
if (keyword.searchTermIsLiteral()) {
|
||||||
|
// literal, exact match
|
||||||
|
if (keyword.searchTermIsWholeWord()) {
|
||||||
|
query = new LuceneQuery(keywordList, keyword);
|
||||||
|
query.escape();
|
||||||
|
} // literal, substring match
|
||||||
|
else {
|
||||||
|
query = new TermsComponentQuery(keywordList, keyword);
|
||||||
|
query.escape();
|
||||||
|
query.setSubstringQuery();
|
||||||
|
}
|
||||||
|
} // regexp
|
||||||
|
else {
|
||||||
|
query = new RegexQuery(keywordList, keyword);
|
||||||
|
}
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the Keyword Search list at absPath an XML list?
|
* Is the Keyword Search list at absPath an XML list?
|
||||||
*
|
*
|
||||||
|
@ -425,14 +425,14 @@ public final class SearchRunner {
|
|||||||
|
|
||||||
int keywordsSearched = 0;
|
int keywordsSearched = 0;
|
||||||
|
|
||||||
for (Keyword keywordQuery : keywords) {
|
for (Keyword keyword : keywords) {
|
||||||
if (this.isCancelled()) {
|
if (this.isCancelled()) {
|
||||||
logger.log(Level.INFO, "Cancel detected, bailing before new keyword processed: {0}", keywordQuery.getSearchTerm()); //NON-NLS
|
logger.log(Level.INFO, "Cancel detected, bailing before new keyword processed: {0}", keyword.getSearchTerm()); //NON-NLS
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String queryStr = keywordQuery.getSearchTerm();
|
final String queryStr = keyword.getSearchTerm();
|
||||||
final KeywordList list = keywordToList.get(queryStr);
|
final KeywordList keywordList = keywordToList.get(queryStr);
|
||||||
|
|
||||||
//new subProgress will be active after the initial query
|
//new subProgress will be active after the initial query
|
||||||
//when we know number of hits to start() with
|
//when we know number of hits to start() with
|
||||||
@ -440,15 +440,7 @@ public final class SearchRunner {
|
|||||||
subProgresses[keywordsSearched - 1].finish();
|
subProgresses[keywordsSearched - 1].finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
KeywordSearchQuery keywordSearchQuery = null;
|
KeywordSearchQuery keywordSearchQuery = KeywordSearchUtil.getQueryForKeyword(keyword, keywordList);
|
||||||
|
|
||||||
boolean isRegex = !keywordQuery.searchTermIsLiteral();
|
|
||||||
if (isRegex) {
|
|
||||||
keywordSearchQuery = new RegexQuery(list, keywordQuery);
|
|
||||||
} else {
|
|
||||||
keywordSearchQuery = new LuceneQuery(list, keywordQuery);
|
|
||||||
keywordSearchQuery.escape();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filtering
|
// Filtering
|
||||||
//limit search to currently ingested data sources
|
//limit search to currently ingested data sources
|
||||||
@ -462,14 +454,14 @@ public final class SearchRunner {
|
|||||||
try {
|
try {
|
||||||
queryResults = keywordSearchQuery.performQuery();
|
queryResults = keywordSearchQuery.performQuery();
|
||||||
} catch (KeywordSearchModuleException | NoOpenCoreException ex) {
|
} catch (KeywordSearchModuleException | NoOpenCoreException ex) {
|
||||||
logger.log(Level.SEVERE, "Error performing query: " + keywordQuery.getSearchTerm(), ex); //NON-NLS
|
logger.log(Level.SEVERE, "Error performing query: " + keyword.getSearchTerm(), ex); //NON-NLS
|
||||||
MessageNotifyUtil.Notify.error(Bundle.SearchRunner_query_exception_msg() + keywordQuery.getSearchTerm(), ex.getCause().getMessage());
|
MessageNotifyUtil.Notify.error(Bundle.SearchRunner_query_exception_msg() + keyword.getSearchTerm(), ex.getCause().getMessage());
|
||||||
//no reason to continue with next query if recovery failed
|
//no reason to continue with next query if recovery failed
|
||||||
//or wait for recovery to kick in and run again later
|
//or wait for recovery to kick in and run again later
|
||||||
//likely case has closed and threads are being interrupted
|
//likely case has closed and threads are being interrupted
|
||||||
return null;
|
return null;
|
||||||
} catch (CancellationException e) {
|
} catch (CancellationException e) {
|
||||||
logger.log(Level.INFO, "Cancel detected, bailing during keyword query: {0}", keywordQuery.getSearchTerm()); //NON-NLS
|
logger.log(Level.INFO, "Cancel detected, bailing during keyword query: {0}", keyword.getSearchTerm()); //NON-NLS
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -487,14 +479,14 @@ public final class SearchRunner {
|
|||||||
int totalUnits = newResults.getKeywords().size();
|
int totalUnits = newResults.getKeywords().size();
|
||||||
subProgresses[keywordsSearched].start(totalUnits);
|
subProgresses[keywordsSearched].start(totalUnits);
|
||||||
int unitProgress = 0;
|
int unitProgress = 0;
|
||||||
String queryDisplayStr = keywordQuery.getSearchTerm();
|
String queryDisplayStr = keyword.getSearchTerm();
|
||||||
if (queryDisplayStr.length() > 50) {
|
if (queryDisplayStr.length() > 50) {
|
||||||
queryDisplayStr = queryDisplayStr.substring(0, 49) + "...";
|
queryDisplayStr = queryDisplayStr.substring(0, 49) + "...";
|
||||||
}
|
}
|
||||||
subProgresses[keywordsSearched].progress(list.getName() + ": " + queryDisplayStr, unitProgress);
|
subProgresses[keywordsSearched].progress(keywordList.getName() + ": " + queryDisplayStr, unitProgress);
|
||||||
|
|
||||||
// Create blackboard artifacts
|
// Create blackboard artifacts
|
||||||
newArtifacts = newResults.writeAllHitsToBlackBoard(null, subProgresses[keywordsSearched], this, list.getIngestMessages());
|
newArtifacts = newResults.writeAllHitsToBlackBoard(null, subProgresses[keywordsSearched], this, keywordList.getIngestMessages());
|
||||||
|
|
||||||
} //if has results
|
} //if has results
|
||||||
|
|
||||||
|
@ -347,6 +347,7 @@ final class TermsComponentQuery implements KeywordSearchQuery {
|
|||||||
if (originalKeyword.getArtifactAttributeType() != ATTRIBUTE_TYPE.TSK_CARD_NUMBER) {
|
if (originalKeyword.getArtifactAttributeType() != ATTRIBUTE_TYPE.TSK_CARD_NUMBER) {
|
||||||
attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm()));
|
attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm()));
|
||||||
attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP, MODULE_NAME, originalKeyword.getSearchTerm()));
|
attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP, MODULE_NAME, originalKeyword.getSearchTerm()));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
newArtifact = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT);
|
newArtifact = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user