Autopsy  4.14.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
CaseMetadata.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2019 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.io.BufferedWriter;
22 import java.io.File;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.OutputStreamWriter;
26 import java.io.StringWriter;
27 import java.nio.charset.StandardCharsets;
28 import java.nio.file.Path;
29 import java.nio.file.Paths;
30 import java.text.DateFormat;
31 import java.text.SimpleDateFormat;
32 import java.util.Date;
33 import java.util.Locale;
34 import javax.xml.parsers.DocumentBuilder;
35 import javax.xml.parsers.DocumentBuilderFactory;
36 import javax.xml.parsers.ParserConfigurationException;
37 import javax.xml.transform.OutputKeys;
38 import javax.xml.transform.Result;
39 import javax.xml.transform.Source;
40 import javax.xml.transform.Transformer;
41 import javax.xml.transform.TransformerException;
42 import javax.xml.transform.TransformerFactory;
43 import javax.xml.transform.dom.DOMSource;
44 import javax.xml.transform.stream.StreamResult;
47 import org.w3c.dom.Document;
48 import org.w3c.dom.Element;
49 import org.w3c.dom.NodeList;
50 import org.xml.sax.SAXException;
51 
55 public final class CaseMetadata {
56 
57  private static final String FILE_EXTENSION = ".aut";
58  private static final String DATE_FORMAT_STRING = "yyyy/MM/dd HH:mm:ss (z)";
59  private static final DateFormat DATE_FORMAT = new SimpleDateFormat(DATE_FORMAT_STRING, Locale.US);
60 
61  /*
62  * Fields from schema version 1
63  */
64  private static final String SCHEMA_VERSION_ONE = "1.0";
65  private final static String ROOT_ELEMENT_NAME = "AutopsyCase"; //NON-NLS
66  private final static String SCHEMA_VERSION_ELEMENT_NAME = "SchemaVersion"; //NON-NLS
67  private final static String CREATED_DATE_ELEMENT_NAME = "CreatedDate"; //NON-NLS
68  private final static String AUTOPSY_VERSION_ELEMENT_NAME = "AutopsyCreatedVersion"; //NON-NLS
69  private final static String CASE_ELEMENT_NAME = "Case"; //NON-NLS
70  private final static String CASE_NAME_ELEMENT_NAME = "Name"; //NON-NLS
71  private final static String CASE_NUMBER_ELEMENT_NAME = "Number"; //NON-NLS
72  private final static String EXAMINER_ELEMENT_NAME = "Examiner"; //NON-NLS
73  private final static String CASE_TYPE_ELEMENT_NAME = "CaseType"; //NON-NLS
74  private final static String CASE_DATABASE_NAME_ELEMENT_NAME = "DatabaseName"; //NON-NLS
75  private final static String TEXT_INDEX_NAME_ELEMENT = "TextIndexName"; //NON-NLS
76 
77  /*
78  * Fields from schema version 2
79  */
80  private static final String SCHEMA_VERSION_TWO = "2.0";
81  private final static String AUTOPSY_CREATED_BY_ELEMENT_NAME = "CreatedByAutopsyVersion"; //NON-NLS
82  private final static String CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME = "Database"; //NON-NLS
83  private final static String TEXT_INDEX_ELEMENT = "TextIndex"; //NON-NLS
84 
85  /*
86  * Fields from schema version 3
87  */
88  private static final String SCHEMA_VERSION_THREE = "3.0";
89  private final static String CASE_DISPLAY_NAME_ELEMENT_NAME = "DisplayName"; //NON-NLS
90  private final static String CASE_DB_NAME_RELATIVE_ELEMENT_NAME = "CaseDatabase"; //NON-NLS
91 
92  /*
93  * Fields from schema version 4
94  */
95  private static final String SCHEMA_VERSION_FOUR = "4.0";
96  private final static String EXAMINER_ELEMENT_PHONE = "ExaminerPhone"; //NON-NLS
97  private final static String EXAMINER_ELEMENT_EMAIL = "ExaminerEmail"; //NON-NLS
98  private final static String CASE_ELEMENT_NOTES = "CaseNotes"; //NON-NLS
99 
100  /*
101  * Fields from schema version 5
102  */
103  private static final String SCHEMA_VERSION_FIVE = "5.0";
104  private final static String ORIGINAL_CASE_ELEMENT_NAME = "OriginalCase"; //NON-NLS
105 
106  /*
107  * Unread fields, regenerated on save.
108  */
109  private final static String MODIFIED_DATE_ELEMENT_NAME = "ModifiedDate"; //NON-NLS
110  private final static String AUTOPSY_SAVED_BY_ELEMENT_NAME = "SavedByAutopsyVersion"; //NON-NLS
111 
112  private final static String CURRENT_SCHEMA_VERSION = SCHEMA_VERSION_FIVE;
113 
114  private final Path metadataFilePath;
116  private String caseName;
118  private String caseDatabaseName;
119  private String caseDatabasePath; // Legacy
120  private String textIndexName; // Legacy
121  private String createdDate;
122  private String createdByVersion;
123  private CaseMetadata originalMetadata = null; // For portable cases
124 
130  public static String getFileExtension() {
131  return FILE_EXTENSION;
132  }
133 
139  public static DateFormat getDateFormat() {
140  return new SimpleDateFormat(DATE_FORMAT_STRING, Locale.US);
141  }
142 
153  CaseMetadata(Case.CaseType caseType, String caseDirectory, String caseName, CaseDetails caseDetails) {
154  this(caseType, caseDirectory, caseName, caseDetails, null);
155  }
156 
168  CaseMetadata(Case.CaseType caseType, String caseDirectory, String caseName, CaseDetails caseDetails, CaseMetadata originalMetadata) {
169  metadataFilePath = Paths.get(caseDirectory, caseDetails.getCaseDisplayName() + FILE_EXTENSION);
170  this.caseType = caseType;
171  this.caseName = caseName;
172  this.caseDetails = caseDetails;
173  caseDatabaseName = "";
174  caseDatabasePath = "";
175  textIndexName = "";
176  createdByVersion = Version.getVersion();
177  createdDate = CaseMetadata.DATE_FORMAT.format(new Date());
178  this.originalMetadata = originalMetadata;
179  }
180 
190  public CaseMetadata(Path metadataFilePath) throws CaseMetadataException {
191  this.metadataFilePath = metadataFilePath;
192  readFromFile();
193  }
194 
203  public static Path getCaseMetadataFilePath(Path directoryPath) {
204  final File[] files = directoryPath.toFile().listFiles();
205  if (files != null) {
206  for (File file : files) {
207  final String fileName = file.getName().toLowerCase();
208  if (fileName.endsWith(CaseMetadata.getFileExtension()) && file.isFile()) {
209  return file.toPath();
210  }
211  }
212  }
213  return null;
214  }
215 
221  Path getFilePath() {
222  return metadataFilePath;
223  }
224 
230  public String getCaseDirectory() {
231  return metadataFilePath.getParent().toString();
232  }
233 
240  return caseType;
241  }
242 
248  public String getCaseName() {
249  return caseName;
250  }
251 
258  return caseDetails;
259  }
260 
266  public String getCaseDisplayName() {
267  return caseDetails.getCaseDisplayName();
268  }
269 
270  void setCaseDetails(CaseDetails newCaseDetails) throws CaseMetadataException {
271  CaseDetails oldCaseDetails = this.caseDetails;
272  this.caseDetails = newCaseDetails;
273  try {
274  writeToFile();
275  } catch (CaseMetadataException ex) {
276  this.caseDetails = oldCaseDetails;
277  throw ex;
278  }
279  }
280 
286  public String getCaseNumber() {
287  return caseDetails.getCaseNumber();
288  }
289 
295  public String getExaminer() {
296  return caseDetails.getExaminerName();
297  }
298 
299  public String getExaminerPhone() {
300  return caseDetails.getExaminerPhone();
301  }
302 
303  public String getExaminerEmail() {
304  return caseDetails.getExaminerEmail();
305  }
306 
307  public String getCaseNotes() {
308  return caseDetails.getCaseNotes();
309  }
310 
316  public String getCaseDatabaseName() {
317  return caseDatabaseName;
318  }
319 
327  void setCaseDatabaseName(String caseDatabaseName) throws CaseMetadataException {
328  String oldCaseDatabaseName = this.caseDatabaseName;
329  this.caseDatabaseName = caseDatabaseName;
330  try {
331  writeToFile();
332  } catch (CaseMetadataException ex) {
333  this.caseDatabaseName = oldCaseDatabaseName;
334  throw ex;
335  }
336  }
337 
344  public String getTextIndexName() {
345  return textIndexName;
346  }
347 
353  public String getCreatedDate() {
354  return createdDate;
355  }
356 
365  void setCreatedDate(String createdDate) throws CaseMetadataException {
366  String oldCreatedDate = createdDate;
367  this.createdDate = createdDate;
368  try {
369  writeToFile();
370  } catch (CaseMetadataException ex) {
371  this.createdDate = oldCreatedDate;
372  throw ex;
373  }
374  }
375 
381  String getCreatedByVersion() {
382  return createdByVersion;
383  }
384 
393  void setCreatedByVersion(String buildVersion) throws CaseMetadataException {
394  String oldCreatedByVersion = this.createdByVersion;
395  this.createdByVersion = buildVersion;
396  try {
397  writeToFile();
398  } catch (CaseMetadataException ex) {
399  this.createdByVersion = oldCreatedByVersion;
400  throw ex;
401  }
402  }
403 
410  void writeToFile() throws CaseMetadataException {
411  try {
412  /*
413  * Create the XML DOM.
414  */
415  Document doc = XMLUtil.createDocument();
416  createXMLDOM(doc);
417  doc.normalize();
418 
419  /*
420  * Prepare the DOM for pretty printing to the metadata file.
421  */
422  Source source = new DOMSource(doc);
423  StringWriter stringWriter = new StringWriter();
424  Result streamResult = new StreamResult(stringWriter);
425  Transformer transformer = TransformerFactory.newInstance().newTransformer();
426  transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //NON-NLS
427  transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); //NON-NLS
428  transformer.transform(source, streamResult);
429 
430  /*
431  * Write the DOM to the metadata file. Add UTF-8 Characterset so it writes to the file
432  * correctly for non-latin characters
433  */
434  try (BufferedWriter fileWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(metadataFilePath.toFile()), StandardCharsets.UTF_8))) {
435  fileWriter.write(stringWriter.toString());
436  fileWriter.flush();
437  }
438 
439  } catch (ParserConfigurationException | TransformerException | IOException ex) {
440  throw new CaseMetadataException(String.format("Error writing to case metadata file %s", metadataFilePath), ex);
441  }
442  }
443 
444  /*
445  * Creates an XML DOM from the case metadata.
446  */
447  private void createXMLDOM(Document doc) {
448  /*
449  * Create the root element and its children.
450  */
451  Element rootElement = doc.createElement(ROOT_ELEMENT_NAME);
452  doc.appendChild(rootElement);
453  createChildElement(doc, rootElement, SCHEMA_VERSION_ELEMENT_NAME, CURRENT_SCHEMA_VERSION);
454  createChildElement(doc, rootElement, CREATED_DATE_ELEMENT_NAME, createdDate);
455  createChildElement(doc, rootElement, MODIFIED_DATE_ELEMENT_NAME, DATE_FORMAT.format(new Date()));
456  createChildElement(doc, rootElement, AUTOPSY_CREATED_BY_ELEMENT_NAME, createdByVersion);
457  createChildElement(doc, rootElement, AUTOPSY_SAVED_BY_ELEMENT_NAME, Version.getVersion());
458  Element caseElement = doc.createElement(CASE_ELEMENT_NAME);
459  rootElement.appendChild(caseElement);
460 
461  /*
462  * Create the children of the case element.
463  */
464  createCaseElements(doc, caseElement, this);
465 
466  /*
467  * Add original case element
468  */
469  Element originalCaseElement = doc.createElement(ORIGINAL_CASE_ELEMENT_NAME);
470  rootElement.appendChild(originalCaseElement);
471  if (originalMetadata != null) {
472  createChildElement(doc, originalCaseElement, CREATED_DATE_ELEMENT_NAME, originalMetadata.createdDate);
473  Element originalCaseDetailsElement = doc.createElement(CASE_ELEMENT_NAME);
474  originalCaseElement.appendChild(originalCaseDetailsElement);
475  createCaseElements(doc, originalCaseDetailsElement, originalMetadata);
476  }
477 
478  }
479 
487  private void createCaseElements(Document doc, Element caseElement, CaseMetadata metadataToWrite) {
488  CaseDetails caseDetailsToWrite = metadataToWrite.caseDetails;
489  createChildElement(doc, caseElement, CASE_NAME_ELEMENT_NAME, metadataToWrite.caseName);
490  createChildElement(doc, caseElement, CASE_DISPLAY_NAME_ELEMENT_NAME, caseDetailsToWrite.getCaseDisplayName());
491  createChildElement(doc, caseElement, CASE_NUMBER_ELEMENT_NAME, caseDetailsToWrite.getCaseNumber());
492  createChildElement(doc, caseElement, EXAMINER_ELEMENT_NAME, caseDetailsToWrite.getExaminerName());
493  createChildElement(doc, caseElement, EXAMINER_ELEMENT_PHONE, caseDetailsToWrite.getExaminerPhone());
494  createChildElement(doc, caseElement, EXAMINER_ELEMENT_EMAIL, caseDetailsToWrite.getExaminerEmail());
495  createChildElement(doc, caseElement, CASE_ELEMENT_NOTES, caseDetailsToWrite.getCaseNotes());
496  createChildElement(doc, caseElement, CASE_TYPE_ELEMENT_NAME, metadataToWrite.caseType.toString());
497  createChildElement(doc, caseElement, CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME, metadataToWrite.caseDatabasePath);
498  createChildElement(doc, caseElement, CASE_DB_NAME_RELATIVE_ELEMENT_NAME, metadataToWrite.caseDatabaseName);
499  createChildElement(doc, caseElement, TEXT_INDEX_ELEMENT, metadataToWrite.textIndexName);
500  }
501 
511  private void createChildElement(Document doc, Element parentElement, String elementName, String elementContent) {
512  Element element = doc.createElement(elementName);
513  element.appendChild(doc.createTextNode(elementContent));
514  parentElement.appendChild(element);
515  }
516 
523  private void readFromFile() throws CaseMetadataException {
524  try {
525  /*
526  * Parse the file into an XML DOM and get the root element.
527  */
528  DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
529  Document doc = builder.parse(this.getFilePath().toFile());
530  doc.getDocumentElement().normalize();
531  Element rootElement = doc.getDocumentElement();
532  if (!rootElement.getNodeName().equals(ROOT_ELEMENT_NAME)) {
533  throw new CaseMetadataException("Case metadata file corrupted");
534  }
535 
536  /*
537  * Get the content of the relevant children of the root element.
538  */
539  String schemaVersion = getElementTextContent(rootElement, SCHEMA_VERSION_ELEMENT_NAME, true);
540  this.createdDate = getElementTextContent(rootElement, CREATED_DATE_ELEMENT_NAME, true);
541  if (schemaVersion.equals(SCHEMA_VERSION_ONE)) {
542  this.createdByVersion = getElementTextContent(rootElement, AUTOPSY_VERSION_ELEMENT_NAME, true);
543  } else {
544  this.createdByVersion = getElementTextContent(rootElement, AUTOPSY_CREATED_BY_ELEMENT_NAME, true);
545  }
546 
547  /*
548  * Get the content of the children of the case element.
549  */
550  NodeList caseElements = doc.getElementsByTagName(CASE_ELEMENT_NAME);
551  if (caseElements.getLength() == 0) {
552  throw new CaseMetadataException("Case metadata file corrupted");
553  }
554  Element caseElement = (Element) caseElements.item(0);
555  this.caseName = getElementTextContent(caseElement, CASE_NAME_ELEMENT_NAME, true);
556  String caseDisplayName;
557  String caseNumber;
558  if (schemaVersion.equals(SCHEMA_VERSION_ONE) || schemaVersion.equals(SCHEMA_VERSION_TWO)) {
559  caseDisplayName = caseName;
560  } else {
561  caseDisplayName = getElementTextContent(caseElement, CASE_DISPLAY_NAME_ELEMENT_NAME, true);
562  }
563  caseNumber = getElementTextContent(caseElement, CASE_NUMBER_ELEMENT_NAME, false);
564  String examinerName = getElementTextContent(caseElement, EXAMINER_ELEMENT_NAME, false);
565  String examinerPhone;
566  String examinerEmail;
567  String caseNotes;
568  if (schemaVersion.equals(SCHEMA_VERSION_ONE) || schemaVersion.equals(SCHEMA_VERSION_TWO) || schemaVersion.equals(SCHEMA_VERSION_THREE)) {
569  examinerPhone = ""; //case had metadata file written before additional examiner details were included
570  examinerEmail = "";
571  caseNotes = "";
572  } else {
573  examinerPhone = getElementTextContent(caseElement, EXAMINER_ELEMENT_PHONE, false);
574  examinerEmail = getElementTextContent(caseElement, EXAMINER_ELEMENT_EMAIL, false);
575  caseNotes = getElementTextContent(caseElement, CASE_ELEMENT_NOTES, false);
576  }
577 
578  this.caseDetails = new CaseDetails(caseDisplayName, caseNumber, examinerName, examinerPhone, examinerEmail, caseNotes);
579  this.caseType = Case.CaseType.fromString(getElementTextContent(caseElement, CASE_TYPE_ELEMENT_NAME, true));
580  if (null == this.caseType) {
581  throw new CaseMetadataException("Case metadata file corrupted");
582  }
583  switch (schemaVersion) {
584  case SCHEMA_VERSION_ONE:
585  this.caseDatabaseName = getElementTextContent(caseElement, CASE_DATABASE_NAME_ELEMENT_NAME, true);
586  this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_NAME_ELEMENT, true);
587  break;
588  case SCHEMA_VERSION_TWO:
589  this.caseDatabaseName = getElementTextContent(caseElement, CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME, true);
590  this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, false);
591  break;
592  default:
593  this.caseDatabaseName = getElementTextContent(caseElement, CASE_DB_NAME_RELATIVE_ELEMENT_NAME, true);
594  this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, false);
595  break;
596  }
597 
598  /*
599  * Fix up the case database name due to a bug that for a time caused
600  * the absolute paths of single-user case databases to be stored.
601  * Derive the missing (absolute/relative) value from the one
602  * present.
603  */
604  Path possibleAbsoluteCaseDbPath = Paths.get(this.caseDatabaseName);
605  Path caseDirectoryPath = Paths.get(getCaseDirectory());
606  if (possibleAbsoluteCaseDbPath.getNameCount() > 1) {
607  this.caseDatabasePath = this.caseDatabaseName;
608  this.caseDatabaseName = caseDirectoryPath.relativize(possibleAbsoluteCaseDbPath).toString();
609  } else {
610  this.caseDatabasePath = caseDirectoryPath.resolve(caseDatabaseName).toAbsolutePath().toString();
611  }
612 
613  } catch (ParserConfigurationException | SAXException | IOException ex) {
614  throw new CaseMetadataException(String.format("Error reading from case metadata file %s", metadataFilePath), ex);
615  }
616  }
617 
630  private String getElementTextContent(Element parentElement, String elementName, boolean contentIsRequired) throws CaseMetadataException {
631  NodeList elementsList = parentElement.getElementsByTagName(elementName);
632  if (elementsList.getLength() == 0) {
633  throw new CaseMetadataException(String.format("Missing %s element from case metadata file %s", elementName, metadataFilePath));
634  }
635  String textContent = elementsList.item(0).getTextContent();
636  if (textContent.isEmpty() && contentIsRequired) {
637  throw new CaseMetadataException(String.format("Empty %s element in case metadata file %s", elementName, metadataFilePath));
638  }
639  return textContent;
640  }
641 
646  public final static class CaseMetadataException extends Exception {
647 
648  private static final long serialVersionUID = 1L;
649 
650  private CaseMetadataException(String message) {
651  super(message);
652  }
653 
654  private CaseMetadataException(String message, Throwable cause) {
655  super(message, cause);
656  }
657  }
658 
668  @Deprecated
669  public String getCaseDatabasePath() throws UnsupportedOperationException {
671  return Paths.get(getCaseDirectory(), caseDatabaseName).toString();
672  } else {
673  throw new UnsupportedOperationException();
674  }
675  }
676 
677 }
static CaseType fromString(String typeName)
Definition: Case.java:197
static Path getCaseMetadataFilePath(Path directoryPath)
void createCaseElements(Document doc, Element caseElement, CaseMetadata metadataToWrite)
void createChildElement(Document doc, Element parentElement, String elementName, String elementContent)
String getElementTextContent(Element parentElement, String elementName, boolean contentIsRequired)

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