Autopsy 4.22.1
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 */
19package org.sleuthkit.autopsy.datamodel;
20
21import java.beans.PropertyChangeEvent;
22import java.beans.PropertyChangeListener;
23import java.sql.ResultSet;
24import java.sql.SQLException;
25import java.util.ArrayList;
26import java.util.EnumSet;
27import java.util.HashMap;
28import java.util.LinkedHashMap;
29import java.util.List;
30import java.util.Map;
31import java.util.Observable;
32import java.util.Observer;
33import java.util.Set;
34import java.util.logging.Level;
35import org.openide.nodes.ChildFactory;
36import org.openide.nodes.Children;
37import org.openide.nodes.Node;
38import org.openide.nodes.Sheet;
39import org.openide.util.NbBundle;
40import org.openide.util.WeakListeners;
41import org.openide.util.lookup.Lookups;
42import org.sleuthkit.autopsy.casemodule.Case;
43import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
44import org.sleuthkit.autopsy.coreutils.Logger;
45import org.sleuthkit.autopsy.ingest.IngestManager;
46import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
47import org.sleuthkit.datamodel.BlackboardArtifact;
48import static org.sleuthkit.datamodel.BlackboardArtifact.Type.TSK_EMAIL_MSG;
49import org.sleuthkit.datamodel.BlackboardAttribute;
50import org.sleuthkit.datamodel.SleuthkitCase;
51import org.sleuthkit.datamodel.SleuthkitCase.CaseDbQuery;
52import org.sleuthkit.datamodel.TskCoreException;
53import org.sleuthkit.autopsy.datamodel.Artifacts.UpdatableCountTypeNode;
54import org.sleuthkit.datamodel.DataArtifact;
55
62public 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;
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;
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
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) {
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 void removeEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition Case.java:757
static void addEventTypeSubscriber(Set< Events > eventTypes, PropertyChangeListener subscriber)
Definition Case.java:712
synchronized static Logger getLogger(String name)
Definition Logger.java:124
UpdatableCountTypeNode(Children children, Lookup lookup, String baseName, long filteringDSObjId, BlackboardArtifact.Type... types)
final Map< String, Map< String, List< Long > > > accounts
List< Long > getArtifactIds(String account, String folder)
static final Set< IngestManager.IngestModuleEvent > INGEST_MODULE_EVENTS_OF_INTEREST
EmailExtracted(SleuthkitCase skCase, long objId)
static String getFolderKey(String accountName, String folderName)
static final Map< String, String > parsePath(String path)
static final Set< IngestManager.IngestJobEvent > INGEST_JOB_EVENTS_OF_INTEREST
static synchronized IngestManager getInstance()
void removeIngestModuleEventListener(final PropertyChangeListener listener)
void removeIngestJobEventListener(final PropertyChangeListener listener)
void addIngestModuleEventListener(final PropertyChangeListener listener)
void addIngestJobEventListener(final PropertyChangeListener listener)

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.