diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/events/TagAddedEvent.java b/Core/src/org/sleuthkit/autopsy/casemodule/events/TagAddedEvent.java index 365a3c0ff8..1f679c47a3 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/events/TagAddedEvent.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/events/TagAddedEvent.java @@ -39,7 +39,7 @@ abstract class TagAddedEvent extends AutopsyEvent implements Seri private transient T tag; /** - * The id of the tag that was added. This will bu used to re-load the + * The id of the tag that was added. This will be used to re-load the * transient tag from the database. */ private final Long tagID; diff --git a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java index 17a0bfda95..a3e87e3008 100644 --- a/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java +++ b/Core/src/org/sleuthkit/autopsy/corecomponents/DataResultViewerTable.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.corecomponents; +import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.FontMetrics; @@ -26,6 +27,7 @@ import java.awt.dnd.DnDConstants; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashSet; @@ -41,6 +43,7 @@ import javax.swing.event.ListSelectionEvent; import javax.swing.event.TableColumnModelEvent; import javax.swing.event.TableColumnModelListener; import javax.swing.table.TableCellRenderer; +import org.netbeans.swing.outline.DefaultOutlineCellRenderer; import org.netbeans.swing.outline.DefaultOutlineModel; import org.openide.explorer.ExplorerManager; import org.openide.explorer.view.OutlineView; @@ -66,6 +69,8 @@ import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer; //@ServiceProvider(service = DataResultViewer.class) public class DataResultViewerTable extends AbstractDataResultViewer { + private static final long serialVersionUID = 1L; + private final String firstColumnLabel = NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.firstColLbl"); /* The properties map maps * key: stored value of column index -> value: property at that index @@ -76,6 +81,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { private final Map> propertiesMap = new TreeMap<>(); private final DummyNodeListener dummyNodeListener = new DummyNodeListener(); private static final String DUMMY_NODE_DISPLAY_NAME = NbBundle.getMessage(DataResultViewerTable.class, "DataResultViewerTable.dummyNodeDisplayName"); + private static final Color TAGGED_COLOR = new Color(200, 210, 220); private Node currentRoot; // When a column in the table is moved, these two variables keep track of where // the column started and where it ended up. @@ -113,6 +119,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { ov.getOutline().setRootVisible(false); ov.getOutline().setDragEnabled(false); + // add a listener so that when columns are moved, the new order is stored ov.getOutline().getColumnModel().addColumnModelListener(new TableColumnModelListener() { @Override public void columnAdded(TableColumnModelEvent e) { @@ -183,6 +190,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { } }); + // add a listener to move columns back if user tries to move the first column out of place ov.getOutline().getTableHeader().addMouseListener(new MouseAdapter() { @Override public void mouseReleased(MouseEvent e) { @@ -387,7 +395,6 @@ public class DataResultViewerTable extends AbstractDataResultViewer { ov.getOutline().setAutoResizeMode((props.size() > 0) ? JTable.AUTO_RESIZE_OFF : JTable.AUTO_RESIZE_ALL_COLUMNS); if (root.getChildren().getNodesCount() != 0) { - final Graphics graphics = ov.getGraphics(); if (graphics != null) { final FontMetrics metrics = graphics.getFontMetrics(); @@ -397,7 +404,7 @@ public class DataResultViewerTable extends AbstractDataResultViewer { for (int column = 0; column < ov.getOutline().getModel().getColumnCount(); column++) { int firstColumnPadding = (column == 0) ? 32 : 0; - int columnWidthLimit = (column == 0) ? 250 : 350; + int columnWidthLimit = (column == 0) ? 350 : 300; int valuesWidth = 0; // find the maximum width needed to fit the values for the first 100 rows, at most @@ -421,6 +428,48 @@ public class DataResultViewerTable extends AbstractDataResultViewer { // if there's no content just auto resize all columns ov.getOutline().setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); } + + /** + * This custom renderer extends the renderer that was already being + * used by the outline table. This renderer colors a row if the + * tags property of the node is not empty. + */ + class ColorTagCustomRenderer extends DefaultOutlineCellRenderer { + private static final long serialVersionUID = 1L; + @Override + public Component getTableCellRendererComponent(JTable table, + Object value, boolean isSelected, boolean hasFocus, int row, int col) { + + Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col); + // only override the color if a node is not selected + if (!isSelected) { + Node node = currentRoot.getChildren().getNodeAt(table.convertRowIndexToModel(row)); + boolean tagFound = false; + if (node != null) { + Node.PropertySet[] propSets = node.getPropertySets(); + if (propSets.length != 0) { + // currently, a node has only one property set, named Sheet.PROPERTIES ("properties") + Node.Property[] props = propSets[0].getProperties(); + for (Property prop : props) { + if (prop.getName().equals("Tags")) { + try { + tagFound = !prop.getValue().equals(""); + } catch (IllegalAccessException | InvocationTargetException ignore) { + } + break; + } + } + } + } + //if the node does have associated tags, set its background color + if (tagFound) { + component.setBackground(TAGGED_COLOR); + } + } + return component; + } + } + ov.getOutline().setDefaultRenderer(Object.class, new ColorTagCustomRenderer()); } /** @@ -547,11 +596,8 @@ public class DataResultViewerTable extends AbstractDataResultViewer { if (SwingUtilities.isEventDispatchThread()) { setupTable(nme.getNode()); } else { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - setupTable(nme.getNode()); - } + SwingUtilities.invokeLater(() -> { + setupTable(nme.getNode()); }); } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java index 0458ec9a59..e3c750826d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractAbstractFileNode.java @@ -1,15 +1,15 @@ /* * Autopsy Forensic Browser - * - * Copyright 2011-2014 Basis Technology Corp. + * + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit 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. @@ -20,17 +20,24 @@ package org.sleuthkit.autopsy.datamodel; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.List; import org.openide.nodes.Children; import java.util.Map; import java.util.logging.Level; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; +import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.ModuleContentEvent; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentTag; import org.sleuthkit.datamodel.TskCoreException; /** @@ -103,9 +110,23 @@ public abstract class AbstractAbstractFileNode extends A // case was closed. Remove listeners so that we don't get called with a stale case handle removeListeners(); } + } else if (eventType.equals(Case.Events.CONTENT_TAG_ADDED.toString())) { + ContentTagAddedEvent event = (ContentTagAddedEvent) evt; + if (event.getAddedTag().getContent().equals(content)) { + updateSheet(); + } + } else if (eventType.equals(Case.Events.CONTENT_TAG_DELETED.toString())) { + ContentTagDeletedEvent event = (ContentTagDeletedEvent) evt; + if (event.getDeletedTagInfo().getContentID() == content.getId()) { + updateSheet(); + } } }; + private void updateSheet() { + this.setSheet(createSheet()); + } + // Note: this order matters for the search result, changed it if the order of property headers on the "KeywordSearchNode"changed public static enum AbstractFilePropertyType { @@ -278,6 +299,24 @@ public abstract class AbstractAbstractFileNode extends A map.put(AbstractFilePropertyType.MIMETYPE.toString(), content.getMIMEType() == null ? "" : content.getMIMEType()); } + /** + * Used by subclasses of AbstractAbstractFileNode to add the tags property + * to their sheets. + * @param ss the modifiable Sheet.Set returned by Sheet.get(Sheet.PROPERTIES) + */ + protected void addTagProperty(Sheet.Set ss) { + final String NO_DESCR = NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.addFileProperty.desc"); + List tags; + try { + tags = Case.getCurrentCase().getServices().getTagsManager().getContentTagsByContent(content); + } catch (TskCoreException ex) { + tags = new ArrayList<>(); + LOGGER.log(Level.SEVERE, "Failed to get tags for content " + content.getName(), ex); + } + ss.put(new NodeProperty<>("Tags", NbBundle.getMessage(AbstractAbstractFileNode.class, "AbstractAbstractFileNode.addFileProperty.tags.displayName"), + NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName()).collect(Collectors.joining(", ")))); + } + static String getContentDisplayName(AbstractFile file) { String name = file.getName(); switch (name) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java index 10bfbcbcee..85e405d27c 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java @@ -22,6 +22,7 @@ import org.openide.nodes.AbstractNode; import org.openide.nodes.Children.Keys; import org.openide.nodes.Node; import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode; import org.sleuthkit.autopsy.datamodel.accounts.FileTypeExtensionFilters; import org.sleuthkit.autopsy.datamodel.accounts.RecentFiles; import org.sleuthkit.autopsy.datamodel.accounts.Accounts; @@ -137,7 +138,7 @@ abstract class AbstractContentChildren extends Keys { @Override public AbstractNode visit(FileTypeExtensionFilters sf) { - return new FileTypesNode(sf.getSleuthkitCase(), null); + return new FileTypesByExtNode(sf.getSleuthkitCase(), null); } @Override @@ -195,6 +196,11 @@ abstract class AbstractContentChildren extends Keys { return new ResultsNode(r.getSleuthkitCase()); } + @Override + public AbstractNode visit(FileTypes ft) { + return new FileTypesNode(ft.getSleuthkitCase()); + } + @Override public AbstractNode visit(Reports reportsItem) { return new Reports.ReportsListNode(); @@ -211,5 +217,10 @@ abstract class AbstractContentChildren extends Keys { NbBundle.getMessage(this.getClass(), "AbstractContentChildren.createAutopsyNodeVisitor.exception.noNodeMsg")); } + + @Override + public AbstractNode visit(FileTypesByMimeType ftByMimeTypeItem) { + return ftByMimeTypeItem.new FileTypesByMimeTypeNode(ftByMimeTypeItem.getSleuthkitCase()); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java index 5beca1dbc4..a6db3c1df1 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractFsContentNode.java @@ -84,6 +84,9 @@ public abstract class AbstractFsContentNode extends Abst ss.put(new NodeProperty<>(HIDE_PARENT, HIDE_PARENT, HIDE_PARENT, HIDE_PARENT)); } + // add tags property to the sheet + addTagProperty(ss); + return s; } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java index d673acb71e..f0d2bcc253 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AutopsyItemVisitor.java @@ -71,6 +71,11 @@ public interface AutopsyItemVisitor { T visit(Accounts accountsItem); + T visit(FileTypes fileTypesItem); + + T visit(FileTypesByMimeType aThis); + + static abstract public class Default implements AutopsyItemVisitor { protected abstract T defaultVisit(AutopsyVisitableItem ec); @@ -99,7 +104,12 @@ public interface AutopsyItemVisitor { public T visit(FileTypeExtensionFilters.ExecutableFilter ef) { return defaultVisit(ef); } - + + @Override + public T visit(FileTypesByMimeType ftByMimeType) { + return defaultVisit(ftByMimeType); + } + @Override public T visit(DeletedContent dc) { return defaultVisit(dc); @@ -170,6 +180,12 @@ public interface AutopsyItemVisitor { return defaultVisit(r); } + + @Override + public T visit(FileTypes ft) { + return defaultVisit(ft); + } + @Override public T visit(Reports reportsItem) { return defaultVisit(reportsItem); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 5975044f60..7d15bb2101 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -18,6 +18,8 @@ */ package org.sleuthkit.autopsy.datamodel; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; @@ -25,6 +27,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; +import java.util.stream.Collectors; import javax.swing.Action; import org.apache.commons.lang3.StringUtils; import org.openide.nodes.Children; @@ -35,6 +38,10 @@ import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent; +import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent; +import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; +import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction; import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; import org.sleuthkit.datamodel.AbstractFile; @@ -43,6 +50,7 @@ import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.Tag; import org.sleuthkit.datamodel.TskCoreException; /** @@ -70,6 +78,39 @@ public class BlackboardArtifactNode extends DisplayableItemNode { private static final Integer[] SHOW_FILE_METADATA = new Integer[]{ BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID(),}; + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(Case.Events.BLACKBOARD_ARTIFACT_TAG_ADDED.toString())) { + BlackBoardArtifactTagAddedEvent event = (BlackBoardArtifactTagAddedEvent) evt; + if (event.getAddedTag().getArtifact().equals(artifact)) { + updateSheet(); + } + } else if (eventType.equals(Case.Events.BLACKBOARD_ARTIFACT_TAG_DELETED.toString())) { + BlackBoardArtifactTagDeletedEvent event = (BlackBoardArtifactTagDeletedEvent) evt; + if (event.getDeletedTagInfo().getArtifactID() == artifact.getArtifactID()) { + updateSheet(); + } + } else if (eventType.equals(Case.Events.CONTENT_TAG_ADDED.toString())) { + ContentTagAddedEvent event = (ContentTagAddedEvent) evt; + if (event.getAddedTag().getContent().equals(associated)) { + updateSheet(); + } + } else if (eventType.equals(Case.Events.CONTENT_TAG_DELETED.toString())) { + ContentTagDeletedEvent event = (ContentTagDeletedEvent) evt; + if (event.getDeletedTagInfo().getContentID()== associated.getId()) { + updateSheet(); + } + } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) { + if (evt.getNewValue() == null) { + // case was closed. Remove listeners so that we don't get called with a stale case handle + removeListeners(); + } + } + } + }; + /** * Construct blackboard artifact node from an artifact and using provided * icon @@ -86,6 +127,7 @@ public class BlackboardArtifactNode extends DisplayableItemNode { this.setName(Long.toString(artifact.getArtifactID())); this.setDisplayName(); this.setIconBaseWithExtension(iconPath); + Case.addPropertyChangeListener(pcl); } /** @@ -103,6 +145,11 @@ public class BlackboardArtifactNode extends DisplayableItemNode { this.setName(Long.toString(artifact.getArtifactID())); this.setDisplayName(); this.setIconBaseWithExtension(ExtractedContent.getIconFilePath(artifact.getArtifactTypeID())); //NON-NLS + Case.addPropertyChangeListener(pcl); + } + + private void removeListeners() { + Case.removePropertyChangeListener(pcl); } @Override @@ -157,7 +204,7 @@ public class BlackboardArtifactNode extends DisplayableItemNode { displayName = associated.getName(); } - // If this is a node for a keyword hit on an artifact, we set the + // If this is a node for a keyword hit on an artifact, we set the // display name to be the artifact type name followed by " Artifact" // e.g. "Messages Artifact". if (artifact != null && artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) { @@ -208,6 +255,7 @@ public class BlackboardArtifactNode extends DisplayableItemNode { ss.put(np); } } + final int artifactTypeId = artifact.getArtifactTypeID(); // If mismatch, add props for extension and file type @@ -294,9 +342,24 @@ public class BlackboardArtifactNode extends DisplayableItemNode { } } + // add properties for tags + List tags = new ArrayList<>(); + try { + tags.addAll(Case.getCurrentCase().getServices().getTagsManager().getBlackboardArtifactTagsByArtifact(artifact)); + tags.addAll(Case.getCurrentCase().getServices().getTagsManager().getContentTagsByContent(associated)); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Failed to get tags for artifact " + artifact.getDisplayName(), ex); + } + ss.put(new NodeProperty<>("Tags", NbBundle.getMessage(AbstractAbstractFileNode.class, "BlackboardArtifactNode.createSheet.tags.displayName"), + NO_DESCR, tags.stream().map(t -> t.getName().getDisplayName()).collect(Collectors.joining(", ")))); + return s; } + private void updateSheet() { + this.setSheet(createSheet()); + } + private String getRootParentName() { String parentName = associated.getName(); Content parent = associated; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java index 3ff339e98d..4647298931 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactTagNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2014 Basis Technology Corp. + * Copyright 2013-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties index cec49dcdeb..4b9d0225a6 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties @@ -124,7 +124,7 @@ FileTypeNode.createSheet.filterType.desc=no description FileTypeNode.createSheet.fileExt.name=File Extensions FileTypeNode.createSheet.fileExt.displayName=File Extensions FileTypeNode.createSheet.fileExt.desc=no description -FileTypesNode.fname.text=File Types +FileTypesNode.fname.text=By Extension FileTypesNode.createSheet.name.name=Name FileTypesNode.createSheet.name.displayName=Name FileTypesNode.createSheet.name.desc=no description @@ -270,3 +270,8 @@ DeleteReportAction.actionDisplayName.multipleReports=Delete Reports DeleteReportAction.actionPerformed.showConfirmDialog.title=Confirm Deletion DeleteReportAction.actionPerformed.showConfirmDialog.single.msg=Do you want to delete 1 report from the case? DeleteReportAction.actionPerformed.showConfirmDialog.multiple.msg=Do you want to delete {0} reports from the case? +AbstractAbstractFileNode.addFileProperty.desc=no description +AbstractAbstractFileNode.addFileProperty.tags.name=Tags +AbstractAbstractFileNode.addFileProperty.tags.displayName=Tags +BlackboardArtifactNode.createSheet.tags.name=Tags +BlackboardArtifactNode.createSheet.tags.displayName=Tags \ No newline at end of file diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java index a1d78644dd..74ab98d517 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentTagNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013 Basis Technology Corp. + * Copyright 2013-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,6 +42,8 @@ import org.sleuthkit.datamodel.TskCoreException; */ class ContentTagNode extends DisplayableItemNode { + private static final Logger LOGGER = Logger.getLogger(ContentTagNode.class.getName()); + private static final String ICON_PATH = "org/sleuthkit/autopsy/images/blue-tag-icon-16.png"; //NON-NLS private final ContentTag tag; @@ -60,7 +62,7 @@ class ContentTagNode extends DisplayableItemNode { try { contentPath = content.getUniquePath(); } catch (TskCoreException ex) { - Logger.getLogger(ContentTagNode.class.getName()).log(Level.SEVERE, "Failed to get path for content (id = " + content.getId() + ")", ex); //NON-NLS + LOGGER.log(Level.SEVERE, "Failed to get path for content (id = " + content.getId() + ")", ex); //NON-NLS contentPath = NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.unavail.path"); } AbstractFile file = content instanceof AbstractFile ? (AbstractFile) content : null; @@ -103,6 +105,7 @@ class ContentTagNode extends DisplayableItemNode { NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileSize.displayName"), "", content.getSize())); + return propertySheet; } @@ -115,7 +118,7 @@ class ContentTagNode extends DisplayableItemNode { if (file != null) { actions.add(ViewFileInTimelineAction.createViewFileAction(file)); } - actions.add(null); // Adds a menu item separator. + actions.add(null); // Adds a menu item separator. actions.add(DeleteContentTagAction.getInstance()); return actions.toArray(new Action[actions.size()]); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java index 35fd2fb0ad..4155d46076 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DataSourcesNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2014 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,6 +23,7 @@ import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.logging.Level; import org.openide.nodes.Sheet; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; @@ -81,9 +82,9 @@ public class DataSourcesNode extends DisplayableItemNode { public void propertyChange(PropertyChangeEvent evt) { String eventType = evt.getPropertyName(); if (eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.toString()) - || eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) - || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString()) - || eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) { + || eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString()) + || eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString())) { reloadKeys(); refreshContentKeys(); } @@ -112,7 +113,7 @@ public class DataSourcesNode extends DisplayableItemNode { currentKeys = Case.getCurrentCase().getDataSources(); setKeys(currentKeys); } catch (TskCoreException | IllegalStateException ex) { - LOGGER.severe("Error getting data sources: " + ex.getMessage()); // NON-NLS + LOGGER.log(Level.SEVERE, "Error getting data sources: {0}", ex.getMessage()); // NON-NLS setKeys(Collections.emptySet()); } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index a8c5714fb8..f7fdb96243 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -22,6 +22,7 @@ import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsChildren.De import org.sleuthkit.autopsy.datamodel.DeletedContent.DeletedContentsNode; import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootChildren.FileSizeNode; import org.sleuthkit.autopsy.datamodel.FileSize.FileSizeRootNode; +import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode; import org.sleuthkit.autopsy.datamodel.accounts.Accounts; /** @@ -57,7 +58,7 @@ public interface DisplayableItemNodeVisitor { */ T visit(ViewsNode vn); - T visit(FileTypeNode fsfn); + T visit(FileTypeByExtNode fsfn); T visit(DeletedContentNode dcn); @@ -67,7 +68,7 @@ public interface DisplayableItemNodeVisitor { T visit(FileSizeNode fsn); - T visit(FileTypesNode sfn); + T visit(FileTypesByExtNode sfn); T visit(RecentFilesNode rfn); @@ -143,6 +144,17 @@ public interface DisplayableItemNodeVisitor { T visit(Accounts.DefaultAccountTypeNode node); + T visit(FileTypes.FileTypesNode fileTypes); + + T visit(FileTypesByMimeType.FileTypesByMimeTypeNode ftByMimeTypeNode); + + T visit(FileTypesByMimeType.MediaTypeNode ftByMimeTypeMediaType); + + T visit(FileTypesByMimeType.MediaSubTypeNode ftByMimeTypeMediaSubType); + + T visit(FileTypesByMimeType.EmptyNode.MessageNode aThis); + + /** * Visitor with an implementable default behavior for all types. Override * specific visit types to not use the default behavior. @@ -201,10 +213,30 @@ public interface DisplayableItemNodeVisitor { } @Override - public T visit(FileTypeNode fsfn) { + public T visit(FileTypeByExtNode fsfn) { return defaultVisit(fsfn); } - + + @Override + public T visit(FileTypesByMimeType.FileTypesByMimeTypeNode ftByMimeTypeNode) { + return defaultVisit(ftByMimeTypeNode); + } + + @Override + public T visit(FileTypesByMimeType.MediaTypeNode ftByMimeTypeMediaTypeNode) { + return defaultVisit(ftByMimeTypeMediaTypeNode); + } + + @Override + public T visit(FileTypesByMimeType.MediaSubTypeNode ftByMimeTypeMediaTypeNode) { + return defaultVisit(ftByMimeTypeMediaTypeNode); + } + + @Override + public T visit(FileTypesByMimeType.EmptyNode.MessageNode ftByMimeTypeEmptyNode) { + return defaultVisit(ftByMimeTypeEmptyNode); + } + @Override public T visit(DeletedContentNode dcn) { return defaultVisit(dcn); @@ -226,7 +258,7 @@ public interface DisplayableItemNodeVisitor { } @Override - public T visit(FileTypesNode sfn) { + public T visit(FileTypesByExtNode sfn) { return defaultVisit(sfn); } @@ -264,7 +296,11 @@ public interface DisplayableItemNodeVisitor { public T visit(ResultsNode rn) { return defaultVisit(rn); } - + + @Override + public T visit(FileTypesNode ft) { + return defaultVisit(ft); + } @Override public T visit(DataSourcesNode in) { return defaultVisit(in); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeByExtNode.java similarity index 96% rename from Core/src/org/sleuthkit/autopsy/datamodel/FileTypeNode.java rename to Core/src/org/sleuthkit/autopsy/datamodel/FileTypeByExtNode.java index aaf626de81..6d94b02035 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypeByExtNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2014 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -48,14 +48,14 @@ import org.sleuthkit.datamodel.TskData; * Node for a specific file type / extension. Children of it will be the files * of that type. */ -public class FileTypeNode extends DisplayableItemNode { +public class FileTypeByExtNode extends DisplayableItemNode { FileTypeExtensionFilters.SearchFilterInterface filter; SleuthkitCase skCase; // deprecated in favor of the version that takes an observable to provide refresh updates @Deprecated - FileTypeNode(FileTypeExtensionFilters.SearchFilterInterface filter, SleuthkitCase skCase) { + FileTypeByExtNode(FileTypeExtensionFilters.SearchFilterInterface filter, SleuthkitCase skCase) { super(Children.create(new FileTypeChildFactory(filter, skCase), true), Lookups.singleton(filter.getDisplayName())); this.filter = filter; this.skCase = skCase; @@ -69,7 +69,7 @@ public class FileTypeNode extends DisplayableItemNode { * @param o Observable that sends updates when the child factories * should refresh */ - FileTypeNode(FileTypeExtensionFilters.SearchFilterInterface filter, SleuthkitCase skCase, Observable o) { + FileTypeByExtNode(FileTypeExtensionFilters.SearchFilterInterface filter, SleuthkitCase skCase, Observable o) { super(Children.create(new FileTypeChildFactory(filter, skCase, o), true), Lookups.singleton(filter.getDisplayName())); this.filter = filter; this.skCase = skCase; diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java new file mode 100644 index 0000000000..54a4c792c8 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypes.java @@ -0,0 +1,103 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.datamodel; + +import java.util.Arrays; +import org.openide.nodes.Sheet; +import org.openide.util.NbBundle; +import org.openide.util.lookup.Lookups; +import org.sleuthkit.autopsy.datamodel.accounts.FileTypeExtensionFilters; +import org.sleuthkit.datamodel.SleuthkitCase; + +/** + * File Types node support + */ +public class FileTypes implements AutopsyVisitableItem { + + private SleuthkitCase skCase; + + FileTypes(SleuthkitCase skCase) { + this.skCase = skCase; + } + + @Override + public T accept(AutopsyItemVisitor v) { + return v.visit(this); + } + + SleuthkitCase getSleuthkitCase() { + return skCase; + } + + /** + * Node which will contain By Mime Type and By Extension nodes. + */ + public static class FileTypesNode extends DisplayableItemNode { + + @NbBundle.Messages("FileTypesNew.name.text=File Types") + private static final String NAME = Bundle.FileTypesNew_name_text(); + + public FileTypesNode(SleuthkitCase sleuthkitCase) { + super(new RootContentChildren(Arrays.asList( + new FileTypeExtensionFilters(sleuthkitCase), + new FileTypesByMimeType(sleuthkitCase) + )), Lookups.singleton(NAME)); + setName(NAME); + setDisplayName(NAME); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file_types.png"); + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + + @Override + @NbBundle.Messages({ + "FileTypesNew.createSheet.name.name=Name", + "FileTypesNew.createSheet.name.displayName=Name", + "FileTypesNew.createSheet.name.desc=no description"}) + 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<>(Bundle.FileTypesNew_createSheet_name_name(), + Bundle.FileTypesNew_createSheet_name_displayName(), + Bundle.FileTypesNew_createSheet_name_desc(), + NAME + )); + return s; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtNode.java similarity index 85% rename from Core/src/org/sleuthkit/autopsy/datamodel/FileTypesNode.java rename to Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtNode.java index daf1a31780..5afb64fef8 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByExtNode.java @@ -37,9 +37,9 @@ import org.sleuthkit.datamodel.SleuthkitCase; /** * Node for root of file types view. Children are nodes for specific types. */ -public class FileTypesNode extends DisplayableItemNode { +public class FileTypesByExtNode extends DisplayableItemNode { - private static final String FNAME = NbBundle.getMessage(FileTypesNode.class, "FileTypesNode.fname.text"); + private static final String FNAME = NbBundle.getMessage(FileTypesByExtNode.class, "FileTypesNode.fname.text"); private final FileTypeExtensionFilters.RootFilter filter; /** * @@ -47,8 +47,8 @@ public class FileTypesNode extends DisplayableItemNode { * @param filter null to display root node of file type tree, pass in * something to provide a sub-node. */ - FileTypesNode(SleuthkitCase skCase, FileTypeExtensionFilters.RootFilter filter) { - super(Children.create(new FileTypesChildren(skCase, filter, null), true), Lookups.singleton(filter == null ? FNAME : filter.getName())); + FileTypesByExtNode(SleuthkitCase skCase, FileTypeExtensionFilters.RootFilter filter) { + super(Children.create(new FileTypesByExtChildren(skCase, filter, null), true), Lookups.singleton(filter == null ? FNAME : filter.getName())); this.filter = filter; init(); } @@ -60,8 +60,8 @@ public class FileTypesNode extends DisplayableItemNode { * @param o Observable that was created by a higher-level node that * provides updates on events */ - private FileTypesNode(SleuthkitCase skCase, FileTypeExtensionFilters.RootFilter filter, Observable o) { - super(Children.create(new FileTypesChildren(skCase, filter, o), true), Lookups.singleton(filter == null ? FNAME : filter.getName())); + private FileTypesByExtNode(SleuthkitCase skCase, FileTypeExtensionFilters.RootFilter filter, Observable o) { + super(Children.create(new FileTypesByExtChildren(skCase, filter, o), true), Lookups.singleton(filter == null ? FNAME : filter.getName())); this.filter = filter; init(); } @@ -122,7 +122,7 @@ public class FileTypesNode extends DisplayableItemNode { /** * */ - static class FileTypesChildren extends ChildFactory { + static class FileTypesByExtChildren extends ChildFactory { private SleuthkitCase skCase; private FileTypeExtensionFilters.RootFilter filter; @@ -135,12 +135,12 @@ public class FileTypesNode extends DisplayableItemNode { * @param o Observable that provides updates based on events being * fired (or null if one needs to be created) */ - public FileTypesChildren(SleuthkitCase skCase, FileTypeExtensionFilters.RootFilter filter, Observable o) { + public FileTypesByExtChildren(SleuthkitCase skCase, FileTypeExtensionFilters.RootFilter filter, Observable o) { super(); this.skCase = skCase; this.filter = filter; if (o == null) { - this.notifier = new FileTypesChildrenObservable(); + this.notifier = new FileTypesByExtChildrenObservable(); } else { this.notifier = o; } @@ -150,9 +150,9 @@ public class FileTypesNode extends DisplayableItemNode { * Listens for case and ingest invest. Updates observers when events are * fired. FileType and FileTypes nodes are all listening to this. */ - private final class FileTypesChildrenObservable extends Observable { + private final class FileTypesByExtChildrenObservable extends Observable { - FileTypesChildrenObservable() { + FileTypesByExtChildrenObservable() { IngestManager.getInstance().addIngestJobEventListener(pcl); IngestManager.getInstance().addIngestModuleEventListener(pcl); Case.addPropertyChangeListener(pcl); @@ -220,11 +220,11 @@ public class FileTypesNode extends DisplayableItemNode { protected Node createNodeForKey(FileTypeExtensionFilters.SearchFilterInterface key) { // make new nodes for the sub-nodes if (key.getName().equals(FileTypeExtensionFilters.RootFilter.TSK_DOCUMENT_FILTER.getName())) { - return new FileTypesNode(skCase, FileTypeExtensionFilters.RootFilter.TSK_DOCUMENT_FILTER, notifier); + return new FileTypesByExtNode(skCase, FileTypeExtensionFilters.RootFilter.TSK_DOCUMENT_FILTER, notifier); } else if (key.getName().equals(FileTypeExtensionFilters.RootFilter.TSK_EXECUTABLE_FILTER.getName())) { - return new FileTypesNode(skCase, FileTypeExtensionFilters.RootFilter.TSK_EXECUTABLE_FILTER, notifier); + return new FileTypesByExtNode(skCase, FileTypeExtensionFilters.RootFilter.TSK_EXECUTABLE_FILTER, notifier); } else { - return new FileTypeNode(key, skCase, notifier); + return new FileTypeByExtNode(key, skCase, notifier); } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java new file mode 100644 index 0000000000..f0fcb90901 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/datamodel/FileTypesByMimeType.java @@ -0,0 +1,555 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit 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.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Observable; +import java.util.Observer; +import java.util.logging.Level; +import org.openide.nodes.AbstractNode; +import org.openide.nodes.ChildFactory; +import org.openide.nodes.Children; +import org.openide.nodes.Node; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.ContentVisitor; +import org.sleuthkit.datamodel.DerivedFile; +import org.sleuthkit.datamodel.Directory; +import org.sleuthkit.datamodel.File; +import org.sleuthkit.datamodel.LayoutFile; +import org.sleuthkit.datamodel.LocalFile; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskData; + +/** + * Class which contains the Nodes for the 'By Mime Type' view located in the + * File Types view, shows all files with a mime type. Will initially be empty + * until file type identification has been performed. Contains a Property Change + * Listener which is checking for changes in IngestJobEvent Completed or + * Cancelled and IngestModuleEvent Content Changed. + */ +public class FileTypesByMimeType extends Observable implements AutopsyVisitableItem { + + private static SleuthkitCase skCase; + /** + * The nodes of this tree will be determined dynamically by the mimetypes + * which exist in the database. This hashmap will store them with the media + * type as the key and a list of media subtypes as the value. + */ + private final HashMap> existingMimeTypes = new HashMap<>(); + private static final Logger LOGGER = Logger.getLogger(FileTypesByMimeType.class.getName()); + + /* + * The pcl is in the class because it has the easiest mechanisms to add + * and remove itself during its life cycles. + */ + private final PropertyChangeListener pcl = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + String eventType = evt.getPropertyName(); + if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString()) + || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString()) + // || eventType.equals(Case.Events.DATA_SOURCE_ADDED.toString()) + || eventType.equals(IngestManager.IngestModuleEvent.CONTENT_CHANGED.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(); + populateHashMap(); + } catch (IllegalStateException notUsed) { + /** + * Case is closed, do nothing. + */ + } + } + } + }; + + /** + * Retrieve the media types by retrieving the keyset from the hashmap. + * + * @return mediaTypes - a list of strings representing all distinct media + * types of files for this case + */ + private List getMediaTypeList() { + synchronized (existingMimeTypes) { + List mediaTypes = new ArrayList<>(existingMimeTypes.keySet()); + Collections.sort(mediaTypes); + return mediaTypes; + } + } + + /** + * Performs the query on the database to get all distinct MIME types of + * files in it, and populate the hashmap with those results. + */ + private void populateHashMap() { + StringBuilder allDistinctMimeTypesQuery = new StringBuilder(); + allDistinctMimeTypesQuery.append("SELECT DISTINCT mime_type from tsk_files where mime_type IS NOT null"); //NON-NLS + allDistinctMimeTypesQuery.append(" AND dir_type = ").append(TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue()); //NON-NLS + allDistinctMimeTypesQuery.append(" AND (type IN (").append(TskData.TSK_DB_FILES_TYPE_ENUM.FS.ordinal()).append(","); //NON-NLS + allDistinctMimeTypesQuery.append(TskData.TSK_DB_FILES_TYPE_ENUM.CARVED.ordinal()).append(","); + allDistinctMimeTypesQuery.append(TskData.TSK_DB_FILES_TYPE_ENUM.DERIVED.ordinal()).append(","); + allDistinctMimeTypesQuery.append(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL.ordinal()).append("))"); + synchronized (existingMimeTypes) { + existingMimeTypes.clear(); + } + + if (getSleuthkitCase() == null) { + + return; + } + try (SleuthkitCase.CaseDbQuery dbQuery = getSleuthkitCase().executeQuery(allDistinctMimeTypesQuery.toString())) { + ResultSet resultSet = dbQuery.getResultSet(); + synchronized (existingMimeTypes) { + while (resultSet.next()) { + final String mime_type = resultSet.getString("mime_type"); //NON-NLS + if (!mime_type.isEmpty()) { + String mimeType[] = mime_type.split("/"); + if (!mimeType[0].isEmpty() && !mimeType[1].isEmpty()) { + if (!existingMimeTypes.containsKey(mimeType[0])) { + existingMimeTypes.put(mimeType[0], new ArrayList<>()); + } + existingMimeTypes.get(mimeType[0]).add(mimeType[1]); + } + } + } + } + } catch (TskCoreException | SQLException ex) { + LOGGER.log(Level.WARNING, "Unable to populate File Types by MIME Type tree view from DB: ", ex); //NON-NLS + } + setChanged(); + notifyObservers(); + } + + FileTypesByMimeType(SleuthkitCase skCase) { + IngestManager.getInstance().addIngestJobEventListener(pcl); + IngestManager.getInstance().addIngestModuleEventListener(pcl); + FileTypesByMimeType.skCase = skCase; + populateHashMap(); + } + + /** + * @return skCase - the sluethkit case + */ + SleuthkitCase getSleuthkitCase() { + return skCase; + } + + @Override + public T accept(AutopsyItemVisitor v) { + return v.visit(this); + } + + /** + * Class which represents the root node of the "By MIME Type" tree, will + * have children of each media type present in the database or no children + * when the file detection module has not been run and MIME type is + * currently unknown. + */ + public class FileTypesByMimeTypeNode extends DisplayableItemNode { + + @NbBundle.Messages("FileTypesByMimeType.name.text=By MIME Type") + final String NAME = Bundle.FileTypesByMimeType_name_text(); + + FileTypesByMimeTypeNode(SleuthkitCase sleuthkitCase) { + super(Children.create(new FileTypesByMimeTypeNodeChildren(), true)); + super.setName(NAME); + super.setDisplayName(NAME); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file_types.png"); + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + public boolean isEmpty() { + return existingMimeTypes.isEmpty(); + } + + } + + /** + * Creates the children for the "By MIME Type" node these children will each + * represent a distinct media type present in the DB + */ + class FileTypesByMimeTypeNodeChildren extends ChildFactory implements Observer { + + public FileTypesByMimeTypeNodeChildren() { + super(); + addObserver(this); + } + + @Override + protected boolean createKeys(List mediaTypeNodes) { + if (!existingMimeTypes.isEmpty()) { + mediaTypeNodes.addAll(getMediaTypeList()); + } + return true; + } + + @Override + protected Node createNodeForKey(String key) { + return new MediaTypeNode(key); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + + } + + /** + * The Media type node created by the FileTypesByMimeTypeNodeChildren and + * contains one of the unique media types present in the database for this + * case. + */ + class MediaTypeNode extends DisplayableItemNode { + + MediaTypeNode(String name) { + super(Children.create(new MediaTypeChildren(name), true)); + setName(name); + setDisplayName(name); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file_types.png"); + } + + @Override + public boolean isLeafTypeNode() { + return false; + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + } + + /** + * Creates children fro media type nodes, children will be MediaSubTypeNodes + * and represent one of the subtypes which are present in the database of + * their media type. + */ + class MediaTypeChildren extends ChildFactory implements Observer { + + String mediaType; + + MediaTypeChildren(String name) { + addObserver(this); + this.mediaType = name; + } + + @Override + protected boolean createKeys(List mediaTypeNodes) { + mediaTypeNodes.addAll(existingMimeTypes.get(mediaType)); + return true; + } + + @Override + protected Node createNodeForKey(String subtype) { + String mimeType = mediaType + "/" + subtype; + return new MediaSubTypeNode(mimeType); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + + } + + /** + * Node which represents the media sub type in the By MIME type tree, the + * media subtype is the portion of the MIME type following the /. + */ + class MediaSubTypeNode extends DisplayableItemNode implements Observer { + + private MediaSubTypeNode(String mimeType) { + super(Children.create(new MediaSubTypeNodeChildren(mimeType), true)); + addObserver(this); + init(mimeType); + } + + private void init(String mimeType) { + super.setName(mimeType); + updateDisplayName(mimeType); + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/file-filter-icon.png"); //NON-NLS + } + + /** + * Updates the display name of the mediaSubTypeNode to include the count + * of files which it represents. + * + * @param mimeType - the complete MimeType, needed for accurate query + * results + */ + private void updateDisplayName(String mimeType) { + + final long count = new MediaSubTypeNodeChildren(mimeType).calculateItems(getSleuthkitCase(), mimeType); + + super.setDisplayName(mimeType.split("/")[1] + " (" + count + ")"); + } + + /** + * This returns true because any MediaSubTypeNode that exists is going + * to be a bottom level node in the Tree view on the left of Autopsy. + * + * @return true + */ + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + + @Override + public void update(Observable o, Object arg) { + updateDisplayName(getName()); + } + } + + /** + * Factory for populating the contents of the Media Sub Type Node with the + * files that match MimeType which is represented by this position in the + * tree. + */ + private class MediaSubTypeNodeChildren extends ChildFactory.Detachable implements Observer { + + private final String mimeType; + + MediaSubTypeNodeChildren(String mimeType) { + super(); + addObserver(this); + this.mimeType = mimeType; + } + + /** + * Get children count without actually loading all nodes + * + * @return count(*) - the number of items that will be shown in this + * items Directory Listing + */ + private long calculateItems(SleuthkitCase sleuthkitCase, String mime_type) { + try { + return sleuthkitCase.countFilesWhere(createQuery(mime_type)); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Error getting file search view count", ex); //NON-NLS + return 0; + } + } + + /** + * Uses the createQuery method to complete the query, Select * from + * tsk_files WHERE. The results from the database will contain the files + * which match this mime type and their information. + * + * @param list - will contain all files and their attributes from the + * tsk_files table where mime_type matches the one specified + * @return true + */ + @Override + protected boolean createKeys(List list) { + try { + List files = skCase.findAllFilesWhere(createQuery(mimeType)); + list.addAll(files); + } catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, "Couldn't get search results", ex); //NON-NLS + } + return true; + } + + /** + * Create the portion of the query following WHERE for a query of the + * database for each file which matches the complete MIME type + * represented by this node. Matches against the mime_type column in + * tsk_files. + * + * @param mimeType - the complete mimetype of the file mediatype/subtype + * @return query.toString - portion of SQL query which will follow a + * WHERE clause. + */ + private String createQuery(String mime_type) { + StringBuilder query = new StringBuilder(); + query.append("(dir_type = ").append(TskData.TSK_FS_NAME_TYPE_ENUM.REG.getValue()).append(")"); //NON-NLS + query.append(" AND (type IN (").append(TskData.TSK_DB_FILES_TYPE_ENUM.FS.ordinal()).append(","); //NON-NLS + query.append(TskData.TSK_DB_FILES_TYPE_ENUM.CARVED.ordinal()).append(","); + query.append(TskData.TSK_DB_FILES_TYPE_ENUM.DERIVED.ordinal()).append(","); + query.append(TskData.TSK_DB_FILES_TYPE_ENUM.LOCAL.ordinal()).append("))"); + if (UserPreferences.hideKnownFilesInViewsTree()) { + query.append(" AND (known IS NULL OR known != ").append(TskData.FileKnown.KNOWN.getFileKnownValue()).append(")"); //NON-NLS + } + query.append(" AND mime_type = '").append(mime_type).append("'"); //NON-NLS + return query.toString(); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + + + /** + * Creates the content to populate the Directory Listing Table view for + * each file + * + * @param key + * @return + */ + @Override + protected Node createNodeForKey(Content key) { + return key.accept(new ContentVisitor.Default() { + @Override + public FileNode visit(File f) { + return new FileNode(f, false); + } + + @Override + public DirectoryNode visit(Directory d) { + return new DirectoryNode(d); + } + + @Override + public LayoutFileNode visit(LayoutFile lf) { + return new LayoutFileNode(lf); + } + + @Override + public LocalFileNode visit(DerivedFile df) { + return new LocalFileNode(df); + } + + @Override + public LocalFileNode visit(LocalFile lf) { + return new LocalFileNode(lf); + } + + @Override + protected AbstractNode defaultVisit(Content di) { + throw new UnsupportedOperationException(NbBundle.getMessage(this.getClass(), "FileTypeChildren.exception.notSupported.msg", di.toString())); + } + }); + } + } + + /** + * EmptyNode Class made for edge case where no mime exist in the database + * yet. Creates a node to display information on why the tree is empty. + * + * Swapped for the FileTypesByMimeType node in + * DirectoryTreeTopComponent.respondSelection + */ + static public class EmptyNode extends AbstractNode { + + public EmptyNode() { + super(Children.create(new EmptyChildFactory(), true)); + + } + + static class EmptyChildFactory extends ChildFactory { + + String FILE_ID_MSG = "Data not available. Run file type identification module."; //NON-NLS + + @Override + protected boolean createKeys(List list) { + list.add(FILE_ID_MSG); + return true; + } + + @Override + protected Node createNodeForKey(String key) { + return new MessageNode(key); + } + + } + + /** + * MessageNode is is the info message that displays in the table view, + * by also extending a DisplayableItemNode type, rather than an + * AbstractNode type it doesn't throw an error when right clicked. + */ + static class MessageNode extends DisplayableItemNode { + + MessageNode(String name) { + super(Children.LEAF); + super.setName(name); + setName(name); + setDisplayName(name); + } + + @Override + public boolean isLeafTypeNode() { + return true; + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + + @Override + public String getItemType() { + return getClass().getName(); + } + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java index f81fd46e08..c7b8983be0 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2015 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -346,7 +346,7 @@ public class KeywordHits implements AutopsyVisitableItem { public class ListNode extends DisplayableItemNode implements Observer { - private String listName; + private final String listName; public ListNode(String listName) { super(Children.create(new TermFactory(listName), true), Lookups.singleton(listName)); @@ -411,7 +411,7 @@ public class KeywordHits implements AutopsyVisitableItem { private class TermFactory extends ChildFactory.Detachable implements Observer { - private String setName; + private final String setName; private TermFactory(String setName) { super(); @@ -447,8 +447,8 @@ public class KeywordHits implements AutopsyVisitableItem { public class TermNode extends DisplayableItemNode implements Observer { - private String setName; - private String keyword; + private final String setName; + private final String keyword; public TermNode(String setName, String keyword) { super(Children.create(new HitsFactory(setName, keyword), true), Lookups.singleton(keyword)); @@ -509,8 +509,8 @@ public class KeywordHits implements AutopsyVisitableItem { public class HitsFactory extends ChildFactory.Detachable implements Observer { - private String keyword; - private String setName; + private final String keyword; + private final String setName; public HitsFactory(String setName, String keyword) { super(); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java index 0e97757401..495737488b 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LayoutFileNode.java @@ -86,6 +86,9 @@ public class LayoutFileNode extends AbstractAbstractFileNode { ss.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue())); } + // add tags property to the sheet + addTagProperty(ss); + return s; } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java index e2f701d044..ecccb26272 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/LocalFileNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2014 Basis Technology Corp. + * Copyright 2013-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,6 +19,7 @@ package org.sleuthkit.autopsy.datamodel; import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -74,7 +75,9 @@ public class LocalFileNode extends AbstractAbstractFileNode { for (Map.Entry entry : map.entrySet()) { ss.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue())); } - // @@@ add more properties here... + + // add tags property to the sheet + addTagProperty(ss); return s; } @@ -82,9 +85,7 @@ public class LocalFileNode extends AbstractAbstractFileNode { @Override public Action[] getActions(boolean context) { List actionsList = new ArrayList<>(); - for (Action a : super.getActions(true)) { - actionsList.add(a); - } + actionsList.addAll(Arrays.asList(super.getActions(true))); actionsList.add(new ViewContextAction(NbBundle.getMessage(this.getClass(), "LocalFileNode.viewFileInDir.text"), this.content)); actionsList.add(null); // creates a menu separator actionsList.add(new NewWindowViewAction( diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java index 181ff43311..2363e2cf27 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ResultsNode.java @@ -33,6 +33,8 @@ public class ResultsNode extends DisplayableItemNode { @NbBundle.Messages("ResultsNode.name.text=Results") public static final String NAME = Bundle.ResultsNode_name_text(); + + public ResultsNode(SleuthkitCase sleuthkitCase) { super(new RootContentChildren(Arrays.asList( new ExtractedContent(sleuthkitCase), diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java index 2d89ef3cca..b8127fe5a7 100755 --- a/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/Tags.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2015 Basis Technology Corp. + * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -46,8 +46,8 @@ import org.sleuthkit.datamodel.TskCoreException; * factory built on top of the NetBeans Children.Keys class. */ public class Tags implements AutopsyVisitableItem { - // Creation of a RootNode object corresponding to a Tags object is done - // by a CreateAutopsyNodeVisitor dispatched from the AbstractContentChildren + // Creation of a RootNode object corresponding to a Tags object is done + // by a CreateAutopsyNodeVisitor dispatched from the AbstractContentChildren // override of Children.Keys.createNodes(). private final TagResults tagResults = new TagResults(); @@ -325,7 +325,7 @@ public class Tags implements AutopsyVisitableItem { public class ContentTagTypeNode extends DisplayableItemNode implements Observer { private final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS - private TagName tagName; + private final TagName tagName; public ContentTagTypeNode(TagName tagName) { super(Children.create(new ContentTagNodeFactory(tagName), true), Lookups.singleton(tagName.getDisplayName() + " " + CONTENT_DISPLAY_NAME)); @@ -423,7 +423,7 @@ public class Tags implements AutopsyVisitableItem { */ public class BlackboardArtifactTagTypeNode extends DisplayableItemNode implements Observer { - private TagName tagName; + private final TagName tagName; private final String ICON_PATH = "org/sleuthkit/autopsy/images/tag-folder-blue-icon-16.png"; //NON-NLS public BlackboardArtifactTagTypeNode(TagName tagName) { @@ -505,7 +505,7 @@ public class Tags implements AutopsyVisitableItem { // The blackboard artifact tags to be wrapped are used as the keys. return new BlackboardArtifactTagNode(key); } - + @Override public void update(Observable o, Object arg) { refresh(true); diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java index a2bf942f55..a2c09299d1 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ViewsNode.java @@ -36,7 +36,7 @@ public class ViewsNode extends DisplayableItemNode { public ViewsNode(SleuthkitCase sleuthkitCase) { super(new RootContentChildren(Arrays.asList( - new FileTypeExtensionFilters(sleuthkitCase), + new FileTypes(sleuthkitCase), // June '15: Recent Files was removed because it was not useful w/out filtering // add it back in if we can filter the results to a more managable size. // new RecentFiles(sleuthkitCase), diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java index 9cf67f4649..246c6d6e1d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/VirtualDirectoryNode.java @@ -148,6 +148,7 @@ public class VirtualDirectoryNode extends AbstractAbstractFileNode entry : map.entrySet()) { ss.put(new NodeProperty<>(entry.getKey(), entry.getKey(), NO_DESCR, entry.getValue())); } + addTagProperty(ss); } else { ss.put(new NodeProperty<>(Bundle.VirtualDirectoryNode_createSheet_type_name(), Bundle.VirtualDirectoryNode_createSheet_type_displayName(), diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index 97d5edf35b..de20df20e7 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -42,6 +42,7 @@ import org.sleuthkit.autopsy.datamodel.DirectoryNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.FileNode; +import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode; import org.sleuthkit.autopsy.datamodel.LayoutFileNode; import org.sleuthkit.autopsy.datamodel.LocalFileNode; import org.sleuthkit.autopsy.datamodel.Reports; @@ -235,7 +236,13 @@ public class DataResultFilterNode extends FilterNode { // The base class Action is "Collapse All", inappropriate. return null; } - + + @Override + public List visit(FileTypesNode fileTypes) { + return defaultVisit(fileTypes); + } + + @Override protected List defaultVisit(DisplayableItemNode ditem) { //preserve the default node's actions @@ -275,6 +282,7 @@ public class DataResultFilterNode extends FilterNode { } return c; } + } /* @@ -326,7 +334,14 @@ public class DataResultFilterNode extends FilterNode { protected AbstractAction defaultVisit(DisplayableItemNode c) { return openChild(c); } + + @Override + public AbstractAction visit(FileTypesNode fileTypes) { + return openChild(fileTypes); + } + + /** * Tell the originating ExplorerManager to display the given * dataModelNode. diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java index 4ea3d721f0..2b10d57f09 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java @@ -29,6 +29,7 @@ import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.FileNode; +import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode; import org.sleuthkit.autopsy.datamodel.LayoutFileNode; import org.sleuthkit.autopsy.datamodel.LocalFileNode; import org.sleuthkit.autopsy.datamodel.SlackFileNode; @@ -242,6 +243,12 @@ class DirectoryTreeFilterChildren extends FilterNode.Children { return visitDeep(vdn); //return ! vdn.hasContentChildren(); } + + @Override + public Boolean visit(FileTypesNode ft) { + return defaultVisit(ft); + } + } private static class ShowItemVisitor extends DisplayableItemNodeVisitor.Default { @@ -284,5 +291,11 @@ class DirectoryTreeFilterChildren extends FilterNode.Children { return true; //return vdn.hasContentChildren(); } + + @Override + public Boolean visit(FileTypesNode fileTypes) { + return defaultVisit(fileTypes); + } + } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java index dbd38cf9aa..22485148ff 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeTopComponent.java @@ -61,7 +61,9 @@ import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.DataSources; import org.sleuthkit.autopsy.datamodel.DataSourcesNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; +import org.sleuthkit.autopsy.datamodel.FileTypesByMimeType.EmptyNode; import org.sleuthkit.autopsy.datamodel.ExtractedContent; +import org.sleuthkit.autopsy.datamodel.FileTypesByMimeType; import org.sleuthkit.autopsy.datamodel.KeywordHits; import org.sleuthkit.autopsy.datamodel.KnownFileFilterNode; import org.sleuthkit.autopsy.datamodel.Reports; @@ -364,7 +366,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat items.add(new Tags()); items.add(new Reports()); contentChildren = new RootContentChildren(items); - + Node root = new AbstractNode(contentChildren) { /** * to override the right click action in the white blank @@ -635,6 +637,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat if (origin == null) { return; } + Node originNode = origin.getNode(); //set node, wrap in filter node first to filter out children @@ -644,7 +647,17 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat // Create a TableFilterNode with knowledge of the node's type to allow for column order settings - if (originNode instanceof DisplayableItemNode) { + + //Special case for when File Type Identification has not yet been run and + //there are no mime types to populate Files by Mime Type Tree + if (originNode instanceof FileTypesByMimeType.FileTypesByMimeTypeNode + && ((FileTypesByMimeType.FileTypesByMimeTypeNode) originNode).isEmpty()) { + EmptyNode emptyNode = new EmptyNode(); + Node emptyDrfn = new DataResultFilterNode(emptyNode, DirectoryTreeTopComponent.this.em); + Node emptyKffn = new KnownFileFilterNode(emptyDrfn, KnownFileFilterNode.getSelectionContext(emptyNode)); + Node emptySffn = new SlackFileFilterNode(emptyKffn, SlackFileFilterNode.getSelectionContext(originNode)); + dataResult.setNode(new TableFilterNode(emptySffn, true, "This Node Is Empty")); + } else if (originNode instanceof DisplayableItemNode) { dataResult.setNode(new TableFilterNode(sffn, true, ((DisplayableItemNode) originNode).getItemType())); } else { dataResult.setNode(new TableFilterNode(sffn, true)); @@ -788,8 +801,7 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat * Set the selected node using a path to a previously selected node. * * @param previouslySelectedNodePath Path to a previously selected node. - * @param rootNodeName Name of the root node to match, may be - * null. + * @param rootNodeName Name of the root node to match, may be null. */ private void setSelectedNode(final String[] previouslySelectedNodePath, final String rootNodeName) { if (previouslySelectedNodePath == null) { diff --git a/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.java b/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.java index dfebc0c718..3982cb2779 100755 --- a/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.java +++ b/Core/src/org/sleuthkit/autopsy/filesearch/MimeTypePanel.java @@ -1,31 +1,35 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Autopsy Forensic Browser + * + * Copyright 2011-2016 Basis Technology Corp. + * Contact: carrier sleuthkit 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.filesearch; -import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.SortedSet; import java.util.logging.Level; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; -import org.apache.tika.mime.MediaType; -import org.apache.tika.mime.MimeTypes; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; -/** - * - * @author oliver - */ public class MimeTypePanel extends javax.swing.JPanel { - private static final SortedSet mediaTypes = MimeTypes.getDefaultMimeTypes().getMediaTypeRegistry().getTypes(); private static final Logger logger = Logger.getLogger(MimeTypePanel.class.getName()); private static final long serialVersionUID = 1L; @@ -45,8 +49,8 @@ public class MimeTypePanel extends javax.swing.JPanel { private String[] getMimeTypeArray() { Set fileTypesCollated = new HashSet<>(); - for (MediaType mediaType : mediaTypes) { - fileTypesCollated.add(mediaType.toString()); + for (String mediaType : FileTypeDetector.getStandardDetectedTypes()) { + fileTypesCollated.add(mediaType); } FileTypeDetector fileTypeDetector; @@ -78,7 +82,7 @@ public class MimeTypePanel extends javax.swing.JPanel { boolean isSelected() { return this.mimeTypeCheckBox.isSelected(); } - + void setComponentsEnabled() { boolean enabled = this.isSelected(); this.mimeTypeList.setEnabled(enabled); diff --git a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java index f226f22d5d..f9b8b468cd 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestJob.java @@ -159,7 +159,7 @@ final class DataSourceIngestJob { private ProgressHandle fileIngestProgress; private String currentFileIngestModule = ""; private String currentFileIngestTask = ""; - private List ingestModules = new ArrayList<>(); + private final List ingestModules = new ArrayList<>(); private IngestJobInfo ingestJob; /** @@ -171,12 +171,12 @@ final class DataSourceIngestJob { * Constructs an object that encapsulates a data source and the ingest * module pipelines used to process it. * - * @param parentJob The ingest job of which this data source ingest - * job is a part. - * @param dataSource The data source to be ingested. - * @param settings The settings for the ingest job. + * @param parentJob The ingest job of which this data source ingest job is a + * part. + * @param dataSource The data source to be ingested. + * @param settings The settings for the ingest job. * @param runInteractively Whether or not this job should use NetBeans - * progress handles. + * progress handles. */ DataSourceIngestJob(IngestJob parentJob, Content dataSource, IngestJobSettings settings, boolean runInteractively) { this.parentJob = parentJob; @@ -277,12 +277,12 @@ final class DataSourceIngestJob { * collection as they are added to the output collection. * * @param ingestModuleTemplates A mapping of ingest module factory class - * names to ingest module templates. - * @param pipelineConfig An ordered list of ingest module factory - * class names representing an ingest pipeline. + * names to ingest module templates. + * @param pipelineConfig An ordered list of ingest module factory class + * names representing an ingest pipeline. * * @return An ordered list of ingest module templates, i.e., an - * uninstantiated pipeline. + * uninstantiated pipeline. */ private static List getConfiguredIngestModuleTemplates(Map ingestModuleTemplates, List pipelineConfig) { List templates = new ArrayList<>(); @@ -331,16 +331,6 @@ final class DataSourceIngestJob { return this.settings.getProcessUnallocatedSpace(); } - /** - * Queries whether or not unallocated space should be processed as part of - * this job. - * - * @return True or false. - */ - String runIngestModulesOnFilter() { - return this.settings.getRunIngestModulesOnFilter(); - } - /** * Checks to see if this job has at least one ingest pipeline. * @@ -746,9 +736,8 @@ final class DataSourceIngestJob { * @param task A file ingest task. * * @throws InterruptedException if the thread executing this code is - * interrupted while blocked on taking from or - * putting to the file ingest pipelines - * collection. + * interrupted while blocked on taking from or putting to the file ingest + * pipelines collection. */ void process(FileIngestTask task) throws InterruptedException { try { @@ -849,7 +838,7 @@ final class DataSourceIngestJob { * process the data source is known. * * @param workUnits Total number of work units for the processing of the - * data source. + * data source. */ void switchDataSourceIngestProgressBarToDeterminate(int workUnits) { if (this.doUI && !this.cancelled) { @@ -914,7 +903,7 @@ final class DataSourceIngestJob { * mode. The task name is the "subtitle" under the display name. * * @param currentTask The task name. - * @param workUnits Number of work units performed. + * @param workUnits Number of work units performed. */ void advanceDataSourceIngestProgressBar(String currentTask, int workUnits) { if (this.doUI && !this.cancelled) { @@ -1056,7 +1045,7 @@ final class DataSourceIngestJob { * To be used for more detailed cancelling. * * @param moduleName Name of module currently running. - * @param taskName Name of file the module is running on. + * @param taskName Name of file the module is running on. */ void setCurrentFileIngestModule(String moduleName, String taskName) { this.currentFileIngestModule = moduleName; @@ -1170,7 +1159,7 @@ final class DataSourceIngestJob { * Gets time these statistics were collected. * * @return The statistics collection time as number of milliseconds - * since January 1, 1970, 00:00:00 GMT. + * since January 1, 1970, 00:00:00 GMT. */ long getSnapshotTime() { return snapShotTime; @@ -1200,7 +1189,7 @@ final class DataSourceIngestJob { * Gets the time the ingest job was started. * * @return The start time as number of milliseconds since January 1, - * 1970, 00:00:00 GMT. + * 1970, 00:00:00 GMT. */ long getJobStartTime() { return jobStartTime; @@ -1300,7 +1289,7 @@ final class DataSourceIngestJob { * ingest modules * * @return A list of canceled data source level ingest module display - * names, possibly empty. + * names, possibly empty. */ List getCancelledDataSourceIngestModules() { return Collections.unmodifiableList(this.cancelledDataSourceModules); diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java index b0f95c93fa..af796f0f6b 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java @@ -19,11 +19,13 @@ package org.sleuthkit.autopsy.modules.filetypeid; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.SortedSet; +import java.util.TreeSet; import java.util.logging.Level; +import java.util.stream.Collectors; import org.apache.tika.Tika; -import org.apache.tika.mime.MediaType; import org.apache.tika.mime.MimeTypes; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; @@ -49,6 +51,7 @@ public class FileTypeDetector { private final byte buffer[] = new byte[BUFFER_SIZE]; private final List userDefinedFileTypes; private final List autopsyDefinedFileTypes; + private static SortedSet detectedTypes; //no optional parameters /** * Constructs an object that detects the MIME type of a file by an @@ -100,6 +103,21 @@ public class FileTypeDetector { || isDetectableByTika(mimeType); } + /** + * Returns an unmodifiable list of standard MIME types that does not contain + * types with optional parameters. The list has no duplicate types and is in + * alphabetical order. + * + * @return an unmodifiable view of a set of MIME types + */ + public static synchronized SortedSet getStandardDetectedTypes() { + if (detectedTypes == null) { + detectedTypes = org.apache.tika.mime.MimeTypes.getDefaultMimeTypes().getMediaTypeRegistry().getTypes() + .stream().filter(t -> !t.hasParameters()).map(s -> s.toString()).collect(Collectors.toCollection(TreeSet::new)); + } + return Collections.unmodifiableSortedSet(detectedTypes); + } + /** * Determines whether or not a given MIME type is detectable as a * user-defined MIME type by this detector. @@ -126,15 +144,7 @@ public class FileTypeDetector { * @return True or false. */ private boolean isDetectableByTika(String mimeType) { - String[] split = mimeType.split("/"); - if (split.length == 2) { - String type = split[0]; - String subtype = split[1]; - MediaType mediaType = new MediaType(type, subtype); - SortedSet m = MimeTypes.getDefaultMimeTypes().getMediaTypeRegistry().getTypes(); - return m.contains(mediaType); - } - return false; + return FileTypeDetector.getStandardDetectedTypes().contains(removeOptionalParameter(mimeType)); } /** @@ -196,7 +206,10 @@ public class FileTypeDetector { */ String mimeType = file.getMIMEType(); if (null != mimeType) { - return mimeType; + // We remove the optional parameter to allow this method to work + // with legacy databases that may contain MIME types with the + // optional parameter attached. + return removeOptionalParameter(mimeType); } /* @@ -248,6 +261,10 @@ public class FileTypeDetector { * Remove the Tika suffix from the MIME type name. */ mimeType = tikaType.replace("tika-", ""); //NON-NLS + /* + * Remove the optional parameter from the MIME type. + */ + mimeType = removeOptionalParameter(mimeType); } catch (Exception ignored) { /* @@ -288,6 +305,20 @@ public class FileTypeDetector { return mimeType; } + /** + * Removes the optional parameter from a MIME type string + * @param mimeType + * @return MIME type without the optional parameter + */ + private String removeOptionalParameter(String mimeType) { + int indexOfSemicolon = mimeType.indexOf(";"); + if (indexOfSemicolon != -1 ) { + return mimeType.substring(0, indexOfSemicolon).trim(); + } else { + return mimeType; + } + } + /** * Determines whether or not the a file matches a user-defined custom file * type. diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/TikaFileTypeDetector.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/TikaFileTypeDetector.java index e8bd4ab29b..c237fe6222 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/TikaFileTypeDetector.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/TikaFileTypeDetector.java @@ -24,9 +24,6 @@ import org.apache.tika.mime.MediaType; import org.apache.tika.mime.MimeTypes; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.TskCoreException; /** * @deprecated Use org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java index 188160499a..b7a8f51031 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2014-2016 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,15 +24,12 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.SortedSet; import java.util.logging.Level; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JOptionPane; -import org.apache.tika.mime.MediaType; -import org.apache.tika.mime.MimeTypes; import org.openide.DialogDisplayer; import org.openide.NotifyDescriptor; import org.openide.util.NbBundle; @@ -58,7 +55,6 @@ final class FilesSetRulePanel extends javax.swing.JPanel { "FilesSetRulePanel.ZeroFileSizeError=File size condition value must not be 0 (Unless = is selected)." }) - private static final SortedSet mediaTypes = MimeTypes.getDefaultMimeTypes().getMediaTypeRegistry().getTypes(); private static final Logger logger = Logger.getLogger(FilesSetRulePanel.class.getName()); private static final String SLEUTHKIT_PATH_SEPARATOR = "/"; // NON-NLS private static final List ILLEGAL_FILE_NAME_CHARS = InterestingItemDefsManager.getIllegalFileNameChars(); @@ -112,8 +108,8 @@ final class FilesSetRulePanel extends javax.swing.JPanel { private void populateMimeTypesComboBox() { Set fileTypesCollated = new HashSet<>(); - for (MediaType mediaType : mediaTypes) { - fileTypesCollated.add(mediaType.toString()); + for (String mediaType : FileTypeDetector.getStandardDetectedTypes()) { + fileTypesCollated.add(mediaType); } FileTypeDetector fileTypeDetector; @@ -327,7 +323,7 @@ final class FilesSetRulePanel extends javax.swing.JPanel { } } - // The path condition, if specified, must either be a regular expression + // The path condition, if specified, must either be a regular expression // that compiles or a string without illegal file path chars. if (this.pathCheck.isSelected()) { if (this.pathTextField.getText().isEmpty()) { @@ -418,7 +414,7 @@ final class FilesSetRulePanel extends javax.swing.JPanel { } } else { logger.log(Level.SEVERE, "Attempt to get name condition with illegal chars"); // NON-NLS - throw new IllegalStateException("The files set rule panel name condition is not in a valid state"); // NON-NLS + throw new IllegalStateException("The files set rule panel name condition is not in a valid state"); // NON-NLS } } return condition; @@ -504,7 +500,7 @@ final class FilesSetRulePanel extends javax.swing.JPanel { condition = new FilesSet.Rule.ParentPathCondition(path); } else { logger.log(Level.SEVERE, "Attempt to get path condition with illegal chars"); // NON-NLS - throw new IllegalStateException("The files set rule panel path condition is not in a valid state"); // NON-NLS + throw new IllegalStateException("The files set rule panel path condition is not in a valid state"); // NON-NLS } } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.java index a230ce8157..9e44fe05a2 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.java @@ -25,7 +25,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.SortedSet; import java.util.TreeMap; import java.util.logging.Level; import javax.swing.DefaultListModel; @@ -33,8 +32,6 @@ import javax.swing.JButton; import javax.swing.JOptionPane; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; -import org.apache.tika.mime.MediaType; -import org.apache.tika.mime.MimeTypes; import org.netbeans.spi.options.OptionsPanelController; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponents.OptionsPanel; @@ -58,7 +55,6 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp "IngestFileFilter.title=Ingest File Set" }) - private static final SortedSet MEDIA_TYPES = MimeTypes.getDefaultMimeTypes().getMediaTypeRegistry().getTypes(); private final DefaultListModel setsListModel = new DefaultListModel<>(); private final DefaultListModel rulesListModel = new DefaultListModel<>(); private final Logger logger = Logger.getLogger(InterestingItemDefsPanel.class.getName()); @@ -68,9 +64,9 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp private final String settingsLegacyFileName; private final String ruleDialogTitle; - // The following is a map of interesting files set names to interesting - // files set definitions. It is a snapshot of the files set definitions - // obtained from the interesting item definitions manager at the time the + // The following is a map of interesting files set names to interesting + // files set definitions. It is a snapshot of the files set definitions + // obtained from the interesting item definitions manager at the time the // the panel is loaded. When the panel saves or stores its settings, these // definitions, possibly changed, are submitted back to the interesting item // definitions manager. Note that it is a tree map to aid in displaying @@ -113,9 +109,11 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp } else { setName(Bundle.InterestingItemDefsPanel_Title()); } + setName(Bundle.InterestingItemDefsPanel_Title()); + Set fileTypesCollated = new HashSet<>(); - for (MediaType mediaType : MEDIA_TYPES) { - fileTypesCollated.add(mediaType.toString()); + for (String mediaType : FileTypeDetector.getStandardDetectedTypes()) { + fileTypesCollated.add(mediaType); } FileTypeDetector fileTypeDetector; @@ -180,14 +178,14 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp this.filesSets = new TreeMap<>(); } - // Populate the list model for the interesting files sets list + // Populate the list model for the interesting files sets list // component. for (FilesSet set : this.filesSets.values()) { this.setsListModel.addElement(set); } if (!this.filesSets.isEmpty()) { - // Select the first files set by default. The list selections + // Select the first files set by default. The list selections // listeners will then populate the other components. EventQueue.invokeLater(() -> { InterestingItemDefsPanel.this.setsList.setSelectedIndex(0); @@ -247,7 +245,7 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp // components. FilesSet selectedSet = InterestingItemDefsPanel.this.setsList.getSelectedValue(); if (selectedSet != null) { - // Populate the components that display the properties of the + // Populate the components that display the properties of the // selected files set. InterestingItemDefsPanel.this.setDescriptionTextArea.setText(selectedSet.getDescription()); InterestingItemDefsPanel.this.ignoreKnownFilesCheckbox.setSelected(selectedSet.ignoresKnownFiles()); @@ -294,7 +292,7 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp FilesSet.Rule.MimeTypeCondition mimeTypeCondition = rule.getMimeTypeCondition(); FilesSet.Rule.FileSizeCondition fileSizeCondition = rule.getFileSizeCondition(); - // Populate the components that display the properties of the + // Populate the components that display the properties of the // selected rule. if (nameCondition != null) { InterestingItemDefsPanel.this.fileNameTextField.setText(nameCondition.getTextToMatch()); @@ -369,7 +367,7 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp panel = new FilesSetPanel(); } - // Do a dialog box with the files set panel until the user either enters + // Do a dialog box with the files set panel until the user either enters // a valid definition or cancels. Note that the panel gives the user // feedback when isValidDefinition() is called. int option = JOptionPane.OK_OPTION; @@ -390,7 +388,7 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp Map rules = new HashMap<>(); if (selectedSet != null) { // Interesting file sets are immutable for thread safety, - // so editing a files set definition is a replacement operation. + // so editing a files set definition is a replacement operation. // Preserve the existing rules from the set being edited. rules.putAll(selectedSet.getRules()); } @@ -415,7 +413,7 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp // Creating a new rule definition. panel = new FilesSetRulePanel(okButton, cancelButton, (settingsLegacyFileName.equals(""))); } - // Do a dialog box with the files set panel until the user either enters + // Do a dialog box with the files set panel until the user either enters // a valid definition or cancels. Note that the panel gives the user // feedback when isValidDefinition() is called. int option = JOptionPane.OK_OPTION; @@ -425,12 +423,12 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp if (option == JOptionPane.OK_OPTION) { // Interesting file sets are immutable for thread safety, - // so editing a files set rule definition is a replacement + // so editing a files set rule definition is a replacement // operation. Preserve the existing rules from the set being edited. FilesSet selectedSet = this.setsList.getSelectedValue(); Map rules = new HashMap<>(selectedSet.getRules()); - // Remove the "old" rule definition and add the new/edited + // Remove the "old" rule definition and add the new/edited // definition. if (selectedRule != null) { rules.remove(selectedRule.getUuid()); @@ -438,19 +436,18 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp FilesSet.Rule newRule = new FilesSet.Rule(panel.getRuleName(), panel.getFileNameCondition(), panel.getMetaTypeCondition(), panel.getPathCondition(), panel.getMimeTypeCondition(), panel.getFileSizeCondition()); rules.put(newRule.getUuid(), newRule); - // Add the new/edited files set definition, replacing any previous + // Add the new/edited files set definition, replacing any previous // definition with the same name and refreshing the display. this.replaceFilesSet(selectedSet, selectedSet.getName(), selectedSet.getDescription(), selectedSet.ignoresKnownFiles(), rules, selectedSet.processesUnallocatedSpace()); - // Select the new/edited rule. Queue it up so it happens after the - // selection listeners react to the selection of the "new" files + // Select the new/edited rule. Queue it up so it happens after the + // selection listeners react to the selection of the "new" files // set. EventQueue.invokeLater(() -> { this.rulesList.setSelectedValue(newRule, true); }); } } - /** * Adds an interesting files set definition to the collection of definitions @@ -463,8 +460,8 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp * @param ignoresKnownFiles Whether or not the files set ignores known * files. * @param rules The set membership rules for the set. - * @param processesUnallocatedSpace Whether or not this set of rules processes - * unallocated space + * @param processesUnallocatedSpace Whether or not this set of rules + * processes unallocated space */ void replaceFilesSet(FilesSet oldSet, String name, String description, boolean ignoresKnownFiles, Map rules, boolean processesUnallocatedSpace) { if (oldSet != null) { @@ -485,8 +482,8 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp this.setsListModel.addElement(set); } - // Select the new/edited files set definition in the set definitions - // list. This will cause the selection listeners to repopulate the + // Select the new/edited files set definition in the set definitions + // list. This will cause the selection listeners to repopulate the // subordinate components. this.setsList.setSelectedValue(newSet, true); } @@ -496,7 +493,6 @@ final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel imp * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ - @SuppressWarnings("unchecked") // //GEN-BEGIN:initComponents private void initComponents() { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java index 4e4b55d5f7..fe87ac26cc 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java @@ -31,6 +31,8 @@ import org.sleuthkit.autopsy.coreutils.Logger; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrRequest.METHOD; import org.apache.solr.client.solrj.response.QueryResponse; +import org.openide.util.NbBundle.Messages; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.autopsy.datamodel.TextMarkupLookup; import org.sleuthkit.autopsy.keywordsearch.KeywordQueryFilter.FilterType; @@ -100,6 +102,7 @@ class HighlightedText implements IndexedText, TextMarkupLookup { * The main goal of this method is to figure out which pages / chunks have * hits. */ + @Messages({"HighlightedText.query.exception.msg=Could not perform the query to get chunk info and get highlights:"}) private void loadPageInfo() { if (isPageInfoLoaded) { return; @@ -143,8 +146,9 @@ class HighlightedText implements IndexedText, TextMarkupLookup { chunksQuery.addFilter(new KeywordQueryFilter(FilterType.CHUNK, this.objectId)); try { hits = chunksQuery.performQuery(); - } catch (NoOpenCoreException ex) { - logger.log(Level.INFO, "Could not get chunk info and get highlights", ex); //NON-NLS + } catch (KeywordSearchModuleException | NoOpenCoreException ex) { + logger.log(Level.SEVERE, "Could not perform the query to get chunk info and get highlights:" + keywordQuery.getSearchTerm(), ex); //NON-NLS + MessageNotifyUtil.Notify.error(Bundle.HighlightedText_query_exception_msg() + keywordQuery.getSearchTerm(), ex.getCause().getMessage()); return; } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQuery.java index f373c2d162..34e530956a 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQuery.java @@ -36,11 +36,12 @@ interface KeywordSearchQuery { * execute query and return results without publishing them return results * for all matching terms * + * @throws KeywordSearchModuleException error while executing Solr term query * @throws NoOpenCoreException if query failed due to server error, this * could be a notification to stop processing * @return */ - QueryResults performQuery() throws NoOpenCoreException; + QueryResults performQuery() throws KeywordSearchModuleException, NoOpenCoreException; /** * Set an optional filter to narrow down the search Adding multiple filters diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java index 97beb9e9c6..8d940468ae 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java @@ -38,6 +38,7 @@ import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.corecomponents.DataResultTopComponent; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode; import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode; import org.sleuthkit.autopsy.datamodel.KeyValue; @@ -142,6 +143,7 @@ class KeywordSearchResultFactory extends ChildFactory { * * @return */ + @NbBundle.Messages({"KeywordSearchResultFactory.query.exception.msg=Could not perform the query "}) private boolean createFlatKeys(QueryRequest queryRequest, List toPopulate) { /** * Check the validity of the requested query. @@ -158,10 +160,11 @@ class KeywordSearchResultFactory extends ChildFactory { QueryResults queryResults; try { queryResults = keywordSearchQuery.performQuery(); - } catch (NoOpenCoreException ex) { + } catch (KeywordSearchModuleException | NoOpenCoreException ex) { logger.log(Level.SEVERE, "Could not perform the query " + keywordSearchQuery.getQueryString(), ex); //NON-NLS + MessageNotifyUtil.Notify.error(Bundle.KeywordSearchResultFactory_query_exception_msg() + keywordSearchQuery.getQueryString(), ex.getCause().getMessage()); return false; - } + } int id = 0; List tempList = new ArrayList<>(); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java index 5702a952f4..0b7941747e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java @@ -32,11 +32,9 @@ import org.apache.solr.client.solrj.SolrRequest.METHOD; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; -import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.EscapeUtil; import org.sleuthkit.autopsy.coreutils.Logger; -import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; @@ -128,7 +126,7 @@ class LuceneQuery implements KeywordSearchQuery { } @Override - public QueryResults performQuery() throws NoOpenCoreException { + public QueryResults performQuery() throws KeywordSearchModuleException, NoOpenCoreException { QueryResults results = new QueryResults(this, keywordList); //in case of single term literal query there is only 1 term boolean showSnippets = KeywordSearchSettings.getShowSnippets(); @@ -199,7 +197,7 @@ class LuceneQuery implements KeywordSearchQuery { * * @throws NoOpenCoreException */ - private List performLuceneQuery(boolean snippets) throws NoOpenCoreException { + private List performLuceneQuery(boolean snippets) throws KeywordSearchModuleException, NoOpenCoreException { List matches = new ArrayList<>(); boolean allMatchesFetched = false; final Server solrServer = KeywordSearch.getServer(); @@ -210,21 +208,15 @@ class LuceneQuery implements KeywordSearchQuery { Map>> highlightResponse; Set uniqueSolrDocumentsWithHits; - try { - response = solrServer.query(q, METHOD.POST); + response = solrServer.query(q, METHOD.POST); - resultList = response.getResults(); + resultList = response.getResults(); - // objectId_chunk -> "text" -> List of previews - highlightResponse = response.getHighlighting(); + // objectId_chunk -> "text" -> List of previews + highlightResponse = response.getHighlighting(); - // get the unique set of files with hits - uniqueSolrDocumentsWithHits = filterOneHitPerDocument(resultList); - } catch (KeywordSearchModuleException ex) { - logger.log(Level.SEVERE, "Error executing Lucene Solr Query: " + keywordString, ex); //NON-NLS - MessageNotifyUtil.Notify.error(NbBundle.getMessage(Server.class, "Server.query.exception.msg", keywordString), ex.getCause().getMessage()); - return matches; - } + // get the unique set of files with hits + uniqueSolrDocumentsWithHits = filterOneHitPerDocument(resultList); // cycle through results in sets of MAX_RESULTS for (int start = 0; !allMatchesFetched; start = start + MAX_RESULTS) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java index 00fd1db2a7..e41a03554a 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java @@ -37,7 +37,9 @@ import org.netbeans.api.progress.aggregate.AggregateProgressHandle; import org.netbeans.api.progress.aggregate.ProgressContributor; import org.openide.util.Cancellable; import org.openide.util.NbBundle; +import org.openide.util.NbBundle.Messages; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.StopWatch; import org.sleuthkit.autopsy.ingest.IngestMessage; import org.sleuthkit.autopsy.ingest.IngestServices; @@ -383,6 +385,7 @@ public final class SearchRunner { } @Override + @Messages("SearchRunner.query.exception.msg=Error performing query:") protected Object doInBackground() throws Exception { final String displayName = NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.doInBackGround.displayName") + (finalRun ? (" - " + NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.doInBackGround.finalizeMsg")) : ""); @@ -453,8 +456,9 @@ public final class SearchRunner { // Do the actual search try { queryResults = keywordSearchQuery.performQuery(); - } catch (NoOpenCoreException ex) { - logger.log(Level.WARNING, "Error performing query: " + keywordQuery.getSearchTerm(), ex); //NON-NLS + } catch (KeywordSearchModuleException | NoOpenCoreException ex) { + logger.log(Level.SEVERE, "Error performing query: " + keywordQuery.getSearchTerm(), ex); //NON-NLS + MessageNotifyUtil.Notify.error(Bundle.SearchRunner_query_exception_msg() + keywordQuery.getSearchTerm(), ex.getCause().getMessage()); //no reason to continue with next query if recovery failed //or wait for recovery to kick in and run again later //likely case has closed and threads are being interrupted @@ -462,9 +466,6 @@ public final class SearchRunner { } catch (CancellationException e) { logger.log(Level.INFO, "Cancel detected, bailing during keyword query: {0}", keywordQuery.getSearchTerm()); //NON-NLS return null; - } catch (Exception e) { - logger.log(Level.WARNING, "Error performing query: " + keywordQuery.getSearchTerm(), e); //NON-NLS - continue; } // calculate new results by substracting results already obtained in this ingest diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java index 49343798b7..e356c0b8c6 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java @@ -261,10 +261,8 @@ final class TermsComponentQuery implements KeywordSearchQuery { * * @throws NoOpenCoreException */ - // TODO: Make it so this cannot cause NPEs; this method should throw - // exceptions instead of logging them and returning null. @Override - public QueryResults performQuery() throws NoOpenCoreException { + public QueryResults performQuery() throws KeywordSearchModuleException, NoOpenCoreException { /* * Do a query using the Solr terms component to find any terms in the * index that match the regex. @@ -278,14 +276,7 @@ final class TermsComponentQuery implements KeywordSearchQuery { termsQuery.setTimeAllowed(TERMS_SEARCH_TIMEOUT); termsQuery.setShowDebugInfo(DEBUG_FLAG); termsQuery.setTermsLimit(MAX_TERMS_QUERY_RESULTS); - List terms = null; - try { - terms = KeywordSearch.getServer().queryTerms(termsQuery).getTerms(SEARCH_FIELD); - } catch (KeywordSearchModuleException ex) { - LOGGER.log(Level.SEVERE, "Error executing the regex terms query: " + keyword.getSearchTerm(), ex); //NON-NLS - //TODO: this is almost certainly wrong and guaranteed to throw a NPE at some point!!!! - } - + List terms = KeywordSearch.getServer().queryTerms(termsQuery).getTerms(SEARCH_FIELD); /* * Do a term query for each term that matched the regex. */ diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/notes.txt b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/notes.txt deleted file mode 100755 index 1c771a55cd..0000000000 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/notes.txt +++ /dev/null @@ -1,5 +0,0 @@ -- Tika has a bug in the true type font parser included in fontbox. It should - be fixed in the next release of Tika (1.5, or a 1.4 point release). Until then - we bypass Tika when it detects a type of "application/x-font-ttf". See - AbstractFileTikaTextExtract::isSupported. This should be removed when we - update Tika. \ No newline at end of file