Autopsy  4.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
KeywordHits.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2015 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.datamodel;
20 
21 import java.beans.PropertyChangeEvent;
22 import java.beans.PropertyChangeListener;
23 import java.sql.ResultSet;
24 import java.sql.SQLException;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.HashSet;
28 import java.util.LinkedHashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Observable;
32 import java.util.Observer;
33 import java.util.Set;
34 import java.util.logging.Level;
35 import org.openide.nodes.ChildFactory;
36 import org.openide.nodes.Children;
37 import org.openide.nodes.Node;
38 import org.openide.nodes.Sheet;
39 import org.openide.util.NbBundle;
40 import org.openide.util.lookup.Lookups;
45 import org.sleuthkit.datamodel.AbstractFile;
46 import org.sleuthkit.datamodel.BlackboardArtifact;
47 import org.sleuthkit.datamodel.BlackboardAttribute;
48 import org.sleuthkit.datamodel.SleuthkitCase;
49 import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery;
50 import org.sleuthkit.datamodel.TskCoreException;
51 import org.sleuthkit.datamodel.TskException;
52 
56 public class KeywordHits implements AutopsyVisitableItem {
57 
58  private SleuthkitCase skCase;
59  private static final Logger logger = Logger.getLogger(KeywordHits.class.getName());
60  private static final String KEYWORD_HITS = NbBundle.getMessage(KeywordHits.class, "KeywordHits.kwHits.text");
61  public static final String NAME = BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getLabel();
62  public static final String SIMPLE_LITERAL_SEARCH = NbBundle
63  .getMessage(KeywordHits.class, "KeywordHits.simpleLiteralSearch.text");
64  public static final String SIMPLE_REGEX_SEARCH = NbBundle
65  .getMessage(KeywordHits.class, "KeywordHits.singleRegexSearch.text");
67 
68  public KeywordHits(SleuthkitCase skCase) {
69  this.skCase = skCase;
70  keywordResults = new KeywordResults();
71  }
72 
73  private final class KeywordResults extends Observable {
74 
75  // Map from listName/Type to Map of keyword to set of artifact Ids
76  // NOTE: the map can be accessed by multiple worker threads and needs to be synchronized
77  private final Map<String, Map<String, Set<Long>>> topLevelMap = new LinkedHashMap<>();
78 
79  KeywordResults() {
80  update();
81  }
82 
83  List<String> getListNames() {
84  synchronized (topLevelMap) {
85  List<String> names = new ArrayList<>(topLevelMap.keySet());
86  // this causes the "Single ..." terms to be in the middle of the results,
87  // which is wierd. Make a custom comparator or do something else to maek them on top
88  //Collections.sort(names);
89  return names;
90  }
91  }
92 
93  List<String> getKeywords(String listName) {
94  List<String> keywords;
95  synchronized (topLevelMap) {
96  keywords = new ArrayList<>(topLevelMap.get(listName).keySet());
97  }
98  Collections.sort(keywords);
99  return keywords;
100  }
101 
102  Set<Long> getArtifactIds(String listName, String keyword) {
103  synchronized (topLevelMap) {
104  return topLevelMap.get(listName).get(keyword);
105  }
106  }
107 
108  // populate maps based on artifactIds
109  void populateMaps(Map<Long, Map<Long, String>> artifactIds) {
110  synchronized (topLevelMap) {
111  topLevelMap.clear();
112 
113  // map of list name to keword to artifact IDs
114  Map<String, Map<String, Set<Long>>> listsMap = new LinkedHashMap<>();
115 
116  // Map from from literal keyword to artifact IDs
117  Map<String, Set<Long>> literalMap = new LinkedHashMap<>();
118 
119  // Map from regex keyword artifact IDs
120  Map<String, Set<Long>> regexMap = new LinkedHashMap<>();
121 
122  // top-level nodes
123  topLevelMap.put(SIMPLE_LITERAL_SEARCH, literalMap);
124  topLevelMap.put(SIMPLE_REGEX_SEARCH, regexMap);
125 
126  for (Map.Entry<Long, Map<Long, String>> art : artifactIds.entrySet()) {
127  long id = art.getKey();
128  Map<Long, String> attributes = art.getValue();
129 
130  // I think we can use attributes.remove(...) here?
131  String listName = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()));
132  String word = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()));
133  String reg = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID()));
134 
135  // part of a list
136  if (listName != null) {
137  if (listsMap.containsKey(listName) == false) {
138  listsMap.put(listName, new LinkedHashMap<String, Set<Long>>());
139  }
140 
141  Map<String, Set<Long>> listMap = listsMap.get(listName);
142  if (listMap.containsKey(word) == false) {
143  listMap.put(word, new HashSet<Long>());
144  }
145 
146  listMap.get(word).add(id);
147  } // regular expression, single term
148  else if (reg != null) {
149  if (regexMap.containsKey(reg) == false) {
150  regexMap.put(reg, new HashSet<Long>());
151  }
152  regexMap.get(reg).add(id);
153  } // literal, single term
154  else {
155  if (literalMap.containsKey(word) == false) {
156  literalMap.put(word, new HashSet<Long>());
157  }
158  literalMap.get(word).add(id);
159  }
160  topLevelMap.putAll(listsMap);
161  }
162  }
163 
164  setChanged();
165  notifyObservers();
166  }
167 
168  @SuppressWarnings("deprecation")
169  public void update() {
170  Map<Long, Map<Long, String>> artifactIds = new LinkedHashMap<>();
171 
172  if (skCase == null) {
173  return;
174  }
175 
176  int setId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID();
177  int wordId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID();
178  int regexId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID();
179  int artId = BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID();
180  String query = "SELECT blackboard_attributes.value_text,blackboard_attributes.artifact_id," //NON-NLS
181  + "blackboard_attributes.attribute_type_id FROM blackboard_attributes,blackboard_artifacts WHERE " //NON-NLS
182  + "(blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id AND " //NON-NLS
183  + "blackboard_artifacts.artifact_type_id=" + artId //NON-NLS
184  + ") AND (attribute_type_id=" + setId + " OR " //NON-NLS
185  + "attribute_type_id=" + wordId + " OR " //NON-NLS
186  + "attribute_type_id=" + regexId + ")"; //NON-NLS
187 
188  try (CaseDbQuery dbQuery = skCase.executeQuery(query)) {
189  ResultSet resultSet = dbQuery.getResultSet();
190  while (resultSet.next()) {
191  String value = resultSet.getString("value_text"); //NON-NLS
192  long artifactId = resultSet.getLong("artifact_id"); //NON-NLS
193  long typeId = resultSet.getLong("attribute_type_id"); //NON-NLS
194  if (!artifactIds.containsKey(artifactId)) {
195  artifactIds.put(artifactId, new LinkedHashMap<Long, String>());
196  }
197  if (!value.equals("")) {
198  artifactIds.get(artifactId).put(typeId, value);
199  }
200  }
201  } catch (TskCoreException | SQLException ex) {
202  logger.log(Level.WARNING, "SQL Exception occurred: ", ex); //NON-NLS
203  }
204 
205  populateMaps(artifactIds);
206  }
207  }
208 
209  @Override
210  public <T> T accept(AutopsyItemVisitor<T> v) {
211  return v.visit(this);
212  }
213 
214  // Created by CreateAutopsyNodeVisitor
215  public class RootNode extends DisplayableItemNode {
216 
217  public RootNode() {
218  super(Children.create(new ListFactory(), true), Lookups.singleton(KEYWORD_HITS));
219  super.setName(NAME);
220  super.setDisplayName(KEYWORD_HITS);
221  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS
222  }
223 
224  @Override
225  public boolean isLeafTypeNode() {
226  return false;
227  }
228 
229  @Override
230  public <T> T accept(DisplayableItemNodeVisitor<T> v) {
231  return v.visit(this);
232  }
233 
234  @Override
235  protected Sheet createSheet() {
236  Sheet s = super.createSheet();
237  Sheet.Set ss = s.get(Sheet.PROPERTIES);
238  if (ss == null) {
239  ss = Sheet.createPropertiesSet();
240  s.put(ss);
241  }
242 
243  ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.name.name"),
244  NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.name.displayName"),
245  NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.name.desc"),
246  getName()));
247 
248  return s;
249  }
250 
251  /*
252  * TODO (AUT-1849): Correct or remove peristent column reordering code
253  *
254  * Added to support this feature.
255  */
256 // @Override
257 // public String getItemType() {
258 // return "KeywordRoot"; //NON-NLS
259 // }
260  }
261 
262  private class ListFactory extends ChildFactory.Detachable<String> implements Observer {
263 
264  private final PropertyChangeListener pcl = new PropertyChangeListener() {
265  @Override
266  public void propertyChange(PropertyChangeEvent evt) {
267  String eventType = evt.getPropertyName();
268  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
275  try {
283  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
284  if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) {
285  keywordResults.update();
286  }
287  } catch (IllegalStateException notUsed) {
291  }
292  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
293  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
300  try {
302  keywordResults.update();
303  } catch (IllegalStateException notUsed) {
307  }
308  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
309  // case was closed. Remove listeners so that we don't get called with a stale case handle
310  if (evt.getNewValue() == null) {
311  removeNotify();
312  skCase = null;
313  }
314  }
315  }
316  };
317 
318  @Override
319  protected void addNotify() {
323  keywordResults.update();
324  keywordResults.addObserver(this);
325  }
326 
327  @Override
328  protected void removeNotify() {
332  keywordResults.deleteObserver(this);
333  }
334 
335  @Override
336  protected boolean createKeys(List<String> list) {
337  list.addAll(keywordResults.getListNames());
338  return true;
339  }
340 
341  @Override
342  protected Node createNodeForKey(String key) {
343  return new ListNode(key);
344  }
345 
346  @Override
347  public void update(Observable o, Object arg) {
348  refresh(true);
349  }
350  }
351 
352  public class ListNode extends DisplayableItemNode implements Observer {
353 
354  private String listName;
355 
356  public ListNode(String listName) {
357  super(Children.create(new TermFactory(listName), true), Lookups.singleton(listName));
358  super.setName(listName);
359  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS
360  this.listName = listName;
362  keywordResults.addObserver(this);
363  }
364 
365  private void updateDisplayName() {
366  int totalDescendants = 0;
367  for (String word : keywordResults.getKeywords(listName)) {
368  Set<Long> ids = keywordResults.getArtifactIds(listName, word);
369  totalDescendants += ids.size();
370  }
371  super.setDisplayName(listName + " (" + totalDescendants + ")");
372  }
373 
374  @Override
375  protected Sheet createSheet() {
376  Sheet s = super.createSheet();
377  Sheet.Set ss = s.get(Sheet.PROPERTIES);
378  if (ss == null) {
379  ss = Sheet.createPropertiesSet();
380  s.put(ss);
381  }
382 
383  ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.listName.name"),
384  NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.listName.displayName"),
385  NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.listName.desc"),
386  listName));
387 
388  ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.numChildren.name"),
389  NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.numChildren.displayName"),
390  NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.numChildren.desc"),
391  keywordResults.getKeywords(listName).size()));
392 
393  return s;
394  }
395 
396  @Override
397  public boolean isLeafTypeNode() {
398  return false;
399  }
400 
401  @Override
402  public <T> T accept(DisplayableItemNodeVisitor<T> v) {
403  return v.visit(this);
404  }
405 
406  @Override
407  public void update(Observable o, Object arg) {
409  }
410 
411  /*
412  * TODO (AUT-1849): Correct or remove peristent column reordering code
413  *
414  * Added to support this feature.
415  */
416 // @Override
417 // public String getItemType() {
418 // return "KeywordList"; //NON-NLS
419 // }
420  }
421 
422  private class TermFactory extends ChildFactory.Detachable<String> implements Observer {
423 
424  private String setName;
425 
426  private TermFactory(String setName) {
427  super();
428  this.setName = setName;
429  }
430 
431  @Override
432  protected void addNotify() {
433  keywordResults.addObserver(this);
434  }
435 
436  @Override
437  protected void removeNotify() {
438  keywordResults.deleteObserver(this);
439  }
440 
441  @Override
442  protected boolean createKeys(List<String> list) {
443  list.addAll(keywordResults.getKeywords(setName));
444  return true;
445  }
446 
447  @Override
448  protected Node createNodeForKey(String key) {
449  return new TermNode(setName, key);
450  }
451 
452  @Override
453  public void update(Observable o, Object arg) {
454  refresh(true);
455  }
456  }
457 
458  public class TermNode extends DisplayableItemNode implements Observer {
459 
460  private String setName;
461  private String keyword;
462 
463  public TermNode(String setName, String keyword) {
464  super(Children.create(new HitsFactory(setName, keyword), true), Lookups.singleton(keyword));
465  super.setName(keyword);
466  this.setName = setName;
467  this.keyword = keyword;
468  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS
470  keywordResults.addObserver(this);
471  }
472 
473  private void updateDisplayName() {
474  super.setDisplayName(keyword + " (" + keywordResults.getArtifactIds(setName, keyword).size() + ")");
475  }
476 
477  @Override
478  public void update(Observable o, Object arg) {
480  }
481 
482  @Override
483  public boolean isLeafTypeNode() {
484  return true;
485  }
486 
487  @Override
488  public <T> T accept(DisplayableItemNodeVisitor<T> v) {
489  return v.visit(this);
490  }
491 
492  @Override
493  protected Sheet createSheet() {
494  Sheet s = super.createSheet();
495  Sheet.Set ss = s.get(Sheet.PROPERTIES);
496  if (ss == null) {
497  ss = Sheet.createPropertiesSet();
498  s.put(ss);
499  }
500 
501  ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.listName.name"),
502  NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.listName.displayName"),
503  NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.listName.desc"),
504  getDisplayName()));
505 
506  ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.name"),
507  NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.displayName"),
508  NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.desc"),
509  keywordResults.getArtifactIds(setName, keyword).size()));
510 
511  return s;
512  }
513 
514  /*
515  * TODO (AUT-1849): Correct or remove peristent column reordering code
516  *
517  * Added to support this feature.
518  */
519 // @Override
520 // public String getItemType() {
521 // return "KeywordTerm"; //NON-NLS
522 // }
523  }
524 
525  public class HitsFactory extends ChildFactory.Detachable<Long> implements Observer {
526 
527  private String keyword;
528  private String setName;
529 
530  public HitsFactory(String setName, String keyword) {
531  super();
532  this.setName = setName;
533  this.keyword = keyword;
534  }
535 
536  @Override
537  protected void addNotify() {
538  keywordResults.addObserver(this);
539  }
540 
541  @Override
542  protected void removeNotify() {
543  keywordResults.deleteObserver(this);
544  }
545 
546  @Override
547  protected boolean createKeys(List<Long> list) {
548  list.addAll(keywordResults.getArtifactIds(setName, keyword));
549  return true;
550  }
551 
552  @Override
553  protected Node createNodeForKey(Long artifactId) {
554  if (skCase == null) {
555  return null;
556  }
557 
558  try {
559  BlackboardArtifact art = skCase.getBlackboardArtifact(artifactId);
561  AbstractFile file;
562  try {
563  file = skCase.getAbstractFileById(art.getObjectID());
564  } catch (TskCoreException ex) {
565  logger.log(Level.SEVERE, "TskCoreException while constructing BlackboardArtifact Node from KeywordHitsKeywordChildren"); //NON-NLS
566  return n;
567  }
568 
569  // It is possible to get a keyword hit on artifacts generated
570  // for the underlying image in which case MAC times are not
571  // available/applicable/useful.
572  if (file == null) {
573  return n;
574  }
575 
577  NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.modTime.name"),
578  NbBundle.getMessage(this.getClass(),
579  "KeywordHits.createNodeForKey.modTime.displayName"),
580  NbBundle.getMessage(this.getClass(),
581  "KeywordHits.createNodeForKey.modTime.desc"),
582  ContentUtils.getStringTime(file.getMtime(), file)));
584  NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.accessTime.name"),
585  NbBundle.getMessage(this.getClass(),
586  "KeywordHits.createNodeForKey.accessTime.displayName"),
587  NbBundle.getMessage(this.getClass(),
588  "KeywordHits.createNodeForKey.accessTime.desc"),
589  ContentUtils.getStringTime(file.getAtime(), file)));
591  NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.chgTime.name"),
592  NbBundle.getMessage(this.getClass(),
593  "KeywordHits.createNodeForKey.chgTime.displayName"),
594  NbBundle.getMessage(this.getClass(),
595  "KeywordHits.createNodeForKey.chgTime.desc"),
596  ContentUtils.getStringTime(file.getCtime(), file)));
597  return n;
598  } catch (TskException ex) {
599  logger.log(Level.WARNING, "TSK Exception occurred", ex); //NON-NLS
600  }
601  return null;
602  }
603 
604  @Override
605  public void update(Observable o, Object arg) {
606  refresh(true);
607  }
608  }
609 }
BlackboardArtifact.Type getBlackboardArtifactType()
void removeIngestModuleEventListener(final PropertyChangeListener listener)
static String getStringTime(long epochSeconds, TimeZone tzone)
static synchronized IngestManager getInstance()
static void removePropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:318
final Map< String, Map< String, Set< Long > > > topLevelMap
void removeIngestJobEventListener(final PropertyChangeListener listener)
void addIngestJobEventListener(final PropertyChangeListener listener)
static void addPropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:306
void addIngestModuleEventListener(final PropertyChangeListener listener)
synchronized static Logger getLogger(String name)
Definition: Logger.java:161

Copyright © 2012-2016 Basis Technology. Generated on: Tue Oct 25 2016
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.