/* * Autopsy Forensic Browser * * Copyright 2011-2016 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.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.logging.Level; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.datamodel.BlackboardArtifact; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_GEN_INFO; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT; import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT; import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskException; /** * Parent of the "extracted content" artifacts to be displayed in the tree. * Other artifacts are displayed under other more specific parents. */ public class ExtractedContent implements AutopsyVisitableItem { private SleuthkitCase skCase; // set to null after case has been closed public static final String NAME = NbBundle.getMessage(RootNode.class, "ExtractedContentNode.name.text"); public ExtractedContent(SleuthkitCase skCase) { this.skCase = skCase; } @Override public <T> T accept(AutopsyItemVisitor<T> v) { return v.visit(this); } public SleuthkitCase getSleuthkitCase() { return skCase; } static String getIconFilePath(int typeID) { String filePath = "org/sleuthkit/autopsy/images/"; //NON-NLS if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK.getTypeID()) { return filePath + "bookmarks.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE.getTypeID()) { return filePath + "cookies.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY.getTypeID()) { return filePath + "history.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD.getTypeID()) { return filePath + "downloads.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_RECENT_OBJECT.getTypeID()) { return filePath + "recent_docs.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID()) { return filePath + "gps_trackpoint.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_INSTALLED_PROG.getTypeID()) { return filePath + "programs.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_DEVICE_ATTACHED.getTypeID()) { return filePath + "usb_devices.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) { return filePath + "mail-icon-16.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_EXTRACTED_TEXT.getTypeID()) { return filePath + "text-file.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_SEARCH_QUERY.getTypeID()) { return filePath + "searchquery.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF.getTypeID()) { return filePath + "camera-icon-16.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_INFO.getTypeID()) { return filePath + "computer.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT.getTypeID()) { return filePath + "account-icon-16.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID()) { return filePath + "contact.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()) { return filePath + "message.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG.getTypeID()) { return filePath + "calllog.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_CALENDAR_ENTRY.getTypeID()) { return filePath + "calendar.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_SPEED_DIAL_ENTRY.getTypeID()) { return filePath + "speeddialentry.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_BLUETOOTH_PAIRING.getTypeID()) { return filePath + "bluetooth.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK.getTypeID()) { return filePath + "gpsfav.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION.getTypeID()) { return filePath + "gps-lastlocation.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH.getTypeID()) { return filePath + "gps-search.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID()) { return filePath + "installed.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED.getTypeID()) { return filePath + "encrypted-file.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_EXT_MISMATCH_DETECTED.getTypeID()) { return filePath + "mismatch-16.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_ROUTE.getTypeID()) { return filePath + "gps_trackpoint.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_REMOTE_DRIVE.getTypeID()) { return filePath + "drive_network.png"; //NON-NLS } else if (typeID == BlackboardArtifact.ARTIFACT_TYPE.TSK_FACE_DETECTED.getTypeID()) { return filePath + "face.png"; //NON-NLS } return filePath + "artifact-icon.png"; //NON-NLS } public class RootNode extends DisplayableItemNode { public RootNode(SleuthkitCase skCase) { super(Children.create(new TypeFactory(), true), Lookups.singleton(NAME)); super.setName(NAME); super.setDisplayName(NAME); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/extracted_content.png"); //NON-NLS } @Override public boolean isLeafTypeNode() { 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(), "ExtractedContentNode.createSheet.name.name"), NbBundle.getMessage(this.getClass(), "ExtractedContentNode.createSheet.name.displayName"), NbBundle.getMessage(this.getClass(), "ExtractedContentNode.createSheet.name.desc"), NAME)); return s; } @Override public String getItemType() { return getClass().getName(); } } /** * Creates the children for the ExtractedContent area of the results tree. * This area has all of the blackboard artifacts that are not displayed in a * more specific form elsewhere in the tree. */ private class TypeFactory extends ChildFactory.Detachable<BlackboardArtifact.Type> { private final ArrayList<BlackboardArtifact.Type> doNotShow = new ArrayList<>(); // maps the artifact type to its child node private final HashMap<BlackboardArtifact.Type, TypeNode> typeNodeList = new HashMap<>(); public TypeFactory() { super(); // these are shown in other parts of the UI tree doNotShow.add(new BlackboardArtifact.Type(TSK_GEN_INFO)); doNotShow.add(new BlackboardArtifact.Type(TSK_EMAIL_MSG)); doNotShow.add(new BlackboardArtifact.Type(TSK_HASHSET_HIT)); doNotShow.add(new BlackboardArtifact.Type(TSK_KEYWORD_HIT)); doNotShow.add(new BlackboardArtifact.Type(TSK_INTERESTING_FILE_HIT)); doNotShow.add(new BlackboardArtifact.Type(TSK_INTERESTING_ARTIFACT_HIT)); doNotShow.add(new BlackboardArtifact.Type(TSK_ACCOUNT)); } private final PropertyChangeListener pcl = (PropertyChangeEvent evt) -> { String eventType = evt.getPropertyName(); if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) { /** * This is a stop gap measure until a different way of handling * the closing of cases is worked out. Currently, remote events * may be received for a case that is already closed. */ try { Case.getCurrentCase(); /** * Due to some unresolved issues with how cases are closed, * it is possible for the event to have a null oldValue if * the event is a remote event. */ final ModuleDataEvent event = (ModuleDataEvent) evt.getOldValue(); if (null != event && !(this.doNotShow.contains(event.getBlackboardArtifactType()))) { refresh(true); } } catch (IllegalStateException notUsed) { /** * Case is closed, do nothing. */ } } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) { /** * This is a stop gap measure until a different way of handling * the closing of cases is worked out. Currently, remote events * may be received for a case that is already closed. */ try { Case.getCurrentCase(); refresh(true); } catch (IllegalStateException notUsed) { /** * Case is closed, do nothing. */ } } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { // case was closed. Remove listeners so that we don't get called with a stale case handle if (evt.getNewValue() == null) { removeNotify(); skCase = null; } } }; @Override protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); Case.addPropertyChangeListener(pcl); } @Override protected void removeNotify() { IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); Case.removePropertyChangeListener(pcl); typeNodeList.clear(); } @Override protected boolean createKeys(List<BlackboardArtifact.Type> list) { //TEST COMMENT if (skCase != null) { try { List<BlackboardArtifact.Type> types = skCase.getArtifactTypesInUse(); types.removeAll(doNotShow); Collections.sort(types, new Comparator<BlackboardArtifact.Type>() { @Override public int compare(BlackboardArtifact.Type a, BlackboardArtifact.Type b) { return a.getDisplayName().compareTo(b.getDisplayName()); } }); list.addAll(types); // the create node method will get called only for new types // refresh the counts if we already created them from a previous update for (BlackboardArtifact.Type art : types) { TypeNode node = typeNodeList.get(art); if (node != null) { node.updateDisplayName(); } } } catch (TskCoreException ex) { Logger.getLogger(TypeFactory.class.getName()).log(Level.SEVERE, "Error getting list of artifacts in use: " + ex.getLocalizedMessage()); //NON-NLS } } return true; } @Override protected Node createNodeForKey(BlackboardArtifact.Type key) { TypeNode node = new TypeNode(key); typeNodeList.put(key, node); return node; } } /** * Node encapsulating blackboard artifact type. This is used on the * left-hand navigation side of the Autopsy UI as the parent node for all of * the artifacts of a given type. Its children will be * BlackboardArtifactNode objects. */ public class TypeNode extends DisplayableItemNode { private BlackboardArtifact.Type type; private long childCount = 0; TypeNode(BlackboardArtifact.Type type) { super(Children.create(new ArtifactFactory(type), true), Lookups.singleton(type.getDisplayName())); super.setName(type.getTypeName()); this.type = type; this.setIconBaseWithExtension(ExtractedContent.getIconFilePath(type.getTypeID())); //NON-NLS updateDisplayName(); } final void updateDisplayName() { if (skCase == null) { return; } // NOTE: This completely destroys our lazy-loading ideal // a performance increase might be had by adding a // "getBlackboardArtifactCount()" method to skCase try { this.childCount = skCase.getBlackboardArtifactsTypeCount(type.getTypeID()); } catch (TskException ex) { Logger.getLogger(TypeNode.class.getName()) .log(Level.WARNING, "Error getting child count", ex); //NON-NLS } super.setDisplayName(type.getDisplayName() + " \u200E(\u200E" + childCount + ")\u200E"); } @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(), "ArtifactTypeNode.createSheet.artType.name"), NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.artType.displayName"), NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.artType.desc"), type.getDisplayName())); ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.childCnt.name"), NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.childCnt.displayName"), NbBundle.getMessage(this.getClass(), "ArtifactTypeNode.createSheet.childCnt.desc"), childCount)); return s; } @Override public <T> T accept(DisplayableItemNodeVisitor<T> v) { return v.visit(this); } @Override public boolean isLeafTypeNode() { return true; } @Override public String getItemType() { return getClass().getName() + type.getDisplayName(); } } /** * Creates children for a given artifact type */ private class ArtifactFactory extends ChildFactory.Detachable<BlackboardArtifact> { private BlackboardArtifact.Type type; public ArtifactFactory(BlackboardArtifact.Type type) { super(); this.type = type; } private final PropertyChangeListener pcl = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { String eventType = evt.getPropertyName(); if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) { /** * Checking for a current case is a stop gap measure until a * different way of handling the closing of cases is worked * out. Currently, remote events may be received for a case * that is already closed. */ try { Case.getCurrentCase(); /** * Even with the check above, it is still possible that * the case will be closed in a different thread before * this code executes. If that happens, it is possible * for the event to have a null oldValue. */ final ModuleDataEvent event = (ModuleDataEvent) evt.getOldValue(); if (null != event && event.getBlackboardArtifactType().equals(type)) { refresh(true); } } catch (IllegalStateException notUsed) { /** * Case is closed, do nothing. */ } } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) { /** * Checking for a current case is a stop gap measure until a * different way of handling the closing of cases is worked * out. Currently, remote events may be received for a case * that is already closed. */ try { Case.getCurrentCase(); refresh(true); } catch (IllegalStateException notUsed) { /** * Case is closed, do nothing. */ } } } }; @Override protected void addNotify() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); } @Override protected void removeNotify() { IngestManager.getInstance().removeIngestJobEventListener(pcl); IngestManager.getInstance().removeIngestModuleEventListener(pcl); } @Override protected boolean createKeys(List<BlackboardArtifact> list) { if (skCase != null) { try { List<BlackboardArtifact> arts = skCase.getBlackboardArtifacts(type.getTypeID()); list.addAll(arts); } catch (TskException ex) { Logger.getLogger(ArtifactFactory.class.getName()).log(Level.SEVERE, "Couldn't get blackboard artifacts from database", ex); //NON-NLS } } return true; } @Override protected Node createNodeForKey(BlackboardArtifact key) { return new BlackboardArtifactNode(key); } } }