Autopsy  4.18.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
AbstractAbstractFileNode.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2012-2021 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.lang.ref.WeakReference;
24 import java.text.MessageFormat;
25 import java.util.ArrayList;
26 import java.util.EnumSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.logging.Level;
31 import java.util.stream.Collectors;
32 import org.apache.commons.lang3.StringUtils;
33 import org.apache.commons.lang3.tuple.Pair;
34 import org.openide.nodes.Sheet;
35 import org.openide.util.NbBundle;
36 import org.openide.util.WeakListeners;
50 import static org.sleuthkit.autopsy.datamodel.Bundle.*;
60 import org.sleuthkit.datamodel.AbstractFile;
61 import org.sleuthkit.datamodel.Content;
62 import org.sleuthkit.datamodel.ContentTag;
63 import org.sleuthkit.datamodel.Tag;
64 import org.sleuthkit.datamodel.TskCoreException;
68 import org.sleuthkit.datamodel.Score;
69 
75 public abstract class AbstractAbstractFileNode<T extends AbstractFile> extends AbstractContentNode<T> {
76 
77  private static final Logger logger = Logger.getLogger(AbstractAbstractFileNode.class.getName());
78 
79  private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE,
81  private static final Set<IngestManager.IngestModuleEvent> INGEST_MODULE_EVENTS_OF_INTEREST = EnumSet.of(CONTENT_CHANGED);
82 
86  AbstractAbstractFileNode(T abstractFile) {
87  super(abstractFile);
88  String ext = abstractFile.getNameExtension();
89  if (StringUtils.isNotBlank(ext)) {
90  ext = "." + ext;
91  // If this is an archive file we will listen for ingest events
92  // that will notify us when new content has been identified.
93  if (FileTypeExtensions.getArchiveExtensions().contains(ext)) {
95  }
96  }
97 
98  try {
99  //See JIRA-5971
100  //Attempt to cache file path during construction of this UI component.
101  this.content.getUniquePath();
102  } catch (TskCoreException ex) {
103  logger.log(Level.SEVERE, String.format("Failed attempt to cache the "
104  + "unique path of the abstract file instance. Name: %s (objID=%d)",
105  this.content.getName(), this.content.getId()), ex);
106  }
107 
109  backgroundTasksPool.submit(new TranslationTask(
110  new WeakReference<>(this), weakPcl));
111  }
112 
113  // Listen for case events so that we can detect when the case is closed
114  // or when tags are added.
116  }
117 
127  @Override
128  protected void finalize() throws Throwable {
129  super.finalize();
130  removeListeners();
131  }
132 
133  private void removeListeners() {
136  }
137 
138  private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> {
139  String eventType = evt.getPropertyName();
140 
141  // Is this a content changed event?
142  if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString())) {
143  if ((evt.getOldValue() instanceof ModuleContentEvent) == false) {
144  return;
145  }
146  ModuleContentEvent moduleContentEvent = (ModuleContentEvent) evt.getOldValue();
147  if ((moduleContentEvent.getSource() instanceof Content) == false) {
148  return;
149  }
150  Content newContent = (Content) moduleContentEvent.getSource();
151 
152  // Does the event indicate that content has been added to *this* file?
153  if (getContent().getId() == newContent.getId()) {
154  // If so, refresh our children.
155  try {
156  // We only want to refresh our parents children if we are in the
157  // data sources branch of the tree. The parent nodes in other
158  // branches of the tree (e.g. File Types and Deleted Files) do
159  // not need to be refreshed.
160  BaseChildFactory.post(getParentNode().getName(), new RefreshKeysEvent());
161  } catch (NullPointerException ex) {
162  // Skip
163  } catch (NoSuchEventBusException ex) {
164  logger.log(Level.WARNING, "Failed to post key refresh event", ex); //NON-NLS
165  }
166  }
167  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
168  if (evt.getNewValue() == null) {
169  // case was closed. Remove listeners so that we don't get called with a stale case handle
170  removeListeners();
171  }
172  /*
173  * No need to do any asynchrony around tag added, deleted or CR
174  * change events, they are so infrequent and user driven that we can
175  * just keep a simple blocking approach, where we go out to the
176  * database ourselves.
177  */
178  } else if (eventType.equals(Case.Events.CONTENT_TAG_ADDED.toString())) {
180  if (event.getAddedTag().getContent().equals(content)) {
181  List<Tag> tags = this.getAllTagsFromDatabase();
182  Pair<Score, String> scorePropAndDescr = getScorePropertyAndDescription(tags);
183  Score value = scorePropAndDescr.getLeft();
184  String descr = scorePropAndDescr.getRight();
186  updateSheet(new NodeProperty<>(SCORE.toString(), SCORE.toString(), descr, value),
187  new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, getCommentProperty(tags, attribute))
188  );
189  }
190  } else if (eventType.equals(Case.Events.CONTENT_TAG_DELETED.toString())) {
192  if (event.getDeletedTagInfo().getContentID() == content.getId()) {
193  List<Tag> tags = getAllTagsFromDatabase();
194  Pair<Score, String> scorePropAndDescr = getScorePropertyAndDescription(tags);
195  Score value = scorePropAndDescr.getLeft();
196  String descr = scorePropAndDescr.getRight();
198  updateSheet(new NodeProperty<>(SCORE.toString(), SCORE.toString(), descr, value),
199  new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, getCommentProperty(tags, attribute))
200  );
201  }
202  } else if (eventType.equals(Case.Events.CR_COMMENT_CHANGED.toString())) {
204  if (event.getContentID() == content.getId()) {
205  List<Tag> tags = getAllTagsFromDatabase();
207  updateSheet(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, getCommentProperty(tags, attribute)));
208  }
209  } else if (eventType.equals(NodeSpecificEvents.TRANSLATION_AVAILABLE.toString())) {
210  this.setDisplayName(evt.getNewValue().toString());
211  //Set the tooltip
212  this.setShortDescription(content.getName());
213  updateSheet(new NodeProperty<>(ORIGINAL_NAME.toString(), ORIGINAL_NAME.toString(), NO_DESCR, content.getName()));
214  } else if (eventType.equals(NodeSpecificEvents.SCO_AVAILABLE.toString()) && !UserPreferences.getHideSCOColumns()) {
215  SCOData scoData = (SCOData) evt.getNewValue();
216  if (scoData.getScoreAndDescription() != null) {
217  updateSheet(new NodeProperty<>(SCORE.toString(), SCORE.toString(), scoData.getScoreAndDescription().getRight(), scoData.getScoreAndDescription().getLeft()));
218  }
219  if (scoData.getComment() != null) {
220  updateSheet(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), NO_DESCR, scoData.getComment()));
221  }
222  if (scoData.getCountAndDescription() != null) {
223  updateSheet(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), scoData.getCountAndDescription().getRight(), scoData.getCountAndDescription().getLeft()));
224  }
225  }
226  };
235  private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
236 
237  /*
238  * This is called when the node is first initialized. Any new updates or
239  * changes happen by directly manipulating the sheet. That means we can fire
240  * off background events everytime this method is called and not worry about
241  * duplicated jobs.
242  */
243  @Override
244  protected synchronized Sheet createSheet() {
245  Sheet sheet = new Sheet();
246  Sheet.Set sheetSet = Sheet.createPropertiesSet();
247  sheet.put(sheetSet);
248 
249  //This will fire off fresh background tasks.
250  List<NodeProperty<?>> newProperties = getProperties();
251  newProperties.forEach((property) -> {
252  sheetSet.put(property);
253  });
254 
255  return sheet;
256  }
257 
258  @NbBundle.Messages({"AbstractAbstractFileNode.nameColLbl=Name",
259  "AbstractAbstractFileNode.originalName=Original Name",
260  "AbstractAbstractFileNode.createSheet.score.name=S",
261  "AbstractAbstractFileNode.createSheet.comment.name=C",
262  "AbstractAbstractFileNode.createSheet.count.name=O",
263  "AbstractAbstractFileNode.locationColLbl=Location",
264  "AbstractAbstractFileNode.modifiedTimeColLbl=Modified Time",
265  "AbstractAbstractFileNode.changeTimeColLbl=Change Time",
266  "AbstractAbstractFileNode.accessTimeColLbl=Access Time",
267  "AbstractAbstractFileNode.createdTimeColLbl=Created Time",
268  "AbstractAbstractFileNode.sizeColLbl=Size",
269  "AbstractAbstractFileNode.flagsDirColLbl=Flags(Dir)",
270  "AbstractAbstractFileNode.flagsMetaColLbl=Flags(Meta)",
271  "AbstractAbstractFileNode.modeColLbl=Mode",
272  "AbstractAbstractFileNode.useridColLbl=UserID",
273  "AbstractAbstractFileNode.groupidColLbl=GroupID",
274  "AbstractAbstractFileNode.metaAddrColLbl=Meta Addr.",
275  "AbstractAbstractFileNode.attrAddrColLbl=Attr. Addr.",
276  "AbstractAbstractFileNode.typeDirColLbl=Type(Dir)",
277  "AbstractAbstractFileNode.typeMetaColLbl=Type(Meta)",
278  "AbstractAbstractFileNode.knownColLbl=Known",
279  "AbstractAbstractFileNode.md5HashColLbl=MD5 Hash",
280  "AbstractAbstractFileNode.sha256HashColLbl=SHA-256 Hash",
281  "AbstractAbstractFileNode.objectId=Object ID",
282  "AbstractAbstractFileNode.mimeType=MIME Type",
283  "AbstractAbstractFileNode.extensionColLbl=Extension"})
285 
286  NAME(AbstractAbstractFileNode_nameColLbl()),
287  ORIGINAL_NAME(AbstractAbstractFileNode_originalName()),
288  SCORE(AbstractAbstractFileNode_createSheet_score_name()),
289  COMMENT(AbstractAbstractFileNode_createSheet_comment_name()),
290  OCCURRENCES(AbstractAbstractFileNode_createSheet_count_name()),
291  LOCATION(AbstractAbstractFileNode_locationColLbl()),
292  MOD_TIME(AbstractAbstractFileNode_modifiedTimeColLbl()),
293  CHANGED_TIME(AbstractAbstractFileNode_changeTimeColLbl()),
294  ACCESS_TIME(AbstractAbstractFileNode_accessTimeColLbl()),
295  CREATED_TIME(AbstractAbstractFileNode_createdTimeColLbl()),
296  SIZE(AbstractAbstractFileNode_sizeColLbl()),
297  FLAGS_DIR(AbstractAbstractFileNode_flagsDirColLbl()),
298  FLAGS_META(AbstractAbstractFileNode_flagsMetaColLbl()),
299  MODE(AbstractAbstractFileNode_modeColLbl()),
300  USER_ID(AbstractAbstractFileNode_useridColLbl()),
301  GROUP_ID(AbstractAbstractFileNode_groupidColLbl()),
302  META_ADDR(AbstractAbstractFileNode_metaAddrColLbl()),
303  ATTR_ADDR(AbstractAbstractFileNode_attrAddrColLbl()),
304  TYPE_DIR(AbstractAbstractFileNode_typeDirColLbl()),
305  TYPE_META(AbstractAbstractFileNode_typeMetaColLbl()),
306  KNOWN(AbstractAbstractFileNode_knownColLbl()),
307  MD5HASH(AbstractAbstractFileNode_md5HashColLbl()),
308  SHA256HASH(AbstractAbstractFileNode_sha256HashColLbl()),
309  ObjectID(AbstractAbstractFileNode_objectId()),
310  MIMETYPE(AbstractAbstractFileNode_mimeType()),
311  EXTENSION(AbstractAbstractFileNode_extensionColLbl());
312 
313  final private String displayString;
314 
315  private AbstractFilePropertyType(String displayString) {
316  this.displayString = displayString;
317  }
318 
319  @Override
320  public String toString() {
321  return displayString;
322  }
323  }
324 
328  private List<NodeProperty<?>> getProperties() {
329  List<NodeProperty<?>> properties = new ArrayList<>();
330  properties.add(new NodeProperty<>(NAME.toString(), NAME.toString(), NO_DESCR, getContentDisplayName(content)));
331  /*
332  * Initialize an empty place holder value. At the bottom, we kick off a
333  * background task that promises to update these values.
334  */
335 
337  properties.add(new NodeProperty<>(ORIGINAL_NAME.toString(), ORIGINAL_NAME.toString(), NO_DESCR, ""));
338  }
339 
340  // Create place holders for S C O
342  properties.add(new NodeProperty<>(SCORE.toString(), SCORE.toString(), VALUE_LOADING, ""));
343  properties.add(new NodeProperty<>(COMMENT.toString(), COMMENT.toString(), VALUE_LOADING, ""));
345  properties.add(new NodeProperty<>(OCCURRENCES.toString(), OCCURRENCES.toString(), VALUE_LOADING, ""));
346  }
347  // Get the SCO columns data in a background task
348  backgroundTasksPool.submit(new GetSCOTask(
349  new WeakReference<>(this), weakPcl));
350  }
351 
352  properties.add(new NodeProperty<>(MOD_TIME.toString(), MOD_TIME.toString(), NO_DESCR, TimeZoneUtils.getFormattedTime(content.getMtime())));
353  properties.add(new NodeProperty<>(CHANGED_TIME.toString(), CHANGED_TIME.toString(), NO_DESCR, TimeZoneUtils.getFormattedTime(content.getCtime())));
354  properties.add(new NodeProperty<>(ACCESS_TIME.toString(), ACCESS_TIME.toString(), NO_DESCR, TimeZoneUtils.getFormattedTime(content.getAtime())));
355  properties.add(new NodeProperty<>(CREATED_TIME.toString(), CREATED_TIME.toString(), NO_DESCR, TimeZoneUtils.getFormattedTime(content.getCrtime())));
356  properties.add(new NodeProperty<>(SIZE.toString(), SIZE.toString(), NO_DESCR, content.getSize()));
357  properties.add(new NodeProperty<>(FLAGS_DIR.toString(), FLAGS_DIR.toString(), NO_DESCR, content.getDirFlagAsString()));
358  properties.add(new NodeProperty<>(FLAGS_META.toString(), FLAGS_META.toString(), NO_DESCR, content.getMetaFlagsAsString()));
359  properties.add(new NodeProperty<>(KNOWN.toString(), KNOWN.toString(), NO_DESCR, content.getKnown().getName()));
360  properties.add(new NodeProperty<>(LOCATION.toString(), LOCATION.toString(), NO_DESCR, getContentPath(content)));
361  properties.add(new NodeProperty<>(MD5HASH.toString(), MD5HASH.toString(), NO_DESCR, StringUtils.defaultString(content.getMd5Hash())));
362  properties.add(new NodeProperty<>(SHA256HASH.toString(), SHA256HASH.toString(), NO_DESCR, StringUtils.defaultString(content.getSha256Hash())));
363  properties.add(new NodeProperty<>(MIMETYPE.toString(), MIMETYPE.toString(), NO_DESCR, StringUtils.defaultString(content.getMIMEType())));
364  properties.add(new NodeProperty<>(EXTENSION.toString(), EXTENSION.toString(), NO_DESCR, content.getNameExtension()));
365 
366  return properties;
367  }
368 
378  @NbBundle.Messages("AbstractAbstractFileNode.tagsProperty.displayName=Tags")
379  @Deprecated
380  protected void addTagProperty(Sheet.Set sheetSet) {
381  List<ContentTag> tags = getContentTagsFromDatabase();
382  sheetSet.put(new NodeProperty<>("Tags", AbstractAbstractFileNode_tagsProperty_displayName(),
383  NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName())
384  .distinct()
385  .collect(Collectors.joining(", "))));
386  }
387 
398  @Deprecated
399  protected static String getHashSetHitsCsvList(AbstractFile file) {
400  try {
401  return StringUtils.join(file.getHashSetNames(), ", ");
402  } catch (TskCoreException tskCoreException) {
403  logger.log(Level.WARNING, "Error getting hashset hits: ", tskCoreException); //NON-NLS
404  return "";
405  }
406  }
407 
408  @NbBundle.Messages({
409  "AbstractAbstractFileNode.createSheet.count.displayName=O",
410  "AbstractAbstractFileNode.createSheet.count.hashLookupNotRun.description=Hash lookup had not been run on this file when the column was populated",
411  "# {0} - occurrenceCount",
412  "AbstractAbstractFileNode.createSheet.count.description=There were {0} datasource(s) found with occurrences of the MD5 correlation value"})
413  @Override
414  protected Pair<Long, String> getCountPropertyAndDescription(CorrelationAttributeInstance.Type attributeType, String attributeValue,
415  String defaultDescription) {
416  Long count = -1L; //The column renderer will not display negative values, negative value used when count unavailble to preserve sorting
417  String description = defaultDescription;
418  try {
419  //don't perform the query if there is no correlation value
420  if (attributeType != null && StringUtils.isNotBlank(attributeValue)) {
422  description = Bundle.AbstractAbstractFileNode_createSheet_count_description(count);
423  } else if (attributeType != null) {
424  description = Bundle.AbstractAbstractFileNode_createSheet_count_hashLookupNotRun_description();
425  }
426  } catch (CentralRepoException ex) {
427  logger.log(Level.WARNING, "Error getting count of datasources with correlation attribute", ex);
429  logger.log(Level.WARNING, "Unable to normalize data to get count of datasources with correlation attribute", ex);
430  }
431  return Pair.of(count, description);
432  }
433 
434  @NbBundle.Messages({
435  "AbstractAbstractFileNode.createSheet.comment.displayName=C"})
436  @Override
437  protected HasCommentStatus getCommentProperty(List<Tag> tags, CorrelationAttributeInstance attribute) {
438 
440 
441  for (Tag tag : tags) {
442  if (!StringUtils.isBlank(tag.getComment())) {
443  //if the tag is null or empty or contains just white space it will indicate there is not a comment
445  break;
446  }
447  }
448  if (attribute != null && !StringUtils.isBlank(attribute.getComment())) {
451  } else {
453  }
454  }
455  return status;
456  }
457 
464  String getTranslatedFileName() {
465  try {
466  return FileNameTranslationUtil.translate(content.getName());
467  } catch (NoServiceProviderException | TranslationException ex) {
468  logger.log(Level.WARNING, MessageFormat.format("Error translating file name (objID={0}))", content.getId()), ex);
469  return "";
470  }
471  }
472 
478  List<ContentTag> getContentTagsFromDatabase() {
479  List<ContentTag> tags = new ArrayList<>();
480  try {
481  tags.addAll(Case.getCurrentCaseThrows().getServices().getTagsManager().getContentTagsByContent(content));
482  } catch (TskCoreException | NoCurrentCaseException ex) {
483  logger.log(Level.SEVERE, "Failed to get tags for content " + content.getName(), ex);
484  }
485  return tags;
486  }
487 
488  @Override
489  protected List<Tag> getAllTagsFromDatabase() {
490  return new ArrayList<>(getContentTagsFromDatabase());
491  }
492 
493  @Override
495  CorrelationAttributeInstance attribute = null;
498  }
499  return attribute;
500  }
501 
502  static String getContentPath(AbstractFile file) {
503  try {
504  return file.getUniquePath();
505  } catch (TskCoreException ex) {
506  logger.log(Level.SEVERE, "Except while calling Content.getUniquePath() on " + file.getName(), ex); //NON-NLS
507  return ""; //NON-NLS
508  }
509  }
510 
511  static String getContentDisplayName(AbstractFile file) {
512  String name = file.getName();
513  switch (name) {
514  case "..":
515  return DirectoryNode.DOTDOTDIR;
516  case ".":
517  return DirectoryNode.DOTDIR;
518  default:
519  return name;
520  }
521  }
522 
533  static public void fillPropertyMap(Map<String, Object> map, AbstractFile content) {
534  map.put(NAME.toString(), getContentDisplayName(content));
535  map.put(LOCATION.toString(), getContentPath(content));
536  map.put(MOD_TIME.toString(), TimeZoneUtils.getFormattedTime(content.getMtime()));
537  map.put(CHANGED_TIME.toString(), TimeZoneUtils.getFormattedTime(content.getCtime()));
538  map.put(ACCESS_TIME.toString(), TimeZoneUtils.getFormattedTime(content.getAtime()));
539  map.put(CREATED_TIME.toString(), TimeZoneUtils.getFormattedTime(content.getCrtime()));
540  map.put(SIZE.toString(), content.getSize());
541  map.put(FLAGS_DIR.toString(), content.getDirFlagAsString());
542  map.put(FLAGS_META.toString(), content.getMetaFlagsAsString());
543  map.put(KNOWN.toString(), content.getKnown().getName());
544  map.put(MD5HASH.toString(), StringUtils.defaultString(content.getMd5Hash()));
545  map.put(SHA256HASH.toString(), StringUtils.defaultString(content.getSha256Hash()));
546  map.put(MIMETYPE.toString(), StringUtils.defaultString(content.getMIMEType()));
547  map.put(EXTENSION.toString(), content.getNameExtension());
548  }
549 }
synchronized void updateSheet(NodeProperty<?>...newProps)
void removeIngestModuleEventListener(final PropertyChangeListener listener)
static synchronized IngestManager getInstance()
static String getFormattedTime(long epochTime)
static final Set< IngestManager.IngestModuleEvent > INGEST_MODULE_EVENTS_OF_INTEREST
static void fillPropertyMap(Map< String, Object > map, AbstractFile content)
Pair< Long, String > getCountPropertyAndDescription(CorrelationAttributeInstance.Type attributeType, String attributeValue, String defaultDescription)
static CorrelationAttributeInstance getCorrAttrForFile(AbstractFile file)
Pair< Score, String > getScorePropertyAndDescription(List< Tag > tags)
static void post(String nodeName, Object event)
void addIngestModuleEventListener(final PropertyChangeListener listener)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
HasCommentStatus getCommentProperty(List< Tag > tags, CorrelationAttributeInstance attribute)
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:676
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:721
Long getCountUniqueCaseDataSourceTuplesHavingTypeValue(CorrelationAttributeInstance.Type aType, String value)

Copyright © 2012-2021 Basis Technology. Generated on: Thu Jul 8 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.