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