diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 8b0d0c9cc8..6e833bbacb 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -59,6 +59,7 @@ import org.sleuthkit.datamodel.SleuthkitJNI.CaseDbHandle.AddImageProcess; * open at a time. Use getCurrentCase() to retrieve the object for the current * case. */ +@SuppressWarnings("deprecation") // TODO: Remove this when ErrorObserver is replaced. public class Case implements SleuthkitCase.ErrorObserver { private static final String autopsyVer = Version.getVersion(); // current version of autopsy. Change it when the version is changed diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index a9c10daac1..73ede28ffc 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -44,8 +44,8 @@ import org.sleuthkit.datamodel.TskException; */ public class BlackboardArtifactNode extends DisplayableItemNode { - private BlackboardArtifact artifact; - private Content associated; + private final BlackboardArtifact artifact; + private final Content associated; private List> customProperties; static final Logger logger = Logger.getLogger(BlackboardArtifactNode.class.getName()); /** @@ -58,6 +58,13 @@ public class BlackboardArtifactNode extends DisplayableItemNode { BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID(), }; + // TODO: This is an unattractive alternative to subclassing BlackboardArtifactNode, + // cut from the same cloth as the equally unattractive SHOW_UNIQUE_PATH array + // above. It should be removed when and if the subclassing is implemented. + private static final Integer[] SHOW_FILE_METADATA = new Integer[] { + BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID(), + }; + /** * Construct blackboard artifact node from an artifact and using provided * icon @@ -179,6 +186,30 @@ public class BlackboardArtifactNode extends DisplayableItemNode { NO_DESCR, sourcePath)); } + + if (Arrays.asList(SHOW_FILE_METADATA).contains(artifactTypeId)) { + AbstractFile file = associated instanceof AbstractFile ? (AbstractFile)associated : null; + ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileModifiedTime.name"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileModifiedTime.displayName"), + "", + file != null ? ContentUtils.getStringTime(file.getMtime(), file) : "")); + ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileChangedTime.name"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileChangedTime.displayName"), + "", + file != null ? ContentUtils.getStringTime(file.getCtime(), file) : "")); + ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileAccessedTime.name"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileAccessedTime.displayName"), + "", + file != null ? ContentUtils.getStringTime(file.getAtime(), file) : "")); + ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileCreatedTime.name"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileCreatedTime.displayName"), + "", + file != null ? ContentUtils.getStringTime(file.getCrtime(), file) : "")); + ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileSize.name"), + NbBundle.getMessage(this.getClass(), "ContentTagNode.createSheet.fileSize.displayName"), + "", + associated.getSize())); + } } else { String dataSourceStr = ""; try { @@ -225,7 +256,7 @@ public class BlackboardArtifactNode extends DisplayableItemNode { * @param np NodeProperty to add */ public void addNodeProperty(NodeProperty np) { - if (customProperties == null) { + if (null == customProperties) { //lazy create the list customProperties = new ArrayList<>(); } @@ -240,6 +271,7 @@ public class BlackboardArtifactNode extends DisplayableItemNode { * put * @param artifact to extract properties from */ + @SuppressWarnings("deprecation") // TODO: Remove this when TSK_TAGGED_ARTIFACT rows aer removed in a database upgrade. private void fillPropertyMap(Map map, BlackboardArtifact artifact) { try { for (BlackboardAttribute attribute : artifact.getAttributes()) { @@ -247,8 +279,8 @@ public class BlackboardArtifactNode extends DisplayableItemNode { //skip some internal attributes that user shouldn't see if (attributeTypeID == ATTRIBUTE_TYPE.TSK_PATH_ID.getTypeID() || attributeTypeID == ATTRIBUTE_TYPE.TSK_TAGGED_ARTIFACT.getTypeID() - || attributeTypeID == ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT.getTypeID()) { - continue; + || attributeTypeID == ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT.getTypeID() + || attributeTypeID == ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID()) { } else { switch (attribute.getValueType()) { case STRING: @@ -337,7 +369,6 @@ public class BlackboardArtifactNode extends DisplayableItemNode { List attributes = artifact.getAttributes(); String keyword = null; String regexp = null; - String origQuery = null; for (BlackboardAttribute att : attributes) { final int attributeTypeID = att.getAttributeTypeID(); if (attributeTypeID == BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID()) { @@ -348,6 +379,7 @@ public class BlackboardArtifactNode extends DisplayableItemNode { } if (keyword != null) { boolean isRegexp = (regexp != null && !regexp.equals("")); + String origQuery; if (isRegexp) { origQuery = regexp; } else { @@ -410,9 +442,9 @@ public class BlackboardArtifactNode extends DisplayableItemNode { return "encrypted-file.png"; //NON-NLS case TSK_EXT_MISMATCH_DETECTED: return "mismatch-16.png"; //NON-NLS - + default: + return "artifact-icon.png"; //NON-NLS } - return "artifact-icon.png"; //NON-NLS } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java index 1d2949caf7..db59b49004 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/InterestingHits.java @@ -108,7 +108,7 @@ public class InterestingHits implements AutopsyVisitableItem { String value = rs.getString("value_text"); //NON-NLS long artifactId = rs.getLong("artifact_id"); //NON-NLS if (!interestingItemsMap.containsKey(value)) { - interestingItemsMap.put(value, new HashSet()); + interestingItemsMap.put(value, new HashSet<>()); } interestingItemsMap.get(value).add(artifactId); } @@ -238,7 +238,7 @@ public class InterestingHits implements AutopsyVisitableItem { } public class SetNameNode extends DisplayableItemNode implements Observer { - private String setName; + private final String setName; public SetNameNode(String setName) {//, Set children) { super(Children.create(new HitFactory(setName), true), Lookups.singleton(setName)); this.setName = setName; @@ -286,7 +286,7 @@ public class InterestingHits implements AutopsyVisitableItem { } private class HitFactory extends ChildFactory implements Observer { - private String setName; + private final String setName; private HitFactory(String setName) { super(); diff --git a/Core/src/org/sleuthkit/autopsy/images/add16.png b/Core/src/org/sleuthkit/autopsy/images/add16.png new file mode 100755 index 0000000000..58e13b4d6c Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/add16.png differ diff --git a/Core/src/org/sleuthkit/autopsy/images/delete16.png b/Core/src/org/sleuthkit/autopsy/images/delete16.png new file mode 100755 index 0000000000..a68b5df1c3 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/delete16.png differ diff --git a/Core/src/org/sleuthkit/autopsy/images/edit16.png b/Core/src/org/sleuthkit/autopsy/images/edit16.png new file mode 100755 index 0000000000..7be65127b7 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/edit16.png differ diff --git a/Core/src/org/sleuthkit/autopsy/images/interesting_item_32x32.png b/Core/src/org/sleuthkit/autopsy/images/interesting_item_32x32.png new file mode 100755 index 0000000000..cce73b4349 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/images/interesting_item_32x32.png differ diff --git a/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestModuleAdapter.java b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestModuleAdapter.java new file mode 100755 index 0000000000..3121a64b55 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/ingest/DataSourceIngestModuleAdapter.java @@ -0,0 +1,36 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 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.ingest; + +import org.sleuthkit.datamodel.Content; + +/** + * An adapter that provides a no-op implementation of the startUp() method for + * data source ingest modules. + */ +public abstract class DataSourceIngestModuleAdapter implements DataSourceIngestModule { + + @Override + public void startUp(IngestJobContext context) throws IngestModuleException { + } + + @Override + public abstract ProcessResult process(Content dataSource, DataSourceIngestModuleProgress progressBar); + +} diff --git a/Core/src/org/sleuthkit/autopsy/ingest/FileIngestModuleAdapter.java b/Core/src/org/sleuthkit/autopsy/ingest/FileIngestModuleAdapter.java new file mode 100755 index 0000000000..13811e6eb4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/ingest/FileIngestModuleAdapter.java @@ -0,0 +1,39 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 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.ingest; + +import org.sleuthkit.datamodel.AbstractFile; + +/** + * An adapter that provides no-op implementations of the startUp() and + * shutDown() methods for file ingest modules. + */ +public abstract class FileIngestModuleAdapter implements FileIngestModule { + + @Override + public void startUp(IngestJobContext context) throws IngestModuleException { + } + + @Override + public abstract ProcessResult process(AbstractFile file); + + @Override + public void shutDown() { + } +} diff --git a/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactoryLoader.java b/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactoryLoader.java index 94cb03e7bd..cfd9b76350 100644 --- a/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactoryLoader.java +++ b/Core/src/org/sleuthkit/autopsy/ingest/IngestModuleFactoryLoader.java @@ -36,6 +36,7 @@ import org.sleuthkit.autopsy.modules.exif.ExifParserModuleFactory; import org.sleuthkit.autopsy.modules.fileextmismatch.FileExtMismatchDetectorModuleFactory; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeIdModuleFactory; import org.sleuthkit.autopsy.modules.hashdatabase.HashLookupModuleFactory; +import org.sleuthkit.autopsy.modules.interestingitems.InterestingItemsIngestModuleFactory; import org.sleuthkit.autopsy.modules.sevenzip.ArchiveFileExtractorModuleFactory; import org.sleuthkit.autopsy.python.JythonModuleLoader; @@ -58,6 +59,7 @@ final class IngestModuleFactoryLoader { add(FileExtMismatchDetectorModuleFactory.class.getCanonicalName()); add(E01VerifierModuleFactory.class.getCanonicalName()); add(AndroidModuleFactory.class.getCanonicalName()); + add(InterestingItemsIngestModuleFactory.class.getCanonicalName()); } }; diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties new file mode 100755 index 0000000000..017c40bc15 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties @@ -0,0 +1,64 @@ +OpenIDE-Module-Display-Category=Ingest Module +OpenIDE-Module-Long-Description=Interesting Files Identifier ingest module. \n\n Identifies interesting files as defined by interesting files rule sets. +OpenIDE-Module-Short-Description=Interesting Files Identifier ingest module. +OpenIDE-Module-Name=Interesting Files Identifier +InterestingItemsIdentifierIngestModule.moduleName=Interesting Files Identifier +InterestingItemsIdentifierIngestModule.moduleDescription=Identifies interesting items as defined by interesting item rule sets. +InterestingFilesIdentifierJobSettingsPanel.filesSetTable.columnModel.title0=Title 1 +InterestingFilesIdentifierJobSettingsPanel.filesSetTable.columnModel.title3=Title 4 +InterestingFilesIdentifierJobSettingsPanel.filesSetTable.columnModel.title2=Title 3 +InterestingFilesIdentifierJobSettingsPanel.filesSetTable.columnModel.title1=Title 2 +InterestingItemDefsPanel.newSetButton.text=New Set... +InterestingItemDefsPanel.editSetButton.text=Edit Set... +InterestingItemDefsPanel.deleteSetButton.text=Delete Set +InterestingItemDefsPanel.newRuleButton.text=New Rule... +InterestingItemDefsPanel.editRuleButton.text=Edit Rule... +InterestingItemDefsPanel.deleteRuleButton.text=Delete Rule +InterestingItemDefsPanel.ruleNameTextField.text= +InterestingItemDefsPanel.setsListLabel.text=Interesting Files Set Definitions: +InterestingItemDefsPanel.selectedSetLabel.text=Selected Set: +InterestingItemDefsPanel.rulesListLabel.text=Rules: +InterestingItemDefsPanel.selectedRuleLabel.text=Selected Rule: +InterestingItemDefsPanel.setDescPanel.border.title=Description +FilesSetPanel.title=Interesting Files Set +FilesSetPanel.messages.filesSetsMustBeNamed=Interesting files sets must be named. +FilesSetPanel.ignoreKnownFilesCheckbox.text=Ignore Known Files +FilesSetPanel.descriptionPanel.border.title=Description +FilesSetPanel.nameLabel.text=Set Name: +FilesSetPanel.descPanel.border.title=Description +FilesSetRulePanel.title=Interesting Files Set Rule +FilesSetRulePanel.extensionRadioButton.text=Extension Only +FilesSetRulePanel.pathPanel.border.title=With parent path: +FilesSetRulePanel.pathRegexCheckBox.text=Regex +FilesSetRulePanel.pathTextField.text= +FilesSetRulePanel.fullNameRadioButton.text=Full Name +FilesSetRulePanel.nameRegexCheckbox.text=Regex +FilesSetRulePanel.namePanel.AccessibleContext.accessibleName=Name +FilesSetRulePanel.namePanel.border.title=Named: +FilesSetRulePanel.ruleNameTextField.text= +FilesSetRulePanel.nameTextField.text= +FilesSetRulePanel.ruleNameLabel.text=Rule Name: +FilesSetRulePanel.typePanel.border.title=Search for: +FilesSetRulePanel.messages.filesSetRulesMustBeNamed=Interesting files set rules must be named. +FilesSetRulePanel.messages.emptyNameFilter=Interesting files sets rules must have a name for which to search. +FilesSetRulePanel.messages.invalidNameRegex=The name regular expression is not valid:\n\n{0} +FilesSetRulePanel.messages.invalidCharInName=The name cannot contain \\, /, :, *, ?, \", <, or > unless it is a regular expression. +FilesSetRulePanel.messages.invalidCharInPath=The path cannot contain \\, :, *, ?, \", <, or > unless it is a regular expression. +FilesSetRulePanel.messages.invalidPathRegex=The path regular expression is not valid:\n\n{0} +FilesSetRulePanel.dirsRadioButton.text=Directories +FilesSetRulePanel.filesRadioButton.text=Files +InterestingItemDefsPanel.typePanel.border.title=Search for: +InterestingItemDefsPanel.bothRadioButton.text=Files and Directories +InterestingItemDefsPanel.dirsRadioButton.text=Directories +InterestingItemDefsPanel.filesRadioButton.text=Files +InterestingItemDefsPanel.fileNamePanel.border.title=Name +InterestingItemDefsPanel.fileNameRegexCheckbox.text=Regex +InterestingItemDefsPanel.fileNameExtensionRadioButton.text=Extension Only +InterestingItemDefsPanel.fileNameTextField.text= +InterestingItemDefsPanel.fileNameRadioButton.text=File Name +FilesSetRulePanel.pathSeparatorInfoLabel.text=Use / as path separator +FilesSetRulePanel.filesAndDirsRadioButton.text=Files and Directories +InterestingItemDefsPanel.rulePathFilterTextField.text= +InterestingItemDefsPanel.rulePathPanel.border.title=Path +InterestingItemDefsPanel.rulePathFilterRegexCheckBox.text=Regex +InterestingItemDefsPanel.ignoreKnownFilesCheckbox.text=Ignore Known Files diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle_ja.properties new file mode 100755 index 0000000000..e69de29bb2 diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestJobSettings.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestJobSettings.java new file mode 100755 index 0000000000..82d724f555 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestJobSettings.java @@ -0,0 +1,63 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 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.modules.interestingitems; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; + +/** + * Ingest job settings for interesting files identifier ingest modules. + */ +final class FilesIdentifierIngestJobSettings implements IngestModuleIngestJobSettings { + + private static final long serialVersionUID = 1L; + private final Set enabledFilesSetNames = new HashSet<>(); + + /** + * Construct the ingest job settings for an interesting files identifier + * ingest module. + * + * @param enabledFilesSetNames The names of the interesting files sets + * that are enabled for the ingest job. + */ + FilesIdentifierIngestJobSettings(List enabledFilesSetNames) { + this.enabledFilesSetNames.addAll(enabledFilesSetNames); + } + + /** + * @inheritDoc + */ + @Override + public long getVersionNumber() { + return FilesIdentifierIngestJobSettings.serialVersionUID; + } + + /** + * Determines whether or not an interesting files set definition is enabled + * for an ingest job. + * + * @param filesSetName The name of the files set definition to check. + * @return True if the file set is enabled, false otherwise. + */ + boolean isInterestingFilesSetEnabled(String filesSetName) { + return (this.enabledFilesSetNames.contains(filesSetName)); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestJobSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestJobSettingsPanel.form new file mode 100755 index 0000000000..ec9fb17f55 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestJobSettingsPanel.form @@ -0,0 +1,65 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestJobSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestJobSettingsPanel.java new file mode 100755 index 0000000000..dda2a8ddf9 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestJobSettingsPanel.java @@ -0,0 +1,303 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 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.modules.interestingitems; + +import java.util.ArrayList; +import java.util.List; +import java.util.Observable; +import java.util.Observer; +import java.util.TreeMap; +import javax.swing.JTable; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableColumn; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; + +/** + * Ingest job settings panel for interesting files identifier ingest modules. + */ +final class FilesIdentifierIngestJobSettingsPanel extends IngestModuleIngestJobSettingsPanel implements Observer { + + private final FilesSetsTableModel tableModel; + + // This map of interesting interesting files set names to files sets is used + // both to sort interesting files sets by name and to detect when a new + // files set is defined, so that the new set can be enabled by default. + private TreeMap filesSetSnapshot; + + /** + * A factory method that avoids publishing the "this" reference from the + * constructor. + * + * @return An instance of the ingest job settings panel interesting files + * identifier ingest modules. + */ + static FilesIdentifierIngestJobSettingsPanel makePanel(FilesIdentifierIngestJobSettings settings) { + FilesIdentifierIngestJobSettingsPanel panel = new FilesIdentifierIngestJobSettingsPanel(settings); + + // Observe the interesting item definitions manager for changes to the + // interesting file set definitions. This is used to keep this panel in + // synch with changes made using the global settings/option panel for + // this module. + InterestingItemDefsManager.getInstance().addObserver(panel); + + return panel; + } + + /** + * Constructs an ingest job settings panel for interesting files identifier + * ingest modules. + */ + private FilesIdentifierIngestJobSettingsPanel(FilesIdentifierIngestJobSettings settings) { + initComponents(); + + /* Make a table row object for each interesting files set, bundling the + * set with an enabled flag. The files sets are loaded into a tree map + * so they are sorted by name. The keys set also serves as a cache of + * set names so that new sets can be easily detected in the override of + * Observer.update(). + */ + List filesSetRows = new ArrayList<>(); + this.filesSetSnapshot = new TreeMap<>(InterestingItemDefsManager.getInstance().getInterestingFilesSets()); + for (FilesSet set : this.filesSetSnapshot.values()) { + filesSetRows.add(new FilesSetRow(set, settings.isInterestingFilesSetEnabled(set.getName()))); + } + + // Make a table model to manage the row objects. + this.tableModel = new FilesSetsTableModel(filesSetRows); + + // Set up the table component that presents the table model that allows + // users to enable and disable interesting files set definitions for an + // ingest job. + this.filesSetTable.setModel(tableModel); + this.filesSetTable.setTableHeader(null); + this.filesSetTable.setRowSelectionAllowed(false); + final int width = this.filesSetScrollPane.getPreferredSize().width; + this.filesSetTable.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN); + TableColumn column; + for (int i = 0; i < this.filesSetTable.getColumnCount(); i++) { + column = this.filesSetTable.getColumnModel().getColumn(i); + if (i == 0) { + column.setPreferredWidth(((int) (width * 0.07))); + } else { + column.setPreferredWidth(((int) (width * 0.92))); + } + } + } + + /** + * @inheritDoc + */ + @Override + public IngestModuleIngestJobSettings getSettings() { + List enabledInterestingFilesSets = new ArrayList<>(); + this.tableModel.filesSetRows.stream().filter((rowModel) -> (rowModel.isEnabled())).forEach((rowModel) -> { + enabledInterestingFilesSets.add(rowModel.getFilesSet().getName()); + }); + return new FilesIdentifierIngestJobSettings(enabledInterestingFilesSets); + } + + /** + * @inheritDoc + */ + @Override + public void update(Observable o, Object arg + ) { + // Get the user's current enabled/disabled settings. + FilesIdentifierIngestJobSettings settings = (FilesIdentifierIngestJobSettings) this.getSettings(); + + // Refresh the view of the interesting files set definitions. + List rowModels = new ArrayList<>(); + TreeMap newFilesSetSnapshot = new TreeMap<>(InterestingItemDefsManager.getInstance().getInterestingFilesSets()); + for (FilesSet set : newFilesSetSnapshot.values()) { + if (this.filesSetSnapshot.keySet().contains(set.getName())) { + // Preserve the current enabled/diabled state of the set. + rowModels.add(new FilesSetRow(set, settings.isInterestingFilesSetEnabled(set.getName()))); + } else { + // New sets are enabled by default. + rowModels.add(new FilesSetRow(set, true)); + } + } + this.tableModel.resetTableData(rowModels); + + // Cache the snapshot so it will be avaialble for the next update. + this.filesSetSnapshot = newFilesSetSnapshot; + } + + /** + * Table model for a JTable component that allows users to enable and + * disable interesting files set definitions for an ingest job. + */ + private final static class FilesSetsTableModel extends AbstractTableModel { + + private List filesSetRows; + + /** + * Constructs a table model for a JTable component that allows users to + * enable and disable interesting files set definitions for an ingest + * job. + * + * @param filesSetRows A collection of row objects that bundles an + * interesting files set with an enabled flag + */ + FilesSetsTableModel(List filesSetRows) { + this.filesSetRows = filesSetRows; + } + + /** + * Refreshes the table with a new set of rows. + * + * @param filesSetRows A collection of row objects that bundles an + * interesting files set with an enabled flag + */ + void resetTableData(List filesSetRows) { + this.filesSetRows = filesSetRows; + this.fireTableDataChanged(); + } + + /** + * @inheritDoc + */ + @Override + public int getRowCount() { + return this.filesSetRows.size(); + } + + /** + * @inheritDoc + */ + @Override + public int getColumnCount() { + return 2; + } + + /** + * @inheritDoc + */ + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + if (columnIndex == 0) { + return this.filesSetRows.get(rowIndex).isEnabled(); + } else { + return this.filesSetRows.get(rowIndex).getFilesSet().getName(); + } + } + + /** + * @inheritDoc + */ + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return (columnIndex == 0); + } + + /** + * @inheritDoc + */ + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + if (columnIndex == 0) { + this.filesSetRows.get(rowIndex).setEnabled((Boolean) aValue); + } + } + + /** + * @inheritDoc + */ + @Override + public Class getColumnClass(int c) { + return getValueAt(0, c).getClass(); + } + } + + /** + * Bundles an interesting files set with an enabled flag. + */ + private final static class FilesSetRow { + + private final FilesSet set; + private boolean enabled; + + FilesSetRow(FilesSet set, boolean enabled) { + this.set = set; + this.enabled = enabled; + } + + FilesSet getFilesSet() { + return this.set; + } + + boolean isEnabled() { + return this.enabled; + } + + void setEnabled(boolean enabled) { + this.enabled = enabled; + } + } + + /** + * This method is called from within the constructor to initialize the form. + * 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() { + + filesSetScrollPane = new javax.swing.JScrollPane(); + filesSetTable = new javax.swing.JTable(); + + filesSetTable.setBackground(new java.awt.Color(240, 240, 240)); + filesSetTable.setModel(new javax.swing.table.DefaultTableModel( + new Object [][] { + + }, + new String [] { + + } + )); + filesSetTable.setShowHorizontalLines(false); + filesSetTable.setShowVerticalLines(false); + filesSetScrollPane.setViewportView(filesSetTable); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(filesSetScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 254, Short.MAX_VALUE) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap() + .addComponent(filesSetScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 188, Short.MAX_VALUE) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JScrollPane filesSetScrollPane; + private javax.swing.JTable filesSetTable; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestModule.java new file mode 100755 index 0000000000..a9b743e791 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesIdentifierIngestModule.java @@ -0,0 +1,131 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 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.modules.interestingitems; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.ingest.FileIngestModule; +import org.sleuthkit.autopsy.ingest.IngestJobContext; +import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * A file ingest module that generates interesting files set hit artifacts for + * files that match interesting files set definitions. + */ +final class FilesIdentifierIngestModule implements FileIngestModule { + + private static final Object sharedResourcesLock = new Object(); + private static final Logger logger = Logger.getLogger(FilesIdentifierIngestModule.class.getName()); + private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); + private static final Map> interestingFileSetsByJob = new ConcurrentHashMap<>(); + private final FilesIdentifierIngestJobSettings settings; + private IngestJobContext context; + + /** + * Construct an interesting files identifier ingest module for an ingest + * job. + * + * @param settings An ingest job settings object for the module. + */ + FilesIdentifierIngestModule(FilesIdentifierIngestJobSettings settings) { + this.settings = settings; + } + + /** + * @inheritDoc + */ + @Override + public void startUp(IngestJobContext context) throws IngestModuleException { + this.context = context; + synchronized (FilesIdentifierIngestModule.sharedResourcesLock) { + if (FilesIdentifierIngestModule.refCounter.incrementAndGet(context.getJobId()) == 1) { + // Starting up the first instance of this module for this ingest + // job, so get the interesting file sets definitions snapshot + // for the job. Note that getting this snapshot atomically via a + // synchronized definitions manager method eliminates the need + // to disable the interesting files set definition UI during ingest. + List filesSets = new ArrayList<>(); + for (FilesSet set : InterestingItemDefsManager.getInstance().getInterestingFilesSets().values()) { + if (settings.isInterestingFilesSetEnabled(set.getName())) { + filesSets.add(set); + } + } + FilesIdentifierIngestModule.interestingFileSetsByJob.put(context.getJobId(), filesSets); + } + } + } + + /** + * @inheritDoc + */ + @Override + public ProcessResult process(AbstractFile file) { + // See if the file belongs to any defined interesting files set. + List filesSets = FilesIdentifierIngestModule.interestingFileSetsByJob.get(this.context.getJobId()); + for (FilesSet filesSet : filesSets) { + String ruleSatisfied = filesSet.fileIsMemberOf(file); + if (ruleSatisfied != null) { + try { + // Post an interesting files set hit artifact to the + // blackboard. + String moduleName = InterestingItemsIngestModuleFactory.getModuleName(); + BlackboardArtifact artifact = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT); + + // Add a set name attribute to the artifact. This adds a + // fair amount of redundant data to the attributes table + // (i.e., rows that differ only in artifact id), but doing + // otherwise would requires reworking the interesting files + // set hit artifact. + BlackboardAttribute setNameAttribute = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID(), moduleName, filesSet.getName()); + artifact.addAttribute(setNameAttribute); + + // Add a category attribute to the artifact to record the + // interesting files set membership rule that was satisfied. + BlackboardAttribute ruleNameAttribute = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY.getTypeID(), moduleName, ruleSatisfied); + artifact.addAttribute(ruleNameAttribute); + + } catch (TskCoreException ex) { + FilesIdentifierIngestModule.logger.log(Level.SEVERE, "Error posting to the blackboard", ex); + } + } + } + return ProcessResult.OK; + } + + /** + * @inheritDoc + */ + @Override + public void shutDown() { + if (refCounter.decrementAndGet(this.context.getJobId()) == 0) { + // Shutting down the last instance of this module for this ingest + // job, so discard the interesting file sets definitions snapshot + // for the job. + FilesIdentifierIngestModule.interestingFileSetsByJob.remove(this.context.getJobId()); + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSet.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSet.java new file mode 100755 index 0000000000..4277e0d9cc --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSet.java @@ -0,0 +1,635 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 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.modules.interestingitems; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TskData; + +/** + * A collection of set membership rules that define an interesting files set. + * The rules are independent, i.e., if any rule is satisfied by a file, the file + * belongs to the set. + * + * Interesting files set definition objects are immutable, so they may be safely + * published to multiple threads. + */ +final class FilesSet { + + private final String name; + private final String description; + private final boolean ignoreKnownFiles; + private final Map rules = new HashMap<>(); + + /** + * Constructs an interesting files set. + * + * @param name The name of the set. + * @param description A description of the set, may be null. + * @param ignoreKnownFiles Whether or not to exclude known files from the + * set. + * @param rules The rules that define the set. May be null, but a set with + * no rules is the empty set. + */ + FilesSet(String name, String description, boolean ignoreKnownFiles, Map rules) { + if ((name == null) || (name.isEmpty())) { + throw new IllegalArgumentException("Interesting files set name cannot be null or empty"); + } + this.name = name; + this.description = (description != null ? description : ""); + this.ignoreKnownFiles = ignoreKnownFiles; + if (rules != null) { + this.rules.putAll(rules); + } + } + + /** + * Gets the name of this interesting files set. + * + * @return A name string. + */ + String getName() { + return this.name; + } + + /** + * Gets the description of this interesting files set. + * + * @return A description string, possibly the empty string. + */ + String getDescription() { + return this.description; + } + + /** + * Returns whether or not this interesting files set ignores known files, + * i.e., files marked as known by a look up in a known files hash set such + * as the National Software Reference Library (NSRL). Note that the + * interesting files set does not do hash set look ups; it simply queries + * the known status of the files when testing them for set membership. + * + * @return True if known files are ignored, false otherwise. + */ + boolean ignoresKnownFiles() { + return this.ignoreKnownFiles; + } + + /** + * Gets a copy of the set membership rules of this interesting files set. + * + * @return A map of set membership rule names to rules, possibly empty. + */ + Map getRules() { + return new HashMap<>(this.rules); + } + + /** + * Determines whether a file is a member of this interesting files set. + * + * @param file A file to test for set membership. + * @return The name of the first set membership rule satisfied by the file, + * will be null if the file does not belong to the set. + */ + String fileIsMemberOf(AbstractFile file) { + if ((this.ignoreKnownFiles) && (file.getKnown() == TskData.FileKnown.KNOWN)) { + return null; + } + for (Rule rule : rules.values()) { + if (rule.isSatisfied(file)) { + return rule.getName(); + } + } + return null; + } + + @Override + public String toString() { + // This override is designed to provide a display name for use with + // javax.swing.DefaultListModel. + return this.name; + } + + /** + * A set membership rule for an interesting files set. The immutability of a + * rule object allows it to be safely published to multiple threads. + */ + static class Rule { + + private final String ruleName; + private final FileNameFilter fileNameFilter; + private final MetaTypeFilter metaTypeFilter; + private final ParentPathFilter pathFilter; + private final List filters = new ArrayList<>(); + + /** + * Construct an interesting files set membership rule. + * + * @param ruleName The name of the rule. + * @param fileNameFilter A file name filter. + * @param metaTypeFilter A file meta-type filter. + * @param pathFilter A file path filter, may be null. + */ + Rule(String ruleName, FileNameFilter fileNameFilter, MetaTypeFilter metaTypeFilter, ParentPathFilter pathFilter) { + if ((ruleName == null) || (ruleName.isEmpty())) { + throw new IllegalArgumentException("Interesting files set rule name cannot be null or empty"); + } + if (fileNameFilter == null) { + throw new IllegalArgumentException("Interesting files set rule file name filter cannot be null"); + } + if (metaTypeFilter == null) { + throw new IllegalArgumentException("Interesting files set rule meta-type filter cannot be null"); + } + this.ruleName = ruleName; + this.fileNameFilter = fileNameFilter; + this.filters.add(fileNameFilter); + this.metaTypeFilter = metaTypeFilter; + this.filters.add(this.metaTypeFilter); + this.pathFilter = pathFilter; + if (this.pathFilter != null) { + this.filters.add(this.pathFilter); + } + } + + /** + * Get the name of the rule. + * + * @return A name string. + */ + String getName() { + return ruleName; + } + + /** + * Get the file name filter for the rule. + * + * @return A file name filter. + */ + FileNameFilter getFileNameFilter() { + return this.fileNameFilter; + } + + /** + * Get the meta-type filter for the rule. + * + * @return A meta-type filter. + */ + MetaTypeFilter getMetaTypeFilter() { + return this.metaTypeFilter; + } + + /** + * Get the path filter for the rule. + * + * @return A path filter, may be null. + */ + ParentPathFilter getPathFilter() { + return this.pathFilter; + } + + /** + * Determines whether or not a file satisfies the rule. + * + * @param file The file to test. + * @return True if the rule is satisfied, false otherwise. + */ + boolean isSatisfied(AbstractFile file) { + for (FileAttributeFilter filter : filters) { + if (!filter.passes(file)) { + return false; + } + } + return true; + } + + /** + * @inheritDoc + */ + @Override + public String toString() { + // This override is designed to provide a display name for use with + // javax.swing.DefaultListModel. + return this.ruleName; + } + + /** + * An interface for the file attribute filters of which interesting + * files set membership rules are composed. + */ + static interface FileAttributeFilter { + + /** + * Tests whether or not a file satisfies the conditions of a filter. + * + * @param file The file to test. + * @return True if the file passes the test, false otherwise. + */ + boolean passes(AbstractFile file); + } + + /** + * A file meta-type filter for an interesting files set membership rule. + * The immutability of a meta-type filter object allows it to be safely + * published to multiple threads. + */ + static final class MetaTypeFilter implements FileAttributeFilter { + + enum Type { + + FILES, + DIRECTORIES, + FILES_AND_DIRECTORIES + } + + private final Type type; + + /** + * Construct a meta-type filter. + * + * @param metaType The meta-type to match, must. + */ + MetaTypeFilter(Type type) { + this.type = type; + } + + /** + * @inheritDoc + */ + @Override + public boolean passes(AbstractFile file) { + switch (this.type) { + case FILES: + return file.getMetaType() == TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG; + case DIRECTORIES: + return file.getMetaType() == TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR; + default: + return file.getMetaType() == TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG + || file.getMetaType() == TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR; + } + } + + /** + * Gets the meta-type the filter matches. + * + * @return A member of the MetaTypeFilter.Type enumeration. + */ + Type getMetaType() { + return this.type; + } + } + + /** + * An interface for file attribute filters that do textual matching. + */ + static interface TextFilter extends FileAttributeFilter { + + /** + * Gets the text the filter matches. + * + * @return The text. + */ + String getTextToMatch(); + + /** + * Queries whether or not the text the filter matches is a regular + * expression. + * + * @return True if the text to be matched is a regular expression, + * false otherwise. + */ + boolean isRegex(); + + /** + * Determines whether a string of text matches the filter. + * + * @param textToMatch The text string. + * @return True if the text matches, false otherwise. + */ + boolean textMatches(String textToMatch); + + } + + /** + * An abstract base class for file attribute filters that do textual + * matching. + */ + private static abstract class AbstractTextFilter implements TextFilter { + + private final TextMatcher textMatcher; + + /** + * Construct a case-insensitive text filter. + * + * @param text The text to be matched. + */ + AbstractTextFilter(String text) { + this.textMatcher = new FilesSet.Rule.CaseInsensitiveStringComparisionMatcher(text); + } + + /** + * Construct a regular expression text filter. + * + * @param regex The regular expression to be matched. + */ + AbstractTextFilter(Pattern regex) { + this.textMatcher = new FilesSet.Rule.RegexMatcher(regex); + } + + /** + * Get the text the filter matches. + * + * @return The text. + */ + @Override + public String getTextToMatch() { + return this.textMatcher.getTextToMatch(); + } + + /** + * Queries whether or not the text the filter matches is a regular + * expression. + * + * @return True if the text to be matched is a regular expression, + * false otherwise. + */ + @Override + public boolean isRegex() { + return this.textMatcher.isRegex(); + } + + /** + * Determines whether a string of text matches the filter. + * + * @param textToMatch The text string. + * @return True if the text matches, false otherwise. + */ + @Override + public boolean textMatches(String textToMatch) { + return this.textMatcher.textMatches(textToMatch); + } + + /** + * @inheritDoc + */ + @Override + public abstract boolean passes(AbstractFile file); + + } + + /** + * A file path filter for an interesting files set membership rule. The + * immutability of a path filter object allows it to be safely published + * to multiple threads. + */ + static final class ParentPathFilter extends AbstractTextFilter { + + /** + * Construct a case-insensitive file path filter. + * + * @param path The path to be matched. + */ + ParentPathFilter(String path) { + super(path); + } + + /** + * Construct a file path regular expression filter. + * + * @param path The path regular expression to be matched. + */ + ParentPathFilter(Pattern path) { + super(path); + } + + /** + * @inheritDoc + */ + @Override + public boolean passes(AbstractFile file) { + return this.textMatches(file.getParentPath()); + } + + } + + /** + * A "tagging" interface to group name and extension filters separately + * from path filters for type safety when constructing rules. + */ + static interface FileNameFilter extends TextFilter { + } + + /** + * A file name filter for an interesting files set membership rule. The + * immutability of a file name filter object allows it to be safely + * published to multiple threads. + */ + static final class FullNameFilter extends AbstractTextFilter implements FileNameFilter { + + /** + * Construct a case-insensitive full file name filter. + * + * @param name The file name to be matched. + */ + FullNameFilter(String name) { + super(name); + } + + /** + * Construct a full file name regular expression filter. + * + * @param name The file name regular expression to be matched. + */ + FullNameFilter(Pattern name) { + super(name); + } + + /** + * @inheritDoc + */ + @Override + public boolean passes(AbstractFile file) { + return this.textMatches(file.getName()); + } + + } + + /** + * A file name extension filter for an interesting files set membership + * rule. The immutability of a file name extension filter object allows + * it to be safely published to multiple threads. + */ + static final class ExtensionFilter extends AbstractTextFilter implements FileNameFilter { + + /** + * Construct a case-insensitive file name extension filter. + * + * @param extension The file name extension to be matched. + */ + ExtensionFilter(String extension) { + // If there is a leading ".", strip it since + // AbstractFile.getFileNameExtension() returns just the + // extension chars and not the dot. + super(extension.startsWith(".") ? extension.substring(1) : extension); + } + + /** + * Construct a file name extension regular expression filter. + * + * @param extension The file name extension regular expression to be + * matched. + */ + ExtensionFilter(Pattern extension) { + super(extension.pattern()); + } + + /** + * @inheritDoc + */ + @Override + public boolean passes(AbstractFile file) { + return this.textMatches(file.getNameExtension()); + } + + } + + /** + * An interface for objects that do textual matches, used to compose a + * text filter. + */ + private static interface TextMatcher { + + /** + * Get the text the matcher examines. + * + * @return The text. + */ + String getTextToMatch(); + + /** + * Queries whether or not the text the matcher examines is a regular + * expression. + * + * @return True if the text to be matched is a regular expression, + * false otherwise. + */ + boolean isRegex(); + + /** + * Determines whether a string of text is matched. + * + * @param textToMatch The text string. + * @return True if the text matches, false otherwise. + */ + boolean textMatches(String subject); + + } + + /** + * A text matcher that does a case-insensitive string comparison. + */ + private static class CaseInsensitiveStringComparisionMatcher implements TextMatcher { + + private final String textToMatch; + + /** + * Construct a text matcher that does a case-insensitive string + * comparison. + * + * @param textToMatch The text to match. + */ + CaseInsensitiveStringComparisionMatcher(String textToMatch) { + this.textToMatch = textToMatch; + } + + /** + * @inheritDoc + */ + @Override + public String getTextToMatch() { + return this.textToMatch; + } + + /** + * @inheritDoc + */ + @Override + public boolean isRegex() { + return false; + } + + /** + * @inheritDoc + */ + @Override + public boolean textMatches(String subject) { + return subject.equalsIgnoreCase(textToMatch); + } + + } + + /** + * A text matcher that does regular expression matching. + */ + private static class RegexMatcher implements TextMatcher { + + private final Pattern regex; + + /** + * Construct a text matcher that does a regular expression + * comparison. + * + * @param regex The regular expression to match. + */ + RegexMatcher(Pattern regex) { + this.regex = regex; + } + + /** + * @inheritDoc + */ + @Override + public String getTextToMatch() { + return this.regex.pattern(); + } + + /** + * @inheritDoc + */ + @Override + public boolean isRegex() { + return true; + } + + /** + * @inheritDoc + */ + @Override + public boolean textMatches(String subject) { + // A single match is sufficient. + return this.regex.matcher(subject).find(); + } + + } + + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetPanel.form b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetPanel.form new file mode 100755 index 0000000000..fa5e501cf3 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetPanel.form @@ -0,0 +1,118 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetPanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetPanel.java new file mode 100755 index 0000000000..23aee8e810 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetPanel.java @@ -0,0 +1,179 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 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.modules.interestingitems; + +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.util.NbBundle; + +/** + * A panel that allows a user to create and edit interesting files set + * definitions. + */ +public class FilesSetPanel extends javax.swing.JPanel { + + /** + * Construct a files set panel in create mode. + */ + FilesSetPanel() { + initComponents(); + } + + /** + * Construct a files set panel in edit mode. + * + * @param filesSet The files set to be edited. + */ + FilesSetPanel(FilesSet filesSet) { + initComponents(); + this.nameTextField.setText(filesSet.getName()); + this.descTextArea.setText(filesSet.getDescription()); + this.ignoreKnownFilesCheckbox.setSelected(filesSet.ignoresKnownFiles()); + } + + /** + * Returns whether or not the data entered in the panel constitutes a valid + * interesting files set definition, displaying a dialog explaining the + * deficiency if the definition is invalid + * + * @return True if the definition is valid, false otherwise. + */ + boolean isValidDefinition() { + if (this.nameTextField.getText().isEmpty()) { + NotifyDescriptor notifyDesc = new NotifyDescriptor.Message( + NbBundle.getMessage(FilesSetPanel.class, "FilesSetPanel.messages.filesSetsMustBeNamed"), + NotifyDescriptor.WARNING_MESSAGE); + DialogDisplayer.getDefault().notify(notifyDesc); + return false; + } + return true; + } + + /** + * Get the name for the interesting files set defined using this panel. + * + * @return A name string. + */ + String getFilesSetName() { + return this.nameTextField.getText(); + } + + /** + * Get the description for the interesting files set defined using this + * panel. + * + * @return A description string. + */ + String getFilesSetDescription() { + return this.descTextArea.getText(); + } + + /** + * Get whether or not the interesting files set defined using this panel + * ignores known files. + * + * @return True if the set ignores known files, false otherwise. + */ + boolean getFileSetIgnoresKnownFiles() { + return this.ignoreKnownFilesCheckbox.isSelected(); + } + + /** + * This method is called from within the constructor to initialize the form. + * 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() { + + nameLabel = new javax.swing.JLabel(); + nameTextField = new javax.swing.JTextField(); + descPanel = new javax.swing.JPanel(); + descScrollPanel = new javax.swing.JScrollPane(); + descTextArea = new javax.swing.JTextArea(); + ignoreKnownFilesCheckbox = new javax.swing.JCheckBox(); + + org.openide.awt.Mnemonics.setLocalizedText(nameLabel, org.openide.util.NbBundle.getMessage(FilesSetPanel.class, "FilesSetPanel.nameLabel.text")); // NOI18N + + descPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(FilesSetPanel.class, "FilesSetPanel.descPanel.border.title"))); // NOI18N + + descTextArea.setColumns(20); + descTextArea.setRows(5); + descScrollPanel.setViewportView(descTextArea); + + javax.swing.GroupLayout descPanelLayout = new javax.swing.GroupLayout(descPanel); + descPanel.setLayout(descPanelLayout); + descPanelLayout.setHorizontalGroup( + descPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(descPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(descScrollPanel) + .addContainerGap()) + ); + descPanelLayout.setVerticalGroup( + descPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, descPanelLayout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(descScrollPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + org.openide.awt.Mnemonics.setLocalizedText(ignoreKnownFilesCheckbox, org.openide.util.NbBundle.getMessage(FilesSetPanel.class, "FilesSetPanel.ignoreKnownFilesCheckbox.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(nameLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 71, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 299, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(descPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(ignoreKnownFilesCheckbox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(nameLabel) + .addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(descPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(ignoreKnownFilesCheckbox) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JPanel descPanel; + private javax.swing.JScrollPane descScrollPanel; + private javax.swing.JTextArea descTextArea; + private javax.swing.JCheckBox ignoreKnownFilesCheckbox; + private javax.swing.JLabel nameLabel; + private javax.swing.JTextField nameTextField; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.form b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.form new file mode 100755 index 0000000000..7e4b61da6a --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.form @@ -0,0 +1,316 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java new file mode 100755 index 0000000000..7fed303b2c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java @@ -0,0 +1,558 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 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.modules.interestingitems; + +import java.util.List; +import java.util.logging.Level; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.Logger; + +/** + * A panel that allows a user to create and edit interesting files set + * membership rules. + */ +final class FilesSetRulePanel extends javax.swing.JPanel { + + 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(); + private static final List ILLEGAL_FILE_PATH_CHARS = InterestingItemDefsManager.getIllegalFilePathChars(); + + /** + * Constructs a files set rule panel in create rule mode. + */ + FilesSetRulePanel() { + initComponents(); + populateComponentsWithDefaultValues(); + } + + /** + * Constructs a files set rule panel in edit rule mode. + * + * @param rule The files set rule to be edited. + */ + FilesSetRulePanel(FilesSet.Rule rule) { + initComponents(); + populateRuleNameComponent(rule); + populateTypeFilterComponents(rule); + populateNameFilterComponents(rule); + populatePathFilterComponents(rule); + } + + /** + * Populates the UI components with default values. + */ + private void populateComponentsWithDefaultValues() { + this.filesRadioButton.setSelected(true); + this.fullNameRadioButton.setSelected(true); + } + + /** + * Populates the UI component that displays the rule name. + * + * @param rule The files set rule to be edited. + */ + private void populateRuleNameComponent(FilesSet.Rule rule) { + this.ruleNameTextField.setText(rule.getName()); + } + + /** + * Populates the UI components that display the meta-type filter for a rule. + * + * @param rule The files set rule to be edited. + */ + private void populateTypeFilterComponents(FilesSet.Rule rule) { + FilesSet.Rule.MetaTypeFilter typeFilter = rule.getMetaTypeFilter(); + switch (typeFilter.getMetaType()) { + case FILES: + this.filesRadioButton.setSelected(true); + break; + case DIRECTORIES: + this.dirsRadioButton.setSelected(true); + break; + case FILES_AND_DIRECTORIES: + this.filesAndDirsRadioButton.setSelected(true); + break; + } + } + + /** + * Populates the UI components that display the name filter for a rule. + * + * @param rule The files set rule to be edited. + */ + private void populateNameFilterComponents(FilesSet.Rule rule) { + FilesSet.Rule.FileNameFilter nameFilter = rule.getFileNameFilter(); + this.nameTextField.setText(nameFilter.getTextToMatch()); + this.nameRegexCheckbox.setSelected(nameFilter.isRegex()); + if (nameFilter instanceof FilesSet.Rule.FullNameFilter) { + this.fullNameRadioButton.setSelected(true); + } else { + this.extensionRadioButton.setSelected(true); + } + } + + /** + * Populates the UI components that display the optional path filter for a + * rule. + * + * @param rule The files set rule to be edited. + */ + private void populatePathFilterComponents(FilesSet.Rule rule) { + FilesSet.Rule.ParentPathFilter pathFilter = rule.getPathFilter(); + if (pathFilter != null) { + this.pathTextField.setText(pathFilter.getTextToMatch()); + this.pathRegexCheckBox.setSelected(pathFilter.isRegex()); + } + } + + /** + * Returns whether or not the data entered in the panel constitutes a valid + * interesting files set membership rule definition, displaying a dialog + * explaining the deficiency if the definition is invalid. + * + * @return True if the definition is valid, false otherwise. + */ + boolean isValidRuleDefinition() { + // The rule must have a name. + if (this.ruleNameTextField.getText().isEmpty()) { + NotifyDescriptor notifyDesc = new NotifyDescriptor.Message( + NbBundle.getMessage(FilesSetPanel.class, "FilesSetRulePanel.messages.filesSetRulesMustBeNamed"), + NotifyDescriptor.WARNING_MESSAGE); + DialogDisplayer.getDefault().notify(notifyDesc); + return false; + } + + // The rule must have name filter text. + if (this.nameTextField.getText().isEmpty()) { + NotifyDescriptor notifyDesc = new NotifyDescriptor.Message( + NbBundle.getMessage(FilesSetPanel.class, "FilesSetRulePanel.messages.emptyNameFilter"), + NotifyDescriptor.WARNING_MESSAGE); + DialogDisplayer.getDefault().notify(notifyDesc); + return false; + } + + // The name filter must either be a regular expression that compiles or + // a string without illegal file name chars. + if (this.nameRegexCheckbox.isSelected()) { + try { + Pattern.compile(this.nameTextField.getText()); + } catch (PatternSyntaxException ex) { + NotifyDescriptor notifyDesc = new NotifyDescriptor.Message( + NbBundle.getMessage(FilesSetPanel.class, "FilesSetRulePanel.messages.invalidNameRegex", ex.getLocalizedMessage()), + NotifyDescriptor.WARNING_MESSAGE); + DialogDisplayer.getDefault().notify(notifyDesc); + return false; + } + } else { + if (!FilesSetRulePanel.containsOnlyLegalChars(this.nameTextField.getText(), FilesSetRulePanel.ILLEGAL_FILE_NAME_CHARS)) { + NotifyDescriptor notifyDesc = new NotifyDescriptor.Message( + NbBundle.getMessage(FilesSetPanel.class, "FilesSetRulePanel.messages.invalidCharInName"), + NotifyDescriptor.WARNING_MESSAGE); + DialogDisplayer.getDefault().notify(notifyDesc); + return false; + } + } + + // The path filter, if specified, must either be a regular expression + // that compiles or a string without illegal file path chars. + if (!this.pathTextField.getText().isEmpty()) { + if (this.pathRegexCheckBox.isSelected()) { + try { + Pattern.compile(this.pathTextField.getText()); + } catch (PatternSyntaxException ex) { + NotifyDescriptor notifyDesc = new NotifyDescriptor.Message( + NbBundle.getMessage(FilesSetPanel.class, "FilesSetRulePanel.messages.invalidPathRegex", ex.getLocalizedMessage()), + NotifyDescriptor.WARNING_MESSAGE); + DialogDisplayer.getDefault().notify(notifyDesc); + return false; + } + } else { + if (!FilesSetRulePanel.containsOnlyLegalChars(this.pathTextField.getText(), FilesSetRulePanel.ILLEGAL_FILE_PATH_CHARS)) { + NotifyDescriptor notifyDesc = new NotifyDescriptor.Message( + NbBundle.getMessage(FilesSetPanel.class, "FilesSetRulePanel.messages.invalidCharInPath"), + NotifyDescriptor.WARNING_MESSAGE); + DialogDisplayer.getDefault().notify(notifyDesc); + return false; + } + } + } + + return true; + } + + /** + * Gets the name of the files set rule created or edited. + * + * @return A name string. + */ + String getRuleName() { + return this.ruleNameTextField.getText(); + } + + /** + * Gets the name filter for the rule that was created or edited. Should only + * be called if isValidDefintion() returns true. + * + * @return A name filter. + * @throws IllegalStateException if the specified name filter is not valid. + */ + FilesSet.Rule.FileNameFilter getFileNameFilter() throws IllegalStateException { + FilesSet.Rule.FileNameFilter filter = null; + if (!this.nameTextField.getText().isEmpty()) { + if (this.nameRegexCheckbox.isSelected()) { + try { + Pattern pattern = Pattern.compile(this.nameTextField.getText()); + if (this.fullNameRadioButton.isSelected()) { + filter = new FilesSet.Rule.FullNameFilter(pattern); + } else { + filter = new FilesSet.Rule.ExtensionFilter(pattern); + } + } catch (PatternSyntaxException ex) { + logger.log(Level.SEVERE, "Attempt to get regex name filter that does not compile", ex); // NON-NLS + throw new IllegalStateException("The files set rule panel name filter is not in a valid state"); // NON-NLS + } + } else { + if (FilesSetRulePanel.containsOnlyLegalChars(this.nameTextField.getText(), FilesSetRulePanel.ILLEGAL_FILE_NAME_CHARS)) { + if (this.fullNameRadioButton.isSelected()) { + filter = new FilesSet.Rule.FullNameFilter(this.nameTextField.getText()); + } else { + filter = new FilesSet.Rule.ExtensionFilter(this.nameTextField.getText()); + } + } else { + logger.log(Level.SEVERE, "Attempt to get name filter with illegal chars"); // NON-NLS + throw new IllegalStateException("The files set rule panel name filter is not in a valid state"); // NON-NLS + } + } + } + return filter; + } + + /** + * Gets the file meta-type filter for the rule that was created or edited. + * + * @return A type filter. + */ + FilesSet.Rule.MetaTypeFilter getMetaTypeFilter() { + if (this.filesRadioButton.isSelected()) { + return new FilesSet.Rule.MetaTypeFilter(FilesSet.Rule.MetaTypeFilter.Type.FILES); + } else if (this.dirsRadioButton.isSelected()) { + return new FilesSet.Rule.MetaTypeFilter(FilesSet.Rule.MetaTypeFilter.Type.DIRECTORIES); + } else { + return new FilesSet.Rule.MetaTypeFilter(FilesSet.Rule.MetaTypeFilter.Type.FILES_AND_DIRECTORIES); + } + } + + /** + * Gets the optional path filter for the rule that was created or edited. + * Should only be called if isValidDefintion() returns true. + * + * @return A path filter or null if no path filter was specified. + * @throws IllegalStateException if the specified path filter is not valid. + */ + FilesSet.Rule.ParentPathFilter getPathFilter() throws IllegalStateException { + FilesSet.Rule.ParentPathFilter filter = null; + if (!this.pathTextField.getText().isEmpty()) { + if (this.pathRegexCheckBox.isSelected()) { + try { + filter = new FilesSet.Rule.ParentPathFilter(Pattern.compile(this.pathTextField.getText())); + } catch (PatternSyntaxException ex) { + logger.log(Level.SEVERE, "Attempt to get malformed path filter", ex); // NON-NLS + throw new IllegalStateException("The files set rule panel path filter is not in a valid state"); // NON-NLS + } + } else { + String path = this.pathTextField.getText(); + if (FilesSetRulePanel.containsOnlyLegalChars(path, FilesSetRulePanel.ILLEGAL_FILE_PATH_CHARS)) { + // Add a leading path separator if omitted. + if (!path.startsWith(FilesSetRulePanel.SLEUTHKIT_PATH_SEPARATOR)) { + path = FilesSetRulePanel.SLEUTHKIT_PATH_SEPARATOR + path; + } + // Add a trailing path separator if omitted. + if (!path.endsWith(FilesSetRulePanel.SLEUTHKIT_PATH_SEPARATOR)) { + path += FilesSetRulePanel.SLEUTHKIT_PATH_SEPARATOR; + } + filter = new FilesSet.Rule.ParentPathFilter(path); + } else { + logger.log(Level.SEVERE, "Attempt to get path filter with illegal chars"); // NON-NLS + throw new IllegalStateException("The files set rule panel path filter is not in a valid state"); // NON-NLS + } + } + } + return filter; + } + + /** + * Checks an input string for the use of illegal characters. + * + * @param toBeChecked The input string. + * @param illegalChars The characters deemed to be illegal. + * @return True if the string does not contain illegal characters, false + * otherwise. + */ + private static boolean containsOnlyLegalChars(String toBeChecked, List illegalChars) { + for (String illegalChar : illegalChars) { + if (toBeChecked.contains(illegalChar)) { + return false; + } + } + return true; + } + + /** + * Sets the state of the name filter UI components consistent with the state + * of the UI components in the type button group. + */ + private void setComponentsForSearchType() { + if (!this.filesRadioButton.isSelected()) { + this.fullNameRadioButton.setSelected(true); + this.extensionRadioButton.setEnabled(false); + } else { + this.extensionRadioButton.setEnabled(true); + } + } + + /** + * This method is called from within the constructor to initialize the form. + * 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() { + + nameButtonGroup = new javax.swing.ButtonGroup(); + typeButtonGroup = new javax.swing.ButtonGroup(); + ruleNameLabel = new javax.swing.JLabel(); + ruleNameTextField = new javax.swing.JTextField(); + namePanel = new javax.swing.JPanel(); + nameRegexCheckbox = new javax.swing.JCheckBox(); + extensionRadioButton = new javax.swing.JRadioButton(); + nameTextField = new javax.swing.JTextField(); + fullNameRadioButton = new javax.swing.JRadioButton(); + typePanel = new javax.swing.JPanel(); + filesRadioButton = new javax.swing.JRadioButton(); + dirsRadioButton = new javax.swing.JRadioButton(); + filesAndDirsRadioButton = new javax.swing.JRadioButton(); + pathPanel = new javax.swing.JPanel(); + pathRegexCheckBox = new javax.swing.JCheckBox(); + pathTextField = new javax.swing.JTextField(); + pathSeparatorInfoLabel = new javax.swing.JLabel(); + + org.openide.awt.Mnemonics.setLocalizedText(ruleNameLabel, org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.ruleNameLabel.text")); // NOI18N + + ruleNameTextField.setText(org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.ruleNameTextField.text")); // NOI18N + + namePanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.namePanel.border.title"))); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(nameRegexCheckbox, org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.nameRegexCheckbox.text")); // NOI18N + + nameButtonGroup.add(extensionRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(extensionRadioButton, org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.extensionRadioButton.text")); // NOI18N + + nameTextField.setText(org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.nameTextField.text")); // NOI18N + + nameButtonGroup.add(fullNameRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(fullNameRadioButton, org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.fullNameRadioButton.text")); // NOI18N + + javax.swing.GroupLayout namePanelLayout = new javax.swing.GroupLayout(namePanel); + namePanel.setLayout(namePanelLayout); + namePanelLayout.setHorizontalGroup( + namePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(namePanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(namePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(namePanelLayout.createSequentialGroup() + .addComponent(fullNameRadioButton) + .addGap(10, 10, 10) + .addComponent(extensionRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 114, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(nameRegexCheckbox) + .addGap(0, 0, Short.MAX_VALUE)) + .addComponent(nameTextField)) + .addContainerGap()) + ); + namePanelLayout.setVerticalGroup( + namePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(namePanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(namePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(fullNameRadioButton) + .addComponent(extensionRadioButton) + .addComponent(nameRegexCheckbox)) + .addContainerGap()) + ); + + typePanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.typePanel.border.title"))); // NOI18N + + typeButtonGroup.add(filesRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(filesRadioButton, org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.filesRadioButton.text")); // NOI18N + filesRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + filesRadioButtonActionPerformed(evt); + } + }); + + typeButtonGroup.add(dirsRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(dirsRadioButton, org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.dirsRadioButton.text")); // NOI18N + dirsRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + dirsRadioButtonActionPerformed(evt); + } + }); + + typeButtonGroup.add(filesAndDirsRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(filesAndDirsRadioButton, org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.filesAndDirsRadioButton.text")); // NOI18N + filesAndDirsRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + filesAndDirsRadioButtonActionPerformed(evt); + } + }); + + javax.swing.GroupLayout typePanelLayout = new javax.swing.GroupLayout(typePanel); + typePanel.setLayout(typePanelLayout); + typePanelLayout.setHorizontalGroup( + typePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(typePanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(filesRadioButton) + .addGap(18, 18, 18) + .addComponent(dirsRadioButton) + .addGap(18, 18, 18) + .addComponent(filesAndDirsRadioButton) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + typePanelLayout.setVerticalGroup( + typePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(typePanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(typePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(filesRadioButton) + .addComponent(dirsRadioButton) + .addComponent(filesAndDirsRadioButton)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + pathPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.pathPanel.border.title"))); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(pathRegexCheckBox, org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.pathRegexCheckBox.text")); // NOI18N + + pathTextField.setText(org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.pathTextField.text")); // NOI18N + + pathSeparatorInfoLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/info-icon-16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(pathSeparatorInfoLabel, org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.pathSeparatorInfoLabel.text")); // NOI18N + + javax.swing.GroupLayout pathPanelLayout = new javax.swing.GroupLayout(pathPanel); + pathPanel.setLayout(pathPanelLayout); + pathPanelLayout.setHorizontalGroup( + pathPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pathPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(pathPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addGroup(pathPanelLayout.createSequentialGroup() + .addComponent(pathRegexCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(pathSeparatorInfoLabel)) + .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 283, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + pathPanelLayout.setVerticalGroup( + pathPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(pathPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(pathPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(pathRegexCheckBox) + .addComponent(pathSeparatorInfoLabel)) + .addContainerGap()) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(typePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(namePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(ruleNameLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 59, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(ruleNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 256, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(pathPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(ruleNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(ruleNameLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGap(18, 18, 18) + .addComponent(typePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(namePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(pathPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + namePanel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.namePanel.AccessibleContext.accessibleName")); // NOI18N + }// //GEN-END:initComponents + + private void filesAndDirsRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_filesAndDirsRadioButtonActionPerformed + setComponentsForSearchType(); + }//GEN-LAST:event_filesAndDirsRadioButtonActionPerformed + + private void dirsRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dirsRadioButtonActionPerformed + setComponentsForSearchType(); + }//GEN-LAST:event_dirsRadioButtonActionPerformed + + private void filesRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_filesRadioButtonActionPerformed + setComponentsForSearchType(); + }//GEN-LAST:event_filesRadioButtonActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JRadioButton dirsRadioButton; + private javax.swing.JRadioButton extensionRadioButton; + private javax.swing.JRadioButton filesAndDirsRadioButton; + private javax.swing.JRadioButton filesRadioButton; + private javax.swing.JRadioButton fullNameRadioButton; + private javax.swing.ButtonGroup nameButtonGroup; + private javax.swing.JPanel namePanel; + private javax.swing.JCheckBox nameRegexCheckbox; + private javax.swing.JTextField nameTextField; + private javax.swing.JPanel pathPanel; + private javax.swing.JCheckBox pathRegexCheckBox; + private javax.swing.JLabel pathSeparatorInfoLabel; + private javax.swing.JTextField pathTextField; + private javax.swing.JLabel ruleNameLabel; + private javax.swing.JTextField ruleNameTextField; + private javax.swing.ButtonGroup typeButtonGroup; + private javax.swing.JPanel typePanel; + // End of variables declaration//GEN-END:variables +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsManager.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsManager.java new file mode 100755 index 0000000000..2f1c31caba --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsManager.java @@ -0,0 +1,577 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 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.modules.interestingitems; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Observable; +import java.util.logging.Level; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.coreutils.XMLUtil; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +/** + * Provides access to interesting item definitions persisted to disk. Clients + * receive copies of the most recent interesting item definitions via + * synchronized methods, allowing the definitions to be safely published to + * multiple threads. + */ +final class InterestingItemDefsManager extends Observable { + + private static final List ILLEGAL_FILE_NAME_CHARS = Collections.unmodifiableList(new ArrayList<>(Arrays.asList("\\", "/", ":", "*", "?", "\"", "<", ">"))); + private static final List ILLEGAL_FILE_PATH_CHARS = Collections.unmodifiableList(new ArrayList<>(Arrays.asList("\\", ":", "*", "?", "\"", "<", ">"))); + private static final String INTERESTING_FILES_SET_DEFS_FILE_NAME = "InterestingFilesSetDefs.xml"; //NON-NLS + private static final String DEFAULT_FILE_SET_DEFS_PATH = PlatformUtil.getUserConfigDirectory() + File.separator + INTERESTING_FILES_SET_DEFS_FILE_NAME; + private static InterestingItemDefsManager instance; + + /** + * Gets the interesting item definitions manager singleton. + */ + synchronized static InterestingItemDefsManager getInstance() { + if (instance == null) { + instance = new InterestingItemDefsManager(); + } + return instance; + } + + /** + * Gets the set of chars deemed to be illegal in file names (Windows). + * + * @return A list of characters. + */ + static List getIllegalFileNameChars() { + return InterestingItemDefsManager.ILLEGAL_FILE_NAME_CHARS; + } + + /** + * Gets the set of chars deemed to be illegal in file path (SleuthKit/Windows). + * + * @return A list of characters. + */ + static List getIllegalFilePathChars() { + return InterestingItemDefsManager.ILLEGAL_FILE_PATH_CHARS; + } + + /** + * Gets a copy of the current interesting files set definitions. + * + * @return A map of interesting files set names to interesting file sets, + * possibly empty. + */ + synchronized Map getInterestingFilesSets() { + return FilesSetXML.readDefinitionsFile(DEFAULT_FILE_SET_DEFS_PATH); + } + + /** + * Sets the current interesting file sets definitions, replacing any + * previous definitions. + * + * @param filesSets A mapping of interesting files set names to files sets, + * used to enforce unique files set names. + */ + synchronized void setInterestingFilesSets(Map filesSets) { + FilesSetXML.writeDefinitionsFile(DEFAULT_FILE_SET_DEFS_PATH, filesSets); + this.setChanged(); + this.notifyObservers(); + } + + /** + * Reads and writes interesting files set definitions to and from disk in + * XML format. + */ + private final static class FilesSetXML { + + private static final Logger logger = Logger.getLogger(FilesSetXML.class.getName()); + private static final String XML_ENCODING = "UTF-8"; //NON-NLS + private static final List illegalFileNameChars = InterestingItemDefsManager.getIllegalFileNameChars(); + + // The following tags and attributes are identical to those used in the + // TSK Framework interesting files set definitions file schema. + private static final String FILE_SETS_ROOT_TAG = "INTERESTING_FILE_SETS"; //NON-NLS + private static final String FILE_SET_TAG = "INTERESTING_FILE_SET"; //NON-NLS + private static final String NAME_RULE_TAG = "NAME"; //NON-NLS + private static final String EXTENSION_RULE_TAG = "EXTENSION"; //NON-NLS + private static final String NAME_ATTR = "name"; //NON-NLS + private static final String DESC_ATTR = "description"; //NON-NLS + private static final String IGNORE_KNOWN_FILES_ATTR = "ignoreKnown"; //NON-NLS + private static final String TYPE_FILTER_ATTR = "typeFilter"; //NON-NLS + private static final String PATH_FILTER_ATTR = "pathFilter"; //NON-NLS + private static final String TYPE_FILTER_VALUE_FILES = "file"; //NON-NLS + private static final String TYPE_FILTER_VALUE_DIRS = "dir"; //NON-NLS + + // The following tags and attributes are currently specific to the + // Autopsy implementation of interesting files set definitions. Autopsy + // definitions that use these will not be able to be used by TSK + // Framework. However, Autopsy can accept TSK Framework definitions: + // + // 1. Rules do not have names in the TSK Framework schema, but rules do + // have names in the Autopsy schema. Names will be synthesized as needed + // to allow Autopsy to use TSK Framework interesting files set + // definitions. + // 2. The TSK Framework has an interesting files module that supports + // simple globbing with "*" characters. Name rules and path filters with + // "*" characters will be converted to regexes to allow Autopsy to use + // TSK Framework interesting files set definitions. + // 3. Type filters are required by Autopsy, but not by TSK Frmaework. + // Missing type filters will defualt to "files" filters. + private static final String REGEX_ATTR = "regex"; //NON-NLS + private static final String PATH_REGEX_ATTR = "pathRegex"; //NON-NLS + private static final String TYPE_FILTER_VALUE_FILES_AND_DIRS = "files_and_dirs"; //NON-NLS + private static final String UNNAMED_LEGACY_RULE_PREFIX = "Unnamed Rule "; // NON-NLS + private static int unnamedLegacyRuleCounter; + + /** + * Reads interesting file set definitions from an XML file. + * + * @param filePath Path of the set definitions file as a string. + * @return The set definitions in a map of set names to sets. + */ + // Note: This method takes a file path to support the possibility of + // multiple intersting files set definition files, e.g., one for + // definitions that ship with Autopsy and one for user definitions. + static Map readDefinitionsFile(String filePath) { + Map filesSets = new HashMap<>(); + + // Check if the file exists. + File defsFile = new File(filePath); + if (!defsFile.exists()) { + return filesSets; + } + + // Check if the file can be read. + if (!defsFile.canRead()) { + logger.log(Level.SEVERE, "Interesting file sets definition file at {0} exists, but cannot be read", filePath); // NON-NLS + return filesSets; + } + + // Parse the XML in the file. + Document doc = XMLUtil.loadDoc(FilesSetXML.class, filePath); + if (doc == null) { + logger.log(Level.SEVERE, "Failed to parse interesting file sets definition file at {0}", filePath); // NON-NLS + return filesSets; + } + + // Get the root element. + Element root = doc.getDocumentElement(); + if (root == null) { + logger.log(Level.SEVERE, "Failed to get root {0} element tag of interesting file sets definition file at {1}", new Object[]{FilesSetXML.FILE_SETS_ROOT_TAG, filePath}); // NON-NLS + return filesSets; + } + + // Read in the files set definitions. + NodeList setElems = root.getElementsByTagName(FILE_SET_TAG); + for (int i = 0; i < setElems.getLength(); ++i) { + readFilesSet((Element) setElems.item(i), filesSets, filePath); + } + + return filesSets; + } + + /** + * Reads in an interesting files set. + * + * @param setElem An interesting files set XML element + * @param filesSets A collection to which the set is to be added. + * @param filePath The source file, used for error reporting. + */ + private static void readFilesSet(Element setElem, Map filesSets, String filePath) { + // The file set must have a unique name. + String setName = setElem.getAttribute(FilesSetXML.NAME_ATTR); + if (setName.isEmpty()) { + logger.log(Level.SEVERE, "Found {0} element without required {1} attribute, ignoring malformed file set definition in interesting file sets definition file at {2}", new Object[]{FilesSetXML.FILE_SET_TAG, FilesSetXML.NAME_ATTR, filePath}); // NON-NLS + return; + } + if (filesSets.containsKey(setName)) { + logger.log(Level.SEVERE, "Found duplicate definition of set named {0} in interesting file sets definition file at {1}, discarding duplicate set", new Object[]{setName, filePath}); // NON-NLS + return; + } + + // The file set may have a description. The empty string is o.k. + String description = setElem.getAttribute(FilesSetXML.DESC_ATTR); + + // The file set may or may not ignore known files. The default behavior + // is to not ignore them. + String ignoreKnown = setElem.getAttribute(FilesSetXML.IGNORE_KNOWN_FILES_ATTR); + boolean ignoreKnownFiles = false; + if (!ignoreKnown.isEmpty()) { + ignoreKnownFiles = Boolean.parseBoolean(ignoreKnown); + } + + // Read file name set membership rules, if any. + FilesSetXML.unnamedLegacyRuleCounter = 1; + Map rules = new HashMap<>(); + NodeList nameRuleElems = setElem.getElementsByTagName(FilesSetXML.NAME_RULE_TAG); + for (int j = 0; j < nameRuleElems.getLength(); ++j) { + Element elem = (Element) nameRuleElems.item(j); + FilesSet.Rule rule = FilesSetXML.readFileNameRule(elem); + if (rule != null) { + if (!rules.containsKey(rule.getName())) { + rules.put(rule.getName(), rule); + } else { + logger.log(Level.SEVERE, "Found duplicate rule {0} for set named {1} in interesting file sets definition file at {2}, discarding malformed set", new Object[]{rule.getName(), setName, filePath}); // NON-NLS + return; + } + } else { + logger.log(Level.SEVERE, "Found malformed rule for set named {0} in interesting file sets definition file at {1}, discarding malformed set", new Object[]{setName, filePath}); // NON-NLS + return; + } + } + + // Read file extension set membership rules, if any. + NodeList extRuleElems = setElem.getElementsByTagName(FilesSetXML.EXTENSION_RULE_TAG); + for (int j = 0; j < extRuleElems.getLength(); ++j) { + Element elem = (Element) extRuleElems.item(j); + FilesSet.Rule rule = FilesSetXML.readFileExtensionRule(elem); + if (rule != null) { + if (!rules.containsKey(rule.getName())) { + rules.put(rule.getName(), rule); + } else { + logger.log(Level.SEVERE, "Found duplicate rule {0} for set named {1} in interesting file sets definition file at {2}, discarding malformed set", new Object[]{rule.getName(), setName, filePath}); + return; + } + } else { + logger.log(Level.SEVERE, "Found malformed rule for set named {0} in interesting file sets definition file at {1}, discarding malformed set", new Object[]{setName, filePath}); + return; + } + } + + // Make the files set. Note that degenerate sets with no rules are + // allowed to facilitate the separation of set definition and rule + // definitions. A set without rules is simply the empty set. + FilesSet set = new FilesSet(setName, description, ignoreKnownFiles, rules); + filesSets.put(set.getName(), set); + } + + /** + * Construct an interesting files set file name rule from the data in an + * XML element. + * + * @param filePath The path of the definitions file. + * @param setName The name of the files set. + * @param elem The file name rule XML element. + * @return A file name rule, or null if there is an error (the error is + * logged). + */ + private static FilesSet.Rule readFileNameRule(Element elem) { + String ruleName = FilesSetXML.readRuleName(elem); + + // The content of the rule tag is a file name filter. It may be a + // regex, or it may be from a TSK Framework rule definition with a + // "*" globbing char, or it may be simple text. + String content = elem.getTextContent(); + FilesSet.Rule.FullNameFilter nameFilter; + String regex = elem.getAttribute(FilesSetXML.REGEX_ATTR); + if ((!regex.isEmpty() && regex.equalsIgnoreCase("true")) || content.contains("*")) { // NON_NLS + Pattern pattern = compileRegex(content); + if (pattern != null) { + nameFilter = new FilesSet.Rule.FullNameFilter(pattern); + } else { + logger.log(Level.SEVERE, "Error compiling " + FilesSetXML.NAME_RULE_TAG + " regex, ignoring malformed '{0}' rule definition", ruleName); // NON-NLS + return null; + } + } else { + for (String illegalChar : illegalFileNameChars) { + if (content.contains(illegalChar)) { + logger.log(Level.SEVERE, FilesSetXML.NAME_RULE_TAG + " content has illegal chars, ignoring malformed '{0}' rule definition", new Object[]{FilesSetXML.NAME_RULE_TAG, ruleName}); // NON-NLS + return null; + } + } + nameFilter = new FilesSet.Rule.FullNameFilter(content); + } + + // Read in the type filter. + FilesSet.Rule.MetaTypeFilter metaTypeFilter = FilesSetXML.readMetaTypeFilter(elem); + if (metaTypeFilter == null) { + // Malformed attribute. + return null; + } + + // Read in the optional path filter. Null is o.k., but if the attribute + // is there, be sure it is not malformed. + FilesSet.Rule.ParentPathFilter pathFilter = null; + if (!elem.getAttribute(FilesSetXML.PATH_FILTER_ATTR).isEmpty() + || !elem.getAttribute(FilesSetXML.PATH_REGEX_ATTR).isEmpty()) { + pathFilter = FilesSetXML.readPathFilter(elem); + if (pathFilter == null) { + // Malformed attribute. + return null; + } + } + + return new FilesSet.Rule(ruleName, nameFilter, metaTypeFilter, pathFilter); + } + + /** + * Construct an interesting files set file name extension rule from the + * data in an XML element. + * + * @param elem The file name extension rule XML element. + * @return A file name extension rule, or null if there is an error (the + * error is logged). + */ + private static FilesSet.Rule readFileExtensionRule(Element elem) { + String ruleName = FilesSetXML.readRuleName(elem); + + // The content of the rule tag is a file name extension filter. It may + // be a regex, or it may be from a TSK Framework rule definition + // with a "*" globbing char. + String content = elem.getTextContent(); + FilesSet.Rule.ExtensionFilter extFilter; + String regex = elem.getAttribute(FilesSetXML.REGEX_ATTR); + if ((!regex.isEmpty() && regex.equalsIgnoreCase("true")) || content.contains("*")) { // NON_NLS + Pattern pattern = compileRegex(content); + if (pattern != null) { + extFilter = new FilesSet.Rule.ExtensionFilter(pattern); + } else { + logger.log(Level.SEVERE, "Error compiling " + FilesSetXML.EXTENSION_RULE_TAG + " regex, ignoring malformed {0} rule definition", ruleName); // NON-NLS + return null; + } + } else { + for (String illegalChar : illegalFileNameChars) { + if (content.contains(illegalChar)) { + logger.log(Level.SEVERE, "{0} content has illegal chars, ignoring malformed {1} rule definition", ruleName); // NON-NLS + return null; + } + } + extFilter = new FilesSet.Rule.ExtensionFilter(content); + } + + // The rule must have a meta-type filter, unless a TSK Framework + // definitions file is being read. + FilesSet.Rule.MetaTypeFilter metaTypeFilter = null; + if (!elem.getAttribute(FilesSetXML.TYPE_FILTER_ATTR).isEmpty()) { + metaTypeFilter = FilesSetXML.readMetaTypeFilter(elem); + if (metaTypeFilter == null) { + // Malformed attribute. + return null; + } + } else { + metaTypeFilter = new FilesSet.Rule.MetaTypeFilter(FilesSet.Rule.MetaTypeFilter.Type.FILES); + } + + // The rule may have a path filter. Null is o.k., but if the attribute + // is there, it must not be malformed. + FilesSet.Rule.ParentPathFilter pathFilter = null; + if (!elem.getAttribute(FilesSetXML.PATH_FILTER_ATTR).isEmpty() + || !elem.getAttribute(FilesSetXML.PATH_REGEX_ATTR).isEmpty()) { + pathFilter = FilesSetXML.readPathFilter(elem); + if (pathFilter == null) { + // Malformed attribute. + return null; + } + } + + return new FilesSet.Rule(ruleName, extFilter, metaTypeFilter, pathFilter); + } + + /** + * Read a rule name attribute from a rule element. + * + * @param elem A rule element. + * @return A rule name. + */ + private static String readRuleName(Element elem) { + // The rule must have a name. + String ruleName = elem.getAttribute(FilesSetXML.NAME_ATTR); + if (ruleName.isEmpty()) { + // Assume a TSK Framework definitions file is being read and + // synthesize a rule name. + ruleName = UNNAMED_LEGACY_RULE_PREFIX + Integer.toString(FilesSetXML.unnamedLegacyRuleCounter++); + } + return ruleName; + } + + /** + * Attempts to compile a regular expression. + * + * @param regex The regular expression. + * @return A pattern object, or null if the compilation fails. + */ + private static Pattern compileRegex(String regex) { + try { + return Pattern.compile(regex); + } catch (PatternSyntaxException ex) { + logger.log(Level.SEVERE, "Error compiling rule regex: " + ex.getMessage(), ex); // NON-NLS + return null; + } + } + + /** + * Construct a meta-type filter for an interesting files set membership + * rule from data in an XML element. + * + * @param ruleElement The XML element. + * @return The meta-type filter, or null if there is an error (logged). + */ + private static FilesSet.Rule.MetaTypeFilter readMetaTypeFilter(Element ruleElement) { + FilesSet.Rule.MetaTypeFilter filter = null; + String filterAttribute = ruleElement.getAttribute(FilesSetXML.TYPE_FILTER_ATTR); + if (!filterAttribute.isEmpty()) { + switch (filterAttribute) { + case FilesSetXML.TYPE_FILTER_VALUE_FILES: + filter = new FilesSet.Rule.MetaTypeFilter(FilesSet.Rule.MetaTypeFilter.Type.FILES); + break; + case FilesSetXML.TYPE_FILTER_VALUE_DIRS: + filter = new FilesSet.Rule.MetaTypeFilter(FilesSet.Rule.MetaTypeFilter.Type.DIRECTORIES); + break; + case FilesSetXML.TYPE_FILTER_VALUE_FILES_AND_DIRS: + filter = new FilesSet.Rule.MetaTypeFilter(FilesSet.Rule.MetaTypeFilter.Type.FILES_AND_DIRECTORIES); + break; + default: + logger.log(Level.SEVERE, "Found {0} " + FilesSetXML.TYPE_FILTER_ATTR + " attribute with unrecognized value ''{0}'', ignoring malformed rule definition", filterAttribute); // NON-NLS + break; + } + } else { + // Accept TSK Framework interesting files set definitions, + // default to files. + filter = new FilesSet.Rule.MetaTypeFilter(FilesSet.Rule.MetaTypeFilter.Type.FILES); + } + return filter; + } + + /** + * Construct a path filter for an interesting files set membership rule + * from data in an XML element. + * + * @param ruleElement The XML element. + * @return The path filter, or null if there is an error (logged). + */ + private static FilesSet.Rule.ParentPathFilter readPathFilter(Element ruleElement) { + FilesSet.Rule.ParentPathFilter filter = null; + String path = ruleElement.getAttribute(FilesSetXML.PATH_FILTER_ATTR); + String pathRegex = ruleElement.getAttribute(FilesSetXML.PATH_REGEX_ATTR); + if (!pathRegex.isEmpty() && path.isEmpty()) { + try { + Pattern pattern = Pattern.compile(pathRegex); + filter = new FilesSet.Rule.ParentPathFilter(pattern); + } catch (PatternSyntaxException ex) { + logger.log(Level.SEVERE, "Error compiling " + FilesSetXML.PATH_REGEX_ATTR + " regex, ignoring malformed path filter definition", ex); // NON-NLS + } + } else if (!path.isEmpty() && pathRegex.isEmpty()) { + filter = new FilesSet.Rule.ParentPathFilter(path); + } + return filter; + } + + /** + * Writes interesting files set definitions to disk as an XML file, + * logging any errors. + * + * @param filePath Path of the set definitions file as a string. + * @returns True if the definitions are written to disk, false + * otherwise. + */ + // Note: This method takes a file path to support the possibility of + // multiple intersting files set definition files, e.g., one for + // definitions that ship with Autopsy and one for user definitions. + static boolean writeDefinitionsFile(String filePath, Map interestingFilesSets) { + DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); + try { + // Create the new XML document. + DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); + Document doc = docBuilder.newDocument(); + Element rootElement = doc.createElement(FilesSetXML.FILE_SETS_ROOT_TAG); + doc.appendChild(rootElement); + + // Add the interesting files sets to the document. + for (FilesSet set : interestingFilesSets.values()) { + // Add the files set element and its attributes. + Element setElement = doc.createElement(FilesSetXML.FILE_SET_TAG); + setElement.setAttribute(FilesSetXML.NAME_ATTR, set.getName()); + setElement.setAttribute(FilesSetXML.DESC_ATTR, set.getDescription()); + setElement.setAttribute(FilesSetXML.IGNORE_KNOWN_FILES_ATTR, Boolean.toString(set.ignoresKnownFiles())); + + // Add the child elements for the set membership rules. + for (FilesSet.Rule rule : set.getRules().values()) { + // Add a rule element with the appropriate name filter + // type tag. + FilesSet.Rule.FileNameFilter nameFilter = rule.getFileNameFilter(); + Element ruleElement; + if (nameFilter instanceof FilesSet.Rule.FullNameFilter) { + ruleElement = doc.createElement(FilesSetXML.NAME_RULE_TAG); + } else { + ruleElement = doc.createElement(FilesSetXML.EXTENSION_RULE_TAG); + } + + // Add the rule name attribute. + ruleElement.setAttribute(FilesSetXML.NAME_ATTR, rule.getName()); + + // Add the name filter regex attribute + ruleElement.setAttribute(FilesSetXML.REGEX_ATTR, Boolean.toString(nameFilter.isRegex())); + + // Add the type filter attribute. + FilesSet.Rule.MetaTypeFilter typeFilter = rule.getMetaTypeFilter(); + switch (typeFilter.getMetaType()) { + case FILES: + ruleElement.setAttribute(FilesSetXML.TYPE_FILTER_ATTR, FilesSetXML.TYPE_FILTER_VALUE_FILES); + break; + case DIRECTORIES: + ruleElement.setAttribute(FilesSetXML.TYPE_FILTER_ATTR, FilesSetXML.TYPE_FILTER_VALUE_DIRS); + break; + default: + ruleElement.setAttribute(FilesSetXML.TYPE_FILTER_ATTR, FilesSetXML.TYPE_FILTER_VALUE_FILES_AND_DIRS); + break; + } + + // Add the optional path filter. + FilesSet.Rule.ParentPathFilter pathFilter = rule.getPathFilter(); + if (pathFilter != null) { + if (pathFilter.isRegex()) { + ruleElement.setAttribute(FilesSetXML.PATH_REGEX_ATTR, pathFilter.getTextToMatch()); + } else { + ruleElement.setAttribute(FilesSetXML.PATH_FILTER_ATTR, pathFilter.getTextToMatch()); + } + } + + // Add the name filter text as the rule element content. + ruleElement.setTextContent(nameFilter.getTextToMatch()); + + setElement.appendChild(ruleElement); + } + + rootElement.appendChild(setElement); + } + + // Overwrite the previous definitions file. Note that the utility + // method logs an error on failure. + return XMLUtil.saveDoc(FilesSetXML.class, filePath, XML_ENCODING, doc); + + } catch (ParserConfigurationException ex) { + logger.log(Level.SEVERE, "Error writing interesting files definition file to " + filePath, ex); // NON-NLS + return false; + } + + } + + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsOptionsPanelController.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsOptionsPanelController.java new file mode 100755 index 0000000000..c95bffb053 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsOptionsPanelController.java @@ -0,0 +1,110 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 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.modules.interestingitems; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import javax.swing.JComponent; +import javax.swing.SwingUtilities; +import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.HelpCtx; +import org.openide.util.Lookup; + +@OptionsPanelController.TopLevelRegistration( + categoryName = "#OptionsCategory_Name_InterestingItemDefinitions", + iconBase = "org/sleuthkit/autopsy/images/interesting_item_32x32.png", + keywords = "#OptionsCategory_Keywords_InterestingItemDefinitions", + keywordsCategory = "InterestingItemDefinitions", + position = 5 +) +@org.openide.util.NbBundle.Messages({"OptionsCategory_Name_InterestingItemDefinitions=Interesting Item Definitions", "OptionsCategory_Keywords_InterestingItemDefinitions=InterestingItemDefinitions"}) +public final class InterestingItemDefsOptionsPanelController extends OptionsPanelController { + + private InterestingItemDefsPanel panel; + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); + private boolean changed; + + @Override + public void update() { + getPanel().load(); + changed = false; + } + + @Override + public void applyChanges() { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + getPanel().store(); + changed = false; + } + }); + } + + @Override + public void cancel() { + // need not do anything special, if no changes have been persisted yet + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public boolean isChanged() { + return changed; + } + + @Override + public HelpCtx getHelpCtx() { + return null; + } + + @Override + public JComponent getComponent(Lookup masterLookup) { + return getPanel(); + } + + @Override + public void addPropertyChangeListener(PropertyChangeListener l) { + pcs.addPropertyChangeListener(l); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener l) { + pcs.removePropertyChangeListener(l); + } + + private InterestingItemDefsPanel getPanel() { + if (panel == null) { + panel = new InterestingItemDefsPanel(); + } + return panel; + } + + void changed() { + if (!changed) { + changed = true; + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true); + } + pcs.firePropertyChange(OptionsPanelController.PROP_VALID, null, null); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.form new file mode 100755 index 0000000000..72ecf6835f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.form @@ -0,0 +1,558 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.java new file mode 100755 index 0000000000..82121c7ed8 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemDefsPanel.java @@ -0,0 +1,793 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 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.modules.interestingitems; + +import java.awt.EventQueue; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; +import javax.swing.DefaultListModel; +import javax.swing.JOptionPane; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.corecomponents.OptionsPanel; +import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel; + +/** + * A panel that allows a user to make interesting item definitions. + */ +final class InterestingItemDefsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { + + private final DefaultListModel setsListModel = new DefaultListModel<>(); + private final DefaultListModel rulesListModel = new DefaultListModel<>(); + + // 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 + // files sets in sorted order by name. + private TreeMap filesSets; + + /** + * Constructs an interesting item definitions panel. + */ + InterestingItemDefsPanel() { + this.initComponents(); + this.setsList.setModel(setsListModel); + this.setsList.addListSelectionListener(new InterestingItemDefsPanel.SetsListSelectionListener()); + this.rulesList.setModel(rulesListModel); + this.rulesList.addListSelectionListener(new InterestingItemDefsPanel.RulesListSelectionListener()); + } + + /** + * @inheritDoc + */ + @Override + public void saveSettings() { + InterestingItemDefsManager.getInstance().setInterestingFilesSets(this.filesSets); + } + + /** + * @inheritDoc + */ + @Override + public void store() { + this.saveSettings(); + } + + /** + * @inheritDoc + */ + @Override + public void load() { + this.resetComponents(); + + // Get a working copy of the interesting files set definitions and sort + // by set name. + this.filesSets = new TreeMap<>(InterestingItemDefsManager.getInstance().getInterestingFilesSets()); + + // 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 + // listeners will then populate the other components. + EventQueue.invokeLater(() -> { + InterestingItemDefsPanel.this.setsList.setSelectedIndex(0); + }); + } + } + + /** + * Clears the list models and resets all of the components. + */ + private void resetComponents() { + this.resetRuleComponents(); + this.setsListModel.clear(); + this.setNameTextField.setText(""); + this.setDescriptionTextArea.setText(""); + this.ignoreKnownFilesCheckbox.setSelected(true); + this.newSetButton.setEnabled(true); + this.editSetButton.setEnabled(false); + this.deleteSetButton.setEnabled(false); + } + + /** + * Clears the rules list model and resets all of the rule-related + * components. + */ + private void resetRuleComponents() { + this.ruleNameTextField.setText(""); + this.fileNameTextField.setText(""); + this.fileNameRadioButton.setSelected(true); + this.fileNameRegexCheckbox.setSelected(false); + this.filesRadioButton.setSelected(true); + this.rulePathFilterTextField.setText(""); + this.rulePathFilterRegexCheckBox.setSelected(false); + this.newRuleButton.setEnabled(!this.setsListModel.isEmpty()); + this.editRuleButton.setEnabled(false); + this.deleteRuleButton.setEnabled(false); + } + + /** + * A list events listener for the interesting files sets list component. + */ + private final class SetsListSelectionListener implements ListSelectionListener { + + @Override + public void valueChanged(ListSelectionEvent e) { + if (e.getValueIsAdjusting()) { + return; + } + + InterestingItemDefsPanel.this.rulesListModel.clear(); + InterestingItemDefsPanel.this.resetRuleComponents(); + + // Get the selected interesting files set and populate the set + // components. + FilesSet selectedSet = InterestingItemDefsPanel.this.setsList.getSelectedValue(); + if (selectedSet != null) { + // Populate the components that display the properties of the + // selected files set. + InterestingItemDefsPanel.this.setNameTextField.setText(selectedSet.getName()); + InterestingItemDefsPanel.this.setDescriptionTextArea.setText(selectedSet.getDescription()); + InterestingItemDefsPanel.this.ignoreKnownFilesCheckbox.setSelected(selectedSet.ignoresKnownFiles()); + + // Enable the new, edit and delete set buttons. + InterestingItemDefsPanel.this.newSetButton.setEnabled(true); + InterestingItemDefsPanel.this.editSetButton.setEnabled(true); + InterestingItemDefsPanel.this.deleteSetButton.setEnabled(true); + + // Populate the rule definitions list, sorted by name. + TreeMap rules = new TreeMap<>(selectedSet.getRules()); + for (FilesSet.Rule rule : rules.values()) { + InterestingItemDefsPanel.this.rulesListModel.addElement(rule); + } + + // Select the first rule by default. + if (!InterestingItemDefsPanel.this.rulesListModel.isEmpty()) { + InterestingItemDefsPanel.this.rulesList.setSelectedIndex(0); + } + } + } + + } + + /** + * A list events listener for the interesting files set rules list + * component. + */ + private final class RulesListSelectionListener implements ListSelectionListener { + + @Override + public void valueChanged(ListSelectionEvent e) { + if (e.getValueIsAdjusting()) { + return; + } + + // Get the selected rule and populate the rule components. + FilesSet.Rule rule = InterestingItemDefsPanel.this.rulesList.getSelectedValue(); + if (rule != null) { + // Get the filters that make up the rule. + FilesSet.Rule.FileNameFilter nameFilter = rule.getFileNameFilter(); + FilesSet.Rule.MetaTypeFilter typeFilter = rule.getMetaTypeFilter(); + FilesSet.Rule.ParentPathFilter pathFilter = rule.getPathFilter(); + + // Populate the components that display the properties of the + // selected rule. + InterestingItemDefsPanel.this.ruleNameTextField.setText(rule.getName()); + InterestingItemDefsPanel.this.fileNameTextField.setText(nameFilter.getTextToMatch()); + InterestingItemDefsPanel.this.fileNameRadioButton.setSelected(nameFilter instanceof FilesSet.Rule.FullNameFilter); + InterestingItemDefsPanel.this.fileNameExtensionRadioButton.setSelected(nameFilter instanceof FilesSet.Rule.ExtensionFilter); + InterestingItemDefsPanel.this.fileNameRegexCheckbox.setSelected(nameFilter.isRegex()); + switch (typeFilter.getMetaType()) { + case FILES: + InterestingItemDefsPanel.this.filesRadioButton.setSelected(true); + break; + case DIRECTORIES: + InterestingItemDefsPanel.this.dirsRadioButton.setSelected(true); + break; + case FILES_AND_DIRECTORIES: + InterestingItemDefsPanel.this.bothRadioButton.setSelected(true); + break; + } + if (pathFilter != null) { + InterestingItemDefsPanel.this.rulePathFilterTextField.setText(pathFilter.getTextToMatch()); + InterestingItemDefsPanel.this.rulePathFilterRegexCheckBox.setSelected(pathFilter.isRegex()); + } else { + InterestingItemDefsPanel.this.rulePathFilterTextField.setText(""); + InterestingItemDefsPanel.this.rulePathFilterRegexCheckBox.setSelected(false); + } + + // Enable the new, edit and delete rule buttons. + InterestingItemDefsPanel.this.newRuleButton.setEnabled(true); + InterestingItemDefsPanel.this.editRuleButton.setEnabled(true); + InterestingItemDefsPanel.this.deleteRuleButton.setEnabled(true); + } else { + InterestingItemDefsPanel.this.resetRuleComponents(); + } + } + + } + + /** + * Display an interesting files set definition panel in a dialog box and + * respond to user interactions with the dialog. + * + * @param selectedSet The currently selected files set, may be null to + * indicate a new interesting files set definition is to be created. + */ + private void doFileSetsDialog(FilesSet selectedSet) { + // Create a files set defintion panle. + FilesSetPanel panel; + if (selectedSet != null) { + // Editing an existing set definition. + panel = new FilesSetPanel(selectedSet); + } else { + // Creating a new set definition. + panel = new FilesSetPanel(); + } + + // 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; + do { + option = JOptionPane.showConfirmDialog(null, panel, NbBundle.getMessage(FilesSetPanel.class, "FilesSetPanel.title"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); + } while (option == JOptionPane.OK_OPTION && !panel.isValidDefinition()); + + if (option == JOptionPane.OK_OPTION) { + 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. + // Preserve the existing rules from the set being edited. + rules.putAll(selectedSet.getRules()); + } + this.replaceFilesSet(selectedSet, panel.getFilesSetName(), panel.getFilesSetDescription(), panel.getFileSetIgnoresKnownFiles(), rules); + } + } + + /** + * Display an interesting files set membership rule definition panel in a + * dialog box and respond to user interactions with the dialog. + * + * @param selectedRule The currently selected rule, may be null to indicate + * a new rule definition is to be created. + */ + private void doFilesSetRuleDialog(FilesSet.Rule selectedRule) { + // Create a files set rule panel. + FilesSetRulePanel panel; + if (selectedRule != null) { + // Editing an existing rule definition. + panel = new FilesSetRulePanel(selectedRule); + } else { + // Creating a new rule definition. + panel = new FilesSetRulePanel(); + } + + // 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; + do { + option = JOptionPane.showConfirmDialog(null, panel, NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.title"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); + } while (option == JOptionPane.OK_OPTION && !panel.isValidRuleDefinition()); + + if (option == JOptionPane.OK_OPTION) { + // Interesting file sets are immutable for thread safety, + // 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 + // definition. + if (selectedRule != null) { + rules.remove(selectedRule.getName()); + } + FilesSet.Rule newRule = new FilesSet.Rule(panel.getRuleName(), panel.getFileNameFilter(), panel.getMetaTypeFilter(), panel.getPathFilter()); + rules.put(newRule.getName(), newRule); + + // 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); + + // 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 + * owned by this panel. If there is a definition with the same name, it will + * be replaced, so this is an add/edit operation. + * + * @param oldSet A set to replace, null if the new set is not a replacement. + * @param name The name of the files set. + * @param description The description of the files set. + * @param ignoresKnownFiles Whether or not the files set ignores known + * files. + * @param rules The set membership rules for the set. + */ + void replaceFilesSet(FilesSet oldSet, String name, String description, boolean ignoresKnownFiles, Map rules) { + if (oldSet != null) { + // Remove the set to be replaced from the working copy if the files + // set definitions. + this.filesSets.remove(oldSet.getName()); + } + + // Make the new/edited set definition and add it to the working copy of + // the files set definitions. + FilesSet newSet = new FilesSet(name, description, ignoresKnownFiles, rules); + this.filesSets.put(newSet.getName(), newSet); + + // Redo the list model for the files set list component, which will make + // everything stays sorted as in the working copy tree set. + InterestingItemDefsPanel.this.setsListModel.clear(); + for (FilesSet set : this.filesSets.values()) { + 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 + // subordinate components. + this.setsList.setSelectedValue(newSet, true); + } + + /** + * This method is called from within the constructor to initialize the form. + * 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() { + + fileNameButtonGroup = new javax.swing.ButtonGroup(); + setsListLabel = new javax.swing.JLabel(); + rulesListLabel = new javax.swing.JLabel(); + setsListScrollPane = new javax.swing.JScrollPane(); + setsList = new javax.swing.JList(); + rulesListScrollPane = new javax.swing.JScrollPane(); + rulesList = new javax.swing.JList(); + selectedSetLabel = new javax.swing.JLabel(); + ignoreKnownFilesCheckbox = new javax.swing.JCheckBox(); + selectedRuleLabel = new javax.swing.JLabel(); + newSetButton = new javax.swing.JButton(); + editSetButton = new javax.swing.JButton(); + deleteSetButton = new javax.swing.JButton(); + newRuleButton = new javax.swing.JButton(); + editRuleButton = new javax.swing.JButton(); + deleteRuleButton = new javax.swing.JButton(); + separator = new javax.swing.JSeparator(); + setNameTextField = new javax.swing.JTextField(); + ruleNameTextField = new javax.swing.JTextField(); + fileNamePanel = new javax.swing.JPanel(); + fileNameRegexCheckbox = new javax.swing.JCheckBox(); + fileNameExtensionRadioButton = new javax.swing.JRadioButton(); + fileNameTextField = new javax.swing.JTextField(); + fileNameRadioButton = new javax.swing.JRadioButton(); + rulePathPanel = new javax.swing.JPanel(); + rulePathFilterRegexCheckBox = new javax.swing.JCheckBox(); + rulePathFilterTextField = new javax.swing.JTextField(); + setDescPanel = new javax.swing.JPanel(); + setDescScrollPanel = new javax.swing.JScrollPane(); + setDescriptionTextArea = new javax.swing.JTextArea(); + typePanel = new javax.swing.JPanel(); + filesRadioButton = new javax.swing.JRadioButton(); + dirsRadioButton = new javax.swing.JRadioButton(); + bothRadioButton = new javax.swing.JRadioButton(); + + org.openide.awt.Mnemonics.setLocalizedText(setsListLabel, org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.setsListLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(rulesListLabel, org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.rulesListLabel.text")); // NOI18N + + setsList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + setsListScrollPane.setViewportView(setsList); + + rulesList.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION); + rulesListScrollPane.setViewportView(rulesList); + + org.openide.awt.Mnemonics.setLocalizedText(selectedSetLabel, org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.selectedSetLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(ignoreKnownFilesCheckbox, org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.ignoreKnownFilesCheckbox.text")); // NOI18N + ignoreKnownFilesCheckbox.setEnabled(false); + + org.openide.awt.Mnemonics.setLocalizedText(selectedRuleLabel, org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.selectedRuleLabel.text")); // NOI18N + + newSetButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/add16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(newSetButton, org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.newSetButton.text")); // NOI18N + newSetButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + newSetButtonActionPerformed(evt); + } + }); + + editSetButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/edit16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(editSetButton, org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.editSetButton.text")); // NOI18N + editSetButton.setEnabled(false); + editSetButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + editSetButtonActionPerformed(evt); + } + }); + + deleteSetButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/delete16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(deleteSetButton, org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.deleteSetButton.text")); // NOI18N + deleteSetButton.setEnabled(false); + deleteSetButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + deleteSetButtonActionPerformed(evt); + } + }); + + newRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/add16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(newRuleButton, org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.newRuleButton.text")); // NOI18N + newRuleButton.setEnabled(false); + newRuleButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + newRuleButtonActionPerformed(evt); + } + }); + + editRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/edit16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(editRuleButton, org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.editRuleButton.text")); // NOI18N + editRuleButton.setEnabled(false); + editRuleButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + editRuleButtonActionPerformed(evt); + } + }); + + deleteRuleButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/images/delete16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(deleteRuleButton, org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.deleteRuleButton.text")); // NOI18N + deleteRuleButton.setEnabled(false); + deleteRuleButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + deleteRuleButtonActionPerformed(evt); + } + }); + + separator.setOrientation(javax.swing.SwingConstants.VERTICAL); + + setNameTextField.setEditable(false); + + ruleNameTextField.setEditable(false); + ruleNameTextField.setText(org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.ruleNameTextField.text")); // NOI18N + + fileNamePanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.fileNamePanel.border.title"))); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(fileNameRegexCheckbox, org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.fileNameRegexCheckbox.text")); // NOI18N + fileNameRegexCheckbox.setEnabled(false); + + fileNameButtonGroup.add(fileNameExtensionRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(fileNameExtensionRadioButton, org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.fileNameExtensionRadioButton.text")); // NOI18N + fileNameExtensionRadioButton.setEnabled(false); + + fileNameTextField.setEditable(false); + fileNameTextField.setText(org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.fileNameTextField.text")); // NOI18N + + fileNameButtonGroup.add(fileNameRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(fileNameRadioButton, org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.fileNameRadioButton.text")); // NOI18N + fileNameRadioButton.setEnabled(false); + + javax.swing.GroupLayout fileNamePanelLayout = new javax.swing.GroupLayout(fileNamePanel); + fileNamePanel.setLayout(fileNamePanelLayout); + fileNamePanelLayout.setHorizontalGroup( + fileNamePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(fileNamePanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(fileNamePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(fileNameTextField) + .addGroup(fileNamePanelLayout.createSequentialGroup() + .addComponent(fileNameRadioButton) + .addGap(10, 10, 10) + .addComponent(fileNameExtensionRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 114, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(18, 18, 18) + .addComponent(fileNameRegexCheckbox))) + .addContainerGap()) + ); + fileNamePanelLayout.setVerticalGroup( + fileNamePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(fileNamePanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(fileNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(fileNamePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(fileNameRadioButton) + .addComponent(fileNameExtensionRadioButton) + .addComponent(fileNameRegexCheckbox)) + .addContainerGap()) + ); + + rulePathPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.rulePathPanel.border.title"))); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(rulePathFilterRegexCheckBox, org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.rulePathFilterRegexCheckBox.text")); // NOI18N + rulePathFilterRegexCheckBox.setEnabled(false); + + rulePathFilterTextField.setEditable(false); + rulePathFilterTextField.setText(org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.rulePathFilterTextField.text")); // NOI18N + + javax.swing.GroupLayout rulePathPanelLayout = new javax.swing.GroupLayout(rulePathPanel); + rulePathPanel.setLayout(rulePathPanelLayout); + rulePathPanelLayout.setHorizontalGroup( + rulePathPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(rulePathPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(rulePathPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(rulePathFilterRegexCheckBox) + .addComponent(rulePathFilterTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 283, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + rulePathPanelLayout.setVerticalGroup( + rulePathPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(rulePathPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(rulePathFilterTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(rulePathFilterRegexCheckBox) + .addContainerGap()) + ); + + setDescPanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.setDescPanel.border.title"))); // NOI18N + + setDescriptionTextArea.setEditable(false); + setDescriptionTextArea.setColumns(20); + setDescriptionTextArea.setRows(5); + setDescScrollPanel.setViewportView(setDescriptionTextArea); + + javax.swing.GroupLayout setDescPanelLayout = new javax.swing.GroupLayout(setDescPanel); + setDescPanel.setLayout(setDescPanelLayout); + setDescPanelLayout.setHorizontalGroup( + setDescPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(setDescPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(setDescScrollPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 296, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + setDescPanelLayout.setVerticalGroup( + setDescPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, setDescPanelLayout.createSequentialGroup() + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(setDescScrollPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap()) + ); + + typePanel.setBorder(javax.swing.BorderFactory.createTitledBorder(org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.typePanel.border.title"))); // NOI18N + + filesRadioButton.setSelected(true); + org.openide.awt.Mnemonics.setLocalizedText(filesRadioButton, org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.filesRadioButton.text")); // NOI18N + filesRadioButton.setEnabled(false); + + org.openide.awt.Mnemonics.setLocalizedText(dirsRadioButton, org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.dirsRadioButton.text")); // NOI18N + dirsRadioButton.setEnabled(false); + + org.openide.awt.Mnemonics.setLocalizedText(bothRadioButton, org.openide.util.NbBundle.getMessage(InterestingItemDefsPanel.class, "InterestingItemDefsPanel.bothRadioButton.text")); // NOI18N + bothRadioButton.setEnabled(false); + + javax.swing.GroupLayout typePanelLayout = new javax.swing.GroupLayout(typePanel); + typePanel.setLayout(typePanelLayout); + typePanelLayout.setHorizontalGroup( + typePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(typePanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(filesRadioButton) + .addGap(18, 18, 18) + .addComponent(dirsRadioButton) + .addGap(18, 18, 18) + .addComponent(bothRadioButton) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + typePanelLayout.setVerticalGroup( + typePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(typePanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(typePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(filesRadioButton) + .addComponent(dirsRadioButton) + .addComponent(bothRadioButton)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addGroup(layout.createSequentialGroup() + .addComponent(newSetButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(editSetButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(deleteSetButton)) + .addComponent(setsListLabel) + .addComponent(setsListScrollPane) + .addComponent(ignoreKnownFilesCheckbox) + .addGroup(layout.createSequentialGroup() + .addComponent(selectedSetLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 71, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(setNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 208, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(setDescPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(separator, javax.swing.GroupLayout.PREFERRED_SIZE, 2, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addGroup(layout.createSequentialGroup() + .addComponent(selectedRuleLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(ruleNameTextField)) + .addComponent(rulesListLabel) + .addComponent(fileNamePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(rulePathPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addGap(10, 10, 10) + .addComponent(newRuleButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(editRuleButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(deleteRuleButton)) + .addComponent(rulesListScrollPane) + .addComponent(typePanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addContainerGap()) + ); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {deleteRuleButton, deleteSetButton, editRuleButton, editSetButton, newRuleButton, newSetButton}); + + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(setsListLabel) + .addComponent(rulesListLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(setsListScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 199, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(selectedSetLabel) + .addComponent(setNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(setDescPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(ignoreKnownFilesCheckbox)) + .addGroup(layout.createSequentialGroup() + .addComponent(rulesListScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 122, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(selectedRuleLabel) + .addComponent(ruleNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGap(16, 16, 16) + .addComponent(typePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(fileNamePanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(rulePathPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(deleteRuleButton) + .addComponent(editRuleButton) + .addComponent(newRuleButton) + .addComponent(deleteSetButton) + .addComponent(editSetButton) + .addComponent(newSetButton))) + .addComponent(separator, javax.swing.GroupLayout.PREFERRED_SIZE, 474, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap()) + ); + + layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {deleteRuleButton, deleteSetButton, editRuleButton, editSetButton, newRuleButton, newSetButton}); + + }// //GEN-END:initComponents + + private void newSetButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newSetButtonActionPerformed + this.doFileSetsDialog(null); + }//GEN-LAST:event_newSetButtonActionPerformed + + private void editSetButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editSetButtonActionPerformed + this.doFileSetsDialog(this.setsList.getSelectedValue()); + }//GEN-LAST:event_editSetButtonActionPerformed + + private void newRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newRuleButtonActionPerformed + this.doFilesSetRuleDialog(null); + }//GEN-LAST:event_newRuleButtonActionPerformed + + private void editRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editRuleButtonActionPerformed + this.doFilesSetRuleDialog(this.rulesList.getSelectedValue()); + }//GEN-LAST:event_editRuleButtonActionPerformed + + private void deleteSetButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteSetButtonActionPerformed + FilesSet selectedSet = this.setsList.getSelectedValue(); + this.filesSets.remove(selectedSet.getName()); + this.setsListModel.removeElement(selectedSet); + + // Select the first of the remaining set definitions. This will cause + // the selection listeners to repopulate the subordinate components. + if (!this.filesSets.isEmpty()) { + this.setsList.setSelectedIndex(0); + } else { + this.resetComponents(); + } + }//GEN-LAST:event_deleteSetButtonActionPerformed + + private void deleteRuleButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteRuleButtonActionPerformed + // Interesting file sets are immutable for thread safety, + // so editing a files set rule definition is a replacement + // operation. Preserve the existing rules from the set being + // edited, except for the deleted rule. + FilesSet oldSet = this.setsList.getSelectedValue(); + Map rules = new HashMap<>(oldSet.getRules()); + FilesSet.Rule selectedRule = this.rulesList.getSelectedValue(); + rules.remove(selectedRule.getName()); + this.replaceFilesSet(oldSet, oldSet.getName(), oldSet.getDescription(), oldSet.ignoresKnownFiles(), rules); + }//GEN-LAST:event_deleteRuleButtonActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JRadioButton bothRadioButton; + private javax.swing.JButton deleteRuleButton; + private javax.swing.JButton deleteSetButton; + private javax.swing.JRadioButton dirsRadioButton; + private javax.swing.JButton editRuleButton; + private javax.swing.JButton editSetButton; + private javax.swing.ButtonGroup fileNameButtonGroup; + private javax.swing.JRadioButton fileNameExtensionRadioButton; + private javax.swing.JPanel fileNamePanel; + private javax.swing.JRadioButton fileNameRadioButton; + private javax.swing.JCheckBox fileNameRegexCheckbox; + private javax.swing.JTextField fileNameTextField; + private javax.swing.JRadioButton filesRadioButton; + private javax.swing.JCheckBox ignoreKnownFilesCheckbox; + private javax.swing.JButton newRuleButton; + private javax.swing.JButton newSetButton; + private javax.swing.JTextField ruleNameTextField; + private javax.swing.JCheckBox rulePathFilterRegexCheckBox; + private javax.swing.JTextField rulePathFilterTextField; + private javax.swing.JPanel rulePathPanel; + private javax.swing.JList rulesList; + private javax.swing.JLabel rulesListLabel; + private javax.swing.JScrollPane rulesListScrollPane; + private javax.swing.JLabel selectedRuleLabel; + private javax.swing.JLabel selectedSetLabel; + private javax.swing.JSeparator separator; + private javax.swing.JPanel setDescPanel; + private javax.swing.JScrollPane setDescScrollPanel; + private javax.swing.JTextArea setDescriptionTextArea; + private javax.swing.JTextField setNameTextField; + private javax.swing.JList setsList; + private javax.swing.JLabel setsListLabel; + private javax.swing.JScrollPane setsListScrollPane; + private javax.swing.JPanel typePanel; + // End of variables declaration//GEN-END:variables + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemsIngestModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemsIngestModuleFactory.java new file mode 100755 index 0000000000..549e75c963 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemsIngestModuleFactory.java @@ -0,0 +1,110 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 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.modules.interestingitems; + +import java.util.ArrayList; +import java.util.List; +import org.openide.util.NbBundle; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.coreutils.Version; +import org.sleuthkit.autopsy.ingest.FileIngestModule; +import org.sleuthkit.autopsy.ingest.IngestModuleFactory; +import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; +import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; +import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; + +/** + * A factory that creates ingest modules that use interesting files set + * definitions to identify files that may be of interest to the user. + */ +@ServiceProvider(service = IngestModuleFactory.class) +final public class InterestingItemsIngestModuleFactory extends IngestModuleFactoryAdapter { + + @Override + public String getModuleDisplayName() { + return getModuleName(); + } + + static String getModuleName() { + return NbBundle.getMessage(InterestingItemsIngestModuleFactory.class, "InterestingItemsIdentifierIngestModule.moduleName"); + } + + @Override + public String getModuleDescription() { + return NbBundle.getMessage(InterestingItemsIngestModuleFactory.class, "InterestingItemsIdentifierIngestModule.moduleDescription"); + } + + @Override + public String getModuleVersionNumber() { + return Version.getVersion(); + } + + @Override + public boolean hasGlobalSettingsPanel() { + return true; + } + + @Override + public IngestModuleGlobalSettingsPanel getGlobalSettingsPanel() { + InterestingItemDefsPanel panel = new InterestingItemDefsPanel(); + panel.load(); + return panel; + } + + @Override + public IngestModuleIngestJobSettings getDefaultIngestJobSettings() { + // All interesting files set definitions are enabled by default. The + // names of the set definitions are stored instead of the set + // definitions to make per ingest job enabling and disabling of the + // definitions independent of the rules that make up the defintions. + // Doing so also keeps the serialization simple. + List enabledFilesSetNames = new ArrayList<>(); + for (String name : InterestingItemDefsManager.getInstance().getInterestingFilesSets().keySet()) { + enabledFilesSetNames.add(name); + } + return new FilesIdentifierIngestJobSettings(enabledFilesSetNames); + } + + @Override + public boolean hasIngestJobSettingsPanel() { + return true; + } + + @Override + public IngestModuleIngestJobSettingsPanel getIngestJobSettingsPanel(IngestModuleIngestJobSettings settings) { + if (!(settings instanceof FilesIdentifierIngestJobSettings)) { + throw new IllegalArgumentException("Settings not instanceof org.sleuthkit.autopsy.modules.interestingitems.InterestingItemsIngestJobSettings"); + } + return FilesIdentifierIngestJobSettingsPanel.makePanel((FilesIdentifierIngestJobSettings) settings); + } + + @Override + public boolean isFileIngestModuleFactory() { + return true; + } + + @Override + public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) { + if (!(settings instanceof FilesIdentifierIngestJobSettings)) { + throw new IllegalArgumentException("Settings not instanceof org.sleuthkit.autopsy.modules.interestingitems.InterestingItemsIngestJobSettings"); + } + return new FilesIdentifierIngestModule((FilesIdentifierIngestJobSettings) settings); + } +}