diff --git a/Experimental/build.xml b/Experimental/build.xml
index a304da0a5d..e5b4012e4c 100644
--- a/Experimental/build.xml
+++ b/Experimental/build.xml
@@ -19,7 +19,7 @@
-
+
diff --git a/Experimental/ivy.xml b/Experimental/ivy.xml
index 64f4b305bf..207cc89e96 100644
--- a/Experimental/ivy.xml
+++ b/Experimental/ivy.xml
@@ -10,5 +10,6 @@
+
diff --git a/Experimental/nbproject/project.properties b/Experimental/nbproject/project.properties
index c5203cbba5..731535afff 100644
--- a/Experimental/nbproject/project.properties
+++ b/Experimental/nbproject/project.properties
@@ -1,5 +1,6 @@
file.reference.c3p0-0.9.5.jar=release/modules/ext/c3p0-0.9.5.jar
file.reference.jackson-core-2.7.0.jar=release/modules/ext/jackson-core-2.7.0.jar
+file.reference.jtidy-r938.jar=release/modules/ext/jtidy-r938.jar
file.reference.LGoodDatePicker-10.3.1.jar=release/modules/ext/LGoodDatePicker-10.3.1.jar
file.reference.mchange-commons-java-0.2.9.jar=release/modules/ext/mchange-commons-java-0.2.9.jar
file.reference.postgresql-9.4-1201-jdbc41.jar=release/modules/ext/postgresql-9.4-1201-jdbc41.jar
diff --git a/Experimental/nbproject/project.xml b/Experimental/nbproject/project.xml
index 953292b3c5..3259799a2a 100644
--- a/Experimental/nbproject/project.xml
+++ b/Experimental/nbproject/project.xml
@@ -170,6 +170,10 @@
org.sleuthkit.autopsy.experimental.autoingest
org.sleuthkit.autopsy.experimental.configuration
+
+ ext/jtidy-r938.jar
+ release/modules/ext/jtidy-r938.jar
+
ext/LGoodDatePicker-10.3.1.jar
release/modules/ext/LGoodDatePicker-10.3.1.jar
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java
index 93e6e5956c..99462f8084 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/AutopsyManifestFileParser.java
@@ -18,6 +18,9 @@
*/
package org.sleuthkit.autopsy.experimental.autoingest;
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -51,56 +54,92 @@ public final class AutopsyManifestFileParser implements ManifestFileParser {
@Override
public boolean fileIsManifest(Path filePath) {
boolean fileIsManifest = false;
- try {
- Path fileName = filePath.getFileName();
- if (fileName.toString().toUpperCase().endsWith(MANIFEST_FILE_NAME_SIGNATURE)) {
- Document doc = this.createManifestDOM(filePath);
- Element docElement = doc.getDocumentElement();
- fileIsManifest = docElement.getTagName().equals(ROOT_ELEM_TAG_NAME);
- }
- } catch (Exception unused) {
- fileIsManifest = false;
+
+ Path fileName = filePath.getFileName();
+ if (fileName.toString().toUpperCase().endsWith(MANIFEST_FILE_NAME_SIGNATURE)) {
+
+ fileIsManifest = (ManifestFileParser.getManifestRootNode(filePath, (str) -> {
+ return (str.compareToIgnoreCase(ROOT_ELEM_TAG_NAME) == 0);
+ }) != null);
}
+
return fileIsManifest;
}
@Override
public Manifest parse(Path filePath) throws ManifestFileParserException {
+ Path tempPath = null;
try {
BasicFileAttributes attrs = Files.readAttributes(filePath, BasicFileAttributes.class);
Date dateFileCreated = new Date(attrs.creationTime().toMillis());
- Document doc = this.createManifestDOM(filePath);
+ Document doc;
+ try {
+ doc = ManifestFileParser.createManifestDOM(filePath);
+ } catch (Exception ex) {
+ // If the above call to createManifestDOM threw an exception
+ // try to fix the given XML file.
+ tempPath = ManifestFileParser.makeTidyManifestFile(filePath);
+ doc = ManifestFileParser.createManifestDOM(tempPath);
+ }
+
XPath xpath = XPathFactory.newInstance().newXPath();
-
+
XPathExpression expr = xpath.compile(CASE_NAME_XPATH);
String caseName = (String) expr.evaluate(doc, XPathConstants.STRING);
if (caseName.isEmpty()) {
throw new ManifestFileParserException("Case name not found, manifest is invalid");
}
-
+
expr = xpath.compile(DEVICE_ID_XPATH);
String deviceId = (String) expr.evaluate(doc, XPathConstants.STRING);
if (deviceId.isEmpty()) {
deviceId = UUID.randomUUID().toString();
}
-
+
expr = xpath.compile(DATA_SOURCE_NAME_XPATH);
String dataSourceName = (String) expr.evaluate(doc, XPathConstants.STRING);
if (dataSourceName.isEmpty()) {
- throw new ManifestFileParserException("Data source path not found, manifest is invalid");
+ throw new ManifestFileParserException("Data source path not found, manifest is invalid");
}
Path dataSourcePath = filePath.getParent().resolve(dataSourceName);
-
+
return new Manifest(filePath, dateFileCreated, caseName, deviceId, dataSourcePath, new HashMap<>());
} catch (Exception ex) {
throw new ManifestFileParserException(String.format("Error parsing manifest %s", filePath), ex);
+ } finally {
+ if (tempPath != null) {
+ tempPath.toFile().delete();
+ }
}
}
- private Document createManifestDOM(Path manifestFilePath) throws ParserConfigurationException, SAXException, IOException {
- DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
- DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
- return docBuilder.parse(manifestFilePath.toFile());
+ /**
+ * Check to see if the given file is an autopsy auto ingest manifest file by
+ * if the root element is ROOT_ELEM_TAG_NAME.
+ *
+ * @param filePath Path to the manifest file.
+ *
+ * @return True if this a well formed autopsy auto ingest manifest file.
+ */
+ private boolean isAutopsyManifestFile(Path filePath) throws IOException {
+ try {
+ Document doc = ManifestFileParser.createManifestDOM(filePath);
+ Element docElement = doc.getDocumentElement();
+ return docElement.getTagName().equals(ROOT_ELEM_TAG_NAME);
+ } catch (Exception unused) {
+ // Double check that this isn't a manifest file that may have bad
+ // characters that will be handled in the process method.
+ try (BufferedReader reader = new BufferedReader(new FileReader(filePath.toFile()))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (line.toLowerCase().contains(ROOT_ELEM_TAG_NAME.toLowerCase())) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
}
}
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestFileParser.java b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestFileParser.java
index 863155afcc..67ff60d8c0 100644
--- a/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestFileParser.java
+++ b/Experimental/src/org/sleuthkit/autopsy/experimental/autoingest/ManifestFileParser.java
@@ -18,31 +18,134 @@
*/
package org.sleuthkit.autopsy.experimental.autoingest;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.function.Predicate;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.tidy.Tidy;
+import org.xml.sax.SAXException;
/**
- * Responsible for parsing the manifest files that
- * describe cases, devices, and data sources.
- * These are used by autoingest to create cases and add
- * data sources to the correct case.
+ * Responsible for parsing the manifest files that describe cases, devices, and
+ * data sources. These are used by autoingest to create cases and add data
+ * sources to the correct case.
*/
public interface ManifestFileParser {
-
+
/**
* Checks if a file is this type of manifest file
+ *
* @param filePath Path to potential manifest file
+ *
* @return True if the file is a manifest that this parser supports
- */
+ */
boolean fileIsManifest(Path filePath);
-
+
/**
- * Parses the given file. Will only be called if
- * fileIsManifest() previously returned true.
+ * Parses the given file. Will only be called if fileIsManifest() previously
+ * returned true.
+ *
* @param filePath Path to manifest file
+ *
* @return Parsed results
- * @throws org.sleuthkit.autopsy.experimental.autoingest.ManifestFileParser.ManifestFileParserException
+ *
+ * @throws
+ * org.sleuthkit.autopsy.experimental.autoingest.ManifestFileParser.ManifestFileParserException
*/
Manifest parse(Path filePath) throws ManifestFileParserException;
+
+ /**
+ * Creates a "tidy" version of the given XML file in same parent directory.
+ *
+ * @param filePath Path to original XML file.
+ *
+ * @return Path to the newly created tidy version of the file.
+ *
+ * @throws IOException
+ */
+ static Path makeTidyManifestFile(Path filePath) throws IOException {
+ File tempFile = null;
+ try{
+ tempFile = File.createTempFile("mani", "tdy", filePath.getParent().toFile());
+
+ try (FileInputStream br = new FileInputStream(filePath.toFile()); FileOutputStream out = new FileOutputStream(tempFile);) {
+ Tidy tidy = new Tidy();
+ tidy.setXmlOut(true);
+ tidy.setXmlTags(true);
+ tidy.parseDOM(br, out);
+ }
+
+ return Paths.get(tempFile.toString());
+ } catch(IOException ex) {
+ // If there is an exception delete the temp file.
+ if(tempFile != null && tempFile.exists()) {
+ tempFile.delete();
+ }
+ throw ex;
+ }
+ }
+
+ /**
+ * Create a new DOM document object for the given manifest file.
+ *
+ * @param manifestFilePath Fully qualified path to manifest file.
+ *
+ * @return DOM document object
+ *
+ * @throws ParserConfigurationException
+ * @throws SAXException
+ * @throws IOException
+ */
+ static Document createManifestDOM(Path manifestFilePath) throws ParserConfigurationException, SAXException, IOException {
+ DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
+ return docBuilder.parse(manifestFilePath.toFile());
+ }
+
+ /**
+ * Return the root node of the given Manifest XML file.
+ *
+ * @param filePath XML filePath
+ * @param isRootTester Predicate method for testing if a string is the root node.
+ *
+ * @return The XML file root node or null if the node was not found or the
+ * file is not an XML file.
+ */
+ static String getManifestRootNode(Path filePath, Predicate isRootTester) {
+ Document doc;
+ Path tempPath = null;
+ try {
+ try {
+ doc = ManifestFileParser.createManifestDOM(filePath);
+ } catch (Exception unused) {
+ // If the above call to createManifestDOM threw an exception
+ // try to fix the given XML file.
+ tempPath = ManifestFileParser.makeTidyManifestFile(filePath);
+ doc = ManifestFileParser.createManifestDOM(tempPath);
+ }
+ Element docElement = doc.getDocumentElement();
+ String rootElementTag = docElement.getTagName();
+ if(isRootTester.test(rootElementTag)) {
+ return rootElementTag;
+ }
+ } catch (Exception unused) {
+ // Unused exception. If an exception is thrown the given XML file
+ // cannot be parsed.
+ } finally {
+ if (tempPath != null) {
+ tempPath.toFile().delete();
+ }
+ }
+ return null;
+ }
public final static class ManifestFileParserException extends Exception {
@@ -67,5 +170,5 @@ public interface ManifestFileParser {
super(message, cause);
}
}
-
+
}