Autopsy  4.20.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
EmailExtracted.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.datamodel;
20 
21 import java.beans.PropertyChangeEvent;
22 import java.beans.PropertyChangeListener;
23 import java.sql.ResultSet;
24 import java.sql.SQLException;
25 import java.util.ArrayList;
26 import java.util.EnumSet;
27 import java.util.HashMap;
28 import java.util.LinkedHashMap;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Observable;
32 import java.util.Observer;
33 import java.util.Set;
34 import java.util.logging.Level;
35 import org.openide.nodes.ChildFactory;
36 import org.openide.nodes.Children;
37 import org.openide.nodes.Node;
38 import org.openide.nodes.Sheet;
39 import org.openide.util.NbBundle;
40 import org.openide.util.WeakListeners;
41 import org.openide.util.lookup.Lookups;
47 import org.sleuthkit.datamodel.BlackboardArtifact;
48 import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_EMAIL_MSG;
49 import org.sleuthkit.datamodel.BlackboardAttribute;
50 import org.sleuthkit.datamodel.SleuthkitCase;
51 import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery;
52 import org.sleuthkit.datamodel.TskCoreException;
54 import org.sleuthkit.datamodel.DataArtifact;
55 
62 public class EmailExtracted implements AutopsyVisitableItem {
63 
64  private static final String LABEL_NAME = BlackboardArtifact.Type.TSK_EMAIL_MSG.getTypeName();
65  private static final Logger logger = Logger.getLogger(EmailExtracted.class.getName());
66  private static final String MAIL_ACCOUNT = NbBundle.getMessage(EmailExtracted.class, "EmailExtracted.mailAccount.text");
67  private static final String MAIL_FOLDER = NbBundle.getMessage(EmailExtracted.class, "EmailExtracted.mailFolder.text");
68  private static final String MAIL_PATH_SEPARATOR = "/";
71 
81  public static final Map<String, String> parsePath(String path) {
82  Map<String, String> parsed = new HashMap<>();
83  String[] split = path == null ? new String[0] : path.split(MAIL_PATH_SEPARATOR);
84  if (split.length < 4) {
85  parsed.put(MAIL_ACCOUNT, NbBundle.getMessage(EmailExtracted.class, "EmailExtracted.defaultAcct.text"));
86  parsed.put(MAIL_FOLDER, NbBundle.getMessage(EmailExtracted.class, "EmailExtracted.defaultFolder.text"));
87  return parsed;
88  }
89  parsed.put(MAIL_ACCOUNT, split[2]);
90  parsed.put(MAIL_FOLDER, split[3]);
91  return parsed;
92  }
93  private SleuthkitCase skCase;
94  private final EmailResults emailResults;
95  private final long filteringDSObjId; // 0 if not filtering/grouping by data source
96 
102  public EmailExtracted(SleuthkitCase skCase) {
103  this(skCase, 0);
104  }
105 
113  public EmailExtracted(SleuthkitCase skCase, long objId) {
114  this.skCase = skCase;
115  this.filteringDSObjId = objId;
116  emailResults = new EmailResults();
117  }
118 
119  @Override
120  public <T> T accept(AutopsyItemVisitor<T> visitor) {
121  return visitor.visit(this);
122  }
123 
124  private final class EmailResults extends Observable {
125 
126  // NOTE: the map can be accessed by multiple worker threads and needs to be synchronized
127  private final Map<String, Map<String, List<Long>>> accounts = new LinkedHashMap<>();
128 
129  EmailResults() {
130  update();
131  }
132 
133  public Set<String> getAccounts() {
134  synchronized (accounts) {
135  return accounts.keySet();
136  }
137  }
138 
139  public Set<String> getFolders(String account) {
140  synchronized (accounts) {
141  return accounts.get(account).keySet();
142  }
143  }
144 
145  public List<Long> getArtifactIds(String account, String folder) {
146  synchronized (accounts) {
147  return accounts.get(account).get(folder);
148  }
149  }
150 
151  @SuppressWarnings("deprecation")
152  public void update() {
153  // clear cache if no case
154  if (skCase == null) {
155  synchronized (accounts) {
156  accounts.clear();
157  }
158  return;
159  }
160 
161  // get artifact id and path (if present) of all email artifacts
162  int emailArtifactId = BlackboardArtifact.Type.TSK_EMAIL_MSG.getTypeID();
163  int pathAttrId = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH.getTypeID();
164 
165  String query = "SELECT \n"
166  + " art.artifact_obj_id AS artifact_obj_id,\n"
167  + " (SELECT value_text FROM blackboard_attributes attr\n"
168  + " WHERE attr.artifact_id = art.artifact_id AND attr.attribute_type_id = " + pathAttrId + "\n"
169  + " LIMIT 1) AS value_text\n"
170  + "FROM \n"
171  + " blackboard_artifacts art\n"
172  + " WHERE art.artifact_type_id = " + emailArtifactId + "\n"
173  + ((filteringDSObjId > 0) ? " AND art.data_source_obj_id = " + filteringDSObjId : "");
174 
175  // form hierarchy of account -> folder -> account id
176  Map<String, Map<String, List<Long>>> newMapping = new HashMap<>();
177 
178  try (CaseDbQuery dbQuery = skCase.executeQuery(query)) {
179  ResultSet resultSet = dbQuery.getResultSet();
180  while (resultSet.next()) {
181  Long artifactObjId = resultSet.getLong("artifact_obj_id");
182  Map<String, String> accountFolderMap = parsePath(resultSet.getString("value_text"));
183  String account = accountFolderMap.get(MAIL_ACCOUNT);
184  String folder = accountFolderMap.get(MAIL_FOLDER);
185 
186  Map<String, List<Long>> folders = newMapping.computeIfAbsent(account, (str) -> new LinkedHashMap<>());
187  List<Long> messages = folders.computeIfAbsent(folder, (str) -> new ArrayList<>());
188  messages.add(artifactObjId);
189  }
190  } catch (TskCoreException | SQLException ex) {
191  logger.log(Level.WARNING, "Cannot initialize email extraction: ", ex); //NON-NLS
192  }
193 
194  synchronized (accounts) {
195  accounts.clear();
196  accounts.putAll(newMapping);
197  }
198 
199  setChanged();
200  notifyObservers();
201  }
202  }
203 
208  public class RootNode extends UpdatableCountTypeNode {
209 
210  public RootNode() {
211  super(Children.create(new AccountFactory(), true),
212  Lookups.singleton(TSK_EMAIL_MSG.getDisplayName()),
213  TSK_EMAIL_MSG.getDisplayName(),
215  TSK_EMAIL_MSG);
216  //super(Children.create(new AccountFactory(), true), Lookups.singleton(DISPLAY_NAME));
217  super.setName(LABEL_NAME);
218  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/mail-icon-16.png"); //NON-NLS
219  emailResults.update();
220  }
221 
222  @Override
223  public boolean isLeafTypeNode() {
224  return false;
225  }
226 
227  @Override
228  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
229  return visitor.visit(this);
230  }
231 
232  @Override
233  protected Sheet createSheet() {
234  Sheet sheet = super.createSheet();
235  Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
236  if (sheetSet == null) {
237  sheetSet = Sheet.createPropertiesSet();
238  sheet.put(sheetSet);
239  }
240 
241  sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.name"),
242  NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.displayName"),
243  NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.desc"),
244  getName()));
245 
246  return sheet;
247  }
248 
249  @Override
250  public String getItemType() {
251  return getClass().getName();
252  }
253  }
254 
258  private class AccountFactory extends ChildFactory.Detachable<String> implements Observer {
259 
260  /*
261  * The pcl is in the class because it has the easiest mechanisms to add
262  * and remove itself during its life cycles.
263  */
264  private final PropertyChangeListener pcl = new PropertyChangeListener() {
265  @Override
266  public void propertyChange(PropertyChangeEvent evt) {
267  String eventType = evt.getPropertyName();
268  if (eventType.equals(IngestManager.IngestModuleEvent.DATA_ADDED.toString())) {
275  try {
283  ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue();
284  if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.Type.TSK_EMAIL_MSG.getTypeID()) {
285  emailResults.update();
286  }
287  } catch (NoCurrentCaseException notUsed) {
291  }
292  } else if (eventType.equals(IngestManager.IngestJobEvent.COMPLETED.toString())
293  || eventType.equals(IngestManager.IngestJobEvent.CANCELLED.toString())) {
300  try {
302  emailResults.update();
303  } catch (NoCurrentCaseException notUsed) {
307  }
308  } else if (eventType.equals(Case.Events.CURRENT_CASE.toString())) {
309  // case was closed. Remove listeners so that we don't get called with a stale case handle
310  if (evt.getNewValue() == null) {
311  removeNotify();
312  skCase = null;
313  }
314  }
315  }
316  };
317 
318  private final PropertyChangeListener weakPcl = WeakListeners.propertyChange(pcl, null);
319 
320  @Override
321  protected void addNotify() {
325  emailResults.update();
326  emailResults.addObserver(this);
327  }
328 
329  @Override
330  protected void finalize() throws Throwable {
331  super.finalize();
335  emailResults.deleteObserver(this);
336  }
337 
338  @Override
339  protected boolean createKeys(List<String> list) {
340  list.addAll(emailResults.getAccounts());
341  return true;
342  }
343 
344  @Override
345  protected Node createNodeForKey(String key) {
346  return new AccountNode(key);
347  }
348 
349  @Override
350  public void update(Observable o, Object arg) {
351  refresh(true);
352  }
353  }
354 
358  public class AccountNode extends DisplayableItemNode implements Observer {
359 
360  private final String accountName;
361 
362  public AccountNode(String accountName) {
363  super(Children.create(new FolderFactory(accountName), true), Lookups.singleton(accountName));
364  super.setName(accountName);
365  this.accountName = accountName;
366  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/account-icon-16.png"); //NON-NLS
368  emailResults.addObserver(this);
369  }
370 
371  private void updateDisplayName() {
372  super.setDisplayName(accountName + " (" + emailResults.getFolders(accountName) + ")");
373  }
374 
375  @Override
376  protected Sheet createSheet() {
377  Sheet sheet = super.createSheet();
378  Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
379  if (sheetSet == null) {
380  sheetSet = Sheet.createPropertiesSet();
381  sheet.put(sheetSet);
382  }
383 
384  sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.name"),
385  NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.displayName"),
386  NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.desc"),
387  getName()));
388 
389  return sheet;
390  }
391 
392  @Override
393  public boolean isLeafTypeNode() {
394  return false;
395  }
396 
397  @Override
398  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
399  return visitor.visit(this);
400  }
401 
402  @Override
403  public void update(Observable o, Object arg) {
405  }
406 
407  @Override
408  public String getItemType() {
409  return getClass().getName();
410  }
411  }
412 
416  private class FolderFactory extends ChildFactory<String> implements Observer {
417 
418  private final String accountName;
419 
420  private FolderFactory(String accountName) {
421  super();
422  this.accountName = accountName;
423  emailResults.addObserver(this);
424  }
425 
426  @Override
427  protected boolean createKeys(List<String> list) {
428  list.addAll(emailResults.getFolders(accountName));
429  return true;
430  }
431 
432  @Override
433  protected Node createNodeForKey(String folderName) {
434  return new FolderNode(accountName, folderName);
435  }
436 
437  @Override
438  public void update(Observable o, Object arg) {
439  refresh(true);
440  }
441  }
442 
456  private static String getFolderKey(String accountName, String folderName) {
457  return accountName + "_" + folderName;
458  }
459 
463  public class FolderNode extends DisplayableItemNode implements Observer {
464 
465  private final String accountName;
466  private final String folderName;
467 
468  public FolderNode(String accountName, String folderName) {
469  super(Children.create(new MessageFactory(accountName, folderName), true), Lookups.singleton(accountName));
470  super.setName(getFolderKey(accountName, folderName));
471  this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/folder-icon-16.png"); //NON-NLS
472  this.accountName = accountName;
473  this.folderName = folderName;
475  emailResults.addObserver(this);
476  }
477 
478  private void updateDisplayName() {
479  super.setDisplayName(folderName + " (" + emailResults.getArtifactIds(accountName, folderName).size() + ")");
480 
481  }
482 
483  @Override
484  public boolean isLeafTypeNode() {
485  return false;
486  }
487 
488  @Override
489  protected Sheet createSheet() {
490  Sheet sheet = super.createSheet();
491  Sheet.Set sheetSet = sheet.get(Sheet.PROPERTIES);
492  if (sheetSet == null) {
493  sheetSet = Sheet.createPropertiesSet();
494  sheet.put(sheetSet);
495  }
496 
497  sheetSet.put(new NodeProperty<>(NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.name"),
498  NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.displayName"),
499  NbBundle.getMessage(this.getClass(), "EmailExtracted.createSheet.name.desc"),
500  getName()));
501 
502  return sheet;
503  }
504 
505  @Override
506  public <T> T accept(DisplayableItemNodeVisitor<T> visitor) {
507  return visitor.visit(this);
508  }
509 
510  @Override
511  public void update(Observable o, Object arg) {
513  }
514 
515  @Override
516  public String getItemType() {
517  return getClass().getName();
518  }
519  }
520 
524  private class MessageFactory extends BaseChildFactory<DataArtifact> implements Observer {
525 
526  private final String accountName;
527  private final String folderName;
528 
529  private MessageFactory(String accountName, String folderName) {
530  super(getFolderKey(accountName, folderName));
531  this.accountName = accountName;
532  this.folderName = folderName;
533  emailResults.addObserver(this);
534  }
535 
536  @Override
537  protected Node createNodeForKey(DataArtifact art) {
538  return new BlackboardArtifactNode(art);
539  }
540 
541  @Override
542  public void update(Observable o, Object arg) {
543  refresh(true);
544  }
545 
546  @Override
547  protected List<DataArtifact> makeKeys() {
548  List<DataArtifact> keys = new ArrayList<>();
549 
550  if (skCase != null) {
551  emailResults.getArtifactIds(accountName, folderName).forEach((id) -> {
552  try {
553  DataArtifact art = skCase.getBlackboard().getDataArtifactById(id);
554  //Cache attributes while we are off the EDT.
555  //See JIRA-5969
556  art.getAttributes();
557  keys.add(art);
558  } catch (TskCoreException ex) {
559  logger.log(Level.WARNING, "Error getting mail messages keys", ex); //NON-NLS
560  }
561  });
562  }
563  return keys;
564  }
565 
566  @Override
567  protected void onAdd() {
568  // No-op
569  }
570 
571  @Override
572  protected void onRemove() {
573  // No-op
574  }
575  }
576 }
static final Map< String, String > parsePath(String path)
BlackboardArtifact.Type getBlackboardArtifactType()
void removeIngestModuleEventListener(final PropertyChangeListener listener)
static synchronized IngestManager getInstance()
void removeIngestJobEventListener(final PropertyChangeListener listener)
static String getFolderKey(String accountName, String folderName)
EmailExtracted(SleuthkitCase skCase, long objId)
void addIngestJobEventListener(final PropertyChangeListener listener)
List< Long > getArtifactIds(String account, String folder)
static final Set< IngestManager.IngestModuleEvent > INGEST_MODULE_EVENTS_OF_INTEREST
static final Set< IngestManager.IngestJobEvent > INGEST_JOB_EVENTS_OF_INTEREST
FolderNode(String accountName, String folderName)
void addIngestModuleEventListener(final PropertyChangeListener listener)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:704
static void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition: Case.java:749
final Map< String, Map< String, List< Long > > > accounts

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