Autopsy  4.4.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  * Unread fields, regenerated on save.
90  */
91  private final static String MODIFIED_DATE_ELEMENT_NAME = "ModifiedDate"; //NON-NLS
92  private final static String AUTOPSY_SAVED_BY_ELEMENT_NAME = "SavedByAutopsyVersion"; //NON-NLS
93 
94  private final static String CURRENT_SCHEMA_VERSION = SCHEMA_VERSION_THREE;
95 
96  private final Path metadataFilePath;
98  private String caseName;
99  private String caseDisplayName;
100  private String caseNumber;
101  private String examiner;
102  private String caseDatabaseName;
103  private String caseDatabasePath; // Legacy
104  private String textIndexName; // Legacy
105  private String createdDate;
106  private String createdByVersion;
107 
113  public static String getFileExtension() {
114  return FILE_EXTENSION;
115  }
116 
130  CaseMetadata(String caseDirectory, Case.CaseType caseType, String caseName, String caseDisplayName, String caseNumber, String examiner) {
131  metadataFilePath = Paths.get(caseDirectory, caseDisplayName + FILE_EXTENSION);
132  this.caseType = caseType;
133  this.caseName = caseName;
134  this.caseDisplayName = caseDisplayName;
135  this.caseNumber = caseNumber;
136  this.examiner = examiner;
137  caseDatabaseName = "";
138  caseDatabasePath = "";
139  textIndexName = "";
140  createdByVersion = Version.getVersion();
141  createdDate = CaseMetadata.DATE_FORMAT.format(new Date());
142  }
143 
153  public CaseMetadata(Path metadataFilePath) throws CaseMetadataException {
154  this.metadataFilePath = metadataFilePath;
155  readFromFile();
156  }
157 
163  Path getFilePath() {
164  return metadataFilePath;
165  }
166 
172  public String getCaseDirectory() {
173  return metadataFilePath.getParent().toString();
174  }
175 
182  return caseType;
183  }
184 
190  public String getCaseName() {
191  return caseName;
192  }
193 
199  public String getCaseDisplayName() {
200  return caseDisplayName;
201  }
202 
210  void setCaseDisplayName(String caseDisplayName) throws CaseMetadataException {
211  String oldCaseDisplayName = this.caseDisplayName;
212  this.caseDisplayName = caseDisplayName;
213  try {
214  writeToFile();
215  } catch (CaseMetadataException ex) {
216  this.caseDisplayName = oldCaseDisplayName;
217  throw ex;
218  }
219  }
220 
226  public String getCaseNumber() {
227  return caseNumber;
228  }
229 
235  public String getExaminer() {
236  return examiner;
237  }
238 
244  public String getCaseDatabaseName() {
245  return caseDatabaseName;
246  }
247 
255  void setCaseDatabaseName(String caseDatabaseName) throws CaseMetadataException {
256  String oldCaseDatabaseName = this.caseDatabaseName;
257  this.caseDatabaseName = caseDatabaseName;
258  try {
259  writeToFile();
260  } catch (CaseMetadataException ex) {
261  this.caseDatabaseName = oldCaseDatabaseName;
262  throw ex;
263  }
264  }
265 
272  public String getTextIndexName() {
273  return textIndexName;
274  }
275 
281  String getCreatedDate() {
282  return createdDate;
283  }
284 
293  void setCreatedDate(String createdDate) throws CaseMetadataException {
294  String oldCreatedDate = createdDate;
295  this.createdDate = createdDate;
296  try {
297  writeToFile();
298  } catch (CaseMetadataException ex) {
299  this.createdDate = oldCreatedDate;
300  throw ex;
301  }
302  }
303 
309  String getCreatedByVersion() {
310  return createdByVersion;
311  }
312 
321  void setCreatedByVersion(String buildVersion) throws CaseMetadataException {
322  String oldCreatedByVersion = this.createdByVersion;
323  this.createdByVersion = buildVersion;
324  try {
325  writeToFile();
326  } catch (CaseMetadataException ex) {
327  this.createdByVersion = oldCreatedByVersion;
328  throw ex;
329  }
330  }
331 
338  private void writeToFile() throws CaseMetadataException {
339  try {
340  /*
341  * Create the XML DOM.
342  */
343  Document doc = XMLUtil.createDocument();
344  createXMLDOM(doc);
345  doc.normalize();
346 
347  /*
348  * Prepare the DOM for pretty printing to the metadata file.
349  */
350  Source source = new DOMSource(doc);
351  StringWriter stringWriter = new StringWriter();
352  Result streamResult = new StreamResult(stringWriter);
353  Transformer transformer = TransformerFactory.newInstance().newTransformer();
354  transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //NON-NLS
355  transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); //NON-NLS
356  transformer.transform(source, streamResult);
357 
358  /*
359  * Write the DOM to the metadata file.
360  */
361  try (BufferedWriter fileWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(metadataFilePath.toFile())))) {
362  fileWriter.write(stringWriter.toString());
363  fileWriter.flush();
364  }
365 
366  } catch (ParserConfigurationException | TransformerException | IOException ex) {
367  throw new CaseMetadataException(String.format("Error writing to case metadata file %s", metadataFilePath), ex);
368  }
369  }
370 
371  /*
372  * Creates an XML DOM from the case metadata.
373  */
374  private void createXMLDOM(Document doc) {
375  /*
376  * Create the root element and its children.
377  */
378  Element rootElement = doc.createElement(ROOT_ELEMENT_NAME);
379  doc.appendChild(rootElement);
380  createChildElement(doc, rootElement, SCHEMA_VERSION_ELEMENT_NAME, CURRENT_SCHEMA_VERSION);
381  createChildElement(doc, rootElement, CREATED_DATE_ELEMENT_NAME, createdDate);
382  createChildElement(doc, rootElement, MODIFIED_DATE_ELEMENT_NAME, DATE_FORMAT.format(new Date()));
383  createChildElement(doc, rootElement, AUTOPSY_CREATED_BY_ELEMENT_NAME, createdByVersion);
384  createChildElement(doc, rootElement, AUTOPSY_SAVED_BY_ELEMENT_NAME, Version.getVersion());
385  Element caseElement = doc.createElement(CASE_ELEMENT_NAME);
386  rootElement.appendChild(caseElement);
387 
388  /*
389  * Create the children of the case element.
390  */
391  createChildElement(doc, caseElement, CASE_NAME_ELEMENT_NAME, caseName);
392  createChildElement(doc, caseElement, CASE_DISPLAY_NAME_ELEMENT_NAME, caseDisplayName);
393  createChildElement(doc, caseElement, CASE_NUMBER_ELEMENT_NAME, caseNumber);
394  createChildElement(doc, caseElement, EXAMINER_ELEMENT_NAME, examiner);
395  createChildElement(doc, caseElement, CASE_TYPE_ELEMENT_NAME, caseType.toString());
396  createChildElement(doc, caseElement, CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME, caseDatabasePath);
397  createChildElement(doc, caseElement, CASE_DB_NAME_RELATIVE_ELEMENT_NAME, caseDatabaseName);
398  createChildElement(doc, caseElement, TEXT_INDEX_ELEMENT, textIndexName);
399  }
400 
410  private void createChildElement(Document doc, Element parentElement, String elementName, String elementContent) {
411  Element element = doc.createElement(elementName);
412  element.appendChild(doc.createTextNode(elementContent));
413  parentElement.appendChild(element);
414  }
415 
422  private void readFromFile() throws CaseMetadataException {
423  try {
424  /*
425  * Parse the file into an XML DOM and get the root element.
426  */
427  DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
428  Document doc = builder.parse(this.getFilePath().toFile());
429  doc.getDocumentElement().normalize();
430  Element rootElement = doc.getDocumentElement();
431  if (!rootElement.getNodeName().equals(ROOT_ELEMENT_NAME)) {
432  throw new CaseMetadataException("Case metadata file corrupted");
433  }
434 
435  /*
436  * Get the content of the relevant children of the root element.
437  */
438  String schemaVersion = getElementTextContent(rootElement, SCHEMA_VERSION_ELEMENT_NAME, true);
439  this.createdDate = getElementTextContent(rootElement, CREATED_DATE_ELEMENT_NAME, true);
440  if (schemaVersion.equals(SCHEMA_VERSION_ONE)) {
441  this.createdByVersion = getElementTextContent(rootElement, AUTOPSY_VERSION_ELEMENT_NAME, true);
442  } else {
443  this.createdByVersion = getElementTextContent(rootElement, AUTOPSY_CREATED_BY_ELEMENT_NAME, true);
444  }
445 
446  /*
447  * Get the content of the children of the case element.
448  */
449  NodeList caseElements = doc.getElementsByTagName(CASE_ELEMENT_NAME);
450  if (caseElements.getLength() == 0) {
451  throw new CaseMetadataException("Case metadata file corrupted");
452  }
453  Element caseElement = (Element) caseElements.item(0);
454  this.caseName = getElementTextContent(caseElement, CASE_NAME_ELEMENT_NAME, true);
455  if (schemaVersion.equals(SCHEMA_VERSION_ONE) || schemaVersion.equals(SCHEMA_VERSION_TWO)) {
456  this.caseDisplayName = caseName;
457  } else {
458  this.caseDisplayName = getElementTextContent(caseElement, CASE_DISPLAY_NAME_ELEMENT_NAME, true);
459  }
460  this.caseNumber = getElementTextContent(caseElement, CASE_NUMBER_ELEMENT_NAME, false);
461  this.examiner = getElementTextContent(caseElement, EXAMINER_ELEMENT_NAME, false);
462  this.caseType = Case.CaseType.fromString(getElementTextContent(caseElement, CASE_TYPE_ELEMENT_NAME, true));
463  if (null == this.caseType) {
464  throw new CaseMetadataException("Case metadata file corrupted");
465  }
466  switch (schemaVersion) {
467  case SCHEMA_VERSION_ONE:
468  this.caseDatabaseName = getElementTextContent(caseElement, CASE_DATABASE_NAME_ELEMENT_NAME, true);
469  this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_NAME_ELEMENT, true);
470  break;
471  case SCHEMA_VERSION_TWO:
472  this.caseDatabaseName = getElementTextContent(caseElement, CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME, true);
473  this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, false);
474  break;
475  default:
476  this.caseDatabaseName = getElementTextContent(caseElement, CASE_DB_NAME_RELATIVE_ELEMENT_NAME, true);
477  this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, false);
478  break;
479  }
480 
481  /*
482  * Fix up the case database name due to a bug that for a time caused
483  * the absolute paths of single-user case databases to be stored.
484  * Derive the missing (absolute/relative) value from the one
485  * present.
486  */
487  Path possibleAbsoluteCaseDbPath = Paths.get(this.caseDatabaseName);
488  Path caseDirectoryPath = Paths.get(getCaseDirectory());
489  if (possibleAbsoluteCaseDbPath.getNameCount() > 1) {
490  this.caseDatabasePath = this.caseDatabaseName;
491  this.caseDatabaseName = caseDirectoryPath.relativize(possibleAbsoluteCaseDbPath).toString();
492  } else {
493  this.caseDatabasePath = caseDirectoryPath.resolve(caseDatabaseName).toAbsolutePath().toString();
494  }
495 
496  } catch (ParserConfigurationException | SAXException | IOException ex) {
497  throw new CaseMetadataException(String.format("Error reading from case metadata file %s", metadataFilePath), ex);
498  }
499  }
500 
513  private String getElementTextContent(Element parentElement, String elementName, boolean contentIsRequired) throws CaseMetadataException {
514  NodeList elementsList = parentElement.getElementsByTagName(elementName);
515  if (elementsList.getLength() == 0) {
516  throw new CaseMetadataException(String.format("Missing %s element from case metadata file %s", elementName, metadataFilePath));
517  }
518  String textContent = elementsList.item(0).getTextContent();
519  if (textContent.isEmpty() && contentIsRequired) {
520  throw new CaseMetadataException(String.format("Empty %s element in case metadata file %s", elementName, metadataFilePath));
521  }
522  return textContent;
523  }
524 
529  public final static class CaseMetadataException extends Exception {
530 
531  private static final long serialVersionUID = 1L;
532 
533  private CaseMetadataException(String message) {
534  super(message);
535  }
536 
537  private CaseMetadataException(String message, Throwable cause) {
538  super(message, cause);
539  }
540  }
541 
551  @Deprecated
552  public String getCaseDatabasePath() throws UnsupportedOperationException {
554  return Paths.get(getCaseDirectory(), caseDatabaseName).toString();
555  } else {
556  throw new UnsupportedOperationException();
557  }
558  }
559 
560 }
static CaseType fromString(String typeName)
Definition: Case.java:178
void createChildElement(Document doc, Element parentElement, String elementName, String elementContent)
String getElementTextContent(Element parentElement, String elementName, boolean contentIsRequired)

Copyright © 2012-2016 Basis Technology. Generated on: Fri Sep 29 2017
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.