Autopsy  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  //fields from schema version 1
58  private static final String SCHEMA_VERSION_ONE = "1.0";
59  private final static String ROOT_ELEMENT_NAME = "AutopsyCase"; //NON-NLS
60  private final static String SCHEMA_VERSION_ELEMENT_NAME = "SchemaVersion"; //NON-NLS
61  private final static String CREATED_DATE_ELEMENT_NAME = "CreatedDate"; //NON-NLS
62  private final static String AUTOPSY_VERSION_ELEMENT_NAME = "AutopsyCreatedVersion"; //NON-NLS
63  private final static String CASE_ELEMENT_NAME = "Case"; //NON-NLS
64  private final static String CASE_NAME_ELEMENT_NAME = "Name"; //NON-NLS
65  private final static String CASE_NUMBER_ELEMENT_NAME = "Number"; //NON-NLS
66  private final static String EXAMINER_ELEMENT_NAME = "Examiner"; //NON-NLS
67  private final static String CASE_TYPE_ELEMENT_NAME = "CaseType"; //NON-NLS
68  private final static String CASE_DATABASE_NAME_ELEMENT_NAME = "DatabaseName"; //NON-NLS
69  private final static String TEXT_INDEX_NAME_ELEMENT = "TextIndexName"; //NON-NLS
70 
71  //fields from schema version 2
72  private static final String SCHEMA_VERSION_TWO = "2.0";
73  private final static String AUTOPSY_CREATED_BY_ELEMENT_NAME = "CreatedByAutopsyVersion"; //NON-NLS
74  private final static String CASE_DATABASE_ABSOLUTE_PATH_ELEMENT_NAME = "Database"; //NON-NLS
75  private final static String TEXT_INDEX_ELEMENT = "TextIndex"; //NON-NLS
76 
77  //fields from schema version 3
78  private static final String SCHEMA_VERSION_THREE = "3.0";
79  private final static String CASE_DISPLAY_NAME_ELEMENT_NAME = "DisplayName"; //NON-NLS
80  private final static String CASE_DATABASE_NAME_RELATIVE_ELEMENT_NAME = "CaseDatabase"; //NON-NLS
81 
82  //unread fields, these are regenerated on save
83  private final static String MODIFIED_DATE_ELEMENT_NAME = "ModifiedDate"; //NON-NLS
84  private final static String AUTOPSY_SAVED_BY_ELEMENT_NAME = "SavedByAutopsyVersion"; //NON-NLS
85 
86  private static final String CURRENT_SCHEMA_VERSION = SCHEMA_VERSION_THREE;
87 
88  private final Path metadataFilePath;
90  private String caseName;
91  private String caseDisplayName;
92  private String caseNumber;
93  private String examiner;
94  private String caseDatabaseName;
95  private String caseDatabasePath;
96  private String textIndexName;
97  private String createdDate;
98  private String createdByVersion;
99 
105  public static String getFileExtension() {
106  return FILE_EXTENSION;
107  }
108 
127  CaseMetadata(String caseDirectory, Case.CaseType caseType, String caseName, String caseDisplayName, String caseNumber, String examiner, String caseDatabase) throws CaseMetadataException {
128  metadataFilePath = Paths.get(caseDirectory, caseDisplayName + FILE_EXTENSION);
129  this.caseType = caseType;
130  this.caseName = caseName;
131  this.caseDisplayName = caseDisplayName;
132  this.caseNumber = caseNumber;
133  this.examiner = examiner;
134  this.caseDatabaseName = caseDatabase;
135  createdByVersion = Version.getVersion();
136  createdDate = CaseMetadata.DATE_FORMAT.format(new Date());
137  writeToFile();
138  }
139 
149  public CaseMetadata(Path metadataFilePath) throws CaseMetadataException {
150  this.metadataFilePath = metadataFilePath;
151  readFromFile();
152  }
153 
159  Path getFilePath() {
160  return metadataFilePath;
161  }
162 
168  public String getCaseDirectory() {
169  return metadataFilePath.getParent().toString();
170  }
171 
178  return caseType;
179  }
180 
186  public String getCaseName() {
187  return caseName;
188  }
189 
195  public String getCaseDisplayName() {
196  return this.caseDisplayName;
197  }
198 
205  void setCaseDisplayName(String caseName) throws CaseMetadataException {
206  String oldCaseName = caseName;
207  this.caseDisplayName = caseName;
208  try {
209  writeToFile();
210  } catch (CaseMetadataException ex) {
211  this.caseDisplayName = oldCaseName;
212  throw ex;
213  }
214  }
215 
221  public String getCaseNumber() {
222  return caseNumber;
223  }
224 
230  public String getExaminer() {
231  return examiner;
232  }
233 
239  public String getCaseDatabaseName() {
240  return caseDatabaseName;
241  }
242 
248  void setTextIndexName(String caseTextIndexName) throws CaseMetadataException {
249  String oldIndexName = caseTextIndexName;
250  this.textIndexName = caseTextIndexName;
251  try {
252  writeToFile();
253  } catch (CaseMetadataException ex) {
254  this.textIndexName = oldIndexName;
255  throw ex;
256  }
257  }
258 
264  public String getTextIndexName() {
265  return textIndexName;
266  }
267 
273  String getCreatedDate() {
274  return createdDate;
275  }
276 
283  void setCreatedDate(String createdDate) throws CaseMetadataException {
284  String oldCreatedDate = createdDate;
285  this.createdDate = createdDate;
286  try {
287  writeToFile();
288  } catch (CaseMetadataException ex) {
289  this.createdDate = oldCreatedDate;
290  throw ex;
291  }
292  }
293 
299  String getCreatedByVersion() {
300  return createdByVersion;
301  }
302 
309  void setCreatedByVersion(String buildVersion) throws CaseMetadataException {
310  String oldCreatedByVersion = this.createdByVersion;
311  this.createdByVersion = buildVersion;
312  try {
313  this.writeToFile();
314  } catch (CaseMetadataException ex) {
315  this.createdByVersion = oldCreatedByVersion;
316  throw ex;
317  }
318  }
319 
326  private void writeToFile() throws CaseMetadataException {
327  try {
328  /*
329  * Create the XML DOM.
330  */
331  Document doc = XMLUtil.createDocument();
332  createXMLDOM(doc);
333  doc.normalize();
334 
335  /*
336  * Prepare the DOM for pretty printing to the metadata file.
337  */
338  Source source = new DOMSource(doc);
339  StringWriter stringWriter = new StringWriter();
340  Result streamResult = new StreamResult(stringWriter);
341  Transformer transformer = TransformerFactory.newInstance().newTransformer();
342  transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //NON-NLS
343  transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); //NON-NLS
344  transformer.transform(source, streamResult);
345 
346  /*
347  * Write the DOM to the metadata file.
348  */
349  try (BufferedWriter fileWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(metadataFilePath.toFile())))) {
350  fileWriter.write(stringWriter.toString());
351  fileWriter.flush();
352  }
353 
354  } catch (ParserConfigurationException | TransformerException | IOException ex) {
355  throw new CaseMetadataException(String.format("Error writing to case metadata file %s", metadataFilePath), ex);
356  }
357  }
358 
359  /*
360  * Creates an XML DOM from the case metadata.
361  */
362  private void createXMLDOM(Document doc) {
363  /*
364  * Create the root element and its children.
365  */
366  Element rootElement = doc.createElement(ROOT_ELEMENT_NAME);
367  doc.appendChild(rootElement);
368  createChildElement(doc, rootElement, SCHEMA_VERSION_ELEMENT_NAME, CURRENT_SCHEMA_VERSION);
369  createChildElement(doc, rootElement, CREATED_DATE_ELEMENT_NAME, createdDate);
370  createChildElement(doc, rootElement, MODIFIED_DATE_ELEMENT_NAME, DATE_FORMAT.format(new Date()));
371  createChildElement(doc, rootElement, AUTOPSY_CREATED_BY_ELEMENT_NAME, createdByVersion);
372  createChildElement(doc, rootElement, AUTOPSY_SAVED_BY_ELEMENT_NAME, Version.getVersion());
373  Element caseElement = doc.createElement(CASE_ELEMENT_NAME);
374  rootElement.appendChild(caseElement);
375 
376  /*
377  * Create the children of the case element.
378  */
379  createChildElement(doc, caseElement, CASE_NAME_ELEMENT_NAME, caseName);
380  createChildElement(doc, caseElement, CASE_DISPLAY_NAME_ELEMENT_NAME, caseDisplayName);
381  createChildElement(doc, caseElement, CASE_NUMBER_ELEMENT_NAME, caseNumber);
382  createChildElement(doc, caseElement, EXAMINER_ELEMENT_NAME, examiner);
383  createChildElement(doc, caseElement, CASE_TYPE_ELEMENT_NAME, caseType.toString());
384  createChildElement(doc, caseElement, CASE_DATABASE_ABSOLUTE_PATH_ELEMENT_NAME, caseDatabasePath);
385  createChildElement(doc, caseElement, CASE_DATABASE_NAME_RELATIVE_ELEMENT_NAME, caseDatabaseName);
386  createChildElement(doc, caseElement, TEXT_INDEX_ELEMENT, textIndexName);
387  }
388 
398  private void createChildElement(Document doc, Element parentElement, String elementName, String elementContent) {
399  Element element = doc.createElement(elementName);
400  element.appendChild(doc.createTextNode(elementContent));
401  parentElement.appendChild(element);
402  }
403 
410  private void readFromFile() throws CaseMetadataException {
411  try {
412  /*
413  * Parse the file into an XML DOM and get the root element.
414  */
415  DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
416  Document doc = builder.parse(this.getFilePath().toFile());
417  doc.getDocumentElement().normalize();
418  Element rootElement = doc.getDocumentElement();
419  if (!rootElement.getNodeName().equals(ROOT_ELEMENT_NAME)) {
420  throw new CaseMetadataException("Case metadata file corrupted");
421  }
422 
423  /*
424  * Get the content of the relevant children of the root element.
425  */
426  String schemaVersion = getElementTextContent(rootElement, SCHEMA_VERSION_ELEMENT_NAME, true);
427  this.createdDate = getElementTextContent(rootElement, CREATED_DATE_ELEMENT_NAME, true);
428  if (schemaVersion.equals(SCHEMA_VERSION_ONE)) {
429  this.createdByVersion = getElementTextContent(rootElement, AUTOPSY_VERSION_ELEMENT_NAME, true);
430  } else {
431  this.createdByVersion = getElementTextContent(rootElement, AUTOPSY_CREATED_BY_ELEMENT_NAME, true);
432  }
433 
434  /*
435  * Get the content of the children of the case element.
436  */
437  NodeList caseElements = doc.getElementsByTagName(CASE_ELEMENT_NAME);
438  if (caseElements.getLength() == 0) {
439  throw new CaseMetadataException("Case metadata file corrupted");
440  }
441  Element caseElement = (Element) caseElements.item(0);
442  this.caseName = getElementTextContent(caseElement, CASE_NAME_ELEMENT_NAME, true);
443  if (schemaVersion.equals(SCHEMA_VERSION_ONE) || schemaVersion.equals(SCHEMA_VERSION_TWO)) {
444  this.caseDisplayName = caseName;
445  } else {
446  this.caseDisplayName = getElementTextContent(caseElement, CASE_DISPLAY_NAME_ELEMENT_NAME, true);
447  }
448  this.caseNumber = getElementTextContent(caseElement, CASE_NUMBER_ELEMENT_NAME, false);
449  this.examiner = getElementTextContent(caseElement, EXAMINER_ELEMENT_NAME, false);
450  this.caseType = Case.CaseType.fromString(getElementTextContent(caseElement, CASE_TYPE_ELEMENT_NAME, true));
451  if (null == this.caseType) {
452  throw new CaseMetadataException("Case metadata file corrupted");
453  }
454  if (schemaVersion.equals(SCHEMA_VERSION_ONE)) {
455  this.caseDatabaseName = getElementTextContent(caseElement, CASE_DATABASE_NAME_ELEMENT_NAME, true);
456  this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_NAME_ELEMENT, true);
457  } else if (schemaVersion.equals(SCHEMA_VERSION_TWO)) {
458  this.caseDatabaseName = getElementTextContent(caseElement, CASE_DATABASE_ABSOLUTE_PATH_ELEMENT_NAME, true);
459  this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, false);
460  } else {
461  this.caseDatabaseName = getElementTextContent(caseElement, CASE_DATABASE_NAME_RELATIVE_ELEMENT_NAME, true);
462  this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, false);
463  }
464 
465  /*
466  * Fix up the case database name due to a bug that for a time caused
467  * the absolute paths of single-user case databases to be stored.
468  * Derive the missing (absolute/relative) value from the one
469  * present.
470  */
471  Path possibleAbsoluteCaseDbPath = Paths.get(this.caseDatabaseName);
472  Path caseDirectoryPath = Paths.get(getCaseDirectory());
473  if (possibleAbsoluteCaseDbPath.getNameCount() > 1) {
474  this.caseDatabasePath = this.caseDatabaseName;
475  this.caseDatabaseName = caseDirectoryPath.relativize(possibleAbsoluteCaseDbPath).toString();
476  } else {
477  this.caseDatabasePath = caseDirectoryPath.resolve(caseDatabaseName).toAbsolutePath().toString();
478  }
479 
480  } catch (ParserConfigurationException | SAXException | IOException ex) {
481  throw new CaseMetadataException(String.format("Error reading from case metadata file %s", metadataFilePath), ex);
482  }
483  }
484 
497  private String getElementTextContent(Element parentElement, String elementName, boolean contentIsRequired) throws CaseMetadataException {
498  NodeList elementsList = parentElement.getElementsByTagName(elementName);
499  if (elementsList.getLength() == 0) {
500  throw new CaseMetadataException(String.format("Missing %s element from case metadata file %s", elementName, metadataFilePath));
501  }
502  String textContent = elementsList.item(0).getTextContent();
503  if (textContent.isEmpty() && contentIsRequired) {
504  throw new CaseMetadataException(String.format("Empty %s element in case metadata file %s", elementName, metadataFilePath));
505  }
506  return textContent;
507  }
508 
513  public final static class CaseMetadataException extends Exception {
514 
515  private static final long serialVersionUID = 1L;
516 
517  private CaseMetadataException(String message) {
518  super(message);
519  }
520 
521  private CaseMetadataException(String message, Throwable cause) {
522  super(message, cause);
523  }
524  }
525 
535  @Deprecated
536  public String getCaseDatabasePath() throws UnsupportedOperationException {
538  return Paths.get(getCaseDirectory(), caseDatabaseName).toString();
539  } else {
540  throw new UnsupportedOperationException();
541  }
542  }
543 
544 }
static CaseType fromString(String typeName)
Definition: Case.java:189
static final String CASE_DATABASE_NAME_RELATIVE_ELEMENT_NAME
void createChildElement(Document doc, Element parentElement, String elementName, String elementContent)
static final String CASE_DATABASE_ABSOLUTE_PATH_ELEMENT_NAME
String getElementTextContent(Element parentElement, String elementName, boolean contentIsRequired)

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