19 package org.sleuthkit.autopsy.discovery;
33 import java.util.ArrayList;
34 import java.util.List;
35 import java.util.stream.Collectors;
36 import org.openide.util.NbBundle;
45 class FileSearchFiltering {
57 static List<ResultFile> runQueries(List<FileFilter> filters, SleuthkitCase caseDb, CentralRepository centralRepoDb)
throws FileSearchException {
59 throw new FileSearchException(
"Case DB parameter is null");
62 String combinedQuery =
"";
63 for (FileFilter filter : filters) {
64 if (!filter.getWhereClause().isEmpty()) {
65 if (!combinedQuery.isEmpty()) {
66 combinedQuery +=
" AND ";
68 combinedQuery +=
"(" + filter.getWhereClause() +
")";
72 if (combinedQuery.isEmpty()) {
74 throw new FileSearchException(
"Selected filters do not include a case database query");
77 return getResultList(filters, combinedQuery, caseDb, centralRepoDb);
78 }
catch (TskCoreException ex) {
79 throw new FileSearchException(
"Error querying case database", ex);
97 private static List<ResultFile> getResultList(List<FileFilter> filters, String combinedQuery, SleuthkitCase caseDb, CentralRepository centralRepoDb)
throws TskCoreException, FileSearchException {
99 List<ResultFile> resultList =
new ArrayList<>();
100 List<AbstractFile> sqlResults = caseDb.findAllFilesWhere(combinedQuery);
103 if (sqlResults.isEmpty()) {
108 for (AbstractFile abstractFile : sqlResults) {
109 resultList.add(
new ResultFile(abstractFile));
113 for (FileFilter filter : filters) {
114 if (filter.useAlternateFilter()) {
115 resultList = filter.applyAlternateFilter(resultList, caseDb, centralRepoDb);
118 if (resultList.isEmpty()) {
128 static abstract class FileFilter {
137 abstract String getWhereClause();
145 boolean useAlternateFilter() {
163 List<ResultFile> applyAlternateFilter(List<ResultFile> currentResults, SleuthkitCase caseDb,
164 CentralRepository centralRepoDb)
throws FileSearchException {
165 return new ArrayList<>();
173 abstract String getDesc();
179 static class SizeFilter
extends FileFilter {
181 private final List<FileSize> fileSizes;
188 SizeFilter(List<FileSize> fileSizes) {
189 this.fileSizes = fileSizes;
193 String getWhereClause() {
194 String queryStr =
"";
195 for (FileSize size : fileSizes) {
196 if (!queryStr.isEmpty()) {
199 if (size.getMaxBytes() != FileSize.NO_MAXIMUM) {
200 queryStr +=
"(size > \'" + size.getMinBytes() +
"\' AND size <= \'" + size.getMaxBytes() +
"\')";
202 queryStr +=
"(size >= \'" + size.getMinBytes() +
"\')";
210 "FileSearchFiltering.SizeFilter.desc=Size(s): {0}",
211 "FileSearchFiltering.SizeFilter.or=, "})
215 for (FileSize size : fileSizes) {
216 if (!desc.isEmpty()) {
217 desc += Bundle.FileSearchFiltering_SizeFilter_or();
219 desc += size.getSizeGroup();
221 desc = Bundle.FileSearchFiltering_SizeFilter_desc(desc);
230 static class ParentSearchTerm {
232 private final String searchStr;
233 private final boolean fullPath;
234 private final boolean included;
245 ParentSearchTerm(String searchStr,
boolean isFullPath,
boolean isIncluded) {
246 this.searchStr = searchStr;
247 this.fullPath = isFullPath;
248 this.included = isIncluded;
256 String getSQLForTerm() {
260 return "parent_path=\'" + getSearchStr() +
"\'";
262 return "parent_path LIKE \'%" + getSearchStr() +
"%\'";
266 return "parent_path!=\'" + getSearchStr() +
"\'";
268 return "parent_path NOT LIKE \'%" + getSearchStr() +
"%\'";
274 "FileSearchFiltering.ParentSearchTerm.fullString= (exact)",
275 "FileSearchFiltering.ParentSearchTerm.subString= (substring)",
276 "FileSearchFiltering.ParentSearchTerm.includeString= (include)",
277 "FileSearchFiltering.ParentSearchTerm.excludeString= (exclude)",})
279 public String toString() {
280 String returnString = getSearchStr();
282 returnString += Bundle.FileSearchFiltering_ParentSearchTerm_fullString();
284 returnString += Bundle.FileSearchFiltering_ParentSearchTerm_subString();
287 returnString += Bundle.FileSearchFiltering_ParentSearchTerm_includeString();
289 returnString += Bundle.FileSearchFiltering_ParentSearchTerm_excludeString();
297 boolean isFullPath() {
304 boolean isIncluded() {
311 String getSearchStr() {
319 static class ParentFilter
extends FileFilter {
321 private final List<ParentSearchTerm> parentSearchTerms;
328 ParentFilter(List<ParentSearchTerm> parentSearchTerms) {
329 this.parentSearchTerms = parentSearchTerms;
333 String getWhereClause() {
334 String includeQueryStr =
"";
335 String excludeQueryStr =
"";
336 for (ParentSearchTerm searchTerm : parentSearchTerms) {
337 if (searchTerm.isIncluded()) {
338 if (!includeQueryStr.isEmpty()) {
339 includeQueryStr +=
" OR ";
341 includeQueryStr += searchTerm.getSQLForTerm();
343 if (!excludeQueryStr.isEmpty()) {
344 excludeQueryStr +=
" AND ";
346 excludeQueryStr += searchTerm.getSQLForTerm();
349 if (!includeQueryStr.isEmpty()) {
350 includeQueryStr =
"(" + includeQueryStr +
")";
352 if (!excludeQueryStr.isEmpty()) {
353 excludeQueryStr =
"(" + excludeQueryStr +
")";
355 if (includeQueryStr.isEmpty() || excludeQueryStr.isEmpty()) {
356 return includeQueryStr + excludeQueryStr;
358 return includeQueryStr +
" AND " + excludeQueryStr;
364 "FileSearchFiltering.ParentFilter.desc=Paths matching: {0}",
365 "FileSearchFiltering.ParentFilter.or=, ",
366 "FileSearchFiltering.ParentFilter.exact=(exact match)",
367 "FileSearchFiltering.ParentFilter.substring=(substring)",
368 "FileSearchFiltering.ParentFilter.included=(included)",
369 "FileSearchFiltering.ParentFilter.excluded=(excluded)"})
373 for (ParentSearchTerm searchTerm : parentSearchTerms) {
374 if (!desc.isEmpty()) {
375 desc += Bundle.FileSearchFiltering_ParentFilter_or();
377 if (searchTerm.isFullPath()) {
378 desc += searchTerm.getSearchStr() + Bundle.FileSearchFiltering_ParentFilter_exact();
380 desc += searchTerm.getSearchStr() + Bundle.FileSearchFiltering_ParentFilter_substring();
382 if (searchTerm.isIncluded()) {
383 desc += Bundle.FileSearchFiltering_ParentFilter_included();
385 desc += Bundle.FileSearchFiltering_ParentFilter_excluded();
388 desc = Bundle.FileSearchFiltering_ParentFilter_desc(desc);
396 static class DataSourceFilter
extends FileFilter {
398 private final List<DataSource> dataSources;
405 DataSourceFilter(List<DataSource> dataSources) {
406 this.dataSources = dataSources;
410 String getWhereClause() {
411 String queryStr =
"";
412 for (DataSource ds : dataSources) {
413 if (!queryStr.isEmpty()) {
416 queryStr +=
"\'" + ds.getId() +
"\'";
418 queryStr =
"data_source_obj_id IN (" + queryStr +
")";
424 "FileSearchFiltering.DataSourceFilter.desc=Data source(s): {0}",
425 "FileSearchFiltering.DataSourceFilter.or=, ",
426 "# {0} - Data source name",
427 "# {1} - Data source ID",
428 "FileSearchFiltering.DataSourceFilter.datasource={0}({1})",})
432 for (DataSource ds : dataSources) {
433 if (!desc.isEmpty()) {
434 desc += Bundle.FileSearchFiltering_DataSourceFilter_or();
436 desc += Bundle.FileSearchFiltering_DataSourceFilter_datasource(ds.getName(), ds.getId());
438 desc = Bundle.FileSearchFiltering_DataSourceFilter_desc(desc);
447 static class KeywordListFilter
extends FileFilter {
449 private final List<String> listNames;
456 KeywordListFilter(List<String> listNames) {
457 this.listNames = listNames;
461 String getWhereClause() {
462 String keywordListPart = concatenateNamesForSQL(listNames);
464 String queryStr =
"(obj_id IN (SELECT obj_id from blackboard_artifacts WHERE artifact_id IN "
465 +
"(SELECT artifact_id FROM blackboard_attributes WHERE artifact_type_id = 9 AND attribute_type_ID = 37 "
466 +
"AND (" + keywordListPart +
"))))";
473 "FileSearchFiltering.KeywordListFilter.desc=Keywords in list(s): {0}",})
476 return Bundle.FileSearchFiltering_KeywordListFilter_desc(concatenateSetNamesForDisplay(listNames));
483 static class FileTypeFilter
extends FileFilter {
485 private final List<FileType> categories;
492 FileTypeFilter(List<FileType> categories) {
493 this.categories = categories;
501 FileTypeFilter(FileType category) {
502 this.categories =
new ArrayList<>();
503 this.categories.add(category);
507 String getWhereClause() {
508 String queryStr =
"";
509 for (FileType cat : categories) {
510 for (String type : cat.getMediaTypes()) {
511 if (!queryStr.isEmpty()) {
514 queryStr +=
"\'" + type +
"\'";
517 queryStr =
"mime_type IN (" + queryStr +
")";
523 "FileSearchFiltering.FileTypeFilter.desc=Type: {0}",
524 "FileSearchFiltering.FileTypeFilter.or=, ",})
528 for (FileType cat : categories) {
529 if (!desc.isEmpty()) {
530 desc += Bundle.FileSearchFiltering_FileTypeFilter_or();
532 desc += cat.toString();
534 desc = Bundle.FileSearchFiltering_FileTypeFilter_desc(desc);
542 static class FrequencyFilter
extends FileFilter {
544 private final List<Frequency> frequencies;
551 FrequencyFilter(List<Frequency> frequencies) {
552 this.frequencies = frequencies;
556 String getWhereClause() {
563 boolean useAlternateFilter() {
568 List<ResultFile> applyAlternateFilter(List<ResultFile> currentResults, SleuthkitCase caseDb,
569 CentralRepository centralRepoDb)
throws FileSearchException {
573 if (currentResults.isEmpty()) {
574 throw new FileSearchException(
"Can not run on empty list");
578 FileSearch.FrequencyAttribute freqAttr =
new FileSearch.FrequencyAttribute();
579 freqAttr.addAttributeToResultFiles(currentResults, caseDb, centralRepoDb);
582 List<ResultFile> frequencyResults =
new ArrayList<>();
583 for (ResultFile file : currentResults) {
584 if (frequencies.contains(file.getFrequency())) {
585 frequencyResults.add(file);
588 return frequencyResults;
593 "FileSearchFiltering.FrequencyFilter.desc=Past occurrences: {0}",
594 "FileSearchFiltering.FrequencyFilter.or=, ",})
598 for (Frequency freq : frequencies) {
599 if (!desc.isEmpty()) {
600 desc += Bundle.FileSearchFiltering_FrequencyFilter_or();
602 desc += freq.toString();
604 return Bundle.FileSearchFiltering_FrequencyFilter_desc(desc);
612 static class HashSetFilter
extends FileFilter {
614 private final List<String> setNames;
621 HashSetFilter(List<String> setNames) {
622 this.setNames = setNames;
626 String getWhereClause() {
627 String hashSetPart = concatenateNamesForSQL(setNames);
629 String queryStr =
"(obj_id IN (SELECT obj_id from blackboard_artifacts WHERE artifact_id IN "
630 +
"(SELECT artifact_id FROM blackboard_attributes WHERE artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID()
631 +
" AND attribute_type_ID = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() +
" "
632 +
"AND (" + hashSetPart +
"))))";
639 "FileSearchFiltering.HashSetFilter.desc=Hash set hits in set(s): {0}",})
642 return Bundle.FileSearchFiltering_HashSetFilter_desc(concatenateSetNamesForDisplay(setNames));
650 static class InterestingFileSetFilter
extends FileFilter {
652 private final List<String> setNames;
659 InterestingFileSetFilter(List<String> setNames) {
660 this.setNames = setNames;
664 String getWhereClause() {
665 String intItemSetPart = concatenateNamesForSQL(setNames);
667 String queryStr =
"(obj_id IN (SELECT obj_id from blackboard_artifacts WHERE artifact_id IN "
668 +
"(SELECT artifact_id FROM blackboard_attributes WHERE artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID()
669 +
" AND attribute_type_ID = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID() +
" "
670 +
"AND (" + intItemSetPart +
"))))";
677 "FileSearchFiltering.InterestingItemSetFilter.desc=Interesting item hits in set(s): {0}",})
680 return Bundle.FileSearchFiltering_InterestingItemSetFilter_desc(concatenateSetNamesForDisplay(setNames));
688 static class ObjectDetectionFilter
extends FileFilter {
690 private final List<String> typeNames;
697 ObjectDetectionFilter(List<String> typeNames) {
698 this.typeNames = typeNames;
702 String getWhereClause() {
703 String objTypePart = concatenateNamesForSQL(typeNames);
705 String queryStr =
"(obj_id IN (SELECT obj_id from blackboard_artifacts WHERE artifact_id IN "
706 +
"(SELECT artifact_id FROM blackboard_attributes WHERE artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_OBJECT_DETECTED.getTypeID()
707 +
" AND attribute_type_ID = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION.getTypeID() +
" "
708 +
"AND (" + objTypePart +
"))))";
715 "FileSearchFiltering.ObjectDetectionFilter.desc=Objects detected in set(s): {0}",})
718 return Bundle.FileSearchFiltering_ObjectDetectionFilter_desc(concatenateSetNamesForDisplay(typeNames));
726 static class ScoreFilter
extends FileFilter {
728 private final List<Score> scores;
735 ScoreFilter(List<Score> scores) {
736 this.scores = scores;
740 String getWhereClause() {
745 String hashsetQueryPart =
"";
746 String tagQueryPart =
"";
747 String intItemQueryPart =
"";
749 if (scores.contains(Score.NOTABLE)) {
751 hashsetQueryPart =
" (known = " + TskData.FileKnown.BAD.getFileKnownValue() +
") ";
754 if (scores.contains(Score.INTERESTING)) {
756 intItemQueryPart =
" (obj_id IN (SELECT obj_id from blackboard_artifacts WHERE artifact_type_id = "
757 + BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID() +
")) ";
760 if (scores.contains(Score.NOTABLE) && scores.contains(Score.INTERESTING)) {
762 tagQueryPart =
"(obj_id IN (SELECT obj_id FROM content_tags))";
763 }
else if (scores.contains(Score.NOTABLE)) {
765 tagQueryPart =
"(obj_id IN (SELECT obj_id FROM content_tags WHERE tag_name_id IN (SELECT tag_name_id FROM tag_names WHERE knownStatus = "
766 + TskData.FileKnown.BAD.getFileKnownValue() +
")))";
767 }
else if (scores.contains(Score.INTERESTING)) {
769 tagQueryPart =
"(obj_id IN (SELECT obj_id FROM content_tags WHERE tag_name_id IN (SELECT tag_name_id FROM tag_names WHERE knownStatus != "
770 + TskData.FileKnown.BAD.getFileKnownValue() +
")))";
773 String queryStr = hashsetQueryPart;
774 if (!intItemQueryPart.isEmpty()) {
775 if (!queryStr.isEmpty()) {
778 queryStr += intItemQueryPart;
780 if (!tagQueryPart.isEmpty()) {
781 if (!queryStr.isEmpty()) {
784 queryStr += tagQueryPart;
791 "FileSearchFiltering.ScoreFilter.desc=Score(s) of : {0}",})
794 return Bundle.FileSearchFiltering_ScoreFilter_desc(
795 concatenateSetNamesForDisplay(scores.stream().map(p -> p.toString()).collect(Collectors.toList())));
803 static class TagsFilter
extends FileFilter {
805 private final List<TagName> tagNames;
812 TagsFilter(List<TagName> tagNames) {
813 this.tagNames = tagNames;
817 String getWhereClause() {
819 for (TagName tagName : tagNames) {
820 if (!tagIDs.isEmpty()) {
823 tagIDs += tagName.getId();
826 String queryStr =
"(obj_id IN (SELECT obj_id FROM content_tags WHERE tag_name_id IN (" + tagIDs +
")))";
833 "FileSearchFiltering.TagsFilter.desc=Tagged {0}",
834 "FileSearchFiltering.TagsFilter.or=, ",})
838 for (TagName name : tagNames) {
839 if (!desc.isEmpty()) {
840 desc += Bundle.FileSearchFiltering_TagsFilter_or();
842 desc += name.getDisplayName();
844 return Bundle.FileSearchFiltering_TagsFilter_desc(desc);
852 static class UserCreatedFilter
extends FileFilter {
857 UserCreatedFilter() {
862 String getWhereClause() {
863 return "(obj_id IN (SELECT obj_id from blackboard_artifacts WHERE artifact_id IN "
864 +
"(SELECT artifact_id FROM blackboard_attributes WHERE artifact_type_id = "
865 + BlackboardArtifact.ARTIFACT_TYPE.TSK_USER_CONTENT_SUSPECTED.getTypeID() +
")))";
869 "FileSearchFiltering.UserCreatedFilter.desc=that contain EXIF data",})
872 return Bundle.FileSearchFiltering_UserCreatedFilter_desc();
880 static class NotableFilter
extends FileFilter {
890 String getWhereClause() {
897 boolean useAlternateFilter() {
902 List<ResultFile> applyAlternateFilter(List<ResultFile> currentResults, SleuthkitCase caseDb,
903 CentralRepository centralRepoDb)
throws FileSearchException {
905 if (centralRepoDb == null) {
906 throw new FileSearchException(
"Can not run Previously Notable filter with null Central Repository DB");
911 if (currentResults.isEmpty()) {
912 throw new FileSearchException(
"Can not run on empty list");
916 List<ResultFile> notableResults =
new ArrayList<>();
919 CorrelationAttributeInstance.Type type = CorrelationAttributeInstance.getDefaultCorrelationTypes().get(CorrelationAttributeInstance.FILES_TYPE_ID);
921 for (ResultFile file : currentResults) {
922 if (file.getFirstInstance().getMd5Hash() != null && !file.getFirstInstance().getMd5Hash().isEmpty()) {
925 String value = file.getFirstInstance().getMd5Hash();
926 if (centralRepoDb.getCountArtifactInstancesKnownBad(type, value) > 0) {
927 notableResults.add(file);
931 return notableResults;
932 }
catch (CentralRepoException | CorrelationAttributeNormalizationException ex) {
933 throw new FileSearchException(
"Error querying central repository", ex);
938 "FileSearchFiltering.PreviouslyNotableFilter.desc=that were previously marked as notable",})
941 return Bundle.FileSearchFiltering_PreviouslyNotableFilter_desc();
948 static class KnownFilter
extends FileFilter {
951 String getWhereClause() {
952 return "known!=" + TskData.FileKnown.KNOWN.getFileKnownValue();
956 "FileSearchFiltering.KnownFilter.desc=which are not known"})
959 return Bundle.FileSearchFiltering_KnownFilter_desc();
964 "FileSearchFiltering.concatenateSetNamesForDisplay.comma=, ",})
965 private static String concatenateSetNamesForDisplay(List<String> setNames) {
967 for (String setName : setNames) {
968 if (!desc.isEmpty()) {
969 desc += Bundle.FileSearchFiltering_concatenateSetNamesForDisplay_comma();
984 private static String concatenateNamesForSQL(List<String> setNames) {
986 for (String setName : setNames) {
987 if (!result.isEmpty()) {
990 result +=
"value_text = \'" + setName +
"\'";
995 private FileSearchFiltering() {