Autopsy 4.22.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-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 */
19package org.sleuthkit.autopsy.casemodule;
20
21import java.io.BufferedWriter;
22import java.io.File;
23import java.io.FileOutputStream;
24import java.io.IOException;
25import java.io.OutputStreamWriter;
26import java.io.StringWriter;
27import java.nio.charset.StandardCharsets;
28import java.nio.file.Path;
29import java.nio.file.Paths;
30import java.text.DateFormat;
31import java.text.SimpleDateFormat;
32import java.util.Date;
33import java.util.Locale;
34import javax.xml.parsers.DocumentBuilder;
35import javax.xml.parsers.DocumentBuilderFactory;
36import javax.xml.parsers.ParserConfigurationException;
37import javax.xml.transform.OutputKeys;
38import javax.xml.transform.Result;
39import javax.xml.transform.Source;
40import javax.xml.transform.Transformer;
41import javax.xml.transform.TransformerException;
42import javax.xml.transform.TransformerFactory;
43import javax.xml.transform.dom.DOMSource;
44import javax.xml.transform.stream.StreamResult;
45import org.apache.commons.lang3.StringUtils;
46import org.sleuthkit.autopsy.coreutils.Version;
47import org.sleuthkit.autopsy.coreutils.XMLUtil;
48import org.w3c.dom.Document;
49import org.w3c.dom.Element;
50import org.w3c.dom.NodeList;
51import org.xml.sax.SAXException;
52
56public final class CaseMetadata {
57
58 private static final String FILE_EXTENSION = ".aut";
59 private static final String DATE_FORMAT_STRING = "yyyy/MM/dd HH:mm:ss (z)";
60 private static final DateFormat DATE_FORMAT = new SimpleDateFormat(DATE_FORMAT_STRING, Locale.US);
61
62 /*
63 * Fields from schema version 1
64 */
65 private static final String SCHEMA_VERSION_ONE = "1.0";
66 private final static String ROOT_ELEMENT_NAME = "AutopsyCase"; //NON-NLS
67 private final static String SCHEMA_VERSION_ELEMENT_NAME = "SchemaVersion"; //NON-NLS
68 private final static String CREATED_DATE_ELEMENT_NAME = "CreatedDate"; //NON-NLS
69 private final static String AUTOPSY_VERSION_ELEMENT_NAME = "AutopsyCreatedVersion"; //NON-NLS
70 private final static String CASE_ELEMENT_NAME = "Case"; //NON-NLS
71 private final static String CASE_NAME_ELEMENT_NAME = "Name"; //NON-NLS
72 private final static String CASE_NUMBER_ELEMENT_NAME = "Number"; //NON-NLS
73 private final static String EXAMINER_ELEMENT_NAME = "Examiner"; //NON-NLS
74 private final static String CASE_TYPE_ELEMENT_NAME = "CaseType"; //NON-NLS
75 private final static String CASE_DATABASE_NAME_ELEMENT_NAME = "DatabaseName"; //NON-NLS
76 private final static String TEXT_INDEX_NAME_ELEMENT = "TextIndexName"; //NON-NLS
77
78 /*
79 * Fields from schema version 2
80 */
81 private static final String SCHEMA_VERSION_TWO = "2.0";
82 private final static String AUTOPSY_CREATED_BY_ELEMENT_NAME = "CreatedByAutopsyVersion"; //NON-NLS
83 private final static String CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME = "Database"; //NON-NLS
84 private final static String TEXT_INDEX_ELEMENT = "TextIndex"; //NON-NLS
85
86 /*
87 * Fields from schema version 3
88 */
89 private static final String SCHEMA_VERSION_THREE = "3.0";
90 private final static String CASE_DISPLAY_NAME_ELEMENT_NAME = "DisplayName"; //NON-NLS
91 private final static String CASE_DB_NAME_RELATIVE_ELEMENT_NAME = "CaseDatabase"; //NON-NLS
92
93 /*
94 * Fields from schema version 4
95 */
96 private static final String SCHEMA_VERSION_FOUR = "4.0";
97 private final static String EXAMINER_ELEMENT_PHONE = "ExaminerPhone"; //NON-NLS
98 private final static String EXAMINER_ELEMENT_EMAIL = "ExaminerEmail"; //NON-NLS
99 private final static String CASE_ELEMENT_NOTES = "CaseNotes"; //NON-NLS
100
101 /*
102 * Fields from schema version 5
103 */
104 private static final String SCHEMA_VERSION_FIVE = "5.0";
105 private final static String ORIGINAL_CASE_ELEMENT_NAME = "OriginalCase"; //NON-NLS
106
107 /*
108 * Fields from schema version 6
109 */
110 private static final String SCHEMA_VERSION_SIX = "6.0";
111 private final static String CONTENT_PROVIDER_ELEMENT_NAME = "ContentProvider";
112 private final static String CONTENT_PROVIDER_NAME_ELEMENT_NAME = "Name";
113
114 /*
115 * Unread fields, regenerated on save.
116 */
117 private final static String MODIFIED_DATE_ELEMENT_NAME = "ModifiedDate"; //NON-NLS
118 private final static String AUTOPSY_SAVED_BY_ELEMENT_NAME = "SavedByAutopsyVersion"; //NON-NLS
119
120 private final static String CURRENT_SCHEMA_VERSION = SCHEMA_VERSION_SIX;
121
122 private final Path metadataFilePath;
124 private String caseName;
126 private String caseDatabaseName;
127 private String caseDatabasePath;
128 private String textIndexName; // Legacy
129 private String createdDate;
130 private String createdByVersion;
131 private CaseMetadata originalMetadata = null; // For portable cases
132 private String contentProviderName;
133
139 public static String getFileExtension() {
140 return FILE_EXTENSION;
141 }
142
148 public static DateFormat getDateFormat() {
149 return new SimpleDateFormat(DATE_FORMAT_STRING, Locale.US);
150 }
151
162 CaseMetadata(Case.CaseType caseType, String caseDirectory, String caseName, CaseDetails caseDetails) {
163 this(caseType, caseDirectory, caseName, caseDetails, null);
164 }
165
177 CaseMetadata(Case.CaseType caseType, String caseDirectory, String caseName, CaseDetails caseDetails, CaseMetadata originalMetadata) {
178 metadataFilePath = Paths.get(caseDirectory, caseDetails.getCaseDisplayName() + FILE_EXTENSION);
179 this.caseType = caseType;
180 this.caseName = caseName;
181 this.caseDetails = caseDetails;
182 caseDatabaseName = "";
183 caseDatabasePath = "";
184 textIndexName = "";
185 createdByVersion = Version.getVersion();
186 createdDate = CaseMetadata.DATE_FORMAT.format(new Date());
187 this.originalMetadata = originalMetadata;
188 this.contentProviderName = originalMetadata == null ? null : originalMetadata.contentProviderName;
189 }
190
201 this.metadataFilePath = metadataFilePath;
202 readFromFile();
203 }
204
213 public static Path getCaseMetadataFilePath(Path directoryPath) {
214 final File[] files = directoryPath.toFile().listFiles();
215 if (files != null) {
216 for (File file : files) {
217 final String fileName = file.getName().toLowerCase();
218 if (fileName.endsWith(CaseMetadata.getFileExtension()) && file.isFile()) {
219 return file.toPath();
220 }
221 }
222 }
223 return null;
224 }
225
230 public String getContentProviderName() {
231 return this.contentProviderName;
232 }
233
239 public Path getFilePath() {
240 return metadataFilePath;
241 }
242
248 public String getCaseDirectory() {
249 return StringUtils.isBlank(this.caseDatabasePath)
250 ? metadataFilePath.getParent().toString()
251 : Paths.get(this.caseDatabasePath).getParent().toString();
252 }
253
260 return caseType;
261 }
262
268 public String getCaseName() {
269 return caseName;
270 }
271
278 return caseDetails;
279 }
280
286 public String getCaseDisplayName() {
287 return caseDetails.getCaseDisplayName();
288 }
289
290 void setCaseDetails(CaseDetails newCaseDetails) throws CaseMetadataException {
291 CaseDetails oldCaseDetails = this.caseDetails;
292 this.caseDetails = newCaseDetails;
293 try {
294 writeToFile();
295 } catch (CaseMetadataException ex) {
296 this.caseDetails = oldCaseDetails;
297 throw ex;
298 }
299 }
300
306 public String getCaseNumber() {
307 return caseDetails.getCaseNumber();
308 }
309
315 public String getExaminer() {
316 return caseDetails.getExaminerName();
317 }
318
319 public String getExaminerPhone() {
320 return caseDetails.getExaminerPhone();
321 }
322
323 public String getExaminerEmail() {
324 return caseDetails.getExaminerEmail();
325 }
326
327 public String getCaseNotes() {
328 return caseDetails.getCaseNotes();
329 }
330
336 public String getCaseDatabaseName() {
337 return caseDatabaseName;
338 }
339
347 void setCaseDatabaseName(String caseDatabaseName) throws CaseMetadataException {
348 String oldCaseDatabaseName = this.caseDatabaseName;
349 this.caseDatabaseName = caseDatabaseName;
350 try {
351 writeToFile();
352 } catch (CaseMetadataException ex) {
353 this.caseDatabaseName = oldCaseDatabaseName;
354 throw ex;
355 }
356 }
357
364 public String getTextIndexName() {
365 return textIndexName;
366 }
367
373 public String getCreatedDate() {
374 return createdDate;
375 }
376
385 void setCreatedDate(String createdDate) throws CaseMetadataException {
386 String oldCreatedDate = createdDate;
387 this.createdDate = createdDate;
388 try {
389 writeToFile();
390 } catch (CaseMetadataException ex) {
391 this.createdDate = oldCreatedDate;
392 throw ex;
393 }
394 }
395
401 String getCreatedByVersion() {
402 return createdByVersion;
403 }
404
413 void setCreatedByVersion(String buildVersion) throws CaseMetadataException {
414 String oldCreatedByVersion = this.createdByVersion;
415 this.createdByVersion = buildVersion;
416 try {
417 writeToFile();
418 } catch (CaseMetadataException ex) {
419 this.createdByVersion = oldCreatedByVersion;
420 throw ex;
421 }
422 }
423
430 void writeToFile() throws CaseMetadataException {
431 try {
432 /*
433 * Create the XML DOM.
434 */
435 Document doc = XMLUtil.createDocument();
436 createXMLDOM(doc);
437 doc.normalize();
438
439 /*
440 * Prepare the DOM for pretty printing to the metadata file.
441 */
442 Source source = new DOMSource(doc);
443 StringWriter stringWriter = new StringWriter();
444 Result streamResult = new StreamResult(stringWriter);
445 Transformer transformer = TransformerFactory.newInstance().newTransformer();
446 transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //NON-NLS
447 transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); //NON-NLS
448 transformer.transform(source, streamResult);
449
450 /*
451 * Write the DOM to the metadata file. Add UTF-8 Characterset so it writes to the file
452 * correctly for non-latin characters
453 */
454 try (BufferedWriter fileWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(metadataFilePath.toFile()), StandardCharsets.UTF_8))) {
455 fileWriter.write(stringWriter.toString());
456 fileWriter.flush();
457 }
458
459 } catch (ParserConfigurationException | TransformerException | IOException ex) {
460 throw new CaseMetadataException(String.format("Error writing to case metadata file %s", metadataFilePath), ex);
461 }
462 }
463
464 /*
465 * Creates an XML DOM from the case metadata.
466 */
467 private void createXMLDOM(Document doc) {
468 /*
469 * Create the root element and its children.
470 */
471 Element rootElement = doc.createElement(ROOT_ELEMENT_NAME);
472 doc.appendChild(rootElement);
475 createChildElement(doc, rootElement, MODIFIED_DATE_ELEMENT_NAME, DATE_FORMAT.format(new Date()));
478 Element caseElement = doc.createElement(CASE_ELEMENT_NAME);
479 rootElement.appendChild(caseElement);
480
481 Element contentProviderEl = doc.createElement(CONTENT_PROVIDER_ELEMENT_NAME);
482 rootElement.appendChild(contentProviderEl);
483
484 Element contentProviderNameEl = doc.createElement(CONTENT_PROVIDER_NAME_ELEMENT_NAME);
485 if (this.contentProviderName != null) {
486 contentProviderNameEl.setTextContent(this.contentProviderName);
487 }
488 contentProviderEl.appendChild(contentProviderNameEl);
489
490 /*
491 * Create the children of the case element.
492 */
493 createCaseElements(doc, caseElement, this);
494
495 /*
496 * Add original case element
497 */
498 Element originalCaseElement = doc.createElement(ORIGINAL_CASE_ELEMENT_NAME);
499 rootElement.appendChild(originalCaseElement);
500 if (originalMetadata != null) {
501 createChildElement(doc, originalCaseElement, CREATED_DATE_ELEMENT_NAME, originalMetadata.createdDate);
502 Element originalCaseDetailsElement = doc.createElement(CASE_ELEMENT_NAME);
503 originalCaseElement.appendChild(originalCaseDetailsElement);
504 createCaseElements(doc, originalCaseDetailsElement, originalMetadata);
505 }
506
507 }
508
516 private void createCaseElements(Document doc, Element caseElement, CaseMetadata metadataToWrite) {
517 CaseDetails caseDetailsToWrite = metadataToWrite.caseDetails;
518 createChildElement(doc, caseElement, CASE_NAME_ELEMENT_NAME, metadataToWrite.caseName);
519 createChildElement(doc, caseElement, CASE_DISPLAY_NAME_ELEMENT_NAME, caseDetailsToWrite.getCaseDisplayName());
520 createChildElement(doc, caseElement, CASE_NUMBER_ELEMENT_NAME, caseDetailsToWrite.getCaseNumber());
521 createChildElement(doc, caseElement, EXAMINER_ELEMENT_NAME, caseDetailsToWrite.getExaminerName());
522 createChildElement(doc, caseElement, EXAMINER_ELEMENT_PHONE, caseDetailsToWrite.getExaminerPhone());
523 createChildElement(doc, caseElement, EXAMINER_ELEMENT_EMAIL, caseDetailsToWrite.getExaminerEmail());
524 createChildElement(doc, caseElement, CASE_ELEMENT_NOTES, caseDetailsToWrite.getCaseNotes());
525 createChildElement(doc, caseElement, CASE_TYPE_ELEMENT_NAME, metadataToWrite.caseType.toString());
528 createChildElement(doc, caseElement, TEXT_INDEX_ELEMENT, metadataToWrite.textIndexName);
529 }
530
540 private void createChildElement(Document doc, Element parentElement, String elementName, String elementContent) {
541 Element element = doc.createElement(elementName);
542 element.appendChild(doc.createTextNode(elementContent));
543 parentElement.appendChild(element);
544 }
545
552 private void readFromFile() throws CaseMetadataException {
553 try {
554 /*
555 * Parse the file into an XML DOM and get the root element.
556 */
557 DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
558 Document doc = builder.parse(this.getFilePath().toFile());
559 doc.getDocumentElement().normalize();
560 Element rootElement = doc.getDocumentElement();
561 if (!rootElement.getNodeName().equals(ROOT_ELEMENT_NAME)) {
562 throw new CaseMetadataException("Case metadata file corrupted");
563 }
564
565 /*
566 * Get the content of the relevant children of the root element.
567 */
568 String schemaVersion = getElementTextContent(rootElement, SCHEMA_VERSION_ELEMENT_NAME, true);
569 this.createdDate = getElementTextContent(rootElement, CREATED_DATE_ELEMENT_NAME, true);
570 if (schemaVersion.equals(SCHEMA_VERSION_ONE)) {
571 this.createdByVersion = getElementTextContent(rootElement, AUTOPSY_VERSION_ELEMENT_NAME, true);
572 } else {
573 this.createdByVersion = getElementTextContent(rootElement, AUTOPSY_CREATED_BY_ELEMENT_NAME, true);
574 }
575
576 Element contentProviderEl = getChildElOrNull(rootElement, CONTENT_PROVIDER_ELEMENT_NAME);
577 if (contentProviderEl != null) {
578 Element contentProviderNameEl = getChildElOrNull(contentProviderEl, CONTENT_PROVIDER_NAME_ELEMENT_NAME);
579 this.contentProviderName = contentProviderNameEl != null ? contentProviderNameEl.getTextContent() : null;
580 } else {
581 this.contentProviderName = null;
582 }
583
584 /*
585 * Get the content of the children of the case element.
586 */
587 NodeList caseElements = doc.getElementsByTagName(CASE_ELEMENT_NAME);
588 if (caseElements.getLength() == 0) {
589 throw new CaseMetadataException("Case metadata file corrupted");
590 }
591 Element caseElement = (Element) caseElements.item(0);
592 this.caseName = getElementTextContent(caseElement, CASE_NAME_ELEMENT_NAME, true);
593 String caseDisplayName;
594 String caseNumber;
595 if (schemaVersion.equals(SCHEMA_VERSION_ONE) || schemaVersion.equals(SCHEMA_VERSION_TWO)) {
596 caseDisplayName = caseName;
597 } else {
598 caseDisplayName = getElementTextContent(caseElement, CASE_DISPLAY_NAME_ELEMENT_NAME, true);
599 }
600 caseNumber = getElementTextContent(caseElement, CASE_NUMBER_ELEMENT_NAME, false);
601 String examinerName = getElementTextContent(caseElement, EXAMINER_ELEMENT_NAME, false);
602 String examinerPhone;
603 String examinerEmail;
604 String caseNotes;
605 if (schemaVersion.equals(SCHEMA_VERSION_ONE) || schemaVersion.equals(SCHEMA_VERSION_TWO) || schemaVersion.equals(SCHEMA_VERSION_THREE)) {
606 examinerPhone = ""; //case had metadata file written before additional examiner details were included
607 examinerEmail = "";
608 caseNotes = "";
609 } else {
610 examinerPhone = getElementTextContent(caseElement, EXAMINER_ELEMENT_PHONE, false);
611 examinerEmail = getElementTextContent(caseElement, EXAMINER_ELEMENT_EMAIL, false);
612 caseNotes = getElementTextContent(caseElement, CASE_ELEMENT_NOTES, false);
613 }
614
615 this.caseDetails = new CaseDetails(caseDisplayName, caseNumber, examinerName, examinerPhone, examinerEmail, caseNotes);
616 this.caseType = Case.CaseType.fromString(getElementTextContent(caseElement, CASE_TYPE_ELEMENT_NAME, true));
617 if (null == this.caseType) {
618 throw new CaseMetadataException("Case metadata file corrupted");
619 }
620 switch (schemaVersion) {
622 this.caseDatabaseName = getElementTextContent(caseElement, CASE_DATABASE_NAME_ELEMENT_NAME, true);
623 this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_NAME_ELEMENT, true);
624 break;
626 this.caseDatabaseName = getElementTextContent(caseElement, CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME, true);
627 this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, false);
628 break;
629 default:
630 this.caseDatabasePath = getElementTextContent(caseElement, CASE_DB_ABSOLUTE_PATH_ELEMENT_NAME, false);
631 this.caseDatabaseName = getElementTextContent(caseElement, CASE_DB_NAME_RELATIVE_ELEMENT_NAME, true);
632 this.textIndexName = getElementTextContent(caseElement, TEXT_INDEX_ELEMENT, false);
633 break;
634 }
635
636 /*
637 * Fix up the case database name due to a bug that for a time caused
638 * the absolute paths of single-user case databases to be stored.
639 * Derive the missing (absolute/relative) value from the one
640 * present.
641 */
642 Path possibleAbsoluteCaseDbPath = Paths.get(this.caseDatabaseName);
643 Path caseDirectoryPath = Paths.get(getCaseDirectory());
644 if (possibleAbsoluteCaseDbPath.toFile().isAbsolute()) {
645 this.caseDatabasePath = this.caseDatabaseName;
646 this.caseDatabaseName = caseDirectoryPath.relativize(possibleAbsoluteCaseDbPath).toString();
647 }
648
649 } catch (ParserConfigurationException | SAXException | IOException ex) {
650 throw new CaseMetadataException(String.format("Error reading from case metadata file %s", metadataFilePath), ex);
651 }
652 }
653
654 private Element getChildElOrNull(Element parent, String childTag) {
655 NodeList nl = parent.getElementsByTagName(childTag);
656 if (nl != null && nl.getLength() > 0 && nl.item(0) instanceof Element) {
657 return (Element) nl.item(0);
658 } else {
659 return null;
660 }
661 }
662
675 private String getElementTextContent(Element parentElement, String elementName, boolean contentIsRequired) throws CaseMetadataException {
676 NodeList elementsList = parentElement.getElementsByTagName(elementName);
677 if (elementsList.getLength() == 0) {
678 throw new CaseMetadataException(String.format("Missing %s element from case metadata file %s", elementName, metadataFilePath));
679 }
680 String textContent = elementsList.item(0).getTextContent();
681 if (textContent.isEmpty() && contentIsRequired) {
682 throw new CaseMetadataException(String.format("Empty %s element in case metadata file %s", elementName, metadataFilePath));
683 }
684 return textContent;
685 }
686
691 public final static class CaseMetadataException extends Exception {
692
693 private static final long serialVersionUID = 1L;
694
695 private CaseMetadataException(String message) {
696 super(message);
697 }
698
699 private CaseMetadataException(String message, Throwable cause) {
700 super(message, cause);
701 }
702 }
703
712 public String getCaseDatabasePath() throws UnsupportedOperationException {
714 return StringUtils.isBlank(this.caseDatabasePath)
715 ? this.metadataFilePath.getParent().resolve(this.caseDatabaseName).toString()
716 : this.caseDatabasePath;
717 } else {
718 throw new UnsupportedOperationException();
719 }
720 }
721
722}
void createCaseElements(Document doc, Element caseElement, CaseMetadata metadataToWrite)
Element getChildElOrNull(Element parent, String childTag)
String getElementTextContent(Element parentElement, String elementName, boolean contentIsRequired)
void createChildElement(Document doc, Element parentElement, String elementName, String elementContent)
static Path getCaseMetadataFilePath(Path directoryPath)
static CaseType fromString(String typeName)
Definition Case.java:235

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.