diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java index 6d0aca0f6e..299af11ea0 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/Case.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/Case.java @@ -219,7 +219,7 @@ public class Case implements SleuthkitCase.ErrorObserver { * multi-user (using PostgreSql) */ @NbBundle.Messages({"Case_caseType_singleUser=Single-user case", - "Case_caseType_multiUser=Multi-user case"}) + "Case_caseType_multiUser=Multi-user case"}) public enum CaseType { SINGLE_USER_CASE(Bundle.Case_caseType_singleUser()), @@ -257,6 +257,7 @@ public class Case implements SleuthkitCase.ErrorObserver { private String examiner; private String configFilePath; private final XMLCaseManagement xmlcm; + private final CaseMetadata caseMetadata; private final SleuthkitCase db; // Track the current case (only set with changeCase() method) private static Case currentCase = null; @@ -280,12 +281,13 @@ public class Case implements SleuthkitCase.ErrorObserver { /** * Constructor for the Case class */ - private Case(String name, String number, String examiner, String configFilePath, XMLCaseManagement xmlcm, SleuthkitCase db, CaseType type) { + private Case(String name, String number, String examiner, String configFilePath, CaseMetadata caseMetadata, SleuthkitCase db, CaseType type) { this.name = name; this.number = number; this.examiner = examiner; this.configFilePath = configFilePath; - this.xmlcm = xmlcm; + this.xmlcm = new XMLCaseManagement(); + this.caseMetadata = caseMetadata; this.caseType = type; this.db = db; this.services = new Services(db); @@ -403,46 +405,47 @@ public class Case implements SleuthkitCase.ErrorObserver { /** * Creates a single-user new case. * - * @param caseDir The full path of the case directory. It will be created - * if it doesn't already exist; if it exists, it should - * have been created using Case.createCaseDirectory() to - * ensure that the required sub-directories aere created. - * @param caseName The name of case. + * @param caseDir The full path of the case directory. It will be created if + * it doesn't already exist; if it exists, it should have been created using + * Case.createCaseDirectory() to ensure that the required sub-directories + * aere created. + * @param caseName The name of case. * @param caseNumber The case number, can be the empty string. - * @param examiner The examiner to associate with the case, can be the - * empty string. + * @param examiner The examiner to associate with the case, can be the empty + * string. * * @throws CaseActionException if there is a problem creating the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. If so, - * CaseActionException.getCause will return a - * Throwable (null otherwise). + * exception will have a user-friendly message and may be a wrapper for a + * lower-level exception. If so, CaseActionException.getCause will return a + * Throwable (null otherwise). + * @throws CaseMetadataException if there is a problem creating the case + * metadata. */ - public static void create(String caseDir, String caseName, String caseNumber, String examiner) throws CaseActionException { + public static void create(String caseDir, String caseName, String caseNumber, String examiner) throws CaseActionException, CaseMetadataException { create(caseDir, caseName, caseNumber, examiner, CaseType.SINGLE_USER_CASE); } /** * Creates a new case. * - * @param caseDir The full path of the case directory. It will be created - * if it doesn't already exist; if it exists, it should - * have been created using Case.createCaseDirectory() to - * ensure that the required sub-directories aere created. - * @param caseName The name of case. + * @param caseDir The full path of the case directory. It will be created if + * it doesn't already exist; if it exists, it should have been created using + * Case.createCaseDirectory() to ensure that the required sub-directories + * aere created. + * @param caseName The name of case. * @param caseNumber The case number, can be the empty string. - * @param examiner The examiner to associate with the case, can be the - * empty string. - * @param caseType The type of case (single-user or multi-user). The - * exception will have a user-friendly message and may be - * a wrapper for a lower-level exception. If so, - * CaseActionException.getCause will return a Throwable - * (null otherwise). + * @param examiner The examiner to associate with the case, can be the empty + * string. + * @param caseType The type of case (single-user or multi-user). The + * exception will have a user-friendly message and may be a wrapper for a + * lower-level exception. If so, CaseActionException.getCause will return a + * Throwable (null otherwise). * * @throws CaseActionException if there is a problem creating the case. + * @throws CaseMetadataException if there is a problem creating the case + * metadata. */ - public static void create(String caseDir, String caseName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException { + public static void create(String caseDir, String caseName, String caseNumber, String examiner, CaseType caseType) throws CaseActionException, CaseMetadataException { logger.log(Level.INFO, "Creating case with case directory {0}, caseName {1}", new Object[]{caseDir, caseName}); //NON-NLS /* @@ -506,8 +509,9 @@ public class Case implements SleuthkitCase.ErrorObserver { }); throw new CaseActionException(NbBundle.getMessage(Case.class, "Case.databaseConnectionInfo.error.msg"), ex); } + CaseMetadata metadata = new CaseMetadata(caseType, caseName, caseNumber, examiner, caseDir, dbName, indexName); - Case newCase = new Case(caseName, caseNumber, examiner, configFilePath, xmlcm, db, caseType); + Case newCase = new Case(caseName, caseNumber, examiner, configFilePath, metadata, db, caseType); changeCase(newCase); } @@ -578,11 +582,9 @@ public class Case implements SleuthkitCase.ErrorObserver { * @param caseMetadataFilePath The path of the case metadata file. * * @throws CaseActionException if there is a problem opening the case. The - * exception will have a user-friendly message - * and may be a wrapper for a lower-level - * exception. If so, - * CaseActionException.getCause will return a - * Throwable (null otherwise). + * exception will have a user-friendly message and may be a wrapper for a + * lower-level exception. If so, CaseActionException.getCause will return a + * Throwable (null otherwise). */ public static void open(String caseMetadataFilePath) throws CaseActionException { logger.log(Level.INFO, "Opening case with metadata file path {0}", caseMetadataFilePath); //NON-NLS @@ -671,9 +673,7 @@ public class Case implements SleuthkitCase.ErrorObserver { * TODO (AUT-1885): Replace use of obsolete and unsafe * XMLCaseManagement class with use of CaseMetadata class. */ - XMLCaseManagement xmlcm = new XMLCaseManagement(); - xmlcm.open(caseMetadataFilePath); - Case openedCase = new Case(caseName, caseNumber, examiner, caseMetadataFilePath, xmlcm, db, caseType); + Case openedCase = new Case(caseName, caseNumber, examiner, caseMetadataFilePath, metadata, db, caseType); changeCase(openedCase); } catch (CaseMetadataException ex) { @@ -710,7 +710,7 @@ public class Case implements SleuthkitCase.ErrorObserver { * Sends out event and reopens windows if needed. * * @param imgPaths the paths of the image that being added - * @param imgId the ID of the image that being added + * @param imgId the ID of the image that being added * @param timeZone the timeZone of the image where it's added * * @deprecated As of release 4.0, replaced by {@link #notifyAddingDataSource(java.util.UUID) and @@ -750,8 +750,8 @@ public class Case implements SleuthkitCase.ErrorObserver { * This should not be called from the event dispatch thread (EDT) * * @param dataSourceId A unique identifier for the data source. This UUID - * should be used to call notifyNewDataSource() after - * the data source is added. + * should be used to call notifyNewDataSource() after the data source is + * added. */ public void notifyAddingDataSource(UUID dataSourceId) { eventPublisher.publish(new AddingDataSourceEvent(dataSourceId)); @@ -776,10 +776,9 @@ public class Case implements SleuthkitCase.ErrorObserver { * This should not be called from the event dispatch thread (EDT) * * @param newDataSource New data source added. - * @param dataSourceId A unique identifier for the data source. Should be - * the same UUID used to call - * notifyAddingNewDataSource() when the process of - * adding the data source began. + * @param dataSourceId A unique identifier for the data source. Should be + * the same UUID used to call notifyAddingNewDataSource() when the process + * of adding the data source began. */ public void notifyDataSourceAdded(Content newDataSource, UUID dataSourceId) { eventPublisher.publish(new DataSourceAddedEvent(newDataSource, dataSourceId)); @@ -894,9 +893,9 @@ public class Case implements SleuthkitCase.ErrorObserver { * This should not be called from the EDT. * * @param oldCaseName the old case name that wants to be updated - * @param oldPath the old path that wants to be updated + * @param oldPath the old path that wants to be updated * @param newCaseName the new case name - * @param newPath the new path + * @param newPath the new path */ void updateCaseName(String oldCaseName, String oldPath, String newCaseName, String newPath) throws CaseActionException { try { @@ -1317,7 +1316,7 @@ public class Case implements SleuthkitCase.ErrorObserver { * Adds a subscriber to events from this Autopsy node and other Autopsy * nodes. * - * @param eventName The event the subscriber is interested in. + * @param eventName The event the subscriber is interested in. * @param subscriber The subscriber to add. */ public static void addEventSubscriber(String eventName, PropertyChangeListener subscriber) { @@ -1328,7 +1327,7 @@ public class Case implements SleuthkitCase.ErrorObserver { * Adds a subscriber to events from this Autopsy node and other Autopsy * nodes. * - * @param eventName The event the subscriber is no longer interested in. + * @param eventName The event the subscriber is no longer interested in. * @param subscriber The subscriber to add. */ public static void removeEventSubscriber(String eventName, PropertyChangeListener subscriber) { @@ -1438,7 +1437,7 @@ public class Case implements SleuthkitCase.ErrorObserver { /** * to create the case directory * - * @param caseDir Path to the case directory (typically base + case name) + * @param caseDir Path to the case directory (typically base + case name) * @param caseName the case name (used only for error messages) * * @throws CaseActionException throw if could not create the case dir @@ -1452,7 +1451,7 @@ public class Case implements SleuthkitCase.ErrorObserver { /** * Create the case directory and its needed subfolders. * - * @param caseDir Path to the case directory (typically base + case name) + * @param caseDir Path to the case directory (typically base + case name) * @param caseType The type of case, single-user or multi-user * * @throws CaseActionException throw if could not create the case dir @@ -1671,10 +1670,10 @@ public class Case implements SleuthkitCase.ErrorObserver { /** * Adds a report to the case. * - * @param localPath The path of the report file, must be in the case - * directory or one of its subdirectories. + * @param localPath The path of the report file, must be in the case + * directory or one of its subdirectories. * @param srcModuleName The name of the module that created the report. - * @param reportName The report name, may be empty. + * @param reportName The report name, may be empty. * * @throws TskCoreException */ @@ -1698,9 +1697,9 @@ public class Case implements SleuthkitCase.ErrorObserver { * Deletes reports from the case - deletes it from the disk as well as the * database. * - * @param reports Collection of Report to be deleted from the case. + * @param reports Collection of Report to be deleted from the case. * @param deleteFromDisk Set true to perform reports file deletion from - * disk. + * disk. * * @throws TskCoreException */ diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java index 81e53a1fc9..034f7c41de 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/CaseMetadata.java @@ -18,7 +18,34 @@ */ package org.sleuthkit.autopsy.casemodule; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.StringWriter; import java.nio.file.Path; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.Level; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import org.openide.util.NbBundle; +import static org.sleuthkit.autopsy.casemodule.XMLCaseManagement.TOP_ROOT_NAME; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.w3c.dom.Document; +import org.w3c.dom.Element; /** * Provides access to case metadata. @@ -42,6 +69,37 @@ public final class CaseMetadata { } } + final static String XSDFILE = "CaseSchema.xsd"; //NON-NLS + final static String TOP_ROOT_NAME = "AutopsyCase"; //NON-NLS + final static String CASE_ROOT_NAME = "Case"; //NON-NLS + // general metadata about the case file + final static String NAME = "Name"; //NON-NLS + final static String NUMBER = "Number"; //NON-NLS + final static String EXAMINER = "Examiner"; //NON-NLS + final static String CREATED_DATE_NAME = "CreatedDate"; //NON-NLS + final static String MODIFIED_DATE_NAME = "ModifiedDate"; //NON-NLS + final static String SCHEMA_VERSION_NAME = "SchemaVersion"; //NON-NLS + final static String AUTOPSY_CRVERSION_NAME = "AutopsyCreatedVersion"; //NON-NLS + final static String AUTOPSY_MVERSION_NAME = "AutopsySavedVersion"; //NON-NLS + final static String CASE_TEXT_INDEX_NAME = "TextIndexName"; //NON-NLS + // folders inside case directory + final static String LOG_FOLDER_NAME = "LogFolder"; //NON-NLS + final static String LOG_FOLDER_RELPATH = "Log"; //NON-NLS + final static String TEMP_FOLDER_NAME = "TempFolder"; //NON-NLS + final static String TEMP_FOLDER_RELPATH = "Temp"; //NON-NLS + final static String EXPORT_FOLDER_NAME = "ExportFolder"; //NON-NLS + final static String EXPORT_FOLDER_RELPATH = "Export"; //NON-NLS + final static String CACHE_FOLDER_NAME = "CacheFolder"; //NON-NLS + final static String CACHE_FOLDER_RELPATH = "Cache"; //NON-NLS + final static String CASE_TYPE = "CaseType"; //NON-NLS + final static String DATABASE_NAME = "DatabaseName"; //NON-NLS + // folders attribute + final static String RELATIVE_NAME = "Relative"; // relevant path info NON-NLS + // folder attr values + final static String RELATIVE_TRUE = "true"; // if it's a relative path NON-NLS + final static String RELATIVE_FALSE = "false"; // if it's not a relative path NON-NLS + + private Document doc; private final Case.CaseType caseType; private final String caseName; private final String caseNumber; @@ -49,6 +107,18 @@ public final class CaseMetadata { private final String caseDirectory; private final String caseDatabaseName; private final String caseTextIndexName; + private static final Logger logger = Logger.getLogger(CaseMetadata.class.getName()); + + public CaseMetadata(Case.CaseType caseType, String caseName, String caseNumber, String exainer, String caseDirectory, String caseDatabaseName, String caseTextIndexName) throws CaseMetadataException { + this.caseType = caseType; + this.caseName = caseName; + this.caseNumber = caseNumber; + this.examiner = exainer; + this.caseDirectory = caseDirectory; + this.caseDatabaseName = caseDatabaseName; + this.caseTextIndexName = caseTextIndexName; + this.create(); + } /** * Constructs an object that provides access to case metadata. @@ -110,6 +180,7 @@ public final class CaseMetadata { } catch (CaseActionException ex) { throw new CaseMetadataException(ex.getLocalizedMessage(), ex); } + this.create(); } /** @@ -175,4 +246,145 @@ public final class CaseMetadata { return caseTextIndexName; } + private void create() throws CaseMetadataException { + DocumentBuilder docBuilder; + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + + // throw an error here + try { + docBuilder = docFactory.newDocumentBuilder(); + } catch (ParserConfigurationException ex) { + clear(); + throw new CaseMetadataException( + NbBundle.getMessage(this.getClass(), "XMLCaseManagement.create.exception.msg"), ex); + } + DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss (z)"); + + doc = docBuilder.newDocument(); + Element rootElement = doc.createElement(TOP_ROOT_NAME); // ... + doc.appendChild(rootElement); + + Element crDateElement = doc.createElement(CREATED_DATE_NAME); // ... + crDateElement.appendChild(doc.createTextNode(dateFormat.format(new Date()))); + rootElement.appendChild(crDateElement); + + Element mDateElement = doc.createElement(MODIFIED_DATE_NAME); // ... + mDateElement.appendChild(doc.createTextNode(dateFormat.format(new Date()))); + rootElement.appendChild(mDateElement); + + Element autVerElement = doc.createElement(AUTOPSY_CRVERSION_NAME); // ... + autVerElement.appendChild(doc.createTextNode(System.getProperty("netbeans.buildnumber"))); + rootElement.appendChild(autVerElement); + + Element autSavedVerElement = doc.createElement(AUTOPSY_MVERSION_NAME); // ... + autSavedVerElement.appendChild(doc.createTextNode(System.getProperty("netbeans.buildnumber"))); + rootElement.appendChild(autSavedVerElement); + + Element schVerElement = doc.createElement(SCHEMA_VERSION_NAME); // ... + schVerElement.appendChild(doc.createTextNode(schemaVersion)); + rootElement.appendChild(schVerElement); + + Element caseElement = doc.createElement(CASE_ROOT_NAME); // ... + rootElement.appendChild(caseElement); + + Element nameElement = doc.createElement(NAME); // ... + nameElement.appendChild(doc.createTextNode(caseName)); + caseElement.appendChild(nameElement); + + Element numberElement = doc.createElement(NUMBER); // ... + numberElement.appendChild(doc.createTextNode(String.valueOf(caseNumber))); + caseElement.appendChild(numberElement); + + Element examinerElement = doc.createElement(EXAMINER); // ... + examinerElement.appendChild(doc.createTextNode(examiner)); + caseElement.appendChild(examinerElement); + + Element exportElement = doc.createElement(EXPORT_FOLDER_NAME); // ... + exportElement.appendChild(doc.createTextNode(EXPORT_FOLDER_RELPATH)); + exportElement.setAttribute(RELATIVE_NAME, "true"); //NON-NLS + caseElement.appendChild(exportElement); + + Element logElement = doc.createElement(LOG_FOLDER_NAME); // ... + logElement.appendChild(doc.createTextNode(LOG_FOLDER_RELPATH)); + logElement.setAttribute(RELATIVE_NAME, "true"); //NON-NLS + caseElement.appendChild(logElement); + + Element tempElement = doc.createElement(TEMP_FOLDER_NAME); // ... + tempElement.appendChild(doc.createTextNode(TEMP_FOLDER_RELPATH)); + tempElement.setAttribute(RELATIVE_NAME, "true"); //NON-NLS + caseElement.appendChild(tempElement); + + Element cacheElement = doc.createElement(CACHE_FOLDER_NAME); // ... + cacheElement.appendChild(doc.createTextNode(CACHE_FOLDER_RELPATH)); + cacheElement.setAttribute(RELATIVE_NAME, "true"); //NON-NLS + caseElement.appendChild(cacheElement); + + Element typeElement = doc.createElement(CASE_TYPE); // ... + typeElement.appendChild(doc.createTextNode(caseType.toString())); + caseElement.appendChild(typeElement); + + Element dbNameElement = doc.createElement(DATABASE_NAME); // ... + dbNameElement.appendChild(doc.createTextNode(this.caseDatabaseName)); + caseElement.appendChild(dbNameElement); + + Element indexNameElement = doc.createElement(CASE_TEXT_INDEX_NAME); // ... + indexNameElement.appendChild(doc.createTextNode(this.caseTextIndexName)); + caseElement.appendChild(indexNameElement); + this.writeFile(); + } + + private void writeFile() throws CaseMetadataException { + if (doc == null || caseName.equals("")) { + throw new CaseMetadataException( + NbBundle.getMessage(this.getClass(), "XMLCaseManagement.writeFile.exception.noCase.msg")); + } + + // Prepare the DOM document for writing + Source source = new DOMSource(doc); + + // Prepare the data for the output file + StringWriter sw = new StringWriter(); + Result result = new StreamResult(sw); + + // Write the DOM document to the file + Transformer xformer;// = TransformerFactory.newInstance().newTransformer(); + TransformerFactory tfactory = TransformerFactory.newInstance(); + + try { + xformer = tfactory.newTransformer(); + } catch (TransformerConfigurationException ex) { + logger.log(Level.SEVERE, "Could not setup tranformer and write case file"); //NON-NLS + throw new CaseMetadataException( + NbBundle.getMessage(this.getClass(), "XMLCaseManagement.writeFile.exception.errWriteToFile.msg"), ex); + } + + //Setup indenting to "pretty print" + xformer.setOutputProperty(OutputKeys.INDENT, "yes"); //NON-NLS + xformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); //NON-NLS + + try { + xformer.transform(source, result); + } catch (TransformerException ex) { + logger.log(Level.SEVERE, "Could not run tranformer and write case file"); //NON-NLS + throw new CaseMetadataException( + NbBundle.getMessage(this.getClass(), "XMLCaseManagement.writeFile.exception.errWriteToFile.msg"), ex); + } + + // preparing the output file + String xmlString = sw.toString(); + File file = new File(this.caseDirectory + File.separator + caseName + ".aut"); + + // write the file + try { + BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))); + bw.write(xmlString); + bw.flush(); + bw.close(); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Error writing to case file"); //NON-NLS + throw new CaseMetadataException( + NbBundle.getMessage(this.getClass(), "XMLCaseManagement.writeFile.exception.errWriteToFile.msg"), ex); + } + } + }