diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties index f38bcbc815..830901a6d9 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/Bundle.properties @@ -38,8 +38,6 @@ FilesSetRulePanel.interesting.jLabel5.text=Enter information about files that yo FilesSetRulePanel.ingest.jLabel5.text=Enter information about files that you want to run ingest on. FilesSetRulePanel.nameCheck.text=Name Pattern: FilesSetRulePanel.pathCheck.text=Path Pattern: -FilesSetRulePanel.mimeCheck.text=MIME Type: -FilesSetRulePanel.fileSizeCheck.text=File Size: FilesSetRulePanel.filesRadioButton.text=Files FilesSetRulePanel.dirsRadioButton.text=Directories FilesSetDefsPanel.interesting.setsListLabel.text=Rule Sets: @@ -79,3 +77,10 @@ FilesSetDefsPanel.ingoreUnallocCheckbox.text=Ignore Unallocated Space FilesSetDefsPanel.ingoreUnallocCheckbox.toolTipText=Ignores unallocated space, such as deleted files. May run faster but produce less complete results. FilesSetDefsPanel.ingestWarningLabel.text=Ingest is ongoing, some settings will be unavailable until it finishes. FilesSetDefsPanel.allRadioButton.text=All +FilesSetRulePanel.dateCheck.text=Modified Within: +FilesSetRulePanel.fileSizeCheck.text=File Size: +FilesSetRulePanel.mimeCheck.text=MIME Type: +FilesSetDefsPanel.modifiedDateLabel.text=Modified Within: +FilesSetDefsPanel.daysIncludedTextField.text= +FilesSetDefsPanel.daysIncludedLabel.text=day(s) +FilesSetRulePanel.daysIncludedLabel.text=day(s) diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSet.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSet.java index 38d7285fc1..3a3e9b2360 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSet.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSet.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; import java.util.regex.Pattern; +import org.openide.util.NbBundle; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.TskData; @@ -169,6 +170,7 @@ public final class FilesSet implements Serializable { private final ParentPathCondition pathCondition; private final MimeTypeCondition mimeTypeCondition; private final FileSizeCondition fileSizeCondition; + private final DateCondition dateCondition; private final List conditions = new ArrayList<>(); /** @@ -180,8 +182,10 @@ public final class FilesSet implements Serializable { * @param pathCondition A file path condition, may be null. * @param mimeTypeCondition A file mime type condition, may be null. * @param fileSizeCondition A file size condition, may be null. + * @param dateCondition A file date created or modified condition, + * may be null */ - Rule(String ruleName, FileNameCondition fileNameCondition, MetaTypeCondition metaTypeCondition, ParentPathCondition pathCondition, MimeTypeCondition mimeTypeCondition, FileSizeCondition fileSizeCondition) { + Rule(String ruleName, FileNameCondition fileNameCondition, MetaTypeCondition metaTypeCondition, ParentPathCondition pathCondition, MimeTypeCondition mimeTypeCondition, FileSizeCondition fileSizeCondition, DateCondition dateCondition) { // since ruleName is optional, ruleUUID can be used to uniquely identify a rule. this.uuid = UUID.randomUUID().toString(); if (metaTypeCondition == null) { @@ -216,6 +220,10 @@ public final class FilesSet implements Serializable { if (this.pathCondition != null) { this.conditions.add(this.pathCondition); } + this.dateCondition = dateCondition; + if (this.dateCondition != null) { + this.conditions.add(this.dateCondition); + } } /** @@ -254,6 +262,10 @@ public final class FilesSet implements Serializable { return this.pathCondition; } + DateCondition getDateCondition() { + return this.dateCondition; + } + /** * Determines whether or not a file satisfies the rule. * @@ -270,6 +282,10 @@ public final class FilesSet implements Serializable { return true; } + @NbBundle.Messages({ + "# {0} - daysIncluded", + "FilesSet.rule.dateRule.toString=(modified within {0} day(s))" + }) @Override public String toString() { // This override is designed to provide a display name for use with @@ -283,6 +299,8 @@ public final class FilesSet implements Serializable { } else if (this.fileSizeCondition != null) { return this.ruleName + " (" + fileSizeCondition.getComparator().getSymbol() + " " + fileSizeCondition.getSizeValue() + " " + fileSizeCondition.getUnit().getName() + ")"; + } else if (this.dateCondition != null) { + return this.ruleName + Bundle.FilesSet_rule_dateRule_toString(dateCondition.getDaysIncluded()); } else { return this.ruleName + " ()"; } @@ -537,7 +555,7 @@ public final class FilesSet implements Serializable { case FILES: return file.isFile(); case DIRECTORIES: - return file.getMetaType() == TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR + return file.getMetaType() == TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR || file.getMetaType() == TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_VIRT_DIR; case FILES_AND_DIRECTORIES: return file.getMetaType() == TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_REG @@ -737,6 +755,46 @@ public final class FilesSet implements Serializable { } + /** + * A class for checking whether a file's creation or modification + * occured in a specific range of time + */ + static final class DateCondition implements FileAttributeCondition { + + private final static long SECS_PER_DAY = 60 * 60 * 24; + + private int daysIncluded; + + /** + * Construct a new DateCondition + * + * @param days - files created or modified more recently than this + * number of days will pass + */ + DateCondition(int days) { + daysIncluded = days; + } + + /** + * Get the number of days which this condition allows to pass + * + * @return integer value of the number days which will pass + */ + int getDaysIncluded() { + return daysIncluded; + } + + @Override + public boolean passes(AbstractFile file) { + long dateThreshold = System.currentTimeMillis() / 1000 - daysIncluded * SECS_PER_DAY; + if (file.getCrtime() > dateThreshold || file.getMtime() > dateThreshold) { + return true; + } + return false; + } + + } + /** * A file name extension condition for an interesting files set * membership rule. The immutability of a file name extension condition diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.form index bffb00f33b..4d8f94589f 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.form @@ -90,109 +90,91 @@ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -213,9 +195,9 @@ - - - + + + @@ -223,7 +205,7 @@ - + @@ -232,14 +214,14 @@ - + - + @@ -250,26 +232,32 @@ - + - + - + - + - + + + + + + + @@ -437,7 +425,7 @@ - + @@ -996,6 +984,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java index c67051be97..4fd933b167 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetDefsPanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -251,6 +251,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp this.fileNameRegexCheckbox.setSelected(false); this.filesRadioButton.setSelected(true); this.rulePathConditionTextField.setText(""); + this.daysIncludedTextField.setText(""); this.rulePathConditionRegexCheckBox.setSelected(false); this.mimeTypeComboBox.setSelectedIndex(0); this.equalitySignComboBox.setSelectedIndex(2); @@ -326,7 +327,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp FilesSet.Rule.ParentPathCondition pathCondition = rule.getPathCondition(); FilesSet.Rule.MimeTypeCondition mimeTypeCondition = rule.getMimeTypeCondition(); FilesSet.Rule.FileSizeCondition fileSizeCondition = rule.getFileSizeCondition(); - + FilesSet.Rule.DateCondition dateCondition = rule.getDateCondition(); // Populate the components that display the properties of the // selected rule. if (nameCondition != null) { @@ -372,7 +373,12 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp FilesSetDefsPanel.this.equalitySignComboBox.setSelectedIndex(2); FilesSetDefsPanel.this.fileSizeSpinner.setValue(0); } - + if (dateCondition != null){ + FilesSetDefsPanel.this.daysIncludedTextField.setText(Integer.toString(dateCondition.getDaysIncluded())); + } + else { + FilesSetDefsPanel.this.daysIncludedTextField.setText(""); + } // Enable the new, edit and delete rule buttons. FilesSetDefsPanel.this.newRuleButton.setEnabled(true && canBeEnabled); FilesSetDefsPanel.this.editRuleButton.setEnabled(true && canBeEnabled); @@ -477,7 +483,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp if (selectedRule != null) { rules.remove(selectedRule.getUuid()); } - FilesSet.Rule newRule = new FilesSet.Rule(panel.getRuleName(), panel.getFileNameCondition(), panel.getMetaTypeCondition(), panel.getPathCondition(), panel.getMimeTypeCondition(), panel.getFileSizeCondition()); + FilesSet.Rule newRule = new FilesSet.Rule(panel.getRuleName(), panel.getFileNameCondition(), panel.getMetaTypeCondition(), panel.getPathCondition(), panel.getMimeTypeCondition(), panel.getFileSizeCondition(), panel.getDateCondition()); rules.put(newRule.getUuid(), newRule); // Add the new/edited files set definition, replacing any previous @@ -590,6 +596,9 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp copySetButton = new javax.swing.JButton(); importSetButton = new javax.swing.JButton(); exportSetButton = new javax.swing.JButton(); + modifiedDateLabel = new javax.swing.JLabel(); + daysIncludedTextField = new javax.swing.JTextField(); + daysIncludedLabel = new javax.swing.JLabel(); setFont(getFont().deriveFont(getFont().getStyle() & ~java.awt.Font.BOLD, 11)); @@ -643,7 +652,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp setDescriptionTextArea.setColumns(20); setDescriptionTextArea.setFont(setDescriptionTextArea.getFont().deriveFont(setDescriptionTextArea.getFont().getStyle() & ~java.awt.Font.BOLD, 13)); setDescriptionTextArea.setLineWrap(true); - setDescriptionTextArea.setRows(10); + setDescriptionTextArea.setRows(6); setDescriptionTextArea.setMinimumSize(new java.awt.Dimension(10, 22)); setDescScrollPanel.setViewportView(setDescriptionTextArea); @@ -841,6 +850,17 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp } }); + org.openide.awt.Mnemonics.setLocalizedText(modifiedDateLabel, org.openide.util.NbBundle.getMessage(FilesSetDefsPanel.class, "FilesSetDefsPanel.modifiedDateLabel.text")); // NOI18N + + daysIncludedTextField.setEditable(false); + daysIncludedTextField.setHorizontalAlignment(javax.swing.JTextField.TRAILING); + daysIncludedTextField.setText(org.openide.util.NbBundle.getMessage(FilesSetDefsPanel.class, "FilesSetDefsPanel.daysIncludedTextField.text")); // NOI18N + daysIncludedTextField.setMinimumSize(new java.awt.Dimension(60, 20)); + daysIncludedTextField.setPreferredSize(new java.awt.Dimension(60, 20)); + + org.openide.awt.Mnemonics.setLocalizedText(daysIncludedLabel, org.openide.util.NbBundle.getMessage(FilesSetDefsPanel.class, "FilesSetDefsPanel.daysIncludedLabel.text")); // NOI18N + daysIncludedLabel.setEnabled(false); + javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1); jPanel1.setLayout(jPanel1Layout); jPanel1Layout.setHorizontalGroup( @@ -867,83 +887,74 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp .addComponent(setsListLabel)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(separator, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(jPanel1Layout.createSequentialGroup() .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(rulesListScrollPane, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(setDescScrollPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addGroup(jPanel1Layout.createSequentialGroup() - .addGap(101, 101, 101) - .addComponent(filesRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 47, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(16, 16, 16) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(jLabel7) + .addComponent(jLabel8) + .addComponent(jLabel2) + .addComponent(jLabel4) + .addComponent(modifiedDateLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jLabel3, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(dirsRadioButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(allRadioButton)) - .addGroup(jPanel1Layout.createSequentialGroup() - .addGap(105, 105, 105) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(rulePathConditionTextField) + .addComponent(fileNameTextField, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(mimeTypeComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addGroup(jPanel1Layout.createSequentialGroup() - .addComponent(fileNameRadioButton) - .addGap(4, 4, 4) - .addComponent(fileNameExtensionRadioButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(fileNameRegexCheckbox)) - .addComponent(rulePathConditionRegexCheckBox))) - .addGroup(jPanel1Layout.createSequentialGroup() - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(rulesListLabel) - .addComponent(jLabel1) - .addGroup(jPanel1Layout.createSequentialGroup() - .addComponent(newRuleButton) - .addGap(18, 18, 18) - .addComponent(editRuleButton) - .addGap(18, 18, 18) - .addComponent(deleteRuleButton)) - .addGroup(jPanel1Layout.createSequentialGroup() - .addComponent(ignoreKnownFilesCheckbox) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(ingoreUnallocCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 158, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(jPanel1Layout.createSequentialGroup() - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jLabel5) - .addComponent(jLabel6)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(ingestWarningLabel))))) - .addGap(24, 28, Short.MAX_VALUE)) - .addGroup(jPanel1Layout.createSequentialGroup() - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel1Layout.createSequentialGroup() - .addGap(22, 22, 22) - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel1Layout.createSequentialGroup() - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jLabel7) - .addComponent(jLabel8)) - .addGap(18, 18, 18)) - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel1Layout.createSequentialGroup() - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jLabel3) - .addComponent(jLabel2)) - .addGap(6, 6, 6)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() - .addComponent(jLabel4) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)))) - .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(jPanel1Layout.createSequentialGroup() - .addComponent(equalitySignComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 44, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(equalitySignComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 60, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(fileSizeSpinner, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(fileSizeUnitComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(rulePathConditionTextField) - .addComponent(fileNameTextField, javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(mimeTypeComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup() + .addGroup(jPanel1Layout.createSequentialGroup() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(fileNameRadioButton) + .addComponent(rulePathConditionRegexCheckBox) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(daysIncludedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(daysIncludedLabel)) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(filesRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 47, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(dirsRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(allRadioButton))) + .addGap(0, 0, Short.MAX_VALUE))))) + .addGap(8, 8, 8)) + .addGroup(jPanel1Layout.createSequentialGroup() + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(rulesListLabel) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(ignoreKnownFilesCheckbox) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(ingoreUnallocCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 158, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(jPanel1Layout.createSequentialGroup() .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(rulesListScrollPane, javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(setDescScrollPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))) - .addGap(8, 8, 8)))) + .addComponent(jLabel5) + .addComponent(jLabel6)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(ingestWarningLabel)) + .addGroup(jPanel1Layout.createSequentialGroup() + .addGap(174, 174, 174) + .addComponent(fileNameExtensionRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(fileNameRegexCheckbox)) + .addComponent(jLabel1) + .addGroup(jPanel1Layout.createSequentialGroup() + .addComponent(newRuleButton) + .addGap(18, 18, 18) + .addComponent(editRuleButton) + .addGap(18, 18, 18) + .addComponent(deleteRuleButton))) + .addGap(24, 28, Short.MAX_VALUE)))) ); jPanel1Layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {copySetButton, deleteSetButton, editSetButton, exportSetButton, importSetButton, newSetButton}); @@ -963,15 +974,15 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp .addGap(1, 1, 1)) .addComponent(ingestWarningLabel, javax.swing.GroupLayout.Alignment.TRAILING)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(setDescScrollPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 45, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(6, 6, 6) + .addComponent(setDescScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 69, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(ignoreKnownFilesCheckbox) .addComponent(ingoreUnallocCheckbox, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(rulesListLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(rulesListScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 82, Short.MAX_VALUE) + .addComponent(rulesListScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 61, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(newRuleButton) @@ -979,13 +990,13 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp .addComponent(deleteRuleButton)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jLabel1) - .addGap(8, 8, 8) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel2) .addComponent(filesRadioButton) .addComponent(dirsRadioButton) .addComponent(allRadioButton)) - .addGap(8, 8, 8) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel3) .addComponent(fileNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)) @@ -994,23 +1005,28 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp .addComponent(fileNameRadioButton) .addComponent(fileNameExtensionRadioButton) .addComponent(fileNameRegexCheckbox)) - .addGap(8, 8, 8) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel4) .addComponent(rulePathConditionTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(6, 6, 6) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(rulePathConditionRegexCheckBox) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel7) .addComponent(mimeTypeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel8) .addComponent(equalitySignComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(fileSizeSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(fileSizeUnitComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(5, 5, 5)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(modifiedDateLabel) + .addComponent(daysIncludedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(daysIncludedLabel)) + .addContainerGap()) .addGroup(jPanel1Layout.createSequentialGroup() .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) @@ -1262,6 +1278,8 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JRadioButton allRadioButton; private javax.swing.JButton copySetButton; + private javax.swing.JLabel daysIncludedLabel; + private javax.swing.JTextField daysIncludedTextField; private javax.swing.JButton deleteRuleButton; private javax.swing.JButton deleteSetButton; private javax.swing.JRadioButton dirsRadioButton; @@ -1294,6 +1312,7 @@ public final class FilesSetDefsPanel extends IngestModuleGlobalSettingsPanel imp private javax.swing.JScrollPane jScrollPane2; private javax.swing.JTextArea jTextArea1; private javax.swing.JComboBox mimeTypeComboBox; + private javax.swing.JLabel modifiedDateLabel; private javax.swing.JButton newRuleButton; private javax.swing.JButton newSetButton; private javax.swing.JCheckBox rulePathConditionRegexCheckBox; diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.form b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.form index 4777c108b4..b91bb34d36 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.form +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.form @@ -23,82 +23,80 @@ - - + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - + - + @@ -106,17 +104,22 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + @@ -134,7 +137,7 @@ - + @@ -146,7 +149,13 @@ - + + + + + + + @@ -170,9 +179,6 @@ - - - @@ -380,5 +386,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java index d70a5474ee..4965652c5f 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetRulePanel.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014-2017 Basis Technology Corp. + * Copyright 2014-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,6 @@ package org.sleuthkit.autopsy.modules.interestingitems; import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.util.List; import java.util.SortedSet; import java.util.logging.Level; @@ -50,6 +49,8 @@ final class FilesSetRulePanel extends javax.swing.JPanel { "FilesSetRulePanel.NoMimeTypeError=Please select a valid MIME type.", "FilesSetRulePanel.NoNameError=Name cannot be empty", "FilesSetRulePanel.NoPathError=Path cannot be empty", + "FilesSetRulePanel.DaysIncludedEmptyError=Number of days included cannot be empty.", + "FilesSetRulePanel.DaysIncludedInvalidError=Number of days included must be a positive integer.", "FilesSetRulePanel.ZeroFileSizeError=File size condition value must not be 0 (Unless = is selected)." }) @@ -82,6 +83,7 @@ final class FilesSetRulePanel extends javax.swing.JPanel { } else { populateMimeTypesComboBox(); } + this.dateCheckActionPerformed(null); populateComponentsWithDefaultValues(); this.setButtons(okButton, cancelButton); } @@ -108,12 +110,14 @@ final class FilesSetRulePanel extends javax.swing.JPanel { populateMimeTypesComboBox(); populateMimeConditionComponents(rule); populateSizeConditionComponents(rule); + } populateMimeTypesComboBox(); populateRuleNameComponent(rule); populateTypeConditionComponents(rule); populateNameConditionComponents(rule); populatePathConditionComponents(rule); + populateDateConditionComponents(rule); this.setButtons(okButton, cancelButton); } @@ -176,7 +180,7 @@ final class FilesSetRulePanel extends javax.swing.JPanel { private void setOkButton() { if (this.okButton != null) { this.okButton.setEnabled(this.fileSizeCheck.isSelected() || this.mimeCheck.isSelected() - || this.nameCheck.isSelected() || this.pathCheck.isSelected()); + || this.nameCheck.isSelected() || this.pathCheck.isSelected() || this.dateCheck.isSelected()); } } @@ -274,6 +278,21 @@ final class FilesSetRulePanel extends javax.swing.JPanel { } } + /** + * Populates the UI components that display the optional date condition for + * a rule. + * + * @param rule The files set rule to be edited. + */ + private void populateDateConditionComponents(FilesSet.Rule rule) { + FilesSet.Rule.DateCondition dateCondition = rule.getDateCondition(); + if (dateCondition != null) { + this.dateCheck.setSelected(true); + this.dateCheckActionPerformed(null); + this.daysIncludedTextField.setText(Integer.toString(dateCondition.getDaysIncluded())); + } + } + /** * Returns whether or not the data entered in the panel constitutes a valid * files set membership rule definition, displaying a dialog explaining the @@ -283,7 +302,7 @@ final class FilesSetRulePanel extends javax.swing.JPanel { */ boolean isValidRuleDefinition() { - if (!(this.mimeCheck.isSelected() || this.fileSizeCheck.isSelected() || this.pathCheck.isSelected() || this.nameCheck.isSelected())) { + if (!(this.mimeCheck.isSelected() || this.fileSizeCheck.isSelected() || this.pathCheck.isSelected() || this.nameCheck.isSelected() || this.dateCheck.isSelected())) { NotifyDescriptor notifyDesc = new NotifyDescriptor.Message( Bundle.FilesSetRulePanel_NoConditionError(), NotifyDescriptor.WARNING_MESSAGE); @@ -367,6 +386,28 @@ final class FilesSetRulePanel extends javax.swing.JPanel { } } + if (this.dateCheck.isSelected()) { + if (this.daysIncludedTextField.getText().isEmpty()) { + NotifyDescriptor notifyDesc = new NotifyDescriptor.Message( + Bundle.FilesSetRulePanel_DaysIncludedEmptyError(), + NotifyDescriptor.WARNING_MESSAGE); + DialogDisplayer.getDefault().notify(notifyDesc); + return false; + } + try { + int value = Integer.parseInt(daysIncludedTextField.getText()); + if (value < 0) { + throw new NumberFormatException("Negative numbers are not allowed for the within N days condition"); + } + } catch (NumberFormatException e) { + //field did not contain an integer + NotifyDescriptor notifyDesc = new NotifyDescriptor.Message( + Bundle.FilesSetRulePanel_DaysIncludedInvalidError(), + NotifyDescriptor.WARNING_MESSAGE); + DialogDisplayer.getDefault().notify(notifyDesc); + return false; + } + } return true; } @@ -504,6 +545,23 @@ final class FilesSetRulePanel extends javax.swing.JPanel { return condition; } + /** + * Gets the optional date condition for the rule that was created or edited. + * Should only be called if isValidDefintion() returns true. + * + * @return A date condition or null if no date condition was specified. + * + * @throws IllegalStateException if the specified date condition is not + * valid. + */ + FilesSet.Rule.DateCondition getDateCondition() { + FilesSet.Rule.DateCondition dateCondition = null; + if (!daysIncludedTextField.getText().isEmpty()) { + dateCondition = new FilesSet.Rule.DateCondition(Integer.parseInt(daysIncludedTextField.getText())); + } + return dateCondition; + } + /** * Checks an input string for the use of illegal characters. * @@ -582,15 +640,13 @@ final class FilesSetRulePanel extends javax.swing.JPanel { filesRadioButton = new javax.swing.JRadioButton(); dirsRadioButton = new javax.swing.JRadioButton(); allRadioButton = new javax.swing.JRadioButton(); + daysIncludedTextField = new javax.swing.JTextField(); + daysIncludedLabel = new javax.swing.JLabel(); + dateCheck = new javax.swing.JCheckBox(); 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 - ruleNameTextField.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - ruleNameTextFieldActionPerformed(evt); - } - }); org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.jLabel1.text")); // NOI18N @@ -685,85 +741,99 @@ final class FilesSetRulePanel extends javax.swing.JPanel { } }); + daysIncludedTextField.setEnabled(false); + daysIncludedTextField.setMinimumSize(new java.awt.Dimension(60, 20)); + daysIncludedTextField.setPreferredSize(new java.awt.Dimension(60, 20)); + + org.openide.awt.Mnemonics.setLocalizedText(daysIncludedLabel, org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.daysIncludedLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(dateCheck, org.openide.util.NbBundle.getMessage(FilesSetRulePanel.class, "FilesSetRulePanel.dateCheck.text")); // NOI18N + dateCheck.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + dateCheckActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGap(8, 8, 8) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(ruleNameLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(ruleNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 234, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(ruleNameLabel) + .addGap(5, 5, 5) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(mimeTypeComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(pathTextField) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(equalitySymbolComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(fileSizeSpinner) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(fileSizeComboBox, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + .addGroup(layout.createSequentialGroup() + .addComponent(pathRegexCheckBox) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(pathSeparatorInfoLabel)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(daysIncludedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 69, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(daysIncludedLabel)) + .addComponent(ruleNameTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 249, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(fullNameRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(extensionRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 98, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(nameRegexCheckbox))) + .addGap(1, 1, 1)))) + .addComponent(jLabel5) .addGroup(layout.createSequentialGroup() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(jLabel5) + .addComponent(nameCheck, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.PREFERRED_SIZE, 95, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel1)) + .addGap(16, 16, 16) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() - .addComponent(jLabel1) - .addGap(65, 65, 65) .addComponent(filesRadioButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(dirsRadioButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(allRadioButton))) - .addGap(0, 0, Short.MAX_VALUE)))) - .addGroup(layout.createSequentialGroup() - .addContainerGap() - .addComponent(nameCheck) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 249, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() - .addContainerGap() - .addComponent(pathCheck) - .addGap(4, 4, 4) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addComponent(pathRegexCheckBox) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(pathSeparatorInfoLabel)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addGap(0, 0, Short.MAX_VALUE) - .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 250, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(layout.createSequentialGroup() - .addComponent(fullNameRadioButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(extensionRadioButton, javax.swing.GroupLayout.PREFERRED_SIZE, 114, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addComponent(nameRegexCheckbox) - .addGap(0, 0, Short.MAX_VALUE)))) + .addComponent(allRadioButton)) + .addComponent(nameTextField))))) .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(pathCheck) .addComponent(mimeCheck) - .addComponent(fileSizeCheck)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addGroup(layout.createSequentialGroup() - .addComponent(equalitySymbolComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 36, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(fileSizeSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, 94, javax.swing.GroupLayout.PREFERRED_SIZE) - .addGap(18, 18, 18) - .addComponent(fileSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 82, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addComponent(mimeTypeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 250, javax.swing.GroupLayout.PREFERRED_SIZE)))) + .addComponent(fileSizeCheck) + .addComponent(dateCheck)) + .addGap(0, 0, Short.MAX_VALUE))) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addComponent(jLabel5) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jLabel1) - .addComponent(filesRadioButton) - .addComponent(dirsRadioButton) - .addComponent(allRadioButton)) - .addGap(5, 5, 5) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(nameCheck)) + .addGap(3, 3, 3) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(layout.createSequentialGroup() + .addComponent(jLabel1) + .addGap(10, 10, 10) + .addComponent(nameCheck)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(filesRadioButton) + .addComponent(dirsRadioButton) + .addComponent(allRadioButton)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(nameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(fullNameRadioButton) @@ -777,7 +847,7 @@ final class FilesSetRulePanel extends javax.swing.JPanel { .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(pathRegexCheckBox) .addComponent(pathSeparatorInfoLabel)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 8, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(mimeTypeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(mimeCheck)) @@ -787,7 +857,12 @@ final class FilesSetRulePanel extends javax.swing.JPanel { .addComponent(fileSizeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(fileSizeSpinner, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(fileSizeCheck)) - .addGap(15, 15, 15) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(daysIncludedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(daysIncludedLabel) + .addComponent(dateCheck)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .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)) @@ -795,10 +870,6 @@ final class FilesSetRulePanel extends javax.swing.JPanel { ); }// //GEN-END:initComponents - private void ruleNameTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_ruleNameTextFieldActionPerformed - // TODO add your handling code here: - }//GEN-LAST:event_ruleNameTextFieldActionPerformed - private void nameCheckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nameCheckActionPerformed if (!this.nameCheck.isSelected()) { this.nameTextField.setEnabled(false); @@ -831,15 +902,30 @@ final class FilesSetRulePanel extends javax.swing.JPanel { this.setOkButton(); }//GEN-LAST:event_pathCheckActionPerformed - private void mimeCheckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mimeCheckActionPerformed - if (!this.mimeCheck.isSelected()) { - this.mimeTypeComboBox.setEnabled(false); - this.mimeTypeComboBox.setSelectedIndex(0); + private void filesRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_filesRadioButtonActionPerformed + + this.setComponentsForSearchType(); + }//GEN-LAST:event_filesRadioButtonActionPerformed + + private void dirsRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dirsRadioButtonActionPerformed + this.setComponentsForSearchType(); + }//GEN-LAST:event_dirsRadioButtonActionPerformed + + private void allRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allRadioButtonActionPerformed + this.setComponentsForSearchType(); + }//GEN-LAST:event_allRadioButtonActionPerformed + + private void dateCheckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dateCheckActionPerformed + if (!this.dateCheck.isSelected()) { + this.daysIncludedTextField.setEnabled(false); + this.daysIncludedLabel.setEnabled(false); + this.daysIncludedTextField.setText(""); } else { - this.mimeTypeComboBox.setEnabled(true); + this.daysIncludedTextField.setEnabled(true); + this.daysIncludedLabel.setEnabled(true); } this.setOkButton(); - }//GEN-LAST:event_mimeCheckActionPerformed + }//GEN-LAST:event_dateCheckActionPerformed private void fileSizeCheckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_fileSizeCheckActionPerformed if (!this.fileSizeCheck.isSelected()) { @@ -855,21 +941,21 @@ final class FilesSetRulePanel extends javax.swing.JPanel { this.setOkButton(); }//GEN-LAST:event_fileSizeCheckActionPerformed - private void filesRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_filesRadioButtonActionPerformed - - this.setComponentsForSearchType(); - }//GEN-LAST:event_filesRadioButtonActionPerformed - - private void dirsRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_dirsRadioButtonActionPerformed - this.setComponentsForSearchType(); - }//GEN-LAST:event_dirsRadioButtonActionPerformed - - private void allRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_allRadioButtonActionPerformed - this.setComponentsForSearchType(); - }//GEN-LAST:event_allRadioButtonActionPerformed + private void mimeCheckActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mimeCheckActionPerformed + if (!this.mimeCheck.isSelected()) { + this.mimeTypeComboBox.setEnabled(false); + this.mimeTypeComboBox.setSelectedIndex(0); + } else { + this.mimeTypeComboBox.setEnabled(true); + } + this.setOkButton(); + }//GEN-LAST:event_mimeCheckActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JRadioButton allRadioButton; + private javax.swing.JCheckBox dateCheck; + private javax.swing.JLabel daysIncludedLabel; + private javax.swing.JTextField daysIncludedTextField; private javax.swing.JRadioButton dirsRadioButton; private javax.swing.JComboBox equalitySymbolComboBox; private javax.swing.JRadioButton extensionRadioButton; diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetsManager.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetsManager.java index 6350805ee6..8397d77c72 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetsManager.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetsManager.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2014 Basis Technology Corp. + * Copyright 2014-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -52,7 +52,7 @@ public final class FilesSetsManager extends Observable { { put(Bundle.FilesSetsManager_allFilesAndDirectories(), new Rule(Bundle.FilesSetsManager_allFilesAndDirectories(), null, - new MetaTypeCondition(MetaTypeCondition.Type.ALL), null, null, null)); + new MetaTypeCondition(MetaTypeCondition.Type.ALL), null, null, null, null)); } }); private static final FilesSet FILES_DIRS_UNALLOC_INGEST_FILTER = new FilesSet( @@ -61,7 +61,7 @@ public final class FilesSetsManager extends Observable { { put(Bundle.FilesSetsManager_allFilesDirectoriesAndUnallocated(), new Rule(Bundle.FilesSetsManager_allFilesDirectoriesAndUnallocated(), null, - new MetaTypeCondition(MetaTypeCondition.Type.ALL), null, null, null)); + new MetaTypeCondition(MetaTypeCondition.Type.ALL), null, null, null, null)); } }); diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemsFilesSetSettings.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemsFilesSetSettings.java index de88aee68e..3f4cca4465 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemsFilesSetSettings.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/InterestingItemsFilesSetSettings.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -44,6 +44,7 @@ import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.FileSizeCond import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.MetaTypeCondition; import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.MimeTypeCondition; import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.ParentPathCondition; +import org.sleuthkit.autopsy.modules.interestingitems.FilesSet.Rule.DateCondition; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; @@ -67,6 +68,7 @@ class InterestingItemsFilesSetSettings implements Serializable { 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 NAME_ATTR = "name"; //NON-NLS + private static final String DAYS_INCLUDED_ATTR = "daysIncluded"; private static final String MIME_ATTR = "mimeType"; private static final String FS_COMPARATOR_ATTR = "comparatorSymbol"; private static final String FS_SIZE_ATTR = "sizeValue"; @@ -166,6 +168,35 @@ class InterestingItemsFilesSetSettings implements Serializable { return pathCondition; } + /** + * Construct a date condition for a FilesSet membership rule from data in an + * XML element. + * + * @param ruleElement The XML element. + * + * @return The date condition, or null if there is an error (logged). + * + * @throws + * org.sleuthkit.autopsy.modules.interestingitems.FilesSetsManager.FilesSetsManagerException + */ + private static DateCondition readDateCondition(Element ruleElement) throws FilesSetsManager.FilesSetsManagerException { + // Read in the optional path condition. Null is o.k., but if the attribute + // is there, be sure it is not malformed. + DateCondition dateCondition = null; + if (!ruleElement.getAttribute(DAYS_INCLUDED_ATTR).isEmpty()) { + String daysIncluded = ruleElement.getAttribute(DAYS_INCLUDED_ATTR); + if (!daysIncluded.isEmpty()) { + try { + dateCondition = new DateCondition(Integer.parseInt(daysIncluded)); + } catch (NumberFormatException ex) { + logger.log(Level.SEVERE, "Error creating condition for " + daysIncluded + ", ignoring malformed date condition definition", ex); // NON-NLS + throw new FilesSetsManager.FilesSetsManagerException(String.format("error compiling %s regex", DAYS_INCLUDED_ATTR), ex); + } + } + } + return dateCondition; + } + /** * Attempts to compile a regular expression. * @@ -183,12 +214,13 @@ class InterestingItemsFilesSetSettings implements Serializable { } /** - * Construct a fileset membership rule from the data in an xml element for + * Construct a fileset membership rule from the data in an xml element for * use in a FilesSet. * * @param elem The XML element. * - * @return A file set constructed from the conditions available in the XML element + * @return A file set constructed from the conditions available in the XML + * element * * @throws * org.sleuthkit.autopsy.modules.interestingitems.FilesSetsManager.FilesSetsManagerException @@ -200,17 +232,17 @@ class InterestingItemsFilesSetSettings implements Serializable { ParentPathCondition pathCondition = readPathCondition(elem); MimeTypeCondition mimeCondition = readMimeCondition(elem); FileSizeCondition sizeCondition = readSizeCondition(elem); - //if meta type condition or all four types of conditions the user can create are all null then don't make the rule - if (metaCondition == null || (nameCondition == null && pathCondition == null && mimeCondition == null && sizeCondition == null)) { + DateCondition dateCondition = readDateCondition(elem); //if meta type condition or all four types of conditions the user can create are all null then don't make the rule + if (metaCondition == null || (nameCondition == null && pathCondition == null && mimeCondition == null && sizeCondition == null && dateCondition == null)) { logger.log(Level.WARNING, "Error Reading Rule, " + ruleName + " was either missing a meta condition or contained only a meta condition. No rule was imported."); // NON-NLS throw new FilesSetsManager.FilesSetsManagerException(String.format("Invalid Rule in FilesSet xml, missing necessary conditions for %s", ruleName)); } - return new FilesSet.Rule(ruleName, nameCondition, metaCondition, pathCondition, mimeCondition, sizeCondition); + return new FilesSet.Rule(ruleName, nameCondition, metaCondition, pathCondition, mimeCondition, sizeCondition, dateCondition); } /** - * Construct a file name condition for a FilesSet membership rule from data in an - * XML element. + * Construct a file name condition for a FilesSet membership rule from data + * in an XML element. * * @param ruleElement The XML element. * @@ -256,8 +288,8 @@ class InterestingItemsFilesSetSettings implements Serializable { } /** - * Construct a MIME type condition for a FilesSet membership rule from data in an - * XML element. + * Construct a MIME type condition for a FilesSet membership rule from data + * in an XML element. * * @param ruleElement The XML element. * @@ -275,8 +307,8 @@ class InterestingItemsFilesSetSettings implements Serializable { } /** - * Construct a file size condition for a FilesSet membership rule from data in an - * XML element. + * Construct a file size condition for a FilesSet membership rule from data + * in an XML element. * * @param ruleElement The XML element. * @@ -544,6 +576,13 @@ class InterestingItemsFilesSetSettings implements Serializable { ruleElement.setAttribute(FS_SIZE_ATTR, Integer.toString(sizeCondition.getSizeValue())); ruleElement.setAttribute(FS_UNITS_ATTR, sizeCondition.getUnit().getName()); } + + //Add the optional date condition + DateCondition dateCondition = rule.getDateCondition(); + if (dateCondition != null) { + ruleElement.setAttribute(DAYS_INCLUDED_ATTR, Integer.toString(dateCondition.getDaysIncluded())); + } + setElement.appendChild(ruleElement); } rootElement.appendChild(setElement); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java index b13e1fbca4..0f05f3df99 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java @@ -33,6 +33,7 @@ import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.keywordsearch.KeywordSearchResultFactory.AdHocQueryResult; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.BlackboardArtifact; @@ -52,7 +53,6 @@ public class ExtractedContentViewer implements DataContentViewer { private static final Logger logger = Logger.getLogger(ExtractedContentViewer.class.getName()); - private static final long INVALID_DOCUMENT_ID = 0L; private static final BlackboardAttribute.Type TSK_ASSOCIATED_ARTIFACT_TYPE = new BlackboardAttribute.Type(TSK_ASSOCIATED_ARTIFACT); public static final BlackboardAttribute.Type TSK_ACCOUNT_TYPE = new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE); @@ -92,54 +92,77 @@ public class ExtractedContentViewer implements DataContentViewer { currentNode = node; } - Lookup nodeLookup = node.getLookup(); - AbstractFile content = nodeLookup.lookup(AbstractFile.class); - /* * Assemble a collection of all of the indexed text "sources" for the * node. */ List sources = new ArrayList<>(); + Lookup nodeLookup = node.getLookup(); + + AdHocQueryResult adHocQueryResult = nodeLookup.lookup(AdHocQueryResult.class); + AbstractFile file = null; + BlackboardArtifact artifact; + + /* + * If we have an ad hoc query result, pull the file and artifact objects + * from that. Otherwise, pull them from the lookup. + */ + if (adHocQueryResult != null) { + artifact = adHocQueryResult.getArtifact(); + Content content = adHocQueryResult.getContent(); + if (content instanceof AbstractFile) { + file = (AbstractFile) content; + } + } else { + artifact = nodeLookup.lookup(BlackboardArtifact.class); + file = nodeLookup.lookup(AbstractFile.class); + } + + /* + * First, get text with highlighted hits if this node is for a search + * result. + */ IndexedText highlightedHitText = null; - IndexedText rawContentText = null; - - if (null != content && solrHasContent(content.getId())) { - QueryResults hits = nodeLookup.lookup(QueryResults.class); - BlackboardArtifact artifact = nodeLookup.lookup(BlackboardArtifact.class); - if (hits != null) { + if (adHocQueryResult != null) { + /* + * The node is an ad hoc search result node. + */ + highlightedHitText = new HighlightedText(adHocQueryResult.getSolrObjectId(), adHocQueryResult.getResults()); + } else if (artifact != null) { + if (artifact.getArtifactTypeID() == TSK_KEYWORD_HIT.getTypeID()) { /* - * if there is a QueryReslt object, in the lookup use that. This - * happens when a user selects a row in an ad-hoc search result + * The node is a keyword hit artifact node. */ - highlightedHitText = new HighlightedText(content.getId(), hits); - } else if (artifact != null - && artifact.getArtifactTypeID() == TSK_ACCOUNT.getTypeID()) { try { - // if the artifact is an account artifact, get an account text . - highlightedHitText = getAccountsText(content, nodeLookup); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Failed to create AccountsText for " + content, ex); //NON-NLS - - } - } else if (artifact != null - && artifact.getArtifactTypeID() == TSK_KEYWORD_HIT.getTypeID()) { - try { - //if there is kwh artifact use that to construct the HighlightedText highlightedHitText = new HighlightedText(artifact); } catch (TskCoreException ex) { logger.log(Level.SEVERE, "Failed to create HighlightedText for " + artifact, ex); //NON-NLS } + } else if (artifact.getArtifactTypeID() == TSK_ACCOUNT.getTypeID() && file != null) { + try { + BlackboardAttribute attribute = artifact.getAttribute(TSK_ACCOUNT_TYPE); + if (attribute != null && Account.Type.CREDIT_CARD.getTypeName().equals(attribute.getValueString())) { + /* + * The node is an credit card account node. + */ + highlightedHitText = getAccountsText(file, nodeLookup); + } + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Failed to create AccountsText for " + file, ex); //NON-NLS + } } + } + if (highlightedHitText != null) { + sources.add(highlightedHitText); + } - if (highlightedHitText != null) { - sources.add(highlightedHitText); - } - - /* - * Next, add the "raw" (not highlighted) text, if any, for any - * content associated with the node. - */ - rawContentText = new RawText(content, content.getId()); + /* + * Next, add the "raw" (not highlighted) text, if any, for any file + * associated with the node. + */ + IndexedText rawContentText = null; + if (file != null) { + rawContentText = new RawText(file, file.getId()); sources.add(rawContentText); } @@ -149,22 +172,21 @@ public class ExtractedContentViewer implements DataContentViewer { */ IndexedText rawArtifactText = null; try { - rawArtifactText = getRawArtifactText(nodeLookup); + rawArtifactText = getRawArtifactText(artifact); + if (rawArtifactText != null) { + sources.add(rawArtifactText); + } } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error creating RawText for " + content, ex); //NON-NLS - - } - if (rawArtifactText != null) { - sources.add(rawArtifactText); + logger.log(Level.SEVERE, "Error creating RawText for " + file, ex); //NON-NLS } // Now set the default source to be displayed. - if (null != highlightedHitText) { + if (highlightedHitText != null) { currentSource = highlightedHitText; - } else if (null != rawContentText) { - currentSource = rawContentText; - } else { + } else if (rawArtifactText != null) { currentSource = rawArtifactText; + } else { + currentSource = rawContentText; } // Push the text sources into the panel. @@ -177,16 +199,15 @@ public class ExtractedContentViewer implements DataContentViewer { panel.updateControls(currentSource); String contentName = ""; - if (content != null) { - contentName = content.getName(); + if (file != null) { + contentName = file.getName(); } setPanel(contentName, sources); } - static private IndexedText getRawArtifactText(Lookup nodeLookup) throws TskCoreException { + static private IndexedText getRawArtifactText(BlackboardArtifact artifact) throws TskCoreException { IndexedText rawArtifactText = null; - BlackboardArtifact artifact = nodeLookup.lookup(BlackboardArtifact.class); if (null != artifact) { /* * For keyword hit artifacts, add the text of the artifact that hit, @@ -275,37 +296,69 @@ public class ExtractedContentViewer implements DataContentViewer { } /* - * Is there a credit card or keyword hit artifact in the lookup + * If the lookup of the node contains an ad hoc search result object, + * then there must be indexed text that produced the hit. */ - Collection artifacts = node.getLookup().lookupAll(BlackboardArtifact.class); - if (artifacts != null) { - for (BlackboardArtifact art : artifacts) { - final int artifactTypeID = art.getArtifactTypeID(); - if (artifactTypeID == TSK_ACCOUNT.getTypeID()) { - try { - BlackboardAttribute attribute = art.getAttribute(TSK_ACCOUNT_TYPE); - if (attribute != null && Account.Type.CREDIT_CARD.getTypeName().equals(attribute.getValueString())) { - return true; - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error getting TSK_ACCOUNT_TYPE attribute from artifact " + art.getArtifactID(), ex); + AdHocQueryResult adHocQueryResult = node.getLookup().lookup(AdHocQueryResult.class); + if (adHocQueryResult != null) { + return true; + } + + /* + * If the lookup of the node contains either a keyword hit artifact or + * one to many credit card account artifacts from a credit card account + * numbers search, then there must be indexed text that produced the + * hit(s). + */ + BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class); + if (artifact != null) { + final int artifactTypeID = artifact.getArtifactTypeID(); + if (artifactTypeID == TSK_KEYWORD_HIT.getTypeID()) { + return true; + } else if (artifactTypeID == TSK_ACCOUNT.getTypeID()) { + try { + BlackboardAttribute attribute = artifact.getAttribute(TSK_ACCOUNT_TYPE); + if (attribute != null && Account.Type.CREDIT_CARD.getTypeName().equals(attribute.getValueString())) { + return true; } - } else if (artifactTypeID == TSK_KEYWORD_HIT.getTypeID()) { + } catch (TskCoreException ex) { + /* + * If there is an error, log it and return true. The reason + * for returning true is so that the user will have an + * opportunity to see an error message in the panel when + * this query fails again when setNode is called, instead of + * having an unexpectedly disabled content viewer with no + * other feedback. + */ + logger.log(Level.SEVERE, "Error getting TSK_ACCOUNT_TYPE attribute from artifact " + artifact.getArtifactID(), ex); return true; } } } /* - * No highlighted text for a keyword hit, so is there any indexed text - * at all for this node? + * If the lookup of the node contains an artifact that is neither a + * keyword hit artifact nor a credit card account artifact, check to see + * if there is indexed text for the artifact. */ - long documentID = getDocumentId(node); - if (INVALID_DOCUMENT_ID == documentID) { - return false; + if (artifact != null) { + return solrHasContent(artifact.getArtifactID()); } - return solrHasContent(documentID); + /* + * If the lookup of the node contains no artifacts but does contain a + * file, check to see if there is indexed text for the file. + */ + AbstractFile file = node.getLookup().lookup(AbstractFile.class); + if (file != null) { + return solrHasContent(file.getId()); + } + + /* + * If the lookup of the node contains neither ad hoc search results, nor + * artifacts, nor a file, there is no indexed text. + */ + return false; } @Override @@ -357,57 +410,6 @@ public class ExtractedContentViewer implements DataContentViewer { } } - /** - * Gets the object ID to use as the document ID for accessing any indexed - * text for the given node. - * - * @param node The node. - * - * @return The document ID or zero, which is an invalid document ID. - */ - private Long getDocumentId(Node node) { - /** - * If the node is a Blackboard artifact node for anything other than a - * keyword hit, the document ID for the text extracted from the artifact - * (the concatenation of its attributes) is the artifact ID, a large, - * negative integer. If it is a keyword hit, see if there is an - * associated artifact. If there is, get the associated artifact's ID - * and return it. - */ - BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class); - if (null != artifact) { - if (artifact.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID()) { - return artifact.getArtifactID(); - } else { - try { - // Get the associated artifact attribute and return its value as the ID - BlackboardAttribute blackboardAttribute = artifact.getAttribute(TSK_ASSOCIATED_ARTIFACT_TYPE); - if (blackboardAttribute != null) { - return blackboardAttribute.getValueLong(); - } - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "Error getting associated artifact attributes", ex); //NON-NLS - } - } - } - - /* - * For keyword search hit artifact nodes and all other nodes, the - * document ID for the extracted text is the ID of the associated - * content, if any, unless there is an associated artifact, which is - * handled above. - */ - Content content = node.getLookup().lookup(Content.class); - if (content != null) { - return content.getId(); - } - - /* - * No extracted text, return an invalid docuemnt ID. - */ - return 0L; - } - private class NextFindActionListener implements ActionListener { @Override diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java index 14298a044a..322860416a 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedText.java @@ -68,7 +68,7 @@ class HighlightedText implements IndexedText { final private Server solrServer = KeywordSearch.getServer(); - private final long objectId; + private final long solrObjectId; /* * The keywords to highlight */ @@ -104,14 +104,14 @@ class HighlightedText implements IndexedText { * search results. In that case we have the entire QueryResults object and * need to arrange the paging. * - * @param objectId The objectID of the content whose text will be + * @param solrObjectId The solrObjectId of the content whose text will be * highlighted. * @param QueryResults The QueryResults for the ad-hoc search from whose * results a selection was made leading to this * HighlightedText. */ - HighlightedText(long objectId, QueryResults hits) { - this.objectId = objectId; + HighlightedText(long solrObjectId, QueryResults hits) { + this.solrObjectId = solrObjectId; this.hits = hits; } @@ -127,9 +127,9 @@ class HighlightedText implements IndexedText { this.artifact = artifact; BlackboardAttribute attribute = artifact.getAttribute(TSK_ASSOCIATED_ARTIFACT); if (attribute != null) { - this.objectId = attribute.getValueLong(); + this.solrObjectId = attribute.getValueLong(); } else { - this.objectId = artifact.getObjectID(); + this.solrObjectId = artifact.getObjectID(); } } @@ -143,7 +143,7 @@ class HighlightedText implements IndexedText { return; } - this.numberPages = solrServer.queryNumFileChunks(this.objectId); + this.numberPages = solrServer.queryNumFileChunks(this.solrObjectId); if (artifact != null) { loadPageInfoFromArtifact(); @@ -190,7 +190,7 @@ class HighlightedText implements IndexedText { // Run a query to figure out which chunks for the current object have // hits for this keyword. - chunksQuery.addFilter(new KeywordQueryFilter(FilterType.CHUNK, this.objectId)); + chunksQuery.addFilter(new KeywordQueryFilter(FilterType.CHUNK, this.solrObjectId)); hits = chunksQuery.performQuery(); loadPageInfoFromHits(); @@ -212,7 +212,7 @@ class HighlightedText implements IndexedText { for (KeywordHit hit : hits.getResults(k)) { int chunkID = hit.getChunkId(); if (artifact != null) { - if (chunkID != 0 && this.objectId == hit.getSolrObjectId()) { + if (chunkID != 0 && this.solrObjectId == hit.getSolrObjectId()) { String hit1 = hit.getHit(); if (keywords.stream().anyMatch(hit1::contains)) { numberOfHitsPerPage.put(chunkID, 0); //unknown number of matches in the page @@ -221,7 +221,7 @@ class HighlightedText implements IndexedText { } } } else { - if (chunkID != 0 && this.objectId == hit.getSolrObjectId()) { + if (chunkID != 0 && this.solrObjectId == hit.getSolrObjectId()) { numberOfHitsPerPage.put(chunkID, 0); //unknown number of matches in the page currentHitPerPage.put(chunkID, 0); //set current hit to 0th @@ -350,7 +350,7 @@ class HighlightedText implements IndexedText { SolrQuery q = new SolrQuery(); q.setShowDebugInfo(DEBUG); //debug - String contentIdStr = Long.toString(this.objectId); + String contentIdStr = Long.toString(this.solrObjectId); if (numberPages != 0) { chunkID = Integer.toString(this.currentPage); contentIdStr += "0".equals(chunkID) ? "" : "_" + chunkID; @@ -423,7 +423,7 @@ class HighlightedText implements IndexedText { return "
" + highlightedContent + "
"; //NON-NLS } catch (TskCoreException | KeywordSearchModuleException | NoOpenCoreException ex) { - logger.log(Level.SEVERE, "Error getting highlighted text for Solr doc id " + objectId + ", chunkID " + chunkID + ", highlight query: " + highlightField, ex); //NON-NLS + logger.log(Level.SEVERE, "Error getting highlighted text for Solr doc id " + solrObjectId + ", chunkID " + chunkID + ", highlight query: " + highlightField, ex); //NON-NLS return Bundle.IndexedText_errorMessage_errorGettingText(); } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java index 7674ee5e34..98c7ca0969 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchFilterNode.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-2017 Basis Technology Corp. + * Copyright 2013-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,6 +39,7 @@ import org.sleuthkit.autopsy.actions.AddContentTagAction; import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.directorytree.HashSearchAction; import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; +import org.sleuthkit.autopsy.keywordsearch.KeywordSearchResultFactory.AdHocQueryResult; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.ContentVisitor; @@ -52,12 +53,18 @@ import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.VirtualDirectory; /** - * + * FilterNode containing properties and actions for keyword search. */ class KeywordSearchFilterNode extends FilterNode { - KeywordSearchFilterNode(QueryResults highlights, Node original) { - super(original, null, new ProxyLookup(Lookups.singleton(highlights), original.getLookup())); + /** + * Instantiate a KeywordSearchFilterNode. + * + * @param adHocQueryResult The query content. + * @param original The original source node. + */ + KeywordSearchFilterNode(AdHocQueryResult adHocQueryResult, Node original) { + super(original, null, new ProxyLookup(Lookups.singleton(adHocQueryResult), original.getLookup())); } @Override diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java index a8bc995eae..fbeb4268b7 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java @@ -37,7 +37,6 @@ import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.Node; import org.openide.util.NbBundle; -import org.openide.util.lookup.Lookups; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; @@ -49,6 +48,7 @@ import org.sleuthkit.autopsy.datamodel.KeyValue; import org.sleuthkit.autopsy.datamodel.KeyValueNode; import org.sleuthkit.autopsy.keywordsearch.KeywordSearchResultFactory.KeyValueQueryContent; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW; @@ -66,7 +66,7 @@ import org.sleuthkit.datamodel.TskCoreException; */ class KeywordSearchResultFactory extends ChildFactory { - private static final Logger LOGGER = Logger.getLogger(KeywordSearchResultFactory.class.getName()); + private static final Logger logger = Logger.getLogger(KeywordSearchResultFactory.class.getName()); //common properties (superset of all Node properties) to be displayed as columns static final List COMMON_PROPERTIES @@ -75,10 +75,10 @@ class KeywordSearchResultFactory extends ChildFactory { TSK_KEYWORD, TSK_KEYWORD_REGEXP, TSK_KEYWORD_PREVIEW) - .map(BlackboardAttribute.ATTRIBUTE_TYPE::getDisplayName), + .map(BlackboardAttribute.ATTRIBUTE_TYPE::getDisplayName), Arrays.stream(AbstractAbstractFileNode.AbstractFilePropertyType.values()) - .map(Object::toString)) - .collect(Collectors.toList()); + .map(Object::toString)) + .collect(Collectors.toList()); private final Collection queryRequests; @@ -140,7 +140,7 @@ class KeywordSearchResultFactory extends ChildFactory { try { queryResults = queryRequest.performQuery(); } catch (KeywordSearchModuleException | NoOpenCoreException ex) { - LOGGER.log(Level.SEVERE, "Could not perform the query " + queryRequest.getQueryString(), ex); //NON-NLS + logger.log(Level.SEVERE, "Could not perform the query " + queryRequest.getQueryString(), ex); //NON-NLS MessageNotifyUtil.Notify.error(Bundle.KeywordSearchResultFactory_query_exception_msg() + queryRequest.getQueryString(), ex.getCause().getMessage()); return false; } @@ -148,7 +148,7 @@ class KeywordSearchResultFactory extends ChildFactory { try { tskCase = Case.getCurrentCase().getSleuthkitCase(); } catch (IllegalStateException ex) { - LOGGER.log(Level.SEVERE, "There was no case open.", ex); //NON-NLS + logger.log(Level.SEVERE, "There was no case open.", ex); //NON-NLS return false; } @@ -165,11 +165,11 @@ class KeywordSearchResultFactory extends ChildFactory { try { content = tskCase.getContentById(hit.getContentID()); if (content == null) { - LOGGER.log(Level.SEVERE, "There was a error getting content by id."); //NON-NLS + logger.log(Level.SEVERE, "There was a error getting content by id."); //NON-NLS return false; } } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "There was a error getting content by id.", ex); //NON-NLS + logger.log(Level.SEVERE, "There was a error getting content by id.", ex); //NON-NLS return false; } @@ -188,18 +188,20 @@ class KeywordSearchResultFactory extends ChildFactory { } String hitName; + BlackboardArtifact artifact = null; if (hit.isArtifactHit()) { try { - hitName = tskCase.getBlackboardArtifact(hit.getArtifactID().get()).getDisplayName() + " Artifact"; //NON-NLS + artifact = tskCase.getBlackboardArtifact(hit.getArtifactID().get()); + hitName = artifact.getDisplayName() + " Artifact"; //NON-NLS } catch (TskCoreException ex) { - LOGGER.log(Level.SEVERE, "Error getting blckboard artifact by id", ex); + logger.log(Level.SEVERE, "Error getting blckboard artifact by id", ex); return false; } } else { hitName = contentName; } hitNumber++; - tempList.add(new KeyValueQueryContent(hitName, properties, hitNumber, hit.getSolrObjectId(), content, queryRequest, queryResults)); + tempList.add(new KeyValueQueryContent(hitName, properties, hitNumber, hit.getSolrObjectId(), content, artifact, queryRequest, queryResults)); } @@ -250,13 +252,12 @@ class KeywordSearchResultFactory extends ChildFactory { Node resultNode; if (key instanceof KeyValueQueryContent) { - final Content content = ((KeyValueQueryContent) key).getContent(); - QueryResults hits = ((KeyValueQueryContent) key).getHits(); + AdHocQueryResult adHocQueryResult = new AdHocQueryResult((KeyValueQueryContent) key); - Node kvNode = new KeyValueNode(key, Children.LEAF, Lookups.singleton(content)); + Node kvNode = new KeyValueNode(key, Children.LEAF); //wrap in KeywordSearchFilterNode for the markup content, might need to override FilterNode for more customization - resultNode = new KeywordSearchFilterNode(hits, kvNode); + resultNode = new KeywordSearchFilterNode(adHocQueryResult, kvNode); } else { resultNode = new EmptyNode("This Node Is Empty"); resultNode.setDisplayName(NbBundle.getMessage(this.getClass(), "KeywordSearchResultFactory.createNodeForKey.noResultsFound.text")); @@ -266,6 +267,75 @@ class KeywordSearchResultFactory extends ChildFactory { } + /** + * This class encapsulates content, query results, and an associated Solr + * object ID for storing in the Lookup to be read later. + */ + final class AdHocQueryResult { + + private final long solrObjectId; + private final Content content; + private final BlackboardArtifact artifact; + private final QueryResults results; + + /** + * Instantiate a AdHocQueryResult object. + * + * @param solrObjectId The Solr object ID associated with the object in + * which the hit was found. + * @param content The content for the query result. + * @param artifact The artifact associated with the query result. + * @param results The query results. + */ + AdHocQueryResult(KeyValueQueryContent key) { + this.solrObjectId = key.getSolrObjectId(); + this.content = key.getContent(); + this.artifact = key.getArtifact(); + this.results = key.getHits(); + } + + /** + * Get the Solr object ID associated with the object in which the hit + * was found. This could be a file or an artifact. + * + * @return The Solr object ID. + */ + long getSolrObjectId() { + return solrObjectId; + } + + /** + * Get the content for the query result. This can be either a file or a + * data source, and it may or may not be the content in which the hit + * occurred. If the hit is in a file, the Content object represents that + * file. But if the hit is in an artifact, the Content object represents + * the source file or data source of the artifact. + * + * @return The content object. + */ + Content getContent() { + return content; + } + + /** + * Get the artifact for the query result. + * + * @return The artifact. + */ + BlackboardArtifact getArtifact() { + return artifact; + } + + /** + * Get the query results. + * + * @return The query results. + */ + QueryResults getResults() { + return results; + } + } + /** * Used to display keyword search results in table. Eventually turned into a * node. @@ -275,6 +345,7 @@ class KeywordSearchResultFactory extends ChildFactory { private final long solrObjectId; private final Content content; + private final BlackboardArtifact artifact; private final QueryResults hits; private final KeywordSearchQuery query; @@ -286,15 +357,17 @@ class KeywordSearchResultFactory extends ChildFactory { * @param map Contains content metadata, snippets, etc. * (property map) * @param id User incremented ID - * @param solrObjectId - * @param content File that had the hit. + * @param solrObjectId The ID of the object. + * @param content The content object. + * @param artifact The blackboard artifact. * @param query Query used in search * @param hits Full set of search results (for all files! @@@) */ - KeyValueQueryContent(String name, Map map, int id, long solrObjectId, Content content, KeywordSearchQuery query, QueryResults hits) { + KeyValueQueryContent(String name, Map map, int id, long solrObjectId, Content content, BlackboardArtifact artifact, KeywordSearchQuery query, QueryResults hits) { super(name, map, id); this.solrObjectId = solrObjectId; this.content = content; + this.artifact = artifact; this.hits = hits; this.query = query; @@ -304,6 +377,10 @@ class KeywordSearchResultFactory extends ChildFactory { return content; } + BlackboardArtifact getArtifact() { + return artifact; + } + long getSolrObjectId() { return solrObjectId; } @@ -358,9 +435,9 @@ class KeywordSearchResultFactory extends ChildFactory { try { get(); } catch (InterruptedException | CancellationException ex) { - LOGGER.log(Level.WARNING, "User cancelled writing of ad hoc search query results for '{0}' to the blackboard", query.getQueryString()); //NON-NLS + logger.log(Level.WARNING, "User cancelled writing of ad hoc search query results for '{0}' to the blackboard", query.getQueryString()); //NON-NLS } catch (ExecutionException ex) { - LOGGER.log(Level.SEVERE, "Error writing of ad hoc search query results for " + query.getQueryString() + " to the blackboard", ex); //NON-NLS + logger.log(Level.SEVERE, "Error writing of ad hoc search query results for " + query.getQueryString() + " to the blackboard", ex); //NON-NLS } }