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); } } - + }