diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java index dea544c416..9c66184b47 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java @@ -37,6 +37,7 @@ import javax.swing.JPanel; import javax.swing.JTextArea; import javax.swing.JToggleButton; import org.openide.util.Lookup; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; import org.sleuthkit.autopsy.datasourceprocessors.RawDSProcessor; import org.sleuthkit.autopsy.coreutils.Logger; @@ -56,8 +57,11 @@ final class AddImageWizardSelectDspVisual extends JPanel { AddImageWizardSelectDspVisual(String lastDspUsed) { initComponents(); selectedDsp = lastDspUsed; + //if the last selected DSP was the Local Disk DSP and it would be disabled then we want to select a different DSP + if ((Case.getCurrentCase().getCaseType() == Case.CaseType.MULTI_USER_CASE) && selectedDsp.equals(LocalDiskDSProcessor.getType())) { + selectedDsp = ImageDSProcessor.getType(); + } createDataSourceProcessorButtons(); - //add actionlistner to listen for change } @@ -87,6 +91,7 @@ final class AddImageWizardSelectDspVisual extends JPanel { return selectedDsp; } + @NbBundle.Messages("AddImageWizardSelectDspVisual.multiUserWarning.text=This type of Data Source Processor is not available in multi-user mode") /** * Create the a button for each DataSourceProcessor that should exist as an * option. @@ -110,6 +115,7 @@ final class AddImageWizardSelectDspVisual extends JPanel { constraints.anchor = GridBagConstraints.LINE_START; Dimension spacerBlockDimension = new Dimension(6, 4); // Space between left edge and button, Space between rows for (String dspType : dspList) { + boolean shouldAddMultiUserWarning = false; constraints.weightx = 1; //Add a spacer Filler spacer = new Filler(spacerBlockDimension, spacerBlockDimension, spacerBlockDimension); @@ -120,6 +126,11 @@ final class AddImageWizardSelectDspVisual extends JPanel { //Add the button JToggleButton dspButton = createDspButton(dspType); dspButton.addActionListener(cbActionListener); + if ((Case.getCurrentCase().getCaseType() == Case.CaseType.MULTI_USER_CASE) && dspType.equals(LocalDiskDSProcessor.getType())){ + dspButton.setEnabled(false); //disable the button for local disk DSP when this is a multi user case + dspButton.setSelected(false); + shouldAddMultiUserWarning = true; + } jPanel1.add(dspButton); buttonGroup1.add(dspButton); gridBagLayout.setConstraints(dspButton, constraints); @@ -130,7 +141,14 @@ final class AddImageWizardSelectDspVisual extends JPanel { jPanel1.add(buttonTextSpacer); constraints.gridx++; //Add the text area serving as a label to the right of the button - JTextArea myLabel = new JTextArea(dspType); + + JTextArea myLabel = new JTextArea(); + if (shouldAddMultiUserWarning) { + myLabel.setText(dspType + " - " + NbBundle.getMessage(this.getClass(), "AddImageWizardSelectDspVisual.multiUserWarning.text")); + myLabel.setEnabled(false); //gray out the text + } else { + myLabel.setText(dspType); + } myLabel.setBackground(new Color(240, 240, 240));//matches background of panel myLabel.setEditable(false); myLabel.setWrapStyleWord(true); @@ -169,12 +187,7 @@ final class AddImageWizardSelectDspVisual extends JPanel { } } dspList.add(ImageDSProcessor.getType()); - if (Case.getCurrentCase().getCaseType() != Case.CaseType.MULTI_USER_CASE) { - dspList.add(LocalDiskDSProcessor.getType()); - } else { - // remove LocalDiskDSProcessor from list of DSPs - datasourceProcessorsMap.remove(LocalDiskDSProcessor.getType()); - } + dspList.add(LocalDiskDSProcessor.getType()); dspList.add(LocalFilesDSProcessor.getType()); dspList.add(RawDSProcessor.getType()); // now add any addtional DSPs that haven't already been added diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 7390868798..05588f5d1f 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -140,8 +140,8 @@ CasePropertiesForm.updateCaseName.msgDlg.empty.msg=The caseName cannot be empty. CasePropertiesForm.updateCaseName.msgDlg.empty.title=Error CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.msg=The Case Name cannot contain any of this following symbol\: \\ / \: * ? " < > | CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.title=Error -CasePropertiesForm.updateCaseName.confMsg.msg=Are you sure want to update the case name from "{0}" to "{1}"? -CasePropertiesForm.updateCaseName.confMsg.title=Create directory +CasePropertiesForm.updateCaseName.confMsg.msg=Are you sure you want to update the case name from "{0}" to "{1}"? +CasePropertiesForm.updateCaseName.confMsg.title=Change Case Name CueBannerPanel.title.text=Open Recent Case GeneralFilter.rawImageDesc.text=Raw Images (*.img, *.dd, *.001, *.aa, *.raw, *.bin) GeneralFilter.encaseImageDesc.text=Encase Images (*.e01) @@ -241,4 +241,4 @@ CasePropertiesPanel.lbDbType.text=Case Type: CasePropertiesPanel.examinerLabel.text=Examiner: CasePropertiesPanel.caseNumberLabel.text=Case Number: CasePropertiesPanel.deleteCaseButton.text=Delete Case -LocalDiskPanel.changeDatabasePathCheckbox.text=Change image path in the case to the VHD upon completion +LocalDiskPanel.changeDatabasePathCheckbox.text=Update case to use VHD file upon completion diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties index 82efd7df67..68605bb1b8 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties @@ -104,8 +104,6 @@ CasePropertiesForm.updateCaseName.msgDlg.empty.msg=\u30b1\u30fc\u30b9\u540d\u306 CasePropertiesForm.updateCaseName.msgDlg.empty.title=\u30a8\u30e9\u30fc CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.msg=\u30b1\u30fc\u30b9\u540d\u306b\u306f\u6b21\u306e\u8a18\u53f7\u3092\u542b\u3081\u307e\u305b\u3093\uff1a\\ / \: * ? " < > | CasePropertiesForm.updateCaseName.msgDlg.invalidSymbols.title=\u30a8\u30e9\u30fc -CasePropertiesForm.updateCaseName.confMsg.msg=\u30b1\u30fc\u30b9\u540d\u3092"{0}"\u304b\u3089"{1}"\u306b\u672c\u5f53\u306b\u66f4\u65b0\u3057\u307e\u3059\u304b\uff1f -CasePropertiesForm.updateCaseName.confMsg.title=\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u4f5c\u6210 CueBannerPanel.title.text=\u6700\u8fd1\u958b\u3044\u305f\u30b1\u30fc\u30b9\u3092\u958b\u304f GeneralFilter.rawImageDesc.text=\u30ed\u30fc\u30a4\u30e1\u30fc\u30b8(*.img, *.dd, *.001, *.aa, *.raw, *.bin) GeneralFilter.encaseImageDesc.text=\u30a8\u30f3\u30b1\u30fc\u30b9\u30a4\u30e1\u30fc\u30b8(*.e01) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index e3ab78bf64..5facca3088 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -568,7 +568,8 @@ public class Case { * * IMPORTANT: This method should not be called in the event dispatch thread * (EDT). - * @throws CaseActionException + * + * @throws CaseActionException */ @Messages({ "# {0} - exception message", "Case.closeException.couldNotCloseCase=Error closing case: {0}", @@ -637,6 +638,7 @@ public class Case { "# {0} - exception message", "Case.deleteException.couldNotDeleteCase=Could not delete case: {0}", "Case.progressIndicatorTitle.deletingCase=Deleting Case", "Case.exceptionMessage.cannotDeleteCurrentCase=Cannot delete current case, it must be closed first", + "Case.progressMessage.checkingForOtherUser=Checking to see if another user has the case open...", "Case.progressMessage.deletingTextIndex=Deleting text index...", "Case.progressMessage.deletingCaseDatabase=Deleting case database...", "Case.exceptionMessage.cancelled=Cancelled by user" @@ -672,7 +674,7 @@ public class Case { * First, acquire an exclusive case directory lock. The case * cannot be deleted if another node has it open. */ - progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks()); + progressIndicator.start(Bundle.Case_progressMessage_checkingForOtherUser()); try (CoordinationService.Lock dirLock = CoordinationService.getInstance().tryGetExclusiveLock(CategoryNode.CASES, metadata.getCaseDirectory())) { assert (null != dirLock); @@ -723,8 +725,9 @@ public class Case { } /** - * Sanitizes the case name for use as a PostgreSQL database name and in - * ActiveMQ event channel (topic) names. + * Cleans up the display name for a case to make a suitable case name for + * use in case direcotry paths, coordination service locks, PostgreSQL + * database names, Active MQ message message channels, etc. * * PostgreSQL: * http://www.postgresql.org/docs/9.4/static/sql-syntax-lexical.html 63 @@ -741,7 +744,7 @@ public class Case { * * @throws org.sleuthkit.autopsy.casemodule.Case.IllegalCaseNameException */ - static String sanitizeCaseName(String caseName) throws IllegalCaseNameException { + public static String displayNameToCaseName(String caseName) throws IllegalCaseNameException { String result; @@ -1604,7 +1607,7 @@ public class Case { "Case.progressIndicatorTitle.creatingCase=Creating Case", "Case.progressIndicatorCancelButton.label=Cancel", "Case.progressMessage.preparing=Preparing...", - "Case.progressMessage.acquiringLocks=Preparing to open case resources.
This may take time if another user is upgrading the case." + "Case.progressMessage.openingCaseResources=Preparing to open case resources.
This may take time if another user is upgrading the case." }) private void open(String caseDir, String caseDisplayName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { /* @@ -1613,7 +1616,7 @@ public class Case { */ String caseName; try { - caseName = sanitizeCaseName(caseDisplayName); + caseName = displayNameToCaseName(caseDisplayName); } catch (IllegalCaseNameException ex) { throw new CaseActionException(Bundle.Case_exceptionMessage_wrapperMessage(Bundle.Case_exceptionMessage_illegalCaseName()), ex); } @@ -1652,7 +1655,7 @@ public class Case { * First, acquire an exclusive case name lock to prevent two * nodes from creating the same case at the same time. */ - progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks()); + progressIndicator.start(Bundle.Case_progressMessage_openingCaseResources()); try (CoordinationService.Lock nameLock = Case.acquireExclusiveCaseNameLock(caseName)) { assert (null != nameLock); /* @@ -1872,7 +1875,7 @@ public class Case { * as long as this node has this case open, in order to prevent * deletion of the case by another node. */ - progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks()); + progressIndicator.start(Bundle.Case_progressMessage_openingCaseResources()); acquireSharedCaseDirLock(caseMetadata.getCaseDirectory()); /* * Next, acquire an exclusive case resources lock to ensure only @@ -2131,6 +2134,7 @@ public class Case { * @param progressIndicator A progress indicator. */ @Messages({ + "Case.progressMessage.closingCaseResources=Preparing to close case resources.
This may take time if another user is upgrading the case.", "Case.progressMessage.notifyingCaseEventSubscribers=Notifying case event subscribers...", "Case.progressMessage.clearingTempDirectory=Clearing case temp directory...", "Case.progressMessage.closingCaseLevelServices=Closing case-level services...", @@ -2139,7 +2143,6 @@ public class Case { "Case.progressMessage.closingCaseDatabase=Closing case database...", "Case.progressMessage.tearingDownTskErrorReporting=Tearing down SleuthKit error reporting..." }) - private void close() throws CaseActionException { /* * Set up either a GUI progress indicator or a logging progress @@ -2172,7 +2175,7 @@ public class Case { * node at a time can create/open/upgrade/close the case * resources. */ - progressIndicator.start(Bundle.Case_progressMessage_acquiringLocks()); + progressIndicator.start(Bundle.Case_progressMessage_closingCaseResources()); try (CoordinationService.Lock resourcesLock = acquireExclusiveCaseResourcesLock(caseMetadata.getCaseName())) { assert (null != resourcesLock); close(progressIndicator); @@ -2406,7 +2409,7 @@ public class Case { * An exception to throw when a case name with invalid characters is * encountered. */ - final static class IllegalCaseNameException extends Exception { + public final static class IllegalCaseNameException extends Exception { private static final long serialVersionUID = 1L; diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java index 737cbe8839..8ceda8fc2a 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/NewCaseWizardPanel1.java @@ -204,17 +204,27 @@ class NewCaseWizardPanel1 implements WizardDescriptor.ValidatingPanel | - if (!Case.isValidName(caseName)) { + if (!Case.isValidName(caseDisplayName)) { String errorMsg = NbBundle .getMessage(this.getClass(), "NewCaseWizardPanel1.validate.errMsg.invalidSymbols"); validationError(errorMsg); } else { + String caseName = ""; + try { + caseName = Case.displayNameToCaseName(caseDisplayName); + } catch (Case.IllegalCaseNameException ex) { + String errorMsg = NbBundle + .getMessage(this.getClass(), "NewCaseWizardPanel1.validate.errMsg.invalidSymbols"); + validationError(errorMsg); + } + + String caseDirPath = caseParentDir + caseName; + // check if the directory exist if (new File(caseDirPath).exists()) { // throw a warning to enter new data or delete the existing directory diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java index 6d57f3daac..ddc0ccd1d5 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/SingleUserCaseConverter.java @@ -177,7 +177,7 @@ public class SingleUserCaseConverter { // Create sanitized names for PostgreSQL and Solr SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss"); //NON-NLS Date date = new Date(); - String dbName = Case.sanitizeCaseName(icd.getNewCaseName()) + "_" + dateFormat.format(date); //NON-NLS + String dbName = Case.displayNameToCaseName(icd.getNewCaseName()) + "_" + dateFormat.format(date); //NON-NLS icd.setPostgreSQLDbName(dbName); // Copy items to new hostname folder structure diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index 2b9fb9f2b8..972951ea99 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -90,6 +90,8 @@ public interface DisplayableItemNodeVisitor { T visit(KeywordHits.ListNode khsn); T visit(KeywordHits.TermNode khmln); + + T visit(KeywordHits.RegExpInstanceNode khmln); T visit(HashsetHits.RootNode hhrn); @@ -280,6 +282,11 @@ public interface DisplayableItemNodeVisitor { public T visit(KeywordHits.ListNode khsn) { return defaultVisit(khsn); } + + @Override + public T visit(KeywordHits.RegExpInstanceNode khsn) { + return defaultVisit(khsn); + } @Override public T visit(KeywordHits.TermNode khmln) { diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java index c7b8983be0..72746663e7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -64,17 +64,24 @@ public class KeywordHits implements AutopsyVisitableItem { public static final String SIMPLE_REGEX_SEARCH = NbBundle .getMessage(KeywordHits.class, "KeywordHits.singleRegexSearch.text"); private final KeywordResults keywordResults; - + private final String DUMMY_INSTANCE = "DUMMY_EXACT_MATCH_INSTANCE"; + public KeywordHits(SleuthkitCase skCase) { this.skCase = skCase; keywordResults = new KeywordResults(); } + + /* All of these maps and code assume the following: + * Regexps will have an 'instance' layer that shows the specific words that matched the regexp + * Exact match and substring will not have the instance layer and instead will have the specific hits + * below their term. + */ private final class KeywordResults extends Observable { - // Map from listName/Type to Map of keyword to set of artifact Ids + // Map from listName/Type to Map of keywords/regexp to Map of instance terms to Set of artifact Ids // NOTE: the map can be accessed by multiple worker threads and needs to be synchronized - private final Map>> topLevelMap = new LinkedHashMap<>(); + private final Map>>> topLevelMap = new LinkedHashMap<>(); KeywordResults() { update(); @@ -98,26 +105,66 @@ public class KeywordHits implements AutopsyVisitableItem { Collections.sort(keywords); return keywords; } - - Set getArtifactIds(String listName, String keyword) { + + List getKeywordInstances(String listName, String keyword) { + List instances; synchronized (topLevelMap) { - return topLevelMap.get(listName).get(keyword); + instances = new ArrayList<>(topLevelMap.get(listName).get(keyword).keySet()); + } + Collections.sort(instances); + return instances; + } + + Set getArtifactIds(String listName, String keyword, String keywordInstance) { + synchronized (topLevelMap) { + return topLevelMap.get(listName).get(keyword).get(keywordInstance); } } + void addRegExpToList(Map>> listMap, String regExp, String word, Long id) { + if (listMap.containsKey(regExp) == false) { + listMap.put(regExp, new LinkedHashMap<>()); + } + Map> instanceMap = listMap.get(regExp); + + // get or create keyword instances entry. + if (instanceMap.containsKey(word) == false) { + instanceMap.put(word, new HashSet<>()); + } + + // add this ID to the instance + instanceMap.get(word).add(id); + } + + void addExactMatchToList(Map>> listMap, String word, Long id) { + if (listMap.containsKey(word) == false) { + listMap.put(word, new LinkedHashMap<>()); + } + Map> instanceMap = listMap.get(word); + + // get or create keyword instances entry. + // for exact match, use a dummy instance + if (instanceMap.containsKey(DUMMY_INSTANCE) == false) { + instanceMap.put(DUMMY_INSTANCE, new HashSet<>()); + } + + // add this ID to the instance + instanceMap.get(DUMMY_INSTANCE).add(id); + } + // populate maps based on artifactIds void populateMaps(Map> artifactIds) { synchronized (topLevelMap) { topLevelMap.clear(); // map of list name to keword to artifact IDs - Map>> listsMap = new LinkedHashMap<>(); + Map>>> listsMap = new LinkedHashMap<>(); - // Map from from literal keyword to artifact IDs - Map> literalMap = new LinkedHashMap<>(); + // Map from from literal keyword to instances (which will be empty) to artifact IDs + Map>> literalMap = new LinkedHashMap<>(); - // Map from regex keyword artifact IDs - Map> regexMap = new LinkedHashMap<>(); + // Map from regex keyword artifact to instances to artifact IDs + Map>> regexMap = new LinkedHashMap<>(); // top-level nodes topLevelMap.put(SIMPLE_LITERAL_SEARCH, literalMap); @@ -131,34 +178,47 @@ public class KeywordHits implements AutopsyVisitableItem { String listName = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID())); String word = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID())); String reg = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID())); - + // new in 4.4 + String kwType = attributes.get(Long.valueOf(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE.getTypeID())); + // part of a list if (listName != null) { + // get or create list entry if (listsMap.containsKey(listName) == false) { - listsMap.put(listName, new LinkedHashMap>()); + listsMap.put(listName, new LinkedHashMap<>()); } - - Map> listMap = listsMap.get(listName); - if (listMap.containsKey(word) == false) { - listMap.put(word, new HashSet()); + Map>> listMap = listsMap.get(listName); + + // substring, treated same as exact match + // Enum for "1" is defined in KeywordSearch.java + if ((kwType != null) && (kwType.equals("1"))) { + // original term should be stored in reg + if (reg != null) { + addExactMatchToList(listMap, reg, id); + } else { + addExactMatchToList(listMap, word, id); + } + } + else if (reg != null) { + addRegExpToList(listMap, reg, word, id); + } else { + addExactMatchToList(listMap, word, id); } - - listMap.get(word).add(id); } // regular expression, single term else if (reg != null) { - if (regexMap.containsKey(reg) == false) { - regexMap.put(reg, new HashSet()); + // substring is treated same as exact + if ((kwType != null) && (kwType.equals("1"))) { + // original term should be stored in reg + addExactMatchToList(literalMap, reg, id); + } else { + addRegExpToList(regexMap, reg, word, id); } - regexMap.get(reg).add(id); } // literal, single term else { - if (literalMap.containsKey(word) == false) { - literalMap.put(word, new HashSet()); - } - literalMap.get(word).add(id); - } - topLevelMap.putAll(listsMap); + addExactMatchToList(literalMap, word, id); + } } + topLevelMap.putAll(listsMap); } setChanged(); @@ -167,35 +227,43 @@ public class KeywordHits implements AutopsyVisitableItem { @SuppressWarnings("deprecation") public void update() { + // maps Artifact ID to map of attribute types to attribute values Map> artifactIds = new LinkedHashMap<>(); if (skCase == null) { return; } + // query attributes table for the ones that we need for the tree int setId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID(); int wordId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD.getTypeID(); int regexId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP.getTypeID(); int artId = BlackboardArtifact.ARTIFACT_TYPE.TSK_KEYWORD_HIT.getTypeID(); - String query = "SELECT blackboard_attributes.value_text,blackboard_attributes.artifact_id," //NON-NLS + String query = "SELECT blackboard_attributes.value_text,blackboard_attributes.value_int32," + + "blackboard_attributes.artifact_id," //NON-NLS + "blackboard_attributes.attribute_type_id FROM blackboard_attributes,blackboard_artifacts WHERE " //NON-NLS + "(blackboard_attributes.artifact_id=blackboard_artifacts.artifact_id AND " //NON-NLS + "blackboard_artifacts.artifact_type_id=" + artId //NON-NLS + ") AND (attribute_type_id=" + setId + " OR " //NON-NLS + "attribute_type_id=" + wordId + " OR " //NON-NLS + + "attribute_type_id=" + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE.getTypeID() + " OR " //NON-NLS + "attribute_type_id=" + regexId + ")"; //NON-NLS try (CaseDbQuery dbQuery = skCase.executeQuery(query)) { ResultSet resultSet = dbQuery.getResultSet(); while (resultSet.next()) { - String value = resultSet.getString("value_text"); //NON-NLS + String valueStr = resultSet.getString("value_text"); //NON-NLS long artifactId = resultSet.getLong("artifact_id"); //NON-NLS long typeId = resultSet.getLong("attribute_type_id"); //NON-NLS if (!artifactIds.containsKey(artifactId)) { artifactIds.put(artifactId, new LinkedHashMap()); } - if (!value.equals("")) { - artifactIds.get(artifactId).put(typeId, value); + if (valueStr != null && !valueStr.equals("")) { + artifactIds.get(artifactId).put(typeId, valueStr); + } else { + // Keyword Search Type is an int + Long valueLong = resultSet.getLong("value_int32"); + artifactIds.get(artifactId).put(typeId, valueLong.toString()); } } } catch (TskCoreException | SQLException ex) { @@ -254,6 +322,9 @@ public class KeywordHits implements AutopsyVisitableItem { } } + /** + * Creates the list nodes + */ private class ListFactory extends ChildFactory.Detachable implements Observer { private final PropertyChangeListener pcl = new PropertyChangeListener() { @@ -344,6 +415,9 @@ public class KeywordHits implements AutopsyVisitableItem { } } + /** + * Represents the keyword search lists (or default groupings if list was not given) + */ public class ListNode extends DisplayableItemNode implements Observer { private final String listName; @@ -360,8 +434,10 @@ public class KeywordHits implements AutopsyVisitableItem { private void updateDisplayName() { int totalDescendants = 0; for (String word : keywordResults.getKeywords(listName)) { - Set ids = keywordResults.getArtifactIds(listName, word); - totalDescendants += ids.size(); + for (String instance : keywordResults.getKeywordInstances(listName, word)) { + Set ids = keywordResults.getArtifactIds(listName, word, instance); + totalDescendants += ids.size(); + } } super.setDisplayName(listName + " (" + totalDescendants + ")"); } @@ -409,6 +485,9 @@ public class KeywordHits implements AutopsyVisitableItem { } } + /** + * Creates the nodes that represent search terms + */ private class TermFactory extends ChildFactory.Detachable implements Observer { private final String setName; @@ -445,13 +524,16 @@ public class KeywordHits implements AutopsyVisitableItem { } } + /** + * Represents the search term or regexp that user searched for + */ public class TermNode extends DisplayableItemNode implements Observer { private final String setName; private final String keyword; public TermNode(String setName, String keyword) { - super(Children.create(new HitsFactory(setName, keyword), true), Lookups.singleton(keyword)); + super(Children.create(new RegExpInstancesFactory(setName, keyword), true), Lookups.singleton(keyword)); super.setName(keyword); this.setName = setName; this.keyword = keyword; @@ -461,7 +543,175 @@ public class KeywordHits implements AutopsyVisitableItem { } private void updateDisplayName() { - super.setDisplayName(keyword + " (" + keywordResults.getArtifactIds(setName, keyword).size() + ")"); + int totalDescendants = 0; + + for (String instance : keywordResults.getKeywordInstances(setName, keyword)) { + Set ids = keywordResults.getArtifactIds(setName, keyword, instance); + totalDescendants += ids.size(); + } + + super.setDisplayName(keyword + " (" + totalDescendants + ")"); + } + + @Override + public void update(Observable o, Object arg) { + updateDisplayName(); + } + + @Override + public boolean isLeafTypeNode() { + List instances = keywordResults.getKeywordInstances(setName, keyword); + // is this an exact match + if (instances.size() == 1 && instances.get(0).equals(DUMMY_INSTANCE)) { + return true; + } + else { + return false; + } + } + + @Override + public T accept(DisplayableItemNodeVisitor v) { + return v.visit(this); + } + + @Override + protected Sheet createSheet() { + Sheet s = super.createSheet(); + Sheet.Set ss = s.get(Sheet.PROPERTIES); + if (ss == null) { + ss = Sheet.createPropertiesSet(); + s.put(ss); + } + + ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.listName.name"), + NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.listName.displayName"), + NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.listName.desc"), + getDisplayName())); + + ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.name"), + NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.displayName"), + NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.desc"), + keywordResults.getKeywordInstances(setName, keyword).size())); + + return s; + } + + @Override + public String getItemType() { + return getClass().getName(); + } + } + + // Allows us to pass in either longs or strings + // as they keys for different types of nodes at the + // same level. Probably a better way to do this, but + // it works. + class RegExpInstanceKey { + private final boolean isRegExp; + private String strKey; + private Long longKey; + public RegExpInstanceKey(String key) { + isRegExp = true; + strKey = key; + } + public RegExpInstanceKey(Long key) { + isRegExp = false; + longKey = key; + } + boolean isRegExp() { + return isRegExp; + } + Long getIdKey() { + return longKey; + } + String getRegExpKey() { + return strKey; + } + } + + /** + * Creates the nodes for a given regexp that represent the specific terms that were found + */ + public class RegExpInstancesFactory extends ChildFactory.Detachable implements Observer { + private final String keyword; + private final String setName; + + public RegExpInstancesFactory(String setName, String keyword) { + super(); + this.setName = setName; + this.keyword = keyword; + } + + @Override + protected void addNotify() { + keywordResults.addObserver(this); + } + + @Override + protected void removeNotify() { + keywordResults.deleteObserver(this); + } + + @Override + protected boolean createKeys(List list) { + List instances = keywordResults.getKeywordInstances(setName, keyword); + // The keys are different depending on what we are displaying. + // regexp get another layer to show instances. + // Exact matches don't. + if ((instances.size() == 1) && (instances.get(0).equals(DUMMY_INSTANCE))) { + for (Long id : keywordResults.getArtifactIds(setName, keyword, DUMMY_INSTANCE) ) { + list.add(new RegExpInstanceKey(id)); + } + } else { + for (String instance : instances) { + list.add(new RegExpInstanceKey(instance)); + } + + } + return true; + } + + @Override + protected Node createNodeForKey(RegExpInstanceKey key) { + // if it isn't not a regexp, then skip the 'instance' layer of the tree + if (key.isRegExp() == false) { + return createBlackboardArtifactNode(key.getIdKey()); + } else { + return new RegExpInstanceNode(setName, keyword, key.getRegExpKey()); + } + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + + } + + /** + * Represents a specific term that was found from a regexp + */ + public class RegExpInstanceNode extends DisplayableItemNode implements Observer { + + private final String setName; + private final String keyword; + private final String instance; + + public RegExpInstanceNode(String setName, String keyword, String instance) { + super(Children.create(new HitsFactory(setName, keyword, instance), true), Lookups.singleton(keyword)); + super.setName(keyword); + this.setName = setName; + this.keyword = keyword; + this.instance = instance; + this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/keyword_hits.png"); //NON-NLS + updateDisplayName(); + keywordResults.addObserver(this); + } + + private void updateDisplayName() { + int totalDescendants = keywordResults.getArtifactIds(setName, keyword, instance).size(); + super.setDisplayName(instance + " (" + totalDescendants + ")"); } @Override @@ -496,7 +746,7 @@ public class KeywordHits implements AutopsyVisitableItem { ss.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.name"), NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.displayName"), NbBundle.getMessage(this.getClass(), "KeywordHits.createSheet.filesWithHits.desc"), - keywordResults.getArtifactIds(setName, keyword).size())); + keywordResults.getKeywordInstances(setName, keyword).size())); return s; } @@ -507,15 +757,76 @@ public class KeywordHits implements AutopsyVisitableItem { } } + /** + * Create a blackboard node for the given Keyword Hit artifact + * @param artifactId + * @return Node or null on error + */ + private BlackboardArtifactNode createBlackboardArtifactNode (Long artifactId) { + if (skCase == null) { + return null; + } + + try { + BlackboardArtifact art = skCase.getBlackboardArtifact(artifactId); + BlackboardArtifactNode n = new BlackboardArtifactNode(art); + AbstractFile file; + try { + file = skCase.getAbstractFileById(art.getObjectID()); + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "TskCoreException while constructing BlackboardArtifact Node from KeywordHitsKeywordChildren"); //NON-NLS + return n; + } + + // It is possible to get a keyword hit on artifacts generated + // for the underlying image in which case MAC times are not + // available/applicable/useful. + if (file == null) { + return n; + } + + n.addNodeProperty(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.modTime.name"), + NbBundle.getMessage(this.getClass(), + "KeywordHits.createNodeForKey.modTime.displayName"), + NbBundle.getMessage(this.getClass(), + "KeywordHits.createNodeForKey.modTime.desc"), + ContentUtils.getStringTime(file.getMtime(), file))); + n.addNodeProperty(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.accessTime.name"), + NbBundle.getMessage(this.getClass(), + "KeywordHits.createNodeForKey.accessTime.displayName"), + NbBundle.getMessage(this.getClass(), + "KeywordHits.createNodeForKey.accessTime.desc"), + ContentUtils.getStringTime(file.getAtime(), file))); + n.addNodeProperty(new NodeProperty<>( + NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.chgTime.name"), + NbBundle.getMessage(this.getClass(), + "KeywordHits.createNodeForKey.chgTime.displayName"), + NbBundle.getMessage(this.getClass(), + "KeywordHits.createNodeForKey.chgTime.desc"), + ContentUtils.getStringTime(file.getCtime(), file))); + return n; + } catch (TskException ex) { + logger.log(Level.WARNING, "TSK Exception occurred", ex); //NON-NLS + } + return null; + } + + /** + * Creates nodes for individual files that had hits + */ public class HitsFactory extends ChildFactory.Detachable implements Observer { private final String keyword; private final String setName; + private final String instance; - public HitsFactory(String setName, String keyword) { + public HitsFactory(String setName, String keyword, String instance) { super(); this.setName = setName; this.keyword = keyword; + this.instance = instance; } @Override @@ -530,60 +841,13 @@ public class KeywordHits implements AutopsyVisitableItem { @Override protected boolean createKeys(List list) { - list.addAll(keywordResults.getArtifactIds(setName, keyword)); + list.addAll(keywordResults.getArtifactIds(setName, keyword, instance)); return true; } @Override protected Node createNodeForKey(Long artifactId) { - if (skCase == null) { - return null; - } - - try { - BlackboardArtifact art = skCase.getBlackboardArtifact(artifactId); - BlackboardArtifactNode n = new BlackboardArtifactNode(art); - AbstractFile file; - try { - file = skCase.getAbstractFileById(art.getObjectID()); - } catch (TskCoreException ex) { - logger.log(Level.SEVERE, "TskCoreException while constructing BlackboardArtifact Node from KeywordHitsKeywordChildren"); //NON-NLS - return n; - } - - // It is possible to get a keyword hit on artifacts generated - // for the underlying image in which case MAC times are not - // available/applicable/useful. - if (file == null) { - return n; - } - - n.addNodeProperty(new NodeProperty<>( - NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.modTime.name"), - NbBundle.getMessage(this.getClass(), - "KeywordHits.createNodeForKey.modTime.displayName"), - NbBundle.getMessage(this.getClass(), - "KeywordHits.createNodeForKey.modTime.desc"), - ContentUtils.getStringTime(file.getMtime(), file))); - n.addNodeProperty(new NodeProperty<>( - NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.accessTime.name"), - NbBundle.getMessage(this.getClass(), - "KeywordHits.createNodeForKey.accessTime.displayName"), - NbBundle.getMessage(this.getClass(), - "KeywordHits.createNodeForKey.accessTime.desc"), - ContentUtils.getStringTime(file.getAtime(), file))); - n.addNodeProperty(new NodeProperty<>( - NbBundle.getMessage(this.getClass(), "KeywordHits.createNodeForKey.chgTime.name"), - NbBundle.getMessage(this.getClass(), - "KeywordHits.createNodeForKey.chgTime.displayName"), - NbBundle.getMessage(this.getClass(), - "KeywordHits.createNodeForKey.chgTime.desc"), - ContentUtils.getStringTime(file.getCtime(), file))); - return n; - } catch (TskException ex) { - logger.log(Level.WARNING, "TSK Exception occurred", ex); //NON-NLS - } - return null; + return createBlackboardArtifactNode(artifactId); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java b/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java index 6d4e46cc42..4957a317b6 100644 --- a/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java +++ b/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java @@ -28,24 +28,25 @@ class ProgressPanel extends javax.swing.JPanel { ProgressPanel() { initComponents(); this.progressBar.setMinimum(0); + this.progressBar.setIndeterminate(true); } void setMessage(String message) { this.progressMessage.setText(message); - } - + } + void setInderminate(boolean indeterminate) { this.progressBar.setIndeterminate(indeterminate); } - + void setMaximum(int max) { this.progressBar.setMaximum(max); } - + void setCurrent(int current) { this.progressBar.setValue(current); } - + /** * 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 diff --git a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetPanel.java b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetPanel.java index 02d3fb82e5..962c18c8e1 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/interestingitems/FilesSetPanel.java @@ -29,7 +29,7 @@ import org.sleuthkit.autopsy.modules.interestingitems.FilesSetDefsPanel.PANEL_TY */ public class FilesSetPanel extends javax.swing.JPanel { - @NbBundle.Messages({"FilesSetPanel.ingest.title=File Ingest Filter", "FilesSetPanel.ingest.createNewFilter=Create/edit file ingest filter(s)...", "FilesSetPanel.ingest.messages.filtersMustBeNamed=File ingest filters must be named."}) + @NbBundle.Messages({"FilesSetPanel.ingest.title=File Ingest Filter", "FilesSetPanel.ingest.createNewFilter=Create/edit file ingest filters...", "FilesSetPanel.ingest.messages.filtersMustBeNamed=File ingest filters must be named."}) private static final String CREATE_NEW_FILE_INGEST_FILTER = Bundle.FilesSetPanel_ingest_createNewFilter(); private final String mustBeNamedErrorText; diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 7e2c34131d..dd370b0534 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -68,6 +68,7 @@ import org.apache.solr.client.solrj.impl.HttpSolrServer; import org.openide.util.Lookup; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case.CaseType; +import org.sleuthkit.autopsy.casemodule.Case.IllegalCaseNameException; import org.sleuthkit.autopsy.casemodule.CaseActionException; import org.sleuthkit.autopsy.casemodule.CaseMetadata; import org.sleuthkit.autopsy.coordinationservice.CoordinationService; @@ -205,7 +206,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang SYS_LOGGER.log(Level.INFO, "Set running with desktop GUI runtime property to false"); } catch (RuntimeProperties.RuntimePropertiesException ex) { SYS_LOGGER.log(Level.SEVERE, "Failed to set running with desktop GUI runtime property to false", ex); - } + } } /** @@ -1922,17 +1923,23 @@ public final class AutoIngestManager extends Observable implements PropertyChang */ private Case openCase() throws CoordinationServiceException, CaseManagementException, InterruptedException { Manifest manifest = currentJob.getManifest(); - String caseName = manifest.getCaseName(); - SYS_LOGGER.log(Level.INFO, "Opening case {0} for {1}", new Object[]{caseName, manifest.getFilePath()}); + String caseDisplayName = manifest.getCaseName(); + String caseName; + try { + caseName = Case.displayNameToCaseName(caseDisplayName); + } catch (IllegalCaseNameException ex) { + throw new CaseManagementException(String.format("Error creating or opening case %s for %s", manifest.getCaseName(), manifest.getFilePath()), ex); + } + SYS_LOGGER.log(Level.INFO, "Opening case {0} ({1}) for {2}", new Object[]{caseDisplayName, caseName, manifest.getFilePath()}); currentJob.setStage(AutoIngestJob.Stage.OPENING_CASE); try { Path caseDirectoryPath = PathUtils.findCaseDirectory(rootOutputDirectory, caseName); if (null != caseDirectoryPath) { - Path metadataFilePath = caseDirectoryPath.resolve(manifest.getCaseName() + CaseMetadata.getFileExtension()); + Path metadataFilePath = caseDirectoryPath.resolve(caseName + CaseMetadata.getFileExtension()); Case.openAsCurrentCase(metadataFilePath.toString()); } else { caseDirectoryPath = PathUtils.createCaseFolderPath(rootOutputDirectory, caseName); - Case.createAsCurrentCase(caseDirectoryPath.toString(), currentJob.getManifest().getCaseName(), "", "", CaseType.MULTI_USER_CASE); + Case.createAsCurrentCase(caseDirectoryPath.toString(), caseName, "", "", CaseType.MULTI_USER_CASE); /* * Sleep a bit before releasing the lock to ensure that the * new case folder is visible on the network. @@ -1945,13 +1952,13 @@ public final class AutoIngestManager extends Observable implements PropertyChang return caseForJob; } catch (CaseActionException ex) { - throw new CaseManagementException(String.format("Error creating or opening case %s for %s", manifest.getCaseName(), manifest.getFilePath()), ex); + throw new CaseManagementException(String.format("Error creating or opening case %s (%s) for %s", manifest.getCaseName(), caseName, manifest.getFilePath()), ex); } catch (IllegalStateException ex) { /* * Deal with the unfortunate fact that Case.getCurrentCase * throws IllegalStateException. */ - throw new CaseManagementException(String.format("Error getting current case %s for %s", manifest.getCaseName(), manifest.getFilePath()), ex); + throw new CaseManagementException(String.format("Error getting current case %s (%s) for %s", caseName, manifest.getCaseName(), manifest.getFilePath()), ex); } } @@ -2344,7 +2351,7 @@ public final class AutoIngestManager extends Observable implements PropertyChang throw new AnalysisStartupException("Ingest manager error starting job", ingestJobStartResult.getStartupException()); } } else { - for (String warning : ingestJobSettings.getWarnings()) { + for (String warning : settingsWarnings) { SYS_LOGGER.log(Level.SEVERE, "Ingest job settings error for {0}: {1}", new Object[]{manifestPath, warning}); } currentJob.setErrorsOccurred(true); diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java index 852ab4714f..9b99e8c13f 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/PathUtils.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2015 Basis Technology Corp. + * Copyright 2013-2017 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,6 +26,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.CaseMetadata; import org.sleuthkit.autopsy.casemodule.GeneralFilter; @@ -45,12 +46,18 @@ final class PathUtils { * @return The path of the case folder, or null if it is not found. */ static Path findCaseDirectory(Path folderToSearch, String caseName) { + String sanitizedCaseName; + try { + sanitizedCaseName = Case.displayNameToCaseName(caseName); + } catch (Case.IllegalCaseNameException unused) { + return null; + } File searchFolder = new File(folderToSearch.toString()); if (!searchFolder.isDirectory()) { return null; } Path caseFolderPath = null; - String[] candidateFolders = searchFolder.list(new CaseFolderFilter(caseName)); + String[] candidateFolders = searchFolder.list(new CaseFolderFilter(sanitizedCaseName)); long mostRecentModified = 0; for (String candidateFolder : candidateFolders) { File file = new File(candidateFolder); diff --git a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java index f85b5a4050..6e6bcf3d8b 100644 --- a/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java +++ b/ImageGallery/src/org/sleuthkit/autopsy/imagegallery/ImageGalleryController.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2013-16 Basis Technology Corp. + * Copyright 2011-17 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -182,7 +182,7 @@ public final class ImageGalleryController implements Executor { return groupManager; } - public DrawableDB getDatabase() { + synchronized public DrawableDB getDatabase() { return db; } @@ -306,7 +306,7 @@ public final class ImageGalleryController implements Executor { "ImageGalleryController.noGroupsDlg.msg6=There are no fully analyzed groups to display:" + " the current Group By setting resulted in no groups, " + "or no groups are fully analyzed but ingest is not running."}) - public void checkForGroups() { + synchronized private void checkForGroups() { if (groupManager.getAnalyzedGroups().isEmpty()) { if (IngestManager.getInstance().isIngestRunning()) { if (listeningEnabled.get() == false) { @@ -951,13 +951,15 @@ public final class ImageGalleryController implements Executor { if (isListeningEnabled()) { if (file.isFile()) { try { - if (ImageGalleryModule.isDrawableAndNotKnown(file)) { - //this file should be included and we don't already know about it from hash sets (NSRL) - queueDBWorkerTask(new UpdateFileTask(file, db)); - } else if (FileTypeUtils.getAllSupportedExtensions().contains(file.getNameExtension())) { - //doing this check results in fewer tasks queued up, and faster completion of db update - //this file would have gotten scooped up in initial grab, but actually we don't need it - queueDBWorkerTask(new RemoveFileTask(file, db)); + synchronized (ImageGalleryController.this) { + if (ImageGalleryModule.isDrawableAndNotKnown(file)) { + //this file should be included and we don't already know about it from hash sets (NSRL) + queueDBWorkerTask(new UpdateFileTask(file, db)); + } else if (FileTypeUtils.getAllSupportedExtensions().contains(file.getNameExtension())) { + //doing this check results in fewer tasks queued up, and faster completion of db update + //this file would have gotten scooped up in initial grab, but actually we don't need it + queueDBWorkerTask(new RemoveFileTask(file, db)); + } } } catch (TskCoreException | FileTypeDetector.FileTypeDetectorInitException ex) { //TODO: What to do here? diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java index a1ce0a7425..19f3801d3d 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/DropdownToolbar.java @@ -159,41 +159,50 @@ class DropdownToolbar extends javax.swing.JPanel { String changed = evt.getPropertyName(); if (changed.equals(Case.Events.CURRENT_CASE.toString())) { if (null != evt.getNewValue()) { + boolean disableSearch = false; /* * A case has been opened. */ try { Server server = KeywordSearch.getServer(); - Index indexInfo = server.getIndexInfo(); - if (server.coreIsOpen() && IndexFinder.getCurrentSolrVersion().equals(indexInfo.getSolrVersion())) { - /* - * Solr version is current, so check the Solr - * schema version and selectively enable the ad - * hoc search UI components. - */ - boolean schemaIsCurrent = IndexFinder.getCurrentSchemaVersion().equals(indexInfo.getSchemaVersion()); - listsButton.setEnabled(schemaIsCurrent); - searchDropButton.setEnabled(true); - dropPanel.setRegexSearchEnabled(schemaIsCurrent); - active = true; - } else { - /* - * Unsupported Solr version, disable the ad hoc - * search UI components. - */ - searchDropButton.setEnabled(false); - listsButton.setEnabled(false); - active = false; + if (server.coreIsOpen() == false) { + disableSearch = true; + } + else { + Index indexInfo = server.getIndexInfo(); + if (IndexFinder.getCurrentSolrVersion().equals(indexInfo.getSolrVersion())) { + /* + * Solr version is current, so check the Solr + * schema version and selectively enable the ad + * hoc search UI components. + */ + boolean schemaIsCurrent = IndexFinder.getCurrentSchemaVersion().equals(indexInfo.getSchemaVersion()); + listsButton.setEnabled(schemaIsCurrent); + searchDropButton.setEnabled(true); + dropPanel.setRegexSearchEnabled(schemaIsCurrent); + active = true; + } else { + /* + * Unsupported Solr version, disable the ad hoc + * search UI components. + */ + disableSearch = true; + } } } catch (KeywordSearchModuleException ex) { /* * Error, disable the ad hoc search UI components. */ logger.log(Level.SEVERE, "Error getting text index info", ex); //NON-NLS + disableSearch = true; + } + + if (disableSearch) { searchDropButton.setEnabled(false); listsButton.setEnabled(false); active = false; } + } else { /* * A case has been closed. diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java index a111bc877a..47c71a8213 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/ExtractedContentViewer.java @@ -327,6 +327,9 @@ public class ExtractedContentViewer implements DataContentViewer { */ private boolean solrHasContent(Long objectId) { final Server solrServer = KeywordSearch.getServer(); + if (solrServer.coreIsOpen() == false) + return false; + try { return solrServer.queryIsIndexed(objectId); } catch (NoOpenCoreException | KeywordSearchModuleException ex) { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java index 65fc900569..b09ad5430e 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearch.java @@ -42,8 +42,9 @@ public class KeywordSearch { private static final Logger TIKA_LOGGER = Logger.getLogger("Tika"); //NON-NLS private static final org.sleuthkit.autopsy.coreutils.Logger logger = org.sleuthkit.autopsy.coreutils.Logger.getLogger(Case.class.getName()); + // @@@ We should move this into TskData (or somewhere) because we are using + // this value in the results tree to display substring differently from regexp (KeywordHit.java) public enum QueryType { - LITERAL, SUBSTRING, REGEX }; public static final String NUM_FILES_CHANGE_EVT = "NUM_FILES_CHANGE_EVT"; //NON-NLS diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryDelegator.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryDelegator.java index af5381ba4b..f2dfb53622 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryDelegator.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchQueryDelegator.java @@ -55,22 +55,7 @@ class KeywordSearchQueryDelegator { for (KeywordList keywordList : keywordLists) { for (Keyword keyword : keywordList.getKeywords()) { - KeywordSearchQuery query; - if (keyword.searchTermIsLiteral()) { - // literal, exact match - if (keyword.searchTermIsWholeWord()) { - query = new LuceneQuery(keywordList, keyword); - query.escape(); - } // literal, substring match - else { - query = new TermsComponentQuery(keywordList, keyword); - query.escape(); - query.setSubstringQuery(); - } - } // regexp - else { - query = new RegexQuery(keywordList, keyword); - } + KeywordSearchQuery query = KeywordSearchUtil.getQueryForKeyword(keyword, keywordList); queryDelegates.add(query); } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchUtil.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchUtil.java index bb2a7a4111..cb272811b1 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchUtil.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchUtil.java @@ -126,6 +126,26 @@ class KeywordSearchUtil { return false; } } + + static KeywordSearchQuery getQueryForKeyword(Keyword keyword, KeywordList keywordList) { + KeywordSearchQuery query = null; + if (keyword.searchTermIsLiteral()) { + // literal, exact match + if (keyword.searchTermIsWholeWord()) { + query = new LuceneQuery(keywordList, keyword); + query.escape(); + } // literal, substring match + else { + query = new TermsComponentQuery(keywordList, keyword); + query.escape(); + query.setSubstringQuery(); + } + } // regexp + else { + query = new RegexQuery(keywordList, keyword); + } + return query; + } /** * Is the Keyword Search list at absPath an XML list? diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java index ce0d07bc78..560a90d0db 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SearchRunner.java @@ -425,14 +425,14 @@ public final class SearchRunner { int keywordsSearched = 0; - for (Keyword keywordQuery : keywords) { + for (Keyword keyword : keywords) { if (this.isCancelled()) { - logger.log(Level.INFO, "Cancel detected, bailing before new keyword processed: {0}", keywordQuery.getSearchTerm()); //NON-NLS + logger.log(Level.INFO, "Cancel detected, bailing before new keyword processed: {0}", keyword.getSearchTerm()); //NON-NLS return null; } - final String queryStr = keywordQuery.getSearchTerm(); - final KeywordList list = keywordToList.get(queryStr); + final String queryStr = keyword.getSearchTerm(); + final KeywordList keywordList = keywordToList.get(queryStr); //new subProgress will be active after the initial query //when we know number of hits to start() with @@ -440,15 +440,7 @@ public final class SearchRunner { subProgresses[keywordsSearched - 1].finish(); } - KeywordSearchQuery keywordSearchQuery = null; - - boolean isRegex = !keywordQuery.searchTermIsLiteral(); - if (isRegex) { - keywordSearchQuery = new RegexQuery(list, keywordQuery); - } else { - keywordSearchQuery = new LuceneQuery(list, keywordQuery); - keywordSearchQuery.escape(); - } + KeywordSearchQuery keywordSearchQuery = KeywordSearchUtil.getQueryForKeyword(keyword, keywordList); // Filtering //limit search to currently ingested data sources @@ -462,14 +454,14 @@ public final class SearchRunner { try { queryResults = keywordSearchQuery.performQuery(); } catch (KeywordSearchModuleException | NoOpenCoreException ex) { - logger.log(Level.SEVERE, "Error performing query: " + keywordQuery.getSearchTerm(), ex); //NON-NLS - MessageNotifyUtil.Notify.error(Bundle.SearchRunner_query_exception_msg() + keywordQuery.getSearchTerm(), ex.getCause().getMessage()); + logger.log(Level.SEVERE, "Error performing query: " + keyword.getSearchTerm(), ex); //NON-NLS + MessageNotifyUtil.Notify.error(Bundle.SearchRunner_query_exception_msg() + keyword.getSearchTerm(), ex.getCause().getMessage()); //no reason to continue with next query if recovery failed //or wait for recovery to kick in and run again later //likely case has closed and threads are being interrupted return null; } catch (CancellationException e) { - logger.log(Level.INFO, "Cancel detected, bailing during keyword query: {0}", keywordQuery.getSearchTerm()); //NON-NLS + logger.log(Level.INFO, "Cancel detected, bailing during keyword query: {0}", keyword.getSearchTerm()); //NON-NLS return null; } @@ -487,14 +479,14 @@ public final class SearchRunner { int totalUnits = newResults.getKeywords().size(); subProgresses[keywordsSearched].start(totalUnits); int unitProgress = 0; - String queryDisplayStr = keywordQuery.getSearchTerm(); + String queryDisplayStr = keyword.getSearchTerm(); if (queryDisplayStr.length() > 50) { queryDisplayStr = queryDisplayStr.substring(0, 49) + "..."; } - subProgresses[keywordsSearched].progress(list.getName() + ": " + queryDisplayStr, unitProgress); + subProgresses[keywordsSearched].progress(keywordList.getName() + ": " + queryDisplayStr, unitProgress); // Create blackboard artifacts - newArtifacts = newResults.writeAllHitsToBlackBoard(null, subProgresses[keywordsSearched], this, list.getIngestMessages()); + newArtifacts = newResults.writeAllHitsToBlackBoard(null, subProgresses[keywordsSearched], this, keywordList.getIngestMessages()); } //if has results diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java index 8e7f89c6c5..63ee08c10c 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/SolrSearchService.java @@ -179,7 +179,7 @@ public class SolrSearchService implements KeywordSearchService, AutopsyService { "SolrSearch.checkingForLatestIndex.msg=Looking for text index with latest Solr and schema version", "SolrSearch.indentifyingIndex.msg=Identifying text index for upgrade", "SolrSearch.copyIndex.msg=Copying existing text index", - "SolrSearch.openCore.msg=Creating/Opening text index", + "SolrSearch.openCore.msg=Opening text index", "SolrSearch.complete.msg=Text index successfully opened"}) public void openCaseResources(CaseContext context) throws AutopsyServiceException { ProgressIndicator progress = context.getProgressIndicator(); diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java index 7ecfdbe35a..c1149eee87 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java @@ -347,6 +347,7 @@ final class TermsComponentQuery implements KeywordSearchQuery { if (originalKeyword.getArtifactAttributeType() != ATTRIBUTE_TYPE.TSK_CARD_NUMBER) { attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm())); attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP, MODULE_NAME, originalKeyword.getSearchTerm())); + try { newArtifact = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT); diff --git a/docs/doxygen-user/images/local-disk-data-source.PNG b/docs/doxygen-user/images/local-disk-data-source.PNG index ed123bb376..d9893b7de7 100644 Binary files a/docs/doxygen-user/images/local-disk-data-source.PNG and b/docs/doxygen-user/images/local-disk-data-source.PNG differ