From 19212bcdf1e69faacb1783a3da83cdda94bf8fc8 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Thu, 16 Mar 2017 13:41:23 -0400 Subject: [PATCH 01/23] SearchRunner and KeywordSearchPanel now use same logic to select proper KeywordSearchQuery for Keyword --- .../KeywordSearchQueryDelegator.java | 17 +---------- .../keywordsearch/KeywordSearchUtil.java | 20 +++++++++++++ .../autopsy/keywordsearch/SearchRunner.java | 30 +++++++------------ 3 files changed, 32 insertions(+), 35 deletions(-) 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 From 54e2c86c1dc89f14f5689e568f2daeb2b9af5e1a Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Wed, 22 Mar 2017 15:35:51 -0400 Subject: [PATCH 02/23] debugging --- .../autopsy/keywordsearch/TermsComponentQuery.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java index 7ecfdbe35a..387281c584 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java @@ -281,6 +281,7 @@ final class TermsComponentQuery implements KeywordSearchQuery { * Do a term query for each term that matched the regex. */ QueryResults results = new QueryResults(this); + Set termHits = new HashSet<>(); // ELDEBUG for (Term term : terms) { /* * If searching for credit card account numbers, do a Luhn check on @@ -309,12 +310,15 @@ final class TermsComponentQuery implements KeywordSearchQuery { LuceneQuery termQuery = new LuceneQuery(keywordList, new Keyword(escapedTerm, true)); filters.forEach(termQuery::addFilter); // This appears to be unused QueryResults termQueryResult = termQuery.performQuery(); - Set termHits = new HashSet<>(); + // ELDEBUG Set termHits = new HashSet<>(); for (Keyword word : termQueryResult.getKeywords()) { termHits.addAll(termQueryResult.getResults(word)); } - results.addResult(new Keyword(term.getTerm(), false), new ArrayList<>(termHits)); + // ELDEBUG +// results.addResult(new Keyword(term.getTerm(), false), new ArrayList<>(termHits)); } + // ELDEBUG + results.addResult(new Keyword(originalKeyword.getSearchTerm(), false), new ArrayList<>(termHits)); return results; } @@ -347,6 +351,9 @@ 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())); + + // add original substring search keyword + //attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm())); try { newArtifact = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT); From 690fff3c0c465775c7891c99b100da1af26165e4 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 22 Mar 2017 16:54:53 -0400 Subject: [PATCH 03/23] 2457-removed parens around s in Create/edit filters option --- .../autopsy/modules/interestingitems/FilesSetPanel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From 177fbfb26144c1e46fd40fd0d392772802543f57 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 22 Mar 2017 17:10:02 -0400 Subject: [PATCH 04/23] Fix case properties GUI text --- Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties | 4 ++-- .../src/org/sleuthkit/autopsy/casemodule/Bundle_ja.properties | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 7390868798..5b3313b1fd 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) 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) From dfe7d074b04b8ab56e461610dc2d979cad00711f Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Wed, 22 Mar 2017 17:20:13 -0400 Subject: [PATCH 05/23] Fix inaccurate case operation progress messages --- .../sleuthkit/autopsy/casemodule/Case.java | 216 ++++++++---------- 1 file changed, 99 insertions(+), 117 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index e3ab78bf64..b5dabeda18 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -402,7 +402,7 @@ public class Case { /** * Adds a subscriber to specific case events. * - * @param eventName The event the subscriber is interested in. + * @param eventName The event the subscriber is interested in. * @param subscriber The subscriber (PropertyChangeListener) to add. */ public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) { @@ -412,7 +412,7 @@ public class Case { /** * Removes a subscriber to specific case events. * - * @param eventName The event the subscriber is no longer interested in. + * @param eventName The event the subscriber is no longer interested in. * @param subscriber The subscriber (PropertyChangeListener) to remove. */ public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) { @@ -449,21 +449,19 @@ public class Case { * IMPORTANT: This method should not be called in the event dispatch thread * (EDT). * - * @param caseDir The full path of the case directory. The directory - * will be created if it doesn't already exist; if it - * exists, it is ASSUMED it was created by calling - * createCaseDirectory. + * @param caseDir The full path of the case directory. The directory will be + * created if it doesn't already exist; if it exists, it is ASSUMED it was + * created by calling createCaseDirectory. * @param caseDisplayName The display name of case, which may be changed - * later by the user. - * @param caseNumber The case number, can be the empty string. - * @param examiner The examiner to associate with the case, can be - * the empty string. - * @param caseType The type of case (single-user or multi-user). + * later by the user. + * @param caseNumber The case number, can be the empty string. + * @param examiner The examiner to associate with the case, can be the empty + * string. + * @param caseType The type of case (single-user or multi-user). * * @throws CaseActionException if there is a problem creating the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * exception will have a user-friendly message and may be a wrapper for a + * lower-level exception. */ @Messages({ "# {0} - exception message", "Case.exceptionMessage.wrapperMessage={0}" @@ -503,9 +501,8 @@ public class Case { * @param caseMetadataFilePath The path of the case metadata (.aut) file. * * @throws CaseActionException if there is a problem opening the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * exception will have a user-friendly message and may be a wrapper for a + * lower-level exception. */ @Messages({ "# {0} - exception message", "Case.openException.couldNotOpenCase=Could not open case: {0}", @@ -568,7 +565,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}", @@ -607,9 +605,8 @@ public class Case { * (EDT). * * @throws CaseActionException if there is a problem deleting the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * exception will have a user-friendly message and may be a wrapper for a + * lower-level exception. */ public static void deleteCurrentCase() throws CaseActionException { synchronized (currentCaseWriteLock) { @@ -629,14 +626,14 @@ public class Case { * @param metadata The metadata for the case to delete. * * @throws CaseActionException if there is a problem deleting the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * exception will have a user-friendly message and may be a wrapper for a + * lower-level exception. */ @Messages({ "# {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 +669,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); @@ -777,7 +774,7 @@ public class Case { /** * Creates a case directory and its subdirectories. * - * @param caseDir Path to the case directory (typically base + case name). + * @param caseDir Path to the case directory (typically base + case name). * @param caseType The type of case, single-user or multi-user. * * @throws CaseActionException throw if could not create the case dir @@ -906,7 +903,7 @@ public class Case { * Deletes the case directory of a deleted case and removes the case form * the Recent Cases menu. * - * @param metadata The case metadata. + * @param metadata The case metadata. * @param progressIndicator A progress indicator. */ @Messages({ @@ -935,12 +932,12 @@ public class Case { * Acquires an exclusive case name lock. * * @param caseName The case name (not the case display name, which can be - * changed by a user). + * changed by a user). * * @return The lock. * * @throws CaseActionException with a user-friendly message if the lock - * cannot be acquired. + * cannot be acquired. */ @Messages({"Case.creationException.couldNotAcquireNameLock=Failed to get lock on case name"}) private static CoordinationService.Lock acquireExclusiveCaseNameLock(String caseName) throws CaseActionException { @@ -960,12 +957,12 @@ public class Case { * Acquires an exclusive case resources lock. * * @param caseName The case name (not the case display name, which can be - * changed by a user). + * changed by a user). * * @return The lock. * * @throws CaseActionException with a user-friendly message if the lock - * cannot be acquired. + * cannot be acquired. */ @Messages({"Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"}) private static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseName) throws CaseActionException { @@ -1319,7 +1316,7 @@ public class Case { * the case directory, creating it if it does not exist. * * @return The path to the module output directory, relative to the case - * directory. + * directory. */ public String getModuleOutputDirectoryRelativePath() { Path path = Paths.get(getModuleDirectory()); @@ -1336,8 +1333,7 @@ public class Case { * @return A list of data sources. * * @throws org.sleuthkit.datamodel.TskCoreException if there is a problem - * querying the case - * database. + * querying the case database. */ public List getDataSources() throws TskCoreException { List list = caseDb.getRootObjects(); @@ -1410,8 +1406,8 @@ public class Case { * This should not be called from the event dispatch thread (EDT) * * @param eventId A unique identifier for the event. This UUID must be used - * to call notifyFailedAddingDataSource or - * notifyNewDataSource after the data source is added. + * to call notifyFailedAddingDataSource or notifyNewDataSource after the + * data source is added. */ public void notifyAddingDataSource(UUID eventId) { eventPublisher.publish(new AddingDataSourceEvent(eventId)); @@ -1424,8 +1420,7 @@ public class Case { * This should not be called from the event dispatch thread (EDT) * * @param addingDataSourceEventId The unique identifier for the - * corresponding adding data source event - * (see notifyAddingDataSource). + * corresponding adding data source event (see notifyAddingDataSource). */ public void notifyFailedAddingDataSource(UUID addingDataSourceEventId) { eventPublisher.publish(new AddingDataSourceFailedEvent(addingDataSourceEventId)); @@ -1437,10 +1432,9 @@ public class Case { * * This should not be called from the event dispatch thread (EDT) * - * @param dataSource The data source. + * @param dataSource The data source. * @param addingDataSourceEventId The unique identifier for the - * corresponding adding data source event - * (see notifyAddingDataSource). + * corresponding adding data source event (see notifyAddingDataSource). */ public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) { eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId)); @@ -1493,13 +1487,13 @@ public class Case { /** * Adds a report to the case. * - * @param localPath The path of the report file, must be in the case - * directory or one of its subdirectories. + * @param localPath The path of the report file, must be in the case + * directory or one of its subdirectories. * @param srcModuleName The name of the module that created the report. - * @param reportName The report name, may be empty. + * @param reportName The report name, may be empty. * * @throws TskCoreException if there is a problem adding the report to the - * case database. + * case database. */ public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException { String normalizedLocalPath; @@ -1519,7 +1513,7 @@ public class Case { * @return A collection of report objects. * * @throws TskCoreException if there is a problem querying the case - * database. + * database. */ public List getAllReports() throws TskCoreException { return this.caseDb.getAllReports(); @@ -1553,9 +1547,9 @@ public class Case { * Updates the case display name name. * * @param oldCaseName The old case name. - * @param oldPath The old path name. + * @param oldPath The old path name. * @param newCaseName The new case name. - * @param newPath The new case path. + * @param newPath The new case path. */ void updateCaseName(String oldCaseName, String oldPath, String newCaseName, String newPath) throws CaseActionException { try { @@ -1583,28 +1577,26 @@ public class Case { } /** - * @param caseDir The full path of the case directory. The directory - * will be created if it doesn't already exist; if it - * exists, it is ASSUMED it was created by calling - * createCaseDirectory. + * @param caseDir The full path of the case directory. The directory will be + * created if it doesn't already exist; if it exists, it is ASSUMED it was + * created by calling createCaseDirectory. * @param caseDisplayName The display name of case, which may be changed - * later by the user. - * @param caseNumber The case number, can be the empty string. - * @param examiner The examiner to associate with the case, can be - * the empty string. - * @param caseType The type of case (single-user or multi-user). + * later by the user. + * @param caseNumber The case number, can be the empty string. + * @param examiner The examiner to associate with the case, can be the empty + * string. + * @param caseType The type of case (single-user or multi-user). * * @throws CaseActionException if there is a problem creating the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * exception will have a user-friendly message and may be a wrapper for a + * lower-level exception. */ @Messages({ "Case.exceptionMessage.illegalCaseName=Case name contains illegal characters.", "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 { /* @@ -1652,7 +1644,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); /* @@ -1718,21 +1710,19 @@ public class Case { /** * Creates and opens a new case. * - * @param caseDir The full path of the case directory. The directory - * will be created if it doesn't already exist; if it - * exists, it is ASSUMED it was created by calling - * createCaseDirectory. + * @param caseDir The full path of the case directory. The directory will be + * created if it doesn't already exist; if it exists, it is ASSUMED it was + * created by calling createCaseDirectory. * @param caseDisplayName The display name of case, which may be changed - * later by the user. - * @param caseNumber The case number, can be the empty string. - * @param examiner The examiner to associate with the case, can be - * the empty string. - * @param caseType The type of case (single-user or multi-user). + * later by the user. + * @param caseNumber The case number, can be the empty string. + * @param examiner The examiner to associate with the case, can be the empty + * string. + * @param caseType The type of case (single-user or multi-user). * * @throws CaseActionException if there is a problem creating the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * exception will have a user-friendly message and may be a wrapper for a + * lower-level exception. */ @Messages({ "Case.progressMessage.creatingCaseDirectory=Creating case directory...", @@ -1819,9 +1809,8 @@ public class Case { * @param caseMetadataFilePath The apth to the case metadata file. * * @throws CaseActionException if there is a problem creating the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * exception will have a user-friendly message and may be a wrapper for a + * lower-level exception. */ private void open(Path caseMetadataFilePath) throws CaseActionException { /* @@ -1872,7 +1861,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 @@ -1934,9 +1923,8 @@ public class Case { * @param progressIndicator A progress indicator. * * @throws CaseActionException if there is a problem opening the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * exception will have a user-friendly message and may be a wrapper for a + * lower-level exception. */ @Messages({ "Case.progressMessage.openingCaseDatabase=Opening case database...", @@ -2131,6 +2119,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 +2128,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 +2160,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); @@ -2332,7 +2320,7 @@ public class Case { * @param caseDir The full path of the case directory. * * @throws CaseActionException with a user-friendly message if the lock - * cannot be acquired. + * cannot be acquired. */ @Messages({"Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory."}) private void acquireSharedCaseDirLock(String caseDir) throws CaseActionException { @@ -2425,7 +2413,7 @@ public class Case { * characters is encountered. * * @param message The exception message. - * @param cause The exceptin cause. + * @param cause The exceptin cause. */ IllegalCaseNameException(String message, Throwable cause) { super(message, cause); @@ -2435,20 +2423,18 @@ public class Case { /** * Creates a new, single-user Autopsy case. * - * @param caseDir The full path of the case directory. The directory - * will be created if it doesn't already exist; if it - * exists, it is ASSUMED it was created by calling - * createCaseDirectory. + * @param caseDir The full path of the case directory. The directory will be + * created if it doesn't already exist; if it exists, it is ASSUMED it was + * created by calling createCaseDirectory. * @param caseDisplayName The display name of case, which may be changed - * later by the user. - * @param caseNumber The case number, can be the empty string. - * @param examiner The examiner to associate with the case, can be - * the empty string. + * later by the user. + * @param caseNumber The case number, can be the empty string. + * @param examiner The examiner to associate with the case, can be the empty + * string. * * @throws CaseActionException if there is a problem creating the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * exception will have a user-friendly message and may be a wrapper for a + * lower-level exception. * @deprecated Use createAsCurrentCase instead. */ @Deprecated @@ -2459,21 +2445,19 @@ public class Case { /** * Creates a new Autopsy case and makes it the current case. * - * @param caseDir The full path of the case directory. The directory - * will be created if it doesn't already exist; if it - * exists, it is ASSUMED it was created by calling - * createCaseDirectory. + * @param caseDir The full path of the case directory. The directory will be + * created if it doesn't already exist; if it exists, it is ASSUMED it was + * created by calling createCaseDirectory. * @param caseDisplayName The display name of case, which may be changed - * later by the user. - * @param caseNumber The case number, can be the empty string. - * @param examiner The examiner to associate with the case, can be - * the empty string. - * @param caseType The type of case (single-user or multi-user). + * later by the user. + * @param caseNumber The case number, can be the empty string. + * @param examiner The examiner to associate with the case, can be the empty + * string. + * @param caseType The type of case (single-user or multi-user). * * @throws CaseActionException if there is a problem creating the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * exception will have a user-friendly message and may be a wrapper for a + * lower-level exception. * @deprecated Use createAsCurrentCase instead. */ @Deprecated @@ -2487,9 +2471,8 @@ public class Case { * @param caseMetadataFilePath The path of the case metadata (.aut) file. * * @throws CaseActionException if there is a problem opening the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * exception will have a user-friendly message and may be a wrapper for a + * lower-level exception. * @deprecated Use openAsCurrentCase instead. */ @Deprecated @@ -2501,9 +2484,8 @@ public class Case { * Closes this Autopsy case. * * @throws CaseActionException if there is a problem closing the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. + * exception will have a user-friendly message and may be a wrapper for a + * lower-level exception. * @deprecated Use closeCurrentCase instead. */ @Deprecated @@ -2625,8 +2607,8 @@ public class Case { * Adds an image to the current case after it has been added to the DB. * Sends out event and reopens windows if needed. * - * @param imgPath The path of the image file. - * @param imgId The ID of the image. + * @param imgPath The path of the image file. + * @param imgId The ID of the image. * @param timeZone The time zone of the image. * * @return @@ -2661,7 +2643,7 @@ public class Case { /** * Deletes reports from the case. * - * @param reports Collection of Report to be deleted from the case. + * @param reports Collection of Report to be deleted from the case. * @param deleteFromDisk No longer supported - ignored. * * @throws TskCoreException From 89bcf3dcb0099f2b2b15ab106e41a6e4d329016e Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Wed, 22 Mar 2017 17:45:56 -0400 Subject: [PATCH 06/23] Changes how KWS list substring search results are displayed --- .../autopsy/datamodel/KeywordHits.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java index c7b8983be0..dc3cdc115a 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -139,11 +139,21 @@ public class KeywordHits implements AutopsyVisitableItem { } Map> listMap = listsMap.get(listName); - if (listMap.containsKey(word) == false) { - listMap.put(word, new HashSet()); - } + if (reg == null) { + // litteral search result, use keyword as display name + if (listMap.containsKey(word) == false) { + listMap.put(word, new HashSet()); + } - listMap.get(word).add(id); + listMap.get(word).add(id); + } else { + // regular expression or substring search result, use regular expression as display name. + // this way all hits for the regular expression or substring will be grouped together. + if (listMap.containsKey(reg) == false) { + listMap.put(reg, new HashSet()); + } + listMap.get(reg).add(id); + } } // regular expression, single term else if (reg != null) { if (regexMap.containsKey(reg) == false) { From c91ddf8650f1b4f80afe2431208b001dcce29797 Mon Sep 17 00:00:00 2001 From: Eugene Livis Date: Wed, 22 Mar 2017 17:46:40 -0400 Subject: [PATCH 07/23] Changes to TermsComponentQuery --- .../autopsy/keywordsearch/TermsComponentQuery.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java index 387281c584..c1149eee87 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermsComponentQuery.java @@ -281,7 +281,6 @@ final class TermsComponentQuery implements KeywordSearchQuery { * Do a term query for each term that matched the regex. */ QueryResults results = new QueryResults(this); - Set termHits = new HashSet<>(); // ELDEBUG for (Term term : terms) { /* * If searching for credit card account numbers, do a Luhn check on @@ -310,15 +309,12 @@ final class TermsComponentQuery implements KeywordSearchQuery { LuceneQuery termQuery = new LuceneQuery(keywordList, new Keyword(escapedTerm, true)); filters.forEach(termQuery::addFilter); // This appears to be unused QueryResults termQueryResult = termQuery.performQuery(); - // ELDEBUG Set termHits = new HashSet<>(); + Set termHits = new HashSet<>(); for (Keyword word : termQueryResult.getKeywords()) { termHits.addAll(termQueryResult.getResults(word)); } - // ELDEBUG -// results.addResult(new Keyword(term.getTerm(), false), new ArrayList<>(termHits)); + results.addResult(new Keyword(term.getTerm(), false), new ArrayList<>(termHits)); } - // ELDEBUG - results.addResult(new Keyword(originalKeyword.getSearchTerm(), false), new ArrayList<>(termHits)); return results; } @@ -352,8 +348,6 @@ final class TermsComponentQuery implements KeywordSearchQuery { 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())); - // add original substring search keyword - //attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm())); try { newArtifact = hit.getContent().newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT); From 099e3dda47989aa6c13c456ffc6a0f1d2a30b6e0 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 22 Mar 2017 18:01:34 -0400 Subject: [PATCH 08/23] 2463 disable instead of hide local disk DSP when multi-user --- .../AddImageWizardSelectDspVisual.java | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java index dea544c416..7096ae9642 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; @@ -49,6 +50,7 @@ final class AddImageWizardSelectDspVisual extends JPanel { private static final Logger logger = Logger.getLogger(AddImageWizardSelectDspVisual.class.getName()); private String selectedDsp; + private int indexOfLocalDiskDSP = -1; /** * Creates new form SelectDataSourceProcessorPanel @@ -87,6 +89,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. @@ -108,8 +111,10 @@ final class AddImageWizardSelectDspVisual extends JPanel { constraints.gridy = 0; constraints.weighty = 0; constraints.anchor = GridBagConstraints.LINE_START; + boolean isMultiUserCase = Case.getCurrentCase().getCaseType() == Case.CaseType.MULTI_USER_CASE; 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 +125,11 @@ final class AddImageWizardSelectDspVisual extends JPanel { //Add the button JToggleButton dspButton = createDspButton(dspType); dspButton.addActionListener(cbActionListener); + if (isMultiUserCase && dspList.indexOf(dspType) == indexOfLocalDiskDSP) { + 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 +140,15 @@ 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 @@ -183,6 +196,7 @@ final class AddImageWizardSelectDspVisual extends JPanel { dspList.add(dspType); } } + indexOfLocalDiskDSP = dspList.indexOf(LocalDiskDSProcessor.getType()); return dspList; } From 40ab73615b4e4935bee2596856f008c78f0fcb89 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 22 Mar 2017 18:04:09 -0400 Subject: [PATCH 09/23] 2452 - Updated text for switching to VHD upon completion of image writer --- Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties index 7390868798..05c2693128 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Bundle.properties @@ -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 From eef0042f5ad9bc5946597daf5283bad0c19ee555 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 22 Mar 2017 18:16:12 -0400 Subject: [PATCH 10/23] 2463 - Change default selection when selected DSP would be disabled --- .../casemodule/AddImageWizardSelectDspVisual.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java index 7096ae9642..f7d83edf3b 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java @@ -51,6 +51,7 @@ final class AddImageWizardSelectDspVisual extends JPanel { private static final Logger logger = Logger.getLogger(AddImageWizardSelectDspVisual.class.getName()); private String selectedDsp; private int indexOfLocalDiskDSP = -1; + boolean isMultiUserCase; /** * Creates new form SelectDataSourceProcessorPanel @@ -58,8 +59,12 @@ final class AddImageWizardSelectDspVisual extends JPanel { AddImageWizardSelectDspVisual(String lastDspUsed) { initComponents(); selectedDsp = lastDspUsed; + isMultiUserCase = Case.getCurrentCase().getCaseType() == Case.CaseType.MULTI_USER_CASE; + //if the last selected DSP was the Local Disk DSP and it would be disabled then we want to select a different DSP + if (isMultiUserCase && selectedDsp.equals(LocalDiskDSProcessor.getType())) { + selectedDsp = ImageDSProcessor.getType(); + } createDataSourceProcessorButtons(); - //add actionlistner to listen for change } @@ -111,7 +116,6 @@ final class AddImageWizardSelectDspVisual extends JPanel { constraints.gridy = 0; constraints.weighty = 0; constraints.anchor = GridBagConstraints.LINE_START; - boolean isMultiUserCase = Case.getCurrentCase().getCaseType() == Case.CaseType.MULTI_USER_CASE; Dimension spacerBlockDimension = new Dimension(6, 4); // Space between left edge and button, Space between rows for (String dspType : dspList) { boolean shouldAddMultiUserWarning = false; @@ -140,13 +144,12 @@ 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(); + + JTextArea myLabel = new JTextArea(); if (shouldAddMultiUserWarning) { myLabel.setText(dspType + " - " + NbBundle.getMessage(this.getClass(), "AddImageWizardSelectDspVisual.multiUserWarning.text")); myLabel.setEnabled(false); //gray out the text - } - else { + } else { myLabel.setText(dspType); } myLabel.setBackground(new Color(240, 240, 240));//matches background of panel From da3dceaa9742128b187d17af4cb9d1205d57f8f4 Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Wed, 22 Mar 2017 19:04:58 -0400 Subject: [PATCH 11/23] 2463-Simplified logic for disabling Local Disk DSP when Multi-User --- .../autopsy/casemodule/AddImageWizardSelectDspVisual.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java index f7d83edf3b..9c66184b47 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/AddImageWizardSelectDspVisual.java @@ -50,8 +50,6 @@ final class AddImageWizardSelectDspVisual extends JPanel { private static final Logger logger = Logger.getLogger(AddImageWizardSelectDspVisual.class.getName()); private String selectedDsp; - private int indexOfLocalDiskDSP = -1; - boolean isMultiUserCase; /** * Creates new form SelectDataSourceProcessorPanel @@ -59,9 +57,8 @@ final class AddImageWizardSelectDspVisual extends JPanel { AddImageWizardSelectDspVisual(String lastDspUsed) { initComponents(); selectedDsp = lastDspUsed; - isMultiUserCase = Case.getCurrentCase().getCaseType() == Case.CaseType.MULTI_USER_CASE; //if the last selected DSP was the Local Disk DSP and it would be disabled then we want to select a different DSP - if (isMultiUserCase && selectedDsp.equals(LocalDiskDSProcessor.getType())) { + if ((Case.getCurrentCase().getCaseType() == Case.CaseType.MULTI_USER_CASE) && selectedDsp.equals(LocalDiskDSProcessor.getType())) { selectedDsp = ImageDSProcessor.getType(); } createDataSourceProcessorButtons(); @@ -129,7 +126,7 @@ final class AddImageWizardSelectDspVisual extends JPanel { //Add the button JToggleButton dspButton = createDspButton(dspType); dspButton.addActionListener(cbActionListener); - if (isMultiUserCase && dspList.indexOf(dspType) == indexOfLocalDiskDSP) { + 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; @@ -199,7 +196,6 @@ final class AddImageWizardSelectDspVisual extends JPanel { dspList.add(dspType); } } - indexOfLocalDiskDSP = dspList.indexOf(LocalDiskDSProcessor.getType()); return dspList; } From 1ae5b3b2686f1f8ac0f61ff87e3bfcb78fac0d3d Mon Sep 17 00:00:00 2001 From: millmanorama Date: Thu, 23 Mar 2017 11:38:23 +0100 Subject: [PATCH 12/23] synchronize access to DrawableDB --- .../imagegallery/ImageGalleryController.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) 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? From 1b457f3d03df9940dea75107eb0df5d1b0f4f876 Mon Sep 17 00:00:00 2001 From: Ann Priestman Date: Thu, 23 Mar 2017 10:29:21 -0400 Subject: [PATCH 13/23] Changed working on local disk panel screenshot --- .../images/local-disk-data-source.PNG | Bin 43168 -> 43007 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/doxygen-user/images/local-disk-data-source.PNG b/docs/doxygen-user/images/local-disk-data-source.PNG index ed123bb37672c076d625b462901b1ca83a48eab5..d9893b7de7db8dd400bb022c9a71a5b504fa9f32 100644 GIT binary patch delta 22654 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 AOaK4? delta 22764 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

e!}#;achpwWUjJ)CNyf!c>4eDVSWTQhr;z&V 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 Date: Thu, 23 Mar 2017 17:46:59 -0400 Subject: [PATCH 14/23] Add setIndeterminate to ProgressPanel ctor --- .../sleuthkit/autopsy/casemodule/Case.java | 200 ++++++++++-------- .../autopsy/framework/ProgressPanel.java | 1 + 2 files changed, 111 insertions(+), 90 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index b5dabeda18..b7d52d5c46 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -402,7 +402,7 @@ public class Case { /** * Adds a subscriber to specific case events. * - * @param eventName The event the subscriber is interested in. + * @param eventName The event the subscriber is interested in. * @param subscriber The subscriber (PropertyChangeListener) to add. */ public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) { @@ -412,7 +412,7 @@ public class Case { /** * Removes a subscriber to specific case events. * - * @param eventName The event the subscriber is no longer interested in. + * @param eventName The event the subscriber is no longer interested in. * @param subscriber The subscriber (PropertyChangeListener) to remove. */ public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) { @@ -449,19 +449,21 @@ public class Case { * IMPORTANT: This method should not be called in the event dispatch thread * (EDT). * - * @param caseDir The full path of the case directory. The directory will be - * created if it doesn't already exist; if it exists, it is ASSUMED it was - * created by calling createCaseDirectory. + * @param caseDir The full path of the case directory. The directory + * will be created if it doesn't already exist; if it + * exists, it is ASSUMED it was created by calling + * createCaseDirectory. * @param caseDisplayName The display name of case, which may be changed - * later by the user. - * @param caseNumber The case number, can be the empty string. - * @param examiner The examiner to associate with the case, can be the empty - * string. - * @param caseType The type of case (single-user or multi-user). + * later by the user. + * @param caseNumber The case number, can be the empty string. + * @param examiner The examiner to associate with the case, can be + * the empty string. + * @param caseType The type of case (single-user or multi-user). * * @throws CaseActionException if there is a problem creating the case. The - * exception will have a user-friendly message and may be a wrapper for a - * lower-level exception. + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. */ @Messages({ "# {0} - exception message", "Case.exceptionMessage.wrapperMessage={0}" @@ -501,8 +503,9 @@ public class Case { * @param caseMetadataFilePath The path of the case metadata (.aut) file. * * @throws CaseActionException if there is a problem opening the case. The - * exception will have a user-friendly message and may be a wrapper for a - * lower-level exception. + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. */ @Messages({ "# {0} - exception message", "Case.openException.couldNotOpenCase=Could not open case: {0}", @@ -605,8 +608,9 @@ public class Case { * (EDT). * * @throws CaseActionException if there is a problem deleting the case. The - * exception will have a user-friendly message and may be a wrapper for a - * lower-level exception. + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. */ public static void deleteCurrentCase() throws CaseActionException { synchronized (currentCaseWriteLock) { @@ -626,8 +630,9 @@ public class Case { * @param metadata The metadata for the case to delete. * * @throws CaseActionException if there is a problem deleting the case. The - * exception will have a user-friendly message and may be a wrapper for a - * lower-level exception. + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. */ @Messages({ "# {0} - exception message", "Case.deleteException.couldNotDeleteCase=Could not delete case: {0}", @@ -774,7 +779,7 @@ public class Case { /** * Creates a case directory and its subdirectories. * - * @param caseDir Path to the case directory (typically base + case name). + * @param caseDir Path to the case directory (typically base + case name). * @param caseType The type of case, single-user or multi-user. * * @throws CaseActionException throw if could not create the case dir @@ -903,7 +908,7 @@ public class Case { * Deletes the case directory of a deleted case and removes the case form * the Recent Cases menu. * - * @param metadata The case metadata. + * @param metadata The case metadata. * @param progressIndicator A progress indicator. */ @Messages({ @@ -932,12 +937,12 @@ public class Case { * Acquires an exclusive case name lock. * * @param caseName The case name (not the case display name, which can be - * changed by a user). + * changed by a user). * * @return The lock. * * @throws CaseActionException with a user-friendly message if the lock - * cannot be acquired. + * cannot be acquired. */ @Messages({"Case.creationException.couldNotAcquireNameLock=Failed to get lock on case name"}) private static CoordinationService.Lock acquireExclusiveCaseNameLock(String caseName) throws CaseActionException { @@ -957,12 +962,12 @@ public class Case { * Acquires an exclusive case resources lock. * * @param caseName The case name (not the case display name, which can be - * changed by a user). + * changed by a user). * * @return The lock. * * @throws CaseActionException with a user-friendly message if the lock - * cannot be acquired. + * cannot be acquired. */ @Messages({"Case.creationException.couldNotAcquireResourcesLock=Failed to get lock on case resources"}) private static CoordinationService.Lock acquireExclusiveCaseResourcesLock(String caseName) throws CaseActionException { @@ -1316,7 +1321,7 @@ public class Case { * the case directory, creating it if it does not exist. * * @return The path to the module output directory, relative to the case - * directory. + * directory. */ public String getModuleOutputDirectoryRelativePath() { Path path = Paths.get(getModuleDirectory()); @@ -1333,7 +1338,8 @@ public class Case { * @return A list of data sources. * * @throws org.sleuthkit.datamodel.TskCoreException if there is a problem - * querying the case database. + * querying the case + * database. */ public List getDataSources() throws TskCoreException { List list = caseDb.getRootObjects(); @@ -1406,8 +1412,8 @@ public class Case { * This should not be called from the event dispatch thread (EDT) * * @param eventId A unique identifier for the event. This UUID must be used - * to call notifyFailedAddingDataSource or notifyNewDataSource after the - * data source is added. + * to call notifyFailedAddingDataSource or + * notifyNewDataSource after the data source is added. */ public void notifyAddingDataSource(UUID eventId) { eventPublisher.publish(new AddingDataSourceEvent(eventId)); @@ -1420,7 +1426,8 @@ public class Case { * This should not be called from the event dispatch thread (EDT) * * @param addingDataSourceEventId The unique identifier for the - * corresponding adding data source event (see notifyAddingDataSource). + * corresponding adding data source event + * (see notifyAddingDataSource). */ public void notifyFailedAddingDataSource(UUID addingDataSourceEventId) { eventPublisher.publish(new AddingDataSourceFailedEvent(addingDataSourceEventId)); @@ -1432,9 +1439,10 @@ public class Case { * * This should not be called from the event dispatch thread (EDT) * - * @param dataSource The data source. + * @param dataSource The data source. * @param addingDataSourceEventId The unique identifier for the - * corresponding adding data source event (see notifyAddingDataSource). + * corresponding adding data source event + * (see notifyAddingDataSource). */ public void notifyDataSourceAdded(Content dataSource, UUID addingDataSourceEventId) { eventPublisher.publish(new DataSourceAddedEvent(dataSource, addingDataSourceEventId)); @@ -1487,13 +1495,13 @@ public class Case { /** * Adds a report to the case. * - * @param localPath The path of the report file, must be in the case - * directory or one of its subdirectories. + * @param localPath The path of the report file, must be in the case + * directory or one of its subdirectories. * @param srcModuleName The name of the module that created the report. - * @param reportName The report name, may be empty. + * @param reportName The report name, may be empty. * * @throws TskCoreException if there is a problem adding the report to the - * case database. + * case database. */ public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException { String normalizedLocalPath; @@ -1513,7 +1521,7 @@ public class Case { * @return A collection of report objects. * * @throws TskCoreException if there is a problem querying the case - * database. + * database. */ public List getAllReports() throws TskCoreException { return this.caseDb.getAllReports(); @@ -1547,9 +1555,9 @@ public class Case { * Updates the case display name name. * * @param oldCaseName The old case name. - * @param oldPath The old path name. + * @param oldPath The old path name. * @param newCaseName The new case name. - * @param newPath The new case path. + * @param newPath The new case path. */ void updateCaseName(String oldCaseName, String oldPath, String newCaseName, String newPath) throws CaseActionException { try { @@ -1577,19 +1585,21 @@ public class Case { } /** - * @param caseDir The full path of the case directory. The directory will be - * created if it doesn't already exist; if it exists, it is ASSUMED it was - * created by calling createCaseDirectory. + * @param caseDir The full path of the case directory. The directory + * will be created if it doesn't already exist; if it + * exists, it is ASSUMED it was created by calling + * createCaseDirectory. * @param caseDisplayName The display name of case, which may be changed - * later by the user. - * @param caseNumber The case number, can be the empty string. - * @param examiner The examiner to associate with the case, can be the empty - * string. - * @param caseType The type of case (single-user or multi-user). + * later by the user. + * @param caseNumber The case number, can be the empty string. + * @param examiner The examiner to associate with the case, can be + * the empty string. + * @param caseType The type of case (single-user or multi-user). * * @throws CaseActionException if there is a problem creating the case. The - * exception will have a user-friendly message and may be a wrapper for a - * lower-level exception. + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. */ @Messages({ "Case.exceptionMessage.illegalCaseName=Case name contains illegal characters.", @@ -1710,19 +1720,21 @@ public class Case { /** * Creates and opens a new case. * - * @param caseDir The full path of the case directory. The directory will be - * created if it doesn't already exist; if it exists, it is ASSUMED it was - * created by calling createCaseDirectory. + * @param caseDir The full path of the case directory. The directory + * will be created if it doesn't already exist; if it + * exists, it is ASSUMED it was created by calling + * createCaseDirectory. * @param caseDisplayName The display name of case, which may be changed - * later by the user. - * @param caseNumber The case number, can be the empty string. - * @param examiner The examiner to associate with the case, can be the empty - * string. - * @param caseType The type of case (single-user or multi-user). + * later by the user. + * @param caseNumber The case number, can be the empty string. + * @param examiner The examiner to associate with the case, can be + * the empty string. + * @param caseType The type of case (single-user or multi-user). * * @throws CaseActionException if there is a problem creating the case. The - * exception will have a user-friendly message and may be a wrapper for a - * lower-level exception. + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. */ @Messages({ "Case.progressMessage.creatingCaseDirectory=Creating case directory...", @@ -1809,8 +1821,9 @@ public class Case { * @param caseMetadataFilePath The apth to the case metadata file. * * @throws CaseActionException if there is a problem creating the case. The - * exception will have a user-friendly message and may be a wrapper for a - * lower-level exception. + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. */ private void open(Path caseMetadataFilePath) throws CaseActionException { /* @@ -1923,8 +1936,9 @@ public class Case { * @param progressIndicator A progress indicator. * * @throws CaseActionException if there is a problem opening the case. The - * exception will have a user-friendly message and may be a wrapper for a - * lower-level exception. + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. */ @Messages({ "Case.progressMessage.openingCaseDatabase=Opening case database...", @@ -2320,7 +2334,7 @@ public class Case { * @param caseDir The full path of the case directory. * * @throws CaseActionException with a user-friendly message if the lock - * cannot be acquired. + * cannot be acquired. */ @Messages({"Case.creationException.couldNotAcquireDirLock=Failed to get lock on case directory."}) private void acquireSharedCaseDirLock(String caseDir) throws CaseActionException { @@ -2413,7 +2427,7 @@ public class Case { * characters is encountered. * * @param message The exception message. - * @param cause The exceptin cause. + * @param cause The exceptin cause. */ IllegalCaseNameException(String message, Throwable cause) { super(message, cause); @@ -2423,18 +2437,20 @@ public class Case { /** * Creates a new, single-user Autopsy case. * - * @param caseDir The full path of the case directory. The directory will be - * created if it doesn't already exist; if it exists, it is ASSUMED it was - * created by calling createCaseDirectory. + * @param caseDir The full path of the case directory. The directory + * will be created if it doesn't already exist; if it + * exists, it is ASSUMED it was created by calling + * createCaseDirectory. * @param caseDisplayName The display name of case, which may be changed - * later by the user. - * @param caseNumber The case number, can be the empty string. - * @param examiner The examiner to associate with the case, can be the empty - * string. + * later by the user. + * @param caseNumber The case number, can be the empty string. + * @param examiner The examiner to associate with the case, can be + * the empty string. * * @throws CaseActionException if there is a problem creating the case. The - * exception will have a user-friendly message and may be a wrapper for a - * lower-level exception. + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. * @deprecated Use createAsCurrentCase instead. */ @Deprecated @@ -2445,19 +2461,21 @@ public class Case { /** * Creates a new Autopsy case and makes it the current case. * - * @param caseDir The full path of the case directory. The directory will be - * created if it doesn't already exist; if it exists, it is ASSUMED it was - * created by calling createCaseDirectory. + * @param caseDir The full path of the case directory. The directory + * will be created if it doesn't already exist; if it + * exists, it is ASSUMED it was created by calling + * createCaseDirectory. * @param caseDisplayName The display name of case, which may be changed - * later by the user. - * @param caseNumber The case number, can be the empty string. - * @param examiner The examiner to associate with the case, can be the empty - * string. - * @param caseType The type of case (single-user or multi-user). + * later by the user. + * @param caseNumber The case number, can be the empty string. + * @param examiner The examiner to associate with the case, can be + * the empty string. + * @param caseType The type of case (single-user or multi-user). * * @throws CaseActionException if there is a problem creating the case. The - * exception will have a user-friendly message and may be a wrapper for a - * lower-level exception. + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. * @deprecated Use createAsCurrentCase instead. */ @Deprecated @@ -2471,8 +2489,9 @@ public class Case { * @param caseMetadataFilePath The path of the case metadata (.aut) file. * * @throws CaseActionException if there is a problem opening the case. The - * exception will have a user-friendly message and may be a wrapper for a - * lower-level exception. + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. * @deprecated Use openAsCurrentCase instead. */ @Deprecated @@ -2484,8 +2503,9 @@ public class Case { * Closes this Autopsy case. * * @throws CaseActionException if there is a problem closing the case. The - * exception will have a user-friendly message and may be a wrapper for a - * lower-level exception. + * exception will have a user-friendly message + * and may be a wrapper for a lower-level + * exception. * @deprecated Use closeCurrentCase instead. */ @Deprecated @@ -2607,8 +2627,8 @@ public class Case { * Adds an image to the current case after it has been added to the DB. * Sends out event and reopens windows if needed. * - * @param imgPath The path of the image file. - * @param imgId The ID of the image. + * @param imgPath The path of the image file. + * @param imgId The ID of the image. * @param timeZone The time zone of the image. * * @return @@ -2643,7 +2663,7 @@ public class Case { /** * Deletes reports from the case. * - * @param reports Collection of Report to be deleted from the case. + * @param reports Collection of Report to be deleted from the case. * @param deleteFromDisk No longer supported - ignored. * * @throws TskCoreException diff --git a/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java b/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java index 6d4e46cc42..38c7a209b0 100644 --- a/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java +++ b/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java @@ -28,6 +28,7 @@ class ProgressPanel extends javax.swing.JPanel { ProgressPanel() { initComponents(); this.progressBar.setMinimum(0); + this.progressBar.setIndeterminate(true); } void setMessage(String message) { From 7b8cbc63010aa886505a12fab5567786831914a3 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 23 Mar 2017 18:11:42 -0400 Subject: [PATCH 15/23] Update auto ingest for case display name vs. case name --- Core/src/org/sleuthkit/autopsy/casemodule/Case.java | 11 ++++++----- .../autopsy/casemodule/SingleUserCaseConverter.java | 2 +- .../sleuthkit/autopsy/framework/ProgressPanel.java | 10 +++++----- .../experimental/autoingest/AutoIngestManager.java | 13 ++++++++++--- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index b7d52d5c46..5facca3088 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -725,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 @@ -743,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; @@ -1615,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); } @@ -2408,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/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/framework/ProgressPanel.java b/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java index 38c7a209b0..4957a317b6 100644 --- a/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java +++ b/Core/src/org/sleuthkit/autopsy/framework/ProgressPanel.java @@ -33,20 +33,20 @@ class ProgressPanel extends javax.swing.JPanel { 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/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 7e2c34131d..c669ffb16f 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,8 +1923,14 @@ 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); From aa14a594e215a572325b79bf7a4558056fb884b3 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Thu, 23 Mar 2017 18:18:30 -0400 Subject: [PATCH 16/23] initial attempt at changing the keyword search tree to handle lists with substrings and regexps --- .../datamodel/DisplayableItemNodeVisitor.java | 7 + .../autopsy/datamodel/KeywordHits.java | 287 +++++++++++++++--- 2 files changed, 259 insertions(+), 35 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index 2b9fb9f2b8..5af34027b4 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.InstanceNode 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.InstanceNode 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..6159d3a26d 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -64,7 +64,8 @@ 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(); @@ -72,9 +73,9 @@ public class KeywordHits implements AutopsyVisitableItem { 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 +99,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); @@ -134,31 +175,26 @@ public class KeywordHits implements AutopsyVisitableItem { // 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); + + 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()); - } - regexMap.get(reg).add(id); + addRegExpToList(regexMap, reg, word, 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(); @@ -360,8 +396,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 + ")"); } @@ -451,7 +489,7 @@ public class KeywordHits implements AutopsyVisitableItem { 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 InstancesFactory(setName, keyword), true), Lookups.singleton(keyword)); super.setName(keyword); this.setName = setName; this.keyword = keyword; @@ -461,7 +499,183 @@ 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 + // TODO: Enable this again, maybe? + /* + if (instances.size() == 1 && instances.get(0).equals(DUMMY_INSTANCE)) { + return true; + } + else { + return false; + } +*/ + + 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(); + } + } + + public class InstancesFactory extends ChildFactory.Detachable implements Observer { + private final String keyword; + private final String setName; + + public InstancesFactory(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.addAll(keywordResults.getKeywordInstances(setName, keyword)); + return true; + } + + @Override + protected Node createNodeForKey(String key) { + // + if (key.equals(DUMMY_INSTANCE)) { + // COPY AND PASTED from below, make single method in future + if (skCase == null) { + return null; + } + + Set artifactIds = keywordResults.getArtifactIds(setName, keyword, keyword); + Long artifactId = (Long)artifactIds.toArray()[0]; + 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 new InstanceNode(setName, keyword, key); + } + + @Override + public void update(Observable o, Object arg) { + refresh(true); + } + + } + + public class InstanceNode extends DisplayableItemNode implements Observer { + + private final String setName; + private final String keyword; + private final String instance; + + public InstanceNode(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(keyword + " (" + totalDescendants + ")"); } @Override @@ -496,7 +710,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 +721,18 @@ public class KeywordHits implements AutopsyVisitableItem { } } + 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,7 +747,7 @@ 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; } From 779c7fc9def66ef97f2ee7d4c5022b9aa17f7dfa Mon Sep 17 00:00:00 2001 From: William Schaefer Date: Thu, 23 Mar 2017 19:08:00 -0400 Subject: [PATCH 17/23] Fixed displaying of auto ingest warnings to loop over actual list --- .../autopsy/experimental/autoingest/AutoIngestManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index c669ffb16f..971995aada 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -2351,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); From f8abfd0205a8e1d7e2b038a9b80fc07df7a389e3 Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 23 Mar 2017 19:24:24 -0400 Subject: [PATCH 18/23] Additinoal fixes to case display name/name handling for AIM --- .../experimental/autoingest/AutoIngestManager.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java index 971995aada..dd370b0534 100644 --- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutoIngestManager.java @@ -1935,11 +1935,11 @@ public final class AutoIngestManager extends Observable implements PropertyChang 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. @@ -1952,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); } } From ce1f462e6b6e00e7d86f41e7182e6d4275582adc Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 23 Mar 2017 19:44:20 -0400 Subject: [PATCH 19/23] Additinoal fixes to case display name/name handling for AIM --- .../autopsy/experimental/autoingest/PathUtils.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) 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); From 0a89b0f90d1524c6e7db648221dd692807c1fcbf Mon Sep 17 00:00:00 2001 From: Richard Cordovano Date: Thu, 23 Mar 2017 20:40:07 -0400 Subject: [PATCH 20/23] Make new case wizard create display name/case name aware case dirs --- .../autopsy/casemodule/NewCaseWizardPanel1.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) 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 From 7fd6536182ba70e0f3141153fd93f198eea90967 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Thu, 23 Mar 2017 22:17:10 -0400 Subject: [PATCH 21/23] Update to previous commit to now properly show files --- .../autopsy/datamodel/KeywordHits.java | 67 ++++++++++++++----- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java index 6159d3a26d..049310723f 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -518,17 +518,12 @@ public class KeywordHits implements AutopsyVisitableItem { public boolean isLeafTypeNode() { List instances = keywordResults.getKeywordInstances(setName, keyword); // is this an exact match - // TODO: Enable this again, maybe? - /* if (instances.size() == 1 && instances.get(0).equals(DUMMY_INSTANCE)) { return true; } else { return false; } -*/ - - return false; } @Override @@ -563,8 +558,35 @@ public class KeywordHits implements AutopsyVisitableItem { 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 InstanceKey { + private boolean isRegExp; + private String strKey; + private Long longKey; + public InstanceKey(String key) { + isRegExp = true; + strKey = key; + } + public InstanceKey(Long key) { + isRegExp = false; + longKey = key; + } + boolean isRegExp() { + return isRegExp; + } + Long getIdKey() { + return longKey; + } + String getRegExpKey() { + return strKey; + } + } - public class InstancesFactory extends ChildFactory.Detachable implements Observer { + public class InstancesFactory extends ChildFactory.Detachable implements Observer { private final String keyword; private final String setName; @@ -585,22 +607,35 @@ public class KeywordHits implements AutopsyVisitableItem { } @Override - protected boolean createKeys(List list) { - list.addAll(keywordResults.getKeywordInstances(setName, keyword)); + 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 InstanceKey(id)); + } + } else { + for (String instance : instances) { + list.add(new InstanceKey(instance)); + } + + } return true; } @Override - protected Node createNodeForKey(String key) { + protected Node createNodeForKey(InstanceKey key) { // - if (key.equals(DUMMY_INSTANCE)) { - // COPY AND PASTED from below, make single method in future + if (key.isRegExp() == false) { + // COPY AND PASTED from below, + // TODO make single method if (skCase == null) { return null; } - Set artifactIds = keywordResults.getArtifactIds(setName, keyword, keyword); - Long artifactId = (Long)artifactIds.toArray()[0]; + Long artifactId = key.getIdKey(); try { BlackboardArtifact art = skCase.getBlackboardArtifact(artifactId); BlackboardArtifactNode n = new BlackboardArtifactNode(art); @@ -646,7 +681,7 @@ public class KeywordHits implements AutopsyVisitableItem { } return null; } - return new InstanceNode(setName, keyword, key); + return new InstanceNode(setName, keyword, key.getRegExpKey()); } @Override @@ -675,7 +710,7 @@ public class KeywordHits implements AutopsyVisitableItem { private void updateDisplayName() { int totalDescendants = keywordResults.getArtifactIds(setName, keyword, instance).size(); - super.setDisplayName(keyword + " (" + totalDescendants + ")"); + super.setDisplayName(instance + " (" + totalDescendants + ")"); } @Override @@ -753,6 +788,8 @@ public class KeywordHits implements AutopsyVisitableItem { @Override protected Node createNodeForKey(Long artifactId) { + // TODO Move to private method that can be used + // with copy and pasted code above if (skCase == null) { return null; } From cb111c22a304d8c0f3595aa6a0619a32e94ae654 Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Fri, 24 Mar 2017 09:49:11 -0400 Subject: [PATCH 22/23] desired tree now works, except for the extra layer at the predefined regexps. --- .../datamodel/DisplayableItemNodeVisitor.java | 4 +- .../autopsy/datamodel/KeywordHits.java | 256 +++++++++--------- .../autopsy/keywordsearch/KeywordSearch.java | 3 +- 3 files changed, 137 insertions(+), 126 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java index 5af34027b4..972951ea99 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/DisplayableItemNodeVisitor.java @@ -91,7 +91,7 @@ public interface DisplayableItemNodeVisitor { T visit(KeywordHits.TermNode khmln); - T visit(KeywordHits.InstanceNode khmln); + T visit(KeywordHits.RegExpInstanceNode khmln); T visit(HashsetHits.RootNode hhrn); @@ -284,7 +284,7 @@ public interface DisplayableItemNodeVisitor { } @Override - public T visit(KeywordHits.InstanceNode khsn) { + public T visit(KeywordHits.RegExpInstanceNode khsn) { return defaultVisit(khsn); } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java index 049310723f..72746663e7 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/KeywordHits.java @@ -70,6 +70,12 @@ public class KeywordHits implements AutopsyVisitableItem { 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 { @@ -172,7 +178,9 @@ 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 @@ -181,14 +189,30 @@ public class KeywordHits implements AutopsyVisitableItem { } Map>> listMap = listsMap.get(listName); - if (reg != null) { + // 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); } } // regular expression, single term else if (reg != null) { - addRegExpToList(regexMap, reg, word, id); + // 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); + } } // literal, single term else { addExactMatchToList(literalMap, word, id); @@ -203,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) { @@ -290,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() { @@ -380,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; @@ -447,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; @@ -483,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 InstancesFactory(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; @@ -563,15 +607,15 @@ public class KeywordHits implements AutopsyVisitableItem { // as they keys for different types of nodes at the // same level. Probably a better way to do this, but // it works. - class InstanceKey { - private boolean isRegExp; + class RegExpInstanceKey { + private final boolean isRegExp; private String strKey; private Long longKey; - public InstanceKey(String key) { + public RegExpInstanceKey(String key) { isRegExp = true; strKey = key; } - public InstanceKey(Long key) { + public RegExpInstanceKey(Long key) { isRegExp = false; longKey = key; } @@ -586,11 +630,14 @@ public class KeywordHits implements AutopsyVisitableItem { } } - public class InstancesFactory extends ChildFactory.Detachable implements Observer { + /** + * 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 InstancesFactory(String setName, String keyword) { + public RegExpInstancesFactory(String setName, String keyword) { super(); this.setName = setName; this.keyword = keyword; @@ -607,18 +654,18 @@ public class KeywordHits implements AutopsyVisitableItem { } @Override - protected boolean createKeys(List list) { + 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 InstanceKey(id)); + list.add(new RegExpInstanceKey(id)); } } else { for (String instance : instances) { - list.add(new InstanceKey(instance)); + list.add(new RegExpInstanceKey(instance)); } } @@ -626,62 +673,13 @@ public class KeywordHits implements AutopsyVisitableItem { } @Override - protected Node createNodeForKey(InstanceKey key) { - // + protected Node createNodeForKey(RegExpInstanceKey key) { + // if it isn't not a regexp, then skip the 'instance' layer of the tree if (key.isRegExp() == false) { - // COPY AND PASTED from below, - // TODO make single method - if (skCase == null) { - return null; - } - - Long artifactId = key.getIdKey(); - 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(key.getIdKey()); + } else { + return new RegExpInstanceNode(setName, keyword, key.getRegExpKey()); } - return new InstanceNode(setName, keyword, key.getRegExpKey()); } @Override @@ -691,13 +689,16 @@ public class KeywordHits implements AutopsyVisitableItem { } - public class InstanceNode extends DisplayableItemNode implements Observer { + /** + * 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 InstanceNode(String setName, String keyword, 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; @@ -756,7 +757,65 @@ 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; @@ -788,56 +847,7 @@ public class KeywordHits implements AutopsyVisitableItem { @Override protected Node createNodeForKey(Long artifactId) { - // TODO Move to private method that can be used - // with copy and pasted code above - 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/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 From 22aa75979fe53a03b07ea855e929600a4b38790b Mon Sep 17 00:00:00 2001 From: Brian Carrier Date: Fri, 24 Mar 2017 14:54:36 -0400 Subject: [PATCH 23/23] Fix 2 bugs that caused exceptions when core did not open. Simplified wording --- .../keywordsearch/DropdownToolbar.java | 49 +++++++++++-------- .../keywordsearch/ExtractedContentViewer.java | 3 ++ .../keywordsearch/SolrSearchService.java | 2 +- 3 files changed, 33 insertions(+), 21 deletions(-) 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/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();