Autopsy  4.19.3
Graphical digital forensics platform for The Sleuth Kit and other tools.
QueryResults.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2012-2021 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.keywordsearch;
20 
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Set;
27 import java.util.logging.Level;
28 import javax.swing.SwingUtilities;
29 import javax.swing.SwingWorker;
30 import org.apache.commons.lang.StringUtils;
31 import org.openide.util.NbBundle;
39 import org.sleuthkit.datamodel.AbstractFile;
40 import org.sleuthkit.datamodel.Blackboard;
41 import org.sleuthkit.datamodel.BlackboardArtifact;
42 import org.sleuthkit.datamodel.BlackboardAttribute;
43 import org.sleuthkit.datamodel.Content;
44 import org.sleuthkit.datamodel.SleuthkitCase;
45 import org.sleuthkit.datamodel.TskCoreException;
46 
53 class QueryResults {
54 
55  private static final Logger logger = Logger.getLogger(QueryResults.class.getName());
56  private static final String MODULE_NAME = KeywordSearchModuleFactory.getModuleName();
57  private final KeywordSearchQuery query;
58  private final Map<Keyword, List<KeywordHit>> results = new HashMap<>();
59 
60  private static final int MAX_INBOX_NOTIFICATIONS_PER_KW_TERM = 20;
61 
72  QueryResults(KeywordSearchQuery query) {
73  this.query = query;
74  }
75 
82  KeywordSearchQuery getQuery() {
83  return query;
84  }
85 
94  void addResult(Keyword keyword, List<KeywordHit> hits) {
95  results.put(keyword, hits);
96  }
97 
105  List<KeywordHit> getResults(Keyword keyword) {
106  return results.get(keyword);
107  }
108 
115  Set<Keyword> getKeywords() {
116  return results.keySet();
117  }
118 
145  void process(SwingWorker<?, ?> worker, boolean notifyInbox, boolean saveResults, Long ingestJobId) {
146  final Collection<BlackboardArtifact> hitArtifacts = new ArrayList<>();
147 
148  int notificationCount = 0;
149  for (final Keyword keyword : getKeywords()) {
150  /*
151  * Cancellation check.
152  */
153  if (worker.isCancelled()) {
154  logger.log(Level.INFO, "Processing cancelled, exiting before processing search term {0}", keyword.getSearchTerm()); //NON-NLS
155  return;
156  }
157 
158  /*
159  * Reduce the hits for this keyword to one hit per text source
160  * object so that only one hit artifact is generated per text source
161  * object, no matter how many times the keyword was actually found.
162  */
163  for (KeywordHit hit : getOneHitPerTextSourceObject(keyword)) {
164  /*
165  * Get a snippet (preview) for the hit. Regex queries always
166  * have snippets made from the content_str pulled back from Solr
167  * for executing the search. Other types of queries may or may
168  * not have snippets yet.
169  */
170  String snippet = hit.getSnippet();
171  if (StringUtils.isBlank(snippet)) {
172  final String snippetQuery = KeywordSearchUtil.escapeLuceneQuery(keyword.getSearchTerm());
173  try {
174  snippet = LuceneQuery.querySnippet(snippetQuery, hit.getSolrObjectId(), hit.getChunkId(), !query.isLiteral(), true);
175  } catch (NoOpenCoreException e) {
176  logger.log(Level.SEVERE, "Solr core closed while executing snippet query " + snippetQuery, e); //NON-NLS
177  return; // Stop processing.
178  } catch (Exception e) {
179  logger.log(Level.SEVERE, "Error executing snippet query " + snippetQuery, e); //NON-NLS
180  continue; // Try processing the next hit.
181  }
182  }
183 
184  /*
185  * Get the content (file or artifact) that is the text source
186  * for the hit.
187  */
188  Content content = null;
189  try {
190  SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
191  content = tskCase.getContentById(hit.getContentID());
192  } catch (TskCoreException | NoCurrentCaseException tskCoreException) {
193  logger.log(Level.SEVERE, "Failed to get text source object for keyword hit", tskCoreException); //NON-NLS
194  }
195 
196  if ((content != null) && saveResults) {
197  /*
198  * Post an artifact for the hit to the blackboard.
199  */
200  BlackboardArtifact artifact = query.createKeywordHitArtifact(content, keyword, hit, snippet, query.getKeywordList().getName(), ingestJobId);
201 
202  /*
203  * Send an ingest inbox message for the hit.
204  */
205  if (null != artifact) {
206  hitArtifacts.add(artifact);
207  if (notifyInbox && notificationCount < MAX_INBOX_NOTIFICATIONS_PER_KW_TERM) {
208  // only send ingest inbox messages for the first MAX_INBOX_NOTIFICATIONS_PER_KW_TERM hits
209  // for every KW term (per ingest job, aka data source). Otherwise we can have a situation
210  // where we send tens of thousands of notifications.
211  try {
212  notificationCount++;
213  writeSingleFileInboxMessage(artifact, content);
214  } catch (TskCoreException ex) {
215  logger.log(Level.SEVERE, "Error sending message to ingest messages inbox", ex); //NON-NLS
216  }
217  }
218  }
219  }
220  }
221  }
222 
223  /*
224  * Post the artifacts to the blackboard which will publish an event to
225  * notify subscribers of the new artifacts.
226  */
227  if (!hitArtifacts.isEmpty()) {
228  try {
229  SleuthkitCase tskCase = Case.getCurrentCaseThrows().getSleuthkitCase();
230  Blackboard blackboard = tskCase.getBlackboard();
231 
232  blackboard.postArtifacts(hitArtifacts, MODULE_NAME, ingestJobId);
233  } catch (NoCurrentCaseException | Blackboard.BlackboardException ex) {
234  logger.log(Level.SEVERE, "Failed to post KWH artifact to blackboard.", ex); //NON-NLS
235  }
236  }
237  }
238 
248  private Collection<KeywordHit> getOneHitPerTextSourceObject(Keyword keyword) {
249  /*
250  * For each Solr document (chunk) for a text source object, return only
251  * a single keyword hit from the first chunk of text (the one with the
252  * lowest chunk id).
253  */
254  HashMap< Long, KeywordHit> hits = new HashMap<>();
255  getResults(keyword).forEach((hit) -> {
256  if (!hits.containsKey(hit.getSolrObjectId())) {
257  hits.put(hit.getSolrObjectId(), hit);
258  } else if (hit.getChunkId() < hits.get(hit.getSolrObjectId()).getChunkId()) {
259  hits.put(hit.getSolrObjectId(), hit);
260  }
261  });
262  return hits.values();
263  }
264 
275  private void writeSingleFileInboxMessage(final BlackboardArtifact artifact, final Content hitContent) throws TskCoreException {
276  if (artifact != null && hitContent != null && RuntimeProperties.runningWithGUI()) {
277  final StringBuilder subjectSb = new StringBuilder(1024);
278  if (!query.isLiteral()) {
279  subjectSb.append(NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.regExpHitLbl"));
280  } else {
281  subjectSb.append(NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.kwHitLbl"));
282  }
283 
284  final StringBuilder detailsSb = new StringBuilder(1024);
285  String uniqueKey = null;
286  BlackboardAttribute attr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD));
287  if (attr != null) {
288  final String keyword = attr.getValueString();
289  subjectSb.append(keyword);
290  uniqueKey = keyword.toLowerCase();
291  detailsSb.append("<table border='0' cellpadding='4' width='280'>"); //NON-NLS
292  detailsSb.append("<tr>"); //NON-NLS
293  detailsSb.append(NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.kwHitThLbl"));
294  detailsSb.append("<td>").append(EscapeUtil.escapeHtml(keyword)).append("</td>"); //NON-NLS
295  detailsSb.append("</tr>"); //NON-NLS
296  }
297 
298  //preview
299  attr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW));
300  if (attr != null) {
301  detailsSb.append("<tr>"); //NON-NLS
302  detailsSb.append(NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.previewThLbl"));
303  detailsSb.append("<td>").append(EscapeUtil.escapeHtml(attr.getValueString())).append("</td>"); //NON-NLS
304  detailsSb.append("</tr>"); //NON-NLS
305  }
306 
307  //file
308  detailsSb.append("<tr>"); //NON-NLS
309  detailsSb.append(NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.fileThLbl"));
310  if (hitContent instanceof AbstractFile) {
311  AbstractFile hitFile = (AbstractFile) hitContent;
312  detailsSb.append("<td>").append(hitFile.getParentPath()).append(hitFile.getName()).append("</td>"); //NON-NLS
313  } else {
314  detailsSb.append("<td>").append(hitContent.getName()).append("</td>"); //NON-NLS
315  }
316  detailsSb.append("</tr>"); //NON-NLS
317 
318  //list
319  attr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME));
320  if (attr != null) {
321  detailsSb.append("<tr>"); //NON-NLS
322  detailsSb.append(NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.listThLbl"));
323  detailsSb.append("<td>").append(attr.getValueString()).append("</td>"); //NON-NLS
324  detailsSb.append("</tr>"); //NON-NLS
325  }
326 
327  //regex
328  if (!query.isLiteral()) {
329  attr = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP));
330  if (attr != null) {
331  detailsSb.append("<tr>"); //NON-NLS
332  detailsSb.append(NbBundle.getMessage(this.getClass(), "KeywordSearchIngestModule.regExThLbl"));
333  detailsSb.append("<td>").append(attr.getValueString()).append("</td>"); //NON-NLS
334  detailsSb.append("</tr>"); //NON-NLS
335  }
336  }
337  detailsSb.append("</table>"); //NON-NLS
338 
339  final String key = uniqueKey; // Might be null, but that's supported.
340  SwingUtilities.invokeLater(() -> {
341  IngestServices.getInstance().postMessage(IngestMessage.createDataMessage(MODULE_NAME, subjectSb.toString(), detailsSb.toString(), key, artifact));
342  });
343  }
344  }
345 }

Copyright © 2012-2022 Basis Technology. Generated on: Tue Jun 27 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.