Autopsy  4.9.1
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-2017 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.FileOutputStream;
23 import java.io.IOException;
24 import java.io.OutputStreamWriter;
25 import java.io.StringWriter;
26 import java.nio.file.Path;
27 import java.nio.file.Paths;
28 import java.text.DateFormat;
29 import java.text.SimpleDateFormat;
30 import java.util.Date;
31 import javax.xml.parsers.DocumentBuilder;
32 import javax.xml.parsers.DocumentBuilderFactory;
33 import javax.xml.parsers.ParserConfigurationException;
34 import javax.xml.transform.OutputKeys;
35 import javax.xml.transform.Result;
36 import javax.xml.transform.Source;
37 import javax.xml.transform.Transformer;
38 import javax.xml.transform.TransformerException;
39 import javax.xml.transform.TransformerFactory;
40 import javax.xml.transform.dom.DOMSource;
41 import javax.xml.transform.stream.StreamResult;
44 import org.w3c.dom.Document;
45 import org.w3c.dom.Element;
46 import org.w3c.dom.NodeList;
47 import org.xml.sax.SAXException;
48 
52 public final class CaseMetadata {
53 
54  private static final String FILE_EXTENSION = ".aut";
55  private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss (z)");
56 
57  /*
58  * Fields from schema version 1
59  */
60  private static final String SCHEMA_VERSION_ONE = "1.0";
61  private final static String ROOT_ELEMENT_NAME = "AutopsyCase"; //NON-NLS
62  private final static String SCHEMA_VERSION_ELEMENT_NAME = "SchemaVersion"; //NON-NLS
63  private final static String CREATED_DATE_ELEMENT_NAME = "CreatedDate"; //NON-NLS
64  private final static String AUTOPSY_VERSION_ELEMENT_NAME = "AutopsyCreatedVersion"; //NON-NLS
65  private final static String CASE_ELEMENT_NAME = "Case"; //NON-NLS
66  private final static String CASE_NAME_ELEMENT_NAME = "Name"; //NON-NLS
67  private final static String CASE_NUMBER_ELEMENT_NAME = "Number"; //NON-NLS
68  private final static String EXAMINER_ELEMENT_NAME = "Examiner"; //NON-NLS
69  private final static String CASE_TYPE_ELEMENT_NAME = "CaseType"; //NON-NLS
70  private final static String CASE_DATABASE_NAME_ELEMENT_NAME = "DatabaseName"; //NON-NLS
71  private final static String TEXT_INDEX_NAME_ELEMENT = "TextIndexName"; //NON-NLS
72 
73  /*
74  * Fields from schema version 2
75  */
76  private static final String SCHEMA_VERSION_TWO = "2.0";
77  private final static String AUTOPSY_CREATED_BY_ELEMENT_NAME = "CreatedByAutopsyVersion"; //NON-NLS
78  private final static String CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME = "Database"; //NON-NLS
79  private final static String TEXT_INDEX_ELEMENT = "TextIndex"; //NON-NLS
80 
81  /*
82  * Fields from schema version 3
83  */
84  private static final String SCHEMA_VERSION_THREE = "3.0";
85  private final static String CASE_DISPLAY_NAME_ELEMENT_NAME = "DisplayName"; //NON-NLS
86  private final static String CASE_DB_NAME_RELATIVE_ELEMENT_NAME = "CaseDatabase"; //NON-NLS
87 
88  /*
89  * Fields from schema version 4
90  */
91  private static final String SCHEMA_VERSION_FOUR = "4.0";
92  private final static String EXAMINER_ELEMENT_PHONE = "ExaminerPhone"; //NON-NLS
93  private final static String EXAMINER_ELEMENT_EMAIL = "ExaminerEmail"; //NON-NLS
94  private final static String CASE_ELEMENT_NOTES = "CaseNotes"; //NON-NLS
95  /*
96  * Unread fields, regenerated on save.
97  */
98  private final static String MODIFIED_DATE_ELEMENT_NAME = "ModifiedDate"; //NON-NLS
99  private final static String AUTOPSY_SAVED_BY_ELEMENT_NAME = "SavedByAutopsyVersion"; //NON-NLS
100 
101  private final static String CURRENT_SCHEMA_VERSION = SCHEMA_VERSION_FOUR;
102 
103  private final Path metadataFilePath;
105  private String caseName;
107  private String caseDatabaseName;
108  private String caseDatabasePath; // Legacy
109  private String textIndexName; // Legacy
110  private String createdDate;
111  private String createdByVersion;
112 
118  public static String getFileExtension() {
119  return FILE_EXTENSION;
120  }
121 
135  CaseMetadata(Case.CaseType caseType, String caseDirectory, String caseName, CaseDetails caseDetails) {
136  metadataFilePath = Paths.get(caseDirectory, caseDetails.getCaseDisplayName() + FILE_EXTENSION);
137  this.caseType = caseType;
138  this.caseName = caseName;
139  this.caseDetails = caseDetails;
140  caseDatabaseName = "";
141  caseDatabasePath = "";
142  textIndexName = "";
143  createdByVersion = Version.getVersion();
144  createdDate = CaseMetadata.DATE_FORMAT.format(new Date());
145  }
146 
156  public CaseMetadata(Path metadataFilePath) throws CaseMetadataException {
157  this.metadataFilePath = metadataFilePath;
158  readFromFile();
159  }
160 
166  Path getFilePath() {
167  return metadataFilePath;
168  }
169 
175  public String getCaseDirectory() {
176  return metadataFilePath.getParent().toString();
177  }
178 
185  return caseType;
186  }
187 
193  public String getCaseName() {
194  return caseName;
195  }
196 
203  return caseDetails;
204  }
205 
211  public String getCaseDisplayName() {
212  return caseDetails.getCaseDisplayName();
213  }
214 
215  void setCaseDetails(CaseDetails newCaseDetails) throws CaseMetadataException {
216  CaseDetails oldCaseDetails = this.caseDetails;
217  this.caseDetails = newCaseDetails;
218  try {
219  writeToFile();
220  } catch (CaseMetadataException ex) {
221  this.caseDetails = oldCaseDetails;
222  throw ex;
223  }
224  }
225 
231  public String getCaseNumber() {
232  return caseDetails.getCaseNumber();
233  }
234 
240  public String getExaminer() {
241  return caseDetails.getExaminerName();
242  }
243 
244  public String getExaminerPhone() {
245  return caseDetails.getExaminerPhone();
246  }
247 
248  public String getExaminerEmail() {
249  return caseDetails.getExaminerEmail();
250  }
251 
252  public String getCaseNotes() {
253  return caseDetails.getCaseNotes();
254  }
255 
261  public String getCaseDatabaseName() {
262  return caseDatabaseName;
263  }
264 
272  void setCaseDatabaseName(String caseDatabaseName) throws CaseMetadataException {
273  String oldCaseDatabaseName = this.caseDatabaseName;
274  this.caseDatabaseName = caseDatabaseName;
275  try {
276  writeToFile();
277  } catch (CaseMetadataException ex) {
278  this.caseDatabaseName = oldCaseDatabaseName;
279  throw ex;
280  }
281  }
282 
289  public String getTextIndexName() {
290  return textIndexName;
291  }
292 
298  String getCreatedDate() {
299  return createdDate;
300  }
301 
310  void setCreatedDate(String createdDate) throws CaseMetadataException {
311  String oldCreatedDate = createdDate;
312  this.createdDate = createdDate;
313  try {
314  writeToFile();
315  } catch (CaseMetadataException ex) {
316  this.createdDate = oldCreatedDate;
317  throw ex;
318  }
319  }
320 
326  String getCreatedByVersion() {
327  return createdByVersion;
328  }
329 
338  void setCreatedByVersion(String buildVersion) throws CaseMetadataException {
339  String oldCreatedByVersion = this.createdByVersion;
340  this.createdByVersion = buildVersion;
341  try {
342  writeToFile();
343  } catch (CaseMetadataException ex) {
344  this.createdByVersion = oldCreatedByVersion;
345  throw ex;
346  }
347  }
348 
355  private void writeToFile() throws CaseMetadataException {
356  try {
357  /*
358  * Create the XML DOM.
359  */
360  Document doc = XMLUtil.createDocument();
361  createXMLDOM(doc);
362  doc.normalize();
363 
364  /*
365  * Prepare the DOM for pretty printing to the metadata file.
366  */
367  Source source = new DOMSource(doc);
368  StringWriter stringWriter = new StringWriter();
369  Result streamResult = new StreamResult(stringWriter);
370  Transformer transformer = TransformerFactory.newInstance().newTransformer();
371  transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //NON-NLS
372  transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); //NON-NLS
373  transformer.transform(source, streamResult);
374 
375  /*
376  * Write the DOM to the metadata file.
377  */
378  try (BufferedWriter fileWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(metadataFilePath.toFile())))) {
379  fileWriter.write(stringWriter.toString());
380  fileWriter.flush();
381  }
382 
383  } catch (ParserConfigurationException | TransformerException | IOException ex) {
384  throw new CaseMetadataException(String.format("Error writing to case metadata file %s", metadataFilePath), ex);
385  }
386  }
387 
388  /*
389  * Creates an XML DOM from the case metadata.
390  */
391  private void createXMLDOM(Document doc) {
392  /*
393  * Create the root element and its children.
394  */
395  Element rootElement = doc.createElement(ROOT_ELEMENT_NAME);
396  doc.appendChild(rootElement);
397  createChildElement(doc, rootElement, SCHEMA_VERSION_ELEMENT_NAME, CURRENT_SCHEMA_VERSION);
398  createChildElement(doc, rootElement, CREATED_DATE_ELEMENT_NAME, createdDate);
399  createChildElement(doc, rootElement, MODIFIED_DATE_ELEMENT_NAME, DATE_FORMAT.format(new Date()));
400  createChildElement(doc, rootElement, AUTOPSY_CREATED_BY_ELEMENT_NAME, createdByVersion);
401  createChildElement(doc, rootElement, AUTOPSY_SAVED_BY_ELEMENT_NAME, Version.getVersion());
402  Element caseElement = doc.createElement(CASE_ELEMENT_NAME);
403  rootElement.appendChild(caseElement);
404 
405  /*
406  * Create the children of the case element.
407  */
408  createChildElement(doc, caseElement, CASE_NAME_ELEMENT_NAME, caseName);
409  createChildElement(doc, caseElement, CASE_DISPLAY_NAME_ELEMENT_NAME, caseDetails.getCaseDisplayName());
410  createChildElement(doc, caseElement, CASE_NUMBER_ELEMENT_NAME, caseDetails.getCaseNumber());
411  createChildElement(doc, caseElement, EXAMINER_ELEMENT_NAME, caseDetails.getExaminerName());
412  createChildElement(doc, caseElement, EXAMINER_ELEMENT_PHONE, caseDetails.getExaminerPhone());
413  createChildElement(doc, caseElement, EXAMINER_ELEMENT_EMAIL, caseDetails.getExaminerEmail());
414  createChildElement(doc, caseElement, CASE_ELEMENT_NOTES, caseDetails.getCaseNotes());
415  createChildElement(doc, caseElement, CASE_TYPE_ELEMENT_NAME, caseType.toString());
416  createChildElement(doc, caseElement, CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME, caseDatabasePath);
417  createChildElement(doc, caseElement, CASE_DB_NAME_RELATIVE_ELEMENT_NAME, caseDatabaseName);
418  createChildElement(doc, caseElement, TEXT_INDEX_ELEMENT, textIndexName);
419  }
420 
430  private void createChildElement(Document doc, Element parentElement, String elementName, String elementContent) {
431  Element element = doc.createElement(elementName);
432  element.appendChild(doc.createTextNode(elementContent));
433  parentElement.appendChild(element);
434  }
435 
442  private void readFromFile() throws CaseMetadataException {
443  try {
444  /*
445  * Parse the file into an XML DOM and get the root element.
446  */
447  DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
448  Document doc = builder.parse(this.getFilePath().toFile());
449  doc.getDocumentElement().normalize();
450  Element rootElement = doc.getDocumentElement();
451  if (!rootElement.getNodeName().equals(ROOT_ELEMENT_NAME)) {
452  throw new CaseMetadataException("Case metadata file corrupted");
453  }
454 
455  /*
456  * Get the content of the relevant children of the root element.
457  */
458  String schemaVersion = getElementTextContent(rootElement, SCHEMA_VERSION_ELEMENT_NAME, true);
459  this.createdDate = getElementTextContent(rootElement, CREATED_DATE_ELEMENT_NAME, true);
460  if (schemaVersion.equals(SCHEMA_VERSION_ONE)) {
461  this.createdByVersion = getElementTextContent(rootElement, AUTOPSY_VERSION_ELEMENT_NAME, true);
462  } else {
463  this.createdByVersion = getElementTextContent(rootElement, AUTOPSY_CREATED_BY_ELEMENT_NAME, true);
464  }
465 
466  /*
467  * Get the content of the children of the case element.
468  */
469  NodeList caseElements = doc.getElementsByTagName(CASE_ELEMENT_NAME);
470  if (caseElements.getLength() == 0) {
471  throw new CaseMetadataException("Case metadata file corrupted");
472  }
473  Element caseElement = (Element) caseElements.item(0);
474  this.caseName = getElementTextContent(caseElement, CASE_NAME_ELEMENT_NAME, true);
475  String caseDisplayName;
476  String caseNumber;
477  if (schemaVersion.equals(SCHEMA_VERSION_ONE) || schemaVersion.equals(SCHEMA_VERSION_TWO)) {
478  caseDisplayName = caseName;
479  } else {
480  caseDisplayName = getElementTextContent(caseElement, CASE_DISPLAY_NAME_ELEMENT_NAME, true);
481  }
482  caseNumber = getElementTextContent(caseElement, CASE_NUMBER_ELEMENT_NAME, false);
483  String examinerName = getElementTextContent(caseElement, EXAMINER_ELEMENT_NAME, false);
484  String examinerPhone;
485  String examinerEmail;
486  String caseNotes;
487  if (schemaVersion.equals(SCHEMA_VERSION_ONE) || schemaVersion.equals(SCHEMA_VERSION_TWO) || schemaVersion.equals(SCHEMA_VERSION_THREE)) {
488  examinerPhone = ""; //case had metadata file written before additional examiner details were included
489  examinerEmail = "";
490  caseNotes = "";
491  } else {
492  examinerPhone = getElementTextContent(caseElement, EXAMINER_ELEMENT_PHONE, false);
493  examinerEmail = getElementTextContent(caseElement, EXAMINER_ELEMENT_EMAIL, false);
494  caseNotes = getElementTextContent(caseElement, CASE_ELEMENT_NOTES, false);
495  }
496  this.caseDetails = new CaseDetails(caseDisplayName, caseNumber, examinerName, examinerPhone, examinerEmail, caseNotes);
497  this.caseType = Case.CaseType.fromString(getElementTextContent(caseElement, CASE_TYPE_ELEMENT_NAME, true));
498  if (null == this.caseType) {
499  throw new CaseMetadataException("Case metadata file corrupted");
500  }
501  switch (schemaVersion) {
502  case SCHEMA_VERSION_ONE:
503  this.caseDatabaseName = getElementTextContent(caseElement, CASE_DATABASE_NAME_ELEMENT_NAME, true);
504  this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_NAME_ELEMENT, true);
505  break;
506  case SCHEMA_VERSION_TWO:
507  this.caseDatabaseName = getElementTextContent(caseElement, CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME, true);
508  this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, false);
509  break;
510  default:
511  this.caseDatabaseName = getElementTextContent(caseElement, CASE_DB_NAME_RELATIVE_ELEMENT_NAME, true);
512  this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, false);
513  break;
514  }
515 
516  /*
517  * Fix up the case database name due to a bug that for a time caused
518  * the absolute paths of single-user case databases to be stored.
519  * Derive the missing (absolute/relative) value from the one
520  * present.
521  */
522  Path possibleAbsoluteCaseDbPath = Paths.get(this.caseDatabaseName);
523  Path caseDirectoryPath = Paths.get(getCaseDirectory());
524  if (possibleAbsoluteCaseDbPath.getNameCount() > 1) {
525  this.caseDatabasePath = this.caseDatabaseName;
526  this.caseDatabaseName = caseDirectoryPath.relativize(possibleAbsoluteCaseDbPath).toString();
527  } else {
528  this.caseDatabasePath = caseDirectoryPath.resolve(caseDatabaseName).toAbsolutePath().toString();
529  }
530 
531  } catch (ParserConfigurationException | SAXException | IOException ex) {
532  throw new CaseMetadataException(String.format("Error reading from case metadata file %s", metadataFilePath), ex);
533  }
534  }
535 
548  private String getElementTextContent(Element parentElement, String elementName, boolean contentIsRequired) throws CaseMetadataException {
549  NodeList elementsList = parentElement.getElementsByTagName(elementName);
550  if (elementsList.getLength() == 0) {
551  throw new CaseMetadataException(String.format("Missing %s element from case metadata file %s", elementName, metadataFilePath));
552  }
553  String textContent = elementsList.item(0).getTextContent();
554  if (textContent.isEmpty() && contentIsRequired) {
555  throw new CaseMetadataException(String.format("Empty %s element in case metadata file %s", elementName, metadataFilePath));
556  }
557  return textContent;
558  }
559 
564  public final static class CaseMetadataException extends Exception {
565 
566  private static final long serialVersionUID = 1L;
567 
568  private CaseMetadataException(String message) {
569  super(message);
570  }
571 
572  private CaseMetadataException(String message, Throwable cause) {
573  super(message, cause);
574  }
575  }
576 
586  @Deprecated
587  public String getCaseDatabasePath() throws UnsupportedOperationException {
589  return Paths.get(getCaseDirectory(), caseDatabaseName).toString();
590  } else {
591  throw new UnsupportedOperationException();
592  }
593  }
594 
595 }
static CaseType fromString(String typeName)
Definition: Case.java:183
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: Tue Dec 18 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.