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/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/Bundle.properties b/Core/src/org/sleuthkit/autopsy/datamodel/Bundle.properties index e5ac3f678c..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 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/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/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/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/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