Autopsy  4.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
Case.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2016 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.casemodule;
20 
21 import java.awt.Cursor;
22 import java.awt.Frame;
23 import java.beans.PropertyChangeListener;
24 import java.beans.PropertyChangeSupport;
25 import java.io.BufferedInputStream;
26 import java.io.File;
27 import java.io.FileInputStream;
28 import java.io.IOException;
29 import java.nio.file.InvalidPathException;
30 import java.nio.file.Path;
31 import java.nio.file.Paths;
32 import java.text.DateFormat;
33 import java.text.SimpleDateFormat;
34 import java.util.Date;
35 import java.util.Collection;
36 import java.util.GregorianCalendar;
37 import java.util.HashMap;
38 import java.util.HashSet;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Set;
42 import java.util.TimeZone;
43 import java.util.UUID;
44 import java.util.logging.Level;
45 import java.util.stream.Collectors;
46 import java.util.stream.Stream;
47 import javax.swing.JOptionPane;
48 import javax.swing.SwingUtilities;
49 import org.apache.commons.io.FileUtils;
50 import org.openide.util.NbBundle;
51 import org.openide.util.actions.CallableSystemAction;
52 import org.openide.windows.WindowManager;
78 import org.sleuthkit.datamodel.BlackboardArtifactTag;
79 import org.sleuthkit.datamodel.Content;
80 import org.sleuthkit.datamodel.ContentTag;
81 import org.sleuthkit.datamodel.Image;
82 import org.sleuthkit.datamodel.Report;
83 import org.sleuthkit.datamodel.SleuthkitCase;
84 import org.sleuthkit.datamodel.SleuthkitJNI.CaseDbHandle.AddImageProcess;
85 import org.sleuthkit.datamodel.TskCoreException;
86 import org.sleuthkit.datamodel.TskException;
87 
93 public class Case implements SleuthkitCase.ErrorObserver {
94 
95  private static final String autopsyVer = Version.getVersion(); // current version of autopsy. Change it when the version is changed
96  private static final String EVENT_CHANNEL_NAME = "%s-Case-Events"; //NON-NLS
97  private static String appName = null;
98  volatile private IntervalErrorReportData tskErrorReporter = null;
99  private static final int MIN_SECONDS_BETWEEN_ERROR_REPORTS = 60; // No less than 60 seconds between warnings for errors
100  private static final int MAX_SANITIZED_NAME_LENGTH = 47;
101 
106  public static final String propStartup = "LBL_StartupDialog"; //NON-NLS
107 
113 
118  public enum Events {
119 
215  };
216 
221  @NbBundle.Messages({"Case_caseType_singleUser=Single-user case",
222  "Case_caseType_multiUser=Multi-user case"})
223  public enum CaseType {
224 
225  SINGLE_USER_CASE("Single-user case"), //NON-NLS
226  MULTI_USER_CASE("Multi-user case"); //NON-NLS
227 
228  private final String caseType;
229 
230  private CaseType(String s) {
231  caseType = s;
232  }
233 
234  public boolean equalsName(String otherType) {
235  return (otherType == null) ? false : caseType.equals(otherType);
236  }
237 
238  public static CaseType fromString(String typeName) {
239  if (typeName != null) {
240  for (CaseType c : CaseType.values()) {
241  if (typeName.equalsIgnoreCase(c.caseType)) {
242  return c;
243  }
244  }
245  }
246  return null;
247  }
248 
249  @Override
250  public String toString() {
251  return caseType;
252  }
253 
255  if (fromString(caseType) == SINGLE_USER_CASE) {
256  return Bundle.Case_caseType_singleUser();
257  } else {
258  return Bundle.Case_caseType_multiUser();
259  }
260  }
261  };
262 
263  private String name;
264  private String number;
265  private String examiner;
266  private String configFilePath;
267  private final XMLCaseManagement xmlcm;
268  private final SleuthkitCase db;
269  // Track the current case (only set with changeCase() method)
270  private static Case currentCase = null;
271  private final CaseType caseType;
272  private final Services services;
273  private static final Logger logger = Logger.getLogger(Case.class.getName());
274  static final String CASE_EXTENSION = "aut"; //NON-NLS
275  static final String CASE_DOT_EXTENSION = "." + CASE_EXTENSION;
276  private final static String CACHE_FOLDER = "Cache"; //NON-NLS
277  private final static String EXPORT_FOLDER = "Export"; //NON-NLS
278  private final static String LOG_FOLDER = "Log"; //NON-NLS
279  final static String MODULE_FOLDER = "ModuleOutput"; //NON-NLS
280  private final static String REPORTS_FOLDER = "Reports"; //NON-NLS
281  private final static String TEMP_FOLDER = "Temp"; //NON-NLS
282 
283  // we cache if the case has data in it yet since a few places ask for it and we dont' need to keep going to DB
284  private boolean hasData = false;
285 
286  private CollaborationMonitor collaborationMonitor;
287 
291  private Case(String name, String number, String examiner, String configFilePath, XMLCaseManagement xmlcm, SleuthkitCase db, CaseType type) {
292  this.name = name;
293  this.number = number;
294  this.examiner = examiner;
295  this.configFilePath = configFilePath;
296  this.xmlcm = xmlcm;
297  this.caseType = type;
298  this.db = db;
299  this.services = new Services(db);
300  }
301 
309  public static Case getCurrentCase() {
310  if (currentCase != null) {
311  return currentCase;
312  } else {
313  throw new IllegalStateException(NbBundle.getMessage(Case.class, "Case.getCurCase.exception.noneOpen"));
314  }
315  }
316 
322  public static boolean isCaseOpen() {
323  return currentCase != null;
324  }
325 
333  private static void changeCase(Case newCase) {
334  // close the existing case
335  Case oldCase = Case.currentCase;
336  Case.currentCase = null;
337  if (oldCase != null) {
338  SwingUtilities.invokeLater(() -> {
339  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
340  });
342  doCaseChange(null); //closes windows, etc
343  if (null != oldCase.tskErrorReporter) {
344  oldCase.tskErrorReporter.shutdown(); // stop listening for TSK errors for the old case
345  oldCase.tskErrorReporter = null;
346  }
347  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), oldCase, null));
348  if (CaseType.MULTI_USER_CASE == oldCase.getCaseType()) {
349  if (null != oldCase.collaborationMonitor) {
350  oldCase.collaborationMonitor.shutdown();
351  }
352  eventPublisher.closeRemoteEventChannel();
353  }
354  }
355 
356  if (newCase != null) {
357  currentCase = newCase;
359  // sanity check
360  if (null != currentCase.tskErrorReporter) {
361  currentCase.tskErrorReporter.shutdown();
362  }
363  // start listening for TSK errors for the new case
364  currentCase.tskErrorReporter = new IntervalErrorReportData(currentCase, MIN_SECONDS_BETWEEN_ERROR_REPORTS,
365  NbBundle.getMessage(Case.class, "IntervalErrorReport.ErrorText"));
366  doCaseChange(currentCase);
367  SwingUtilities.invokeLater(() -> {
368  RecentCases.getInstance().addRecentCase(currentCase.name, currentCase.configFilePath); // update the recent cases
369  });
370  if (CaseType.MULTI_USER_CASE == newCase.getCaseType()) {
371  try {
378  eventPublisher.openRemoteEventChannel(String.format(EVENT_CHANNEL_NAME, newCase.getTextIndexName()));
379  currentCase.collaborationMonitor = new CollaborationMonitor();
380  } catch (AutopsyEventException | CollaborationMonitor.CollaborationMonitorException ex) {
381  logger.log(Level.SEVERE, "Failed to setup for collaboration", ex); //NON-NLS
382  MessageNotifyUtil.Notify.error(NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.Title"), NbBundle.getMessage(Case.class, "Case.CollaborationSetup.FailNotify.ErrMsg"));
383  }
384  }
385  eventPublisher.publishLocally(new AutopsyEvent(Events.CURRENT_CASE.toString(), null, currentCase));
386 
387  } else {
389  }
390  SwingUtilities.invokeLater(() -> {
391  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
392  });
393  }
394 
395  @Override
396  public void receiveError(String context, String errorMessage) {
397  /*
398  * NOTE: We are accessing tskErrorReporter from two different threads.
399  * This is ok as long as we only read the value of tskErrorReporter
400  * because tskErrorReporter is declared as volatile.
401  */
402  if (null != tskErrorReporter) {
403  tskErrorReporter.addProblems(context, errorMessage);
404  }
405  }
406 
407  AddImageProcess makeAddImageProcess(String timezone, boolean processUnallocSpace, boolean noFatOrphans) {
408  return this.db.makeAddImageProcess(timezone, processUnallocSpace, noFatOrphans);
409  }
410 
430  public static void create(String caseDir, String caseName, String caseNumber, String examiner) throws CaseActionException {
431  create(caseDir, caseName, caseNumber, examiner, CaseType.SINGLE_USER_CASE);
432  }
433 
453  public static void create(String caseDir, String caseName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException {
454  logger.log(Level.INFO, "Creating case with case directory {0}, caseName {1}", new Object[]{caseDir, caseName}); //NON-NLS
455 
456  /*
457  * Create case directory if it doesn't already exist.
458  */
459  if (new File(caseDir).exists() == false) {
460  Case.createCaseDirectory(caseDir, caseType);
461  }
462 
463  /*
464  * Sanitize the case name, create a unique keyword search index name,
465  * and create a standard (single-user) or unique (multi-user) case
466  * database name.
467  */
468  String santizedCaseName = sanitizeCaseName(caseName);
469  SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
470  Date date = new Date();
471  String indexName = santizedCaseName + "_" + dateFormat.format(date);
472  String dbName = null;
473  if (caseType == CaseType.SINGLE_USER_CASE) {
474  dbName = caseDir + File.separator + "autopsy.db"; //NON-NLS
475  } else if (caseType == CaseType.MULTI_USER_CASE) {
476  dbName = indexName;
477  }
478 
479  /*
480  * Create the case metadata (.aut) file.
481  *
482  * TODO (AUT-1885): Replace use of obsolete and unsafe XMLCaseManagement
483  * class with use of CaseMetadata class.
484  */
485  String configFilePath = caseDir + File.separator + caseName + CASE_DOT_EXTENSION;
486  XMLCaseManagement xmlcm = new XMLCaseManagement();
487  xmlcm.create(caseDir, caseName, examiner, caseNumber, caseType, dbName, indexName);
488  xmlcm.writeFile();
489 
490  /*
491  * Create the case database.
492  */
493  SleuthkitCase db = null;
494  try {
495  if (caseType == CaseType.SINGLE_USER_CASE) {
496  db = SleuthkitCase.newCase(dbName);
497  } else if (caseType == CaseType.MULTI_USER_CASE) {
498  db = SleuthkitCase.newCase(dbName, UserPreferences.getDatabaseConnectionInfo(), caseDir);
499  }
500  } catch (TskCoreException ex) {
501  logger.log(Level.SEVERE, String.format("Error creating a case %s in %s ", caseName, caseDir), ex); //NON-NLS
502  SwingUtilities.invokeLater(() -> {
503  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
504  });
505  /*
506  * SleuthkitCase.newCase throws TskCoreExceptions with user-friendly
507  * messages, so propagate the exception message.
508  */
509  throw new CaseActionException(ex.getMessage(), ex); //NON-NLS
510  } catch (UserPreferencesException ex) {
511  logger.log(Level.SEVERE, "Error accessing case database connection info", ex); //NON-NLS
512  SwingUtilities.invokeLater(() -> {
513  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
514  });
515  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex);
516  }
517 
518  Case newCase = new Case(caseName, caseNumber, examiner, configFilePath, xmlcm, db, caseType);
519  changeCase(newCase);
520  }
521 
550  static String sanitizeCaseName(String caseName) {
551 
552  String result;
553 
554  // Remove all non-ASCII characters
555  result = caseName.replaceAll("[^\\p{ASCII}]", "_"); //NON-NLS
556 
557  // Remove all control characters
558  result = result.replaceAll("[\\p{Cntrl}]", "_"); //NON-NLS
559 
560  // Remove / \ : ? space ' "
561  result = result.replaceAll("[ /?:'\"\\\\]", "_"); //NON-NLS
562 
563  // Make it all lowercase
564  result = result.toLowerCase();
565 
566  // Must start with letter or underscore for PostgreSQL. If not, prepend an underscore.
567  if (result.length() > 0 && !(Character.isLetter(result.codePointAt(0))) && !(result.codePointAt(0) == '_')) {
568  result = "_" + result;
569  }
570 
571  // Chop to 63-16=47 left (63 max for PostgreSQL, taking 16 for the date _20151225_123456)
572  if (result.length() > MAX_SANITIZED_NAME_LENGTH) {
573  result = result.substring(0, MAX_SANITIZED_NAME_LENGTH);
574  }
575 
576  if (result.isEmpty()) {
577  result = "case"; //NON-NLS
578  }
579 
580  return result;
581  }
582 
595  public static void open(String caseMetadataFilePath) throws CaseActionException {
596  logger.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS
597 
598  /*
599  * Verify the extension of the case metadata file.
600  */
601  if (!caseMetadataFilePath.endsWith(CASE_DOT_EXTENSION)) {
602  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.checkFile.msg", CASE_DOT_EXTENSION));
603  }
604 
605  try {
606  /*
607  * Get the case metadata required to open the case database.
608  */
609  CaseMetadata metadata = new CaseMetadata(Paths.get(caseMetadataFilePath));
610  String caseName = metadata.getCaseName();
611  String caseNumber = metadata.getCaseNumber();
612  String examiner = metadata.getExaminer();
613  CaseType caseType = metadata.getCaseType();
614  String caseDir = metadata.getCaseDirectory();
615 
616  /*
617  * Open the case database.
618  */
619  SleuthkitCase db;
620  if (caseType == CaseType.SINGLE_USER_CASE) {
621  String dbPath = Paths.get(caseDir, "autopsy.db").toString(); //NON-NLS
622  db = SleuthkitCase.openCase(dbPath);
623  } else {
625  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.open.exception.multiUserCaseNotEnabled"));
626  }
627  try {
628  db = SleuthkitCase.openCase(metadata.getCaseDatabaseName(), UserPreferences.getDatabaseConnectionInfo(), caseDir);
629  } catch (UserPreferencesException ex) {
630  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex);
631  }
632  }
633 
634  /*
635  * Check for the presence of the UI and do things that can only be
636  * done with user interaction.
637  */
639  /*
640  * If the case database was upgraded for a new schema, notify
641  * the user.
642  */
643  if (null != db.getBackupDatabasePath()) {
644  SwingUtilities.invokeLater(() -> {
645  JOptionPane.showMessageDialog(
646  WindowManager.getDefault().getMainWindow(),
647  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.msg", db.getBackupDatabasePath()),
648  NbBundle.getMessage(Case.class, "Case.open.msgDlg.updated.title"),
649  JOptionPane.INFORMATION_MESSAGE);
650  });
651  }
652 
653  /*
654  * Look for the files for the data sources listed in the case
655  * database and give the user the opportunity to locate any that
656  * are missing.
657  */
658  Map<Long, String> imgPaths = getImagePaths(db);
659  for (Map.Entry<Long, String> entry : imgPaths.entrySet()) {
660  long obj_id = entry.getKey();
661  String path = entry.getValue();
662  boolean fileExists = (pathExists(path) || driveExists(path));
663  if (!fileExists) {
664  int ret = JOptionPane.showConfirmDialog(
665  WindowManager.getDefault().getMainWindow(),
666  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.msg", getAppName(), path),
667  NbBundle.getMessage(Case.class, "Case.checkImgExist.confDlg.doesntExist.title"),
668  JOptionPane.YES_NO_OPTION);
669  if (ret == JOptionPane.YES_OPTION) {
670  MissingImageDialog.makeDialog(obj_id, db);
671  } else {
672  logger.log(Level.WARNING, "Selected image files don't match old files!"); //NON-NLS
673  }
674  }
675  }
676  }
677 
678  /*
679  * TODO (AUT-1885): Replace use of obsolete and unsafe
680  * XMLCaseManagement class with use of CaseMetadata class.
681  */
682  XMLCaseManagement xmlcm = new XMLCaseManagement();
683  xmlcm.open(caseMetadataFilePath);
684  Case openedCase = new Case(caseName, caseNumber, examiner, caseMetadataFilePath, xmlcm, db, caseType);
685  changeCase(openedCase);
686 
687  } catch (CaseMetadataException ex) {
688  throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.metaDataFileCorrupt.exception.msg"), ex); //NON-NLS
689  } catch (TskCoreException ex) {
690  SwingUtilities.invokeLater(() -> {
691  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
692  });
693  /*
694  * SleuthkitCase.openCase throws TskCoreExceptions with
695  * user-friendly messages, so propagate the exception message.
696  */
697  throw new CaseActionException(ex.getMessage(), ex);
698  }
699  }
700 
701  static Map<Long, String> getImagePaths(SleuthkitCase db) { //TODO: clean this up
702  Map<Long, String> imgPaths = new HashMap<>();
703  try {
704  Map<Long, List<String>> imgPathsList = db.getImagePaths();
705  for (Map.Entry<Long, List<String>> entry : imgPathsList.entrySet()) {
706  if (entry.getValue().size() > 0) {
707  imgPaths.put(entry.getKey(), entry.getValue().get(0));
708  }
709  }
710  } catch (TskException ex) {
711  logger.log(Level.WARNING, "Error getting image paths", ex); //NON-NLS
712  }
713  return imgPaths;
714  }
715 
726  @Deprecated
727  public Image addImage(String imgPath, long imgId, String timeZone) throws CaseActionException {
728  try {
729  Image newDataSource = db.getImageById(imgId);
730  notifyDataSourceAdded(newDataSource, UUID.randomUUID());
731  return newDataSource;
732  } catch (Exception ex) {
733  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.addImg.exception.msg"), ex);
734  }
735  }
736 
747  @Deprecated
748  void addLocalDataSource(Content newDataSource) {
749  notifyDataSourceAdded(newDataSource, UUID.randomUUID());
750  }
751 
762  public void notifyAddingDataSource(UUID dataSourceId) {
763  eventPublisher.publish(new AddingDataSourceEvent(dataSourceId));
764  }
765 
774  public void notifyFailedAddingDataSource(UUID dataSourceId) {
775  eventPublisher.publish(new AddingDataSourceFailedEvent(dataSourceId));
776  }
777 
790  public void notifyDataSourceAdded(Content newDataSource, UUID dataSourceId) {
791  eventPublisher.publish(new DataSourceAddedEvent(newDataSource, dataSourceId));
792  }
793 
801  public void notifyContentTagAdded(ContentTag newTag) {
802  eventPublisher.publish(new ContentTagAddedEvent(newTag));
803  }
804 
812  public void notifyContentTagDeleted(ContentTag deletedTag) {
813  eventPublisher.publish(new ContentTagDeletedEvent(deletedTag));
814  }
815 
823  public void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag) {
824  eventPublisher.publish(new BlackBoardArtifactTagAddedEvent(newTag));
825  }
826 
834  public void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag) {
835  eventPublisher.publish(new BlackBoardArtifactTagDeletedEvent(deletedTag));
836  }
837 
842  return services;
843  }
844 
851  public SleuthkitCase getSleuthkitCase() {
852  return this.db;
853  }
854 
858  public void closeCase() throws CaseActionException {
859  changeCase(null);
860  try {
861  services.close();
862  this.xmlcm.close(); // close the xmlcm
863  this.db.close();
864  } catch (Exception e) {
865  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.closeCase.exception.msg"), e);
866  }
867  }
868 
876  void deleteCase(File caseDir) throws CaseActionException {
877  logger.log(Level.INFO, "Deleting case.\ncaseDir: {0}", caseDir); //NON-NLS
878 
879  try {
880 
881  xmlcm.close(); // close the xmlcm
882  boolean result = deleteCaseDirectory(caseDir); // delete the directory
883 
884  RecentCases.getInstance().removeRecentCase(this.name, this.configFilePath); // remove it from the recent case
885  Case.changeCase(null);
886  if (result == false) {
887  throw new CaseActionException(
888  NbBundle.getMessage(this.getClass(), "Case.deleteCase.exception.msg", caseDir));
889  }
890  } catch (Exception ex) {
891  logger.log(Level.SEVERE, "Error deleting the current case dir: " + caseDir, ex); //NON-NLS
892  throw new CaseActionException(
893  NbBundle.getMessage(this.getClass(), "Case.deleteCase.exception.msg2", caseDir), ex);
894  }
895  }
896 
907  void updateCaseName(String oldCaseName, String oldPath, String newCaseName, String newPath) throws CaseActionException {
908  try {
909  xmlcm.setCaseName(newCaseName); // set the case
910  name = newCaseName; // change the local value
911  eventPublisher.publish(new AutopsyEvent(Events.NAME.toString(), oldCaseName, newCaseName));
912  SwingUtilities.invokeLater(() -> {
913  try {
914  RecentCases.getInstance().updateRecentCase(oldCaseName, oldPath, newCaseName, newPath); // update the recent case
915  updateMainWindowTitle(newCaseName);
916  } catch (Exception e) {
917  Logger.getLogger(Case.class.getName()).log(Level.WARNING, "Error: problem updating case name.", e); //NON-NLS
918  }
919  });
920  } catch (Exception e) {
921  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.updateCaseName.exception.msg"), e);
922  }
923  }
924 
933  void updateExaminer(String oldExaminer, String newExaminer) throws CaseActionException {
934  try {
935  xmlcm.setCaseExaminer(newExaminer); // set the examiner
936  examiner = newExaminer;
937  eventPublisher.publish(new AutopsyEvent(Events.EXAMINER.toString(), oldExaminer, newExaminer));
938  } catch (Exception e) {
939  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.updateExaminer.exception.msg"), e);
940  }
941  }
942 
951  void updateCaseNumber(String oldCaseNumber, String newCaseNumber) throws CaseActionException {
952  try {
953  xmlcm.setCaseNumber(newCaseNumber); // set the case number
954  number = newCaseNumber;
955  eventPublisher.publish(new AutopsyEvent(Events.NUMBER.toString(), oldCaseNumber, newCaseNumber));
956  } catch (Exception e) {
957  throw new CaseActionException(NbBundle.getMessage(this.getClass(), "Case.updateCaseNum.exception.msg"), e);
958  }
959  }
960 
966  public static boolean existsCurrentCase() {
967  return currentCase != null;
968  }
969 
975  private void setConfigFilePath(String givenPath) {
976  configFilePath = givenPath;
977  }
978 
984  String getConfigFilePath() {
985  return configFilePath;
986  }
987 
993  public static String getAutopsyVersion() {
994  return autopsyVer;
995  }
996 
1002  public static String getAppName() {
1003  if ((appName == null) || appName.equals("")) {
1004  appName = WindowManager.getDefault().getMainWindow().getTitle();
1005  }
1006  return appName;
1007  }
1008 
1014  public String getName() {
1015  return name;
1016  }
1017 
1023  public String getNumber() {
1024  return number;
1025  }
1026 
1032  public String getExaminer() {
1033  return examiner;
1034  }
1035 
1041  public String getCaseDirectory() {
1042  if (xmlcm == null) {
1043  return "";
1044  } else {
1045  return xmlcm.getCaseDirectory();
1046  }
1047  }
1048 
1055  return this.caseType;
1056  }
1057 
1064  public String getTempDirectory() {
1065  return getDirectory(TEMP_FOLDER);
1066  }
1067 
1074  public String getCacheDirectory() {
1075  return getDirectory(CACHE_FOLDER);
1076  }
1077 
1084  public String getExportDirectory() {
1085  return getDirectory(EXPORT_FOLDER);
1086  }
1087 
1094  public String getLogDirectoryPath() {
1095  return getDirectory(LOG_FOLDER);
1096  }
1097 
1104  public String getReportDirectory() {
1105  return getDirectory(REPORTS_FOLDER);
1106  }
1107 
1114  public String getModuleDirectory() {
1115  return getDirectory(MODULE_FOLDER);
1116  }
1117 
1126  public String getOutputDirectory() {
1127  return getHostDirectory();
1128  }
1129 
1135  private String getDirectory(String input) {
1136  File theDirectory = new File(getHostDirectory() + File.separator + input);
1137  if (!theDirectory.exists()) { // Create it if it doesn't exist already.
1138  theDirectory.mkdirs();
1139  }
1140  return theDirectory.toString();
1141  }
1142 
1151  Path thePath;
1153  thePath = Paths.get(NetworkUtils.getLocalHostName(), MODULE_FOLDER);
1154  } else {
1155  thePath = Paths.get(MODULE_FOLDER);
1156  }
1157  // Do not autocreate this relative path. It will have already been
1158  // created when the case was made.
1159  return thePath.toString();
1160  }
1161 
1170  private String getHostDirectory() {
1171  String caseDirectory = getCaseDirectory();
1172  Path hostPath;
1173  if (caseType == CaseType.MULTI_USER_CASE) {
1174  hostPath = Paths.get(caseDirectory, NetworkUtils.getLocalHostName());
1175  } else {
1176  hostPath = Paths.get(caseDirectory);
1177  }
1178  if (!hostPath.toFile().exists()) {
1179  hostPath.toFile().mkdirs();
1180  }
1181  return hostPath.toString();
1182  }
1183 
1192  @Deprecated
1193  public String getModulesOutputDirAbsPath() {
1194  return getModuleDirectory();
1195  }
1196 
1206  @Deprecated
1207  public static String getModulesOutputDirRelPath() {
1208  return "ModuleOutput"; //NON-NLS
1209  }
1210 
1220  @Deprecated
1221  public static PropertyChangeSupport getPropertyChangeSupport() {
1222  return new PropertyChangeSupport(Case.class);
1223  }
1224 
1232  public List<Content> getDataSources() throws TskCoreException {
1233  List<Content> list = db.getRootObjects();
1234  hasData = (list.size() > 0);
1235  return list;
1236  }
1237 
1243  public String getCreatedDate() {
1244  if (xmlcm == null) {
1245  return "";
1246  } else {
1247  return xmlcm.getCreatedDate();
1248  }
1249  }
1250 
1256  public String getTextIndexName() {
1257  if (xmlcm == null) {
1258  return "";
1259  } else {
1260  return xmlcm.getTextIndexName();
1261  }
1262  }
1263 
1269  public Set<TimeZone> getTimeZone() {
1270  Set<TimeZone> timezones = new HashSet<>();
1271  try {
1272  for (Content c : getDataSources()) {
1273  final Content dataSource = c.getDataSource();
1274  if ((dataSource != null) && (dataSource instanceof Image)) {
1275  Image image = (Image) dataSource;
1276  timezones.add(TimeZone.getTimeZone(image.getTimeZone()));
1277  }
1278  }
1279  } catch (TskCoreException ex) {
1280  logger.log(Level.INFO, "Error getting time zones", ex); //NON-NLS
1281  }
1282  return timezones;
1283  }
1284 
1292  public static synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
1293  addEventSubscriber(Stream.of(Events.values())
1294  .map(Events::toString)
1295  .collect(Collectors.toSet()), listener);
1296  }
1297 
1305  public static synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
1306  removeEventSubscriber(Stream.of(Events.values())
1307  .map(Events::toString)
1308  .collect(Collectors.toSet()), listener);
1309  }
1310 
1318  public static void addEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
1319  eventPublisher.addSubscriber(eventNames, subscriber);
1320  }
1321 
1329  public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) {
1330  eventPublisher.addSubscriber(eventName, subscriber);
1331  }
1332 
1340  public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) {
1341  eventPublisher.removeSubscriber(eventName, subscriber);
1342  }
1343 
1351  public static void removeEventSubscriber(Set<String> eventNames, PropertyChangeListener subscriber) {
1352  eventPublisher.removeSubscriber(eventNames, subscriber);
1353  }
1354 
1362  public static boolean pathExists(String imgPath) {
1363  return new File(imgPath).isFile();
1364  }
1368  private static final String pdisk = "\\\\.\\physicaldrive"; //NON-NLS
1369  private static final String dev = "/dev/"; //NON-NLS
1370 
1371  static boolean isPhysicalDrive(String path) {
1372  return path.toLowerCase().startsWith(pdisk)
1373  || path.toLowerCase().startsWith(dev);
1374  }
1375 
1379  static boolean isPartition(String path) {
1380  return path.toLowerCase().startsWith("\\\\.\\")
1381  && path.toLowerCase().endsWith(":");
1382  }
1383 
1391  static boolean driveExists(String path) {
1392  // Test the drive by reading the first byte and checking if it's -1
1393  BufferedInputStream br = null;
1394  try {
1395  File tmp = new File(path);
1396  br = new BufferedInputStream(new FileInputStream(tmp));
1397  int b = br.read();
1398  return b != -1;
1399  } catch (Exception ex) {
1400  return false;
1401  } finally {
1402  try {
1403  if (br != null) {
1404  br.close();
1405  }
1406  } catch (IOException ex) {
1407  }
1408  }
1409  }
1410 
1420  public static String convertTimeZone(String timezoneID) {
1421 
1422  TimeZone zone = TimeZone.getTimeZone(timezoneID);
1423  int offset = zone.getRawOffset() / 1000;
1424  int hour = offset / 3600;
1425  int min = (offset % 3600) / 60;
1426 
1427  DateFormat dfm = new SimpleDateFormat("z");
1428  dfm.setTimeZone(zone);
1429  boolean hasDaylight = zone.useDaylightTime();
1430  String first = dfm.format(new GregorianCalendar(2010, 1, 1).getTime()).substring(0, 3); // make it only 3 letters code
1431  String second = dfm.format(new GregorianCalendar(2011, 6, 6).getTime()).substring(0, 3); // make it only 3 letters code
1432  int mid = hour * -1;
1433  String result = first + Integer.toString(mid);
1434  if (min != 0) {
1435  result = result + ":" + Integer.toString(min);
1436  }
1437  if (hasDaylight) {
1438  result = result + second;
1439  }
1440 
1441  return result;
1442  }
1443 
1453  @Deprecated
1454  static void createCaseDirectory(String caseDir, String caseName) throws CaseActionException {
1455  createCaseDirectory(caseDir, CaseType.SINGLE_USER_CASE);
1456  }
1457 
1466  static void createCaseDirectory(String caseDir, CaseType caseType) throws CaseActionException {
1467 
1468  File caseDirF = new File(caseDir);
1469  if (caseDirF.exists()) {
1470  if (caseDirF.isFile()) {
1471  throw new CaseActionException(
1472  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existNotDir", caseDir));
1473  } else if (!caseDirF.canRead() || !caseDirF.canWrite()) {
1474  throw new CaseActionException(
1475  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.existCantRW", caseDir));
1476  }
1477  }
1478 
1479  try {
1480  boolean result = (caseDirF).mkdirs(); // create root case Directory
1481  if (result == false) {
1482  throw new CaseActionException(
1483  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreate", caseDir));
1484  }
1485 
1486  // create the folders inside the case directory
1487  String hostClause = "";
1488 
1489  if (caseType == CaseType.MULTI_USER_CASE) {
1490  hostClause = File.separator + NetworkUtils.getLocalHostName();
1491  }
1492  result = result && (new File(caseDir + hostClause + File.separator + EXPORT_FOLDER)).mkdirs()
1493  && (new File(caseDir + hostClause + File.separator + LOG_FOLDER)).mkdirs()
1494  && (new File(caseDir + hostClause + File.separator + TEMP_FOLDER)).mkdirs()
1495  && (new File(caseDir + hostClause + File.separator + CACHE_FOLDER)).mkdirs();
1496 
1497  if (result == false) {
1498  throw new CaseActionException(
1499  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateCaseDir", caseDir));
1500  }
1501 
1502  final String modulesOutDir = caseDir + hostClause + File.separator + MODULE_FOLDER;
1503  result = new File(modulesOutDir).mkdir();
1504  if (result == false) {
1505  throw new CaseActionException(
1506  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateModDir",
1507  modulesOutDir));
1508  }
1509 
1510  final String reportsOutDir = caseDir + hostClause + File.separator + REPORTS_FOLDER;
1511  result = new File(reportsOutDir).mkdir();
1512  if (result == false) {
1513  throw new CaseActionException(
1514  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.cantCreateReportsDir",
1515  modulesOutDir));
1516  }
1517 
1518  } catch (Exception e) {
1519  throw new CaseActionException(
1520  NbBundle.getMessage(Case.class, "Case.createCaseDir.exception.gen", caseDir), e);
1521  }
1522  }
1523 
1531  static boolean deleteCaseDirectory(File casePath) {
1532  logger.log(Level.INFO, "Deleting case directory: {0}", casePath.getAbsolutePath()); //NON-NLS
1533  return FileUtil.deleteDir(casePath);
1534  }
1535 
1539  static public void invokeStartupDialog() {
1541  }
1542 
1550  static public boolean isValidName(String caseName) {
1551  return !(caseName.contains("\\") || caseName.contains("/") || caseName.contains(":")
1552  || caseName.contains("*") || caseName.contains("?") || caseName.contains("\"")
1553  || caseName.contains("<") || caseName.contains(">") || caseName.contains("|"));
1554  }
1555 
1556  static private void clearTempFolder() {
1557  File tempFolder = new File(currentCase.getTempDirectory());
1558  if (tempFolder.isDirectory()) {
1559  File[] files = tempFolder.listFiles();
1560  if (files.length > 0) {
1561  for (File file : files) {
1562  if (file.isDirectory()) {
1563  deleteCaseDirectory(file);
1564  } else {
1565  file.delete();
1566  }
1567  }
1568  }
1569  }
1570  }
1571 
1577  private static void checkSubFolders(Case openedCase) {
1578  String modulesOutputDir = openedCase.getModuleDirectory();
1579  File modulesOutputDirF = new File(modulesOutputDir);
1580  if (!modulesOutputDirF.exists()) {
1581  logger.log(Level.INFO, "Creating modules output dir for the case."); //NON-NLS
1582 
1583  try {
1584  if (!modulesOutputDirF.mkdir()) {
1585  logger.log(Level.SEVERE, "Error creating modules output dir for the case, dir: {0}", modulesOutputDir); //NON-NLS
1586  }
1587  } catch (SecurityException e) {
1588  logger.log(Level.SEVERE, "Error creating modules output dir for the case, dir: " + modulesOutputDir, e); //NON-NLS
1589  }
1590  }
1591  }
1592 
1593  //case change helper
1594  private static void doCaseChange(Case toChangeTo) {
1595  logger.log(Level.INFO, "Changing Case to: {0}", toChangeTo); //NON-NLS
1596  if (toChangeTo != null) { // new case is open
1597 
1598  // clear the temp folder when the case is created / opened
1600  checkSubFolders(toChangeTo);
1601 
1603  // enable these menus
1604  CallableSystemAction.get(AddImageAction.class).setEnabled(true);
1605  CallableSystemAction.get(CaseCloseAction.class).setEnabled(true);
1606  CallableSystemAction.get(CasePropertiesAction.class).setEnabled(true);
1607  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(true); // Delete Case menu
1608 
1609  if (toChangeTo.hasData()) {
1610  // open all top components
1611  SwingUtilities.invokeLater(() -> {
1613  });
1614  } else {
1615  // close all top components
1616  SwingUtilities.invokeLater(() -> {
1618  });
1619  }
1620  }
1621 
1623  SwingUtilities.invokeLater(() -> {
1624  updateMainWindowTitle(currentCase.name);
1625  });
1626  } else {
1627  SwingUtilities.invokeLater(() -> {
1628  Frame f = WindowManager.getDefault().getMainWindow();
1629  f.setTitle(Case.getAppName()); // set the window name to just application name
1630  });
1631  }
1632 
1633  } else { // case is closed
1635 
1636  SwingUtilities.invokeLater(() -> {
1637  // close all top components first
1639 
1640  // disable these menus
1641  CallableSystemAction.get(AddImageAction.class).setEnabled(false); // Add Image menu
1642  CallableSystemAction.get(CaseCloseAction.class).setEnabled(false); // Case Close menu
1643  CallableSystemAction.get(CasePropertiesAction.class).setEnabled(false); // Case Properties menu
1644  CallableSystemAction.get(CaseDeleteAction.class).setEnabled(false); // Delete Case menu
1645  });
1646  }
1647 
1648  //clear pending notifications
1649  SwingUtilities.invokeLater(() -> {
1651  });
1652 
1653  SwingUtilities.invokeLater(() -> {
1654  Frame f = WindowManager.getDefault().getMainWindow();
1655  f.setTitle(Case.getAppName()); // set the window name to just application name
1656  });
1657 
1658  //try to force gc to happen
1659  System.gc();
1660  System.gc();
1661  }
1662 
1663  //log memory usage after case changed
1664  logger.log(Level.INFO, PlatformUtil.getAllMemUsageInfo());
1665 
1666  }
1667 
1668  //case name change helper
1669  private static void updateMainWindowTitle(String newCaseName) {
1670  // update case name
1671  if (!newCaseName.equals("")) {
1672  Frame f = WindowManager.getDefault().getMainWindow();
1673  f.setTitle(newCaseName + " - " + Case.getAppName()); // set the window name to the new value
1674  }
1675  }
1676 
1687  public void addReport(String localPath, String srcModuleName, String reportName) throws TskCoreException {
1688  String normalizedLocalPath;
1689  try {
1690  normalizedLocalPath = Paths.get(localPath).normalize().toString();
1691  } catch (InvalidPathException ex) {
1692  String errorMsg = "Invalid local path provided: " + localPath; // NON-NLS
1693  throw new TskCoreException(errorMsg, ex);
1694  }
1695  Report report = this.db.addReport(normalizedLocalPath, srcModuleName, reportName);
1696  eventPublisher.publish(new ReportAddedEvent(report));
1697  }
1698 
1699  public List<Report> getAllReports() throws TskCoreException {
1700  return this.db.getAllReports();
1701  }
1702 
1713  public void deleteReports(Collection<? extends Report> reports, boolean deleteFromDisk) throws TskCoreException {
1714 
1715  String pathToReportsFolder = Paths.get(this.db.getDbDirPath(), "Reports").normalize().toString(); // NON-NLS
1716  for (Report report : reports) {
1717 
1718  // delete from the database.
1719  this.db.deleteReport(report);
1720 
1721  if (deleteFromDisk) {
1722  // traverse to the root directory of Report report.
1723  String reportPath = report.getPath();
1724  while (!Paths.get(reportPath, "..").normalize().toString().equals(pathToReportsFolder)) { // NON-NLS
1725  reportPath = Paths.get(reportPath, "..").normalize().toString(); // NON-NLS
1726  }
1727 
1728  // delete from the disk.
1729  try {
1730  FileUtils.deleteDirectory(new File(reportPath));
1731  } catch (IOException | SecurityException ex) {
1732  logger.log(Level.WARNING, NbBundle.getMessage(Case.class, "Case.deleteReports.deleteFromDiskException.log.msg"), ex);
1733  JOptionPane.showMessageDialog(null, NbBundle.getMessage(Case.class, "Case.deleteReports.deleteFromDiskException.msg", report.getReportName(), reportPath));
1734  }
1735  }
1736 
1737  eventPublisher.publish(new AutopsyEvent(Events.REPORT_DELETED.toString(), null, null));
1738  }
1739  }
1740 
1746  public boolean hasData() {
1747  // false is also the initial value, so make the DB trip if it is still false
1748  if (!hasData) {
1749  try {
1750  hasData = (getDataSources().size() > 0);
1751  } catch (TskCoreException ex) {
1752  }
1753  }
1754  return hasData;
1755  }
1756 
1757 }
boolean equalsName(String otherType)
Definition: Case.java:234
static final AutopsyEventPublisher eventPublisher
Definition: Case.java:112
List< Content > getDataSources()
Definition: Case.java:1232
void notifyContentTagDeleted(ContentTag deletedTag)
Definition: Case.java:812
static CaseType fromString(String typeName)
Definition: Case.java:238
static String convertTimeZone(String timezoneID)
Definition: Case.java:1420
void notifyBlackBoardArtifactTagDeleted(BlackboardArtifactTag deletedTag)
Definition: Case.java:834
Image addImage(String imgPath, long imgId, String timeZone)
Definition: Case.java:727
static final String propStartup
Definition: Case.java:106
static synchronized IngestManager getInstance()
static boolean existsCurrentCase()
Definition: Case.java:966
Case(String name, String number, String examiner, String configFilePath, XMLCaseManagement xmlcm, SleuthkitCase db, CaseType type)
Definition: Case.java:291
static final Logger logger
Definition: Case.java:273
static final String EXPORT_FOLDER
Definition: Case.java:277
void addSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
synchronized static void setLogDirectory(String directoryPath)
Definition: Logger.java:126
static final String CACHE_FOLDER
Definition: Case.java:276
void setConfigFilePath(String givenPath)
Definition: Case.java:975
void addReport(String localPath, String srcModuleName, String reportName)
Definition: Case.java:1687
static CaseDbConnectionInfo getDatabaseConnectionInfo()
static void changeCase(Case newCase)
Definition: Case.java:333
void deleteReports(Collection<?extends Report > reports, boolean deleteFromDisk)
Definition: Case.java:1713
volatile IntervalErrorReportData tskErrorReporter
Definition: Case.java:98
static boolean isValidName(String caseName)
Definition: Case.java:1550
CollaborationMonitor collaborationMonitor
Definition: Case.java:286
static String getModulesOutputDirRelPath()
Definition: Case.java:1207
static void updateMainWindowTitle(String newCaseName)
Definition: Case.java:1669
static void removeEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:1351
static void checkSubFolders(Case openedCase)
Definition: Case.java:1577
void notifyBlackBoardArtifactTagAdded(BlackboardArtifactTag newTag)
Definition: Case.java:823
static PropertyChangeSupport getPropertyChangeSupport()
Definition: Case.java:1221
void removeSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
String getDirectory(String input)
Definition: Case.java:1135
static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:1340
static synchronized void removePropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:1305
static final int MAX_SANITIZED_NAME_LENGTH
Definition: Case.java:100
static boolean pathExists(String imgPath)
Definition: Case.java:1362
static void open(String caseMetadataFilePath)
Definition: Case.java:595
static final int MIN_SECONDS_BETWEEN_ERROR_REPORTS
Definition: Case.java:99
final XMLCaseManagement xmlcm
Definition: Case.java:267
static void error(String title, String message)
static synchronized void addPropertyChangeListener(PropertyChangeListener listener)
Definition: Case.java:1292
static void create(String caseDir, String caseName, String caseNumber, String examiner)
Definition: Case.java:430
static final String autopsyVer
Definition: Case.java:95
static final String EVENT_CHANNEL_NAME
Definition: Case.java:96
void notifyFailedAddingDataSource(UUID dataSourceId)
Definition: Case.java:774
synchronized static Logger getLogger(String name)
Definition: Logger.java:166
void notifyDataSourceAdded(Content newDataSource, UUID dataSourceId)
Definition: Case.java:790
static final String LOG_FOLDER
Definition: Case.java:278
void receiveError(String context, String errorMessage)
Definition: Case.java:396
static void addEventSubscriber(String eventName, PropertyChangeListener subscriber)
Definition: Case.java:1329
void notifyContentTagAdded(ContentTag newTag)
Definition: Case.java:801
static void create(String caseDir, String caseName, String caseNumber, String examiner, CaseType caseType)
Definition: Case.java:453
void notifyAddingDataSource(UUID dataSourceId)
Definition: Case.java:762
static final String REPORTS_FOLDER
Definition: Case.java:280
static final String TEMP_FOLDER
Definition: Case.java:281
static void addEventSubscriber(Set< String > eventNames, PropertyChangeListener subscriber)
Definition: Case.java:1318
static void doCaseChange(Case toChangeTo)
Definition: Case.java:1594

Copyright © 2012-2015 Basis Technology. Generated on: Wed Apr 6 2016
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.