/* * Autopsy Forensic Browser * * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sleuthkit.autopsy.externalresults; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.ErrorInfo; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.XMLUtil; import org.sleuthkit.datamodel.Content; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; /** * Parses an XML representation of of results data (e.g., artifacts, derived * files, reports) generated by a process external to Autopsy. */ public final class ExternalResultsXMLParser implements ExternalResultsParser { private static final Logger logger = Logger.getLogger(ExternalResultsXMLParser.class.getName()); private static final String XSD_FILE = "autopsy_external_results.xsd"; //NON-NLS private final Content dataSource; private final String resultsFilePath; private ExternalResults resultsData; private List<ErrorInfo> errors = new ArrayList<>(); /** * Tag names for an external results XML file. */ public enum TagNames { ROOT_ELEM("autopsy_results"), //NON-NLS DERIVED_FILES_LIST_ELEM("derived_files"), //NON-NLS DERIVED_FILE_ELEM("derived_file"), //NON-NLS LOCAL_PATH_ELEM("local_path"), //NON-NLS PARENT_FILE_ELEM("parent_file"), //NON-NLS ARTIFACTS_LIST_ELEM("artifacts"), //NON-NLS ARTIFACT_ELEM("artifact"), //NON-NLS SOURCE_FILE_ELEM("source_file"), //NON-NLS ATTRIBUTE_ELEM("attribute"), //NON-NLS VALUE_ELEM("value"), //NON-NLS SOURCE_MODULE_ELEM("source_module"), //NON-NLS REPORTS_LIST_ELEM("reports"), //NON-NLS REPORT_ELEM("report"), //NON-NLS REPORT_NAME_ELEM("report_name"); //NON-NLS private final String text; private TagNames(final String text) { this.text = text; } @Override public String toString() { return this.text; } } /** * Attribute names for an external results XML file. */ public enum AttributeNames { TYPE_ATTR("type"); //NON-NLS private final String text; private AttributeNames(final String text) { this.text = text; } @Override public String toString() { return this.text; } } /** * Attribute values for an external results XML file. */ public enum AttributeValues { VALUE_TYPE_TEXT("text"), //NON-NLS VALUE_TYPE_INT32("int32"), //NON-NLS VALUE_TYPE_INT64("int64"), //NON-NLS VALUE_TYPE_DOUBLE("double"), //NON-NLS VALUE_TYPE_DATETIME("datetime"); //NON-NLS private final String text; private AttributeValues(final String text) { this.text = text; } @Override public String toString() { return this.text; } } /** * Constructor. * * @param dataSource The data source for the results. * @param resultsFilePath Full path of the results file to be parsed. */ public ExternalResultsXMLParser(Content dataSource, String resultsFilePath) { this.dataSource = dataSource; this.resultsFilePath = resultsFilePath; } @Override public ExternalResults parse() { this.errors.clear(); this.resultsData = new ExternalResults(dataSource); try { // Note that XMLUtil.loadDoc() logs a warning if the file does not // conform to the XSD, but still returns a Document object. Until // this behavior is improved, validation is still required. If // XMLUtil.loadDoc() does return null, it failed to load the // document and it logged the error. final Document doc = XMLUtil.loadDoc(ExternalResultsXMLParser.class, this.resultsFilePath, XSD_FILE); if (doc != null) { final Element rootElem = doc.getDocumentElement(); if (rootElem != null && rootElem.getNodeName().equals(TagNames.ROOT_ELEM.toString())) { parseDerivedFiles(rootElem); parseArtifacts(rootElem); parseReports(rootElem); } else { String errorMessage = NbBundle.getMessage(this.getClass(), "ExternalResultsXMLParser.parse.errMsg1.text", TagNames.ROOT_ELEM.toString(), this.resultsFilePath); recordError(errorMessage); } } } catch (Exception ex) { String errorMessage = NbBundle.getMessage(this.getClass(), "ExternalResultsXMLParser.parse.errMsg2.text", this.resultsFilePath); recordError(errorMessage, ex); } return this.resultsData; } @Override public List<ErrorInfo> getErrorInfo() { return new ArrayList<>(this.errors); } private void parseDerivedFiles(Element rootElement) { // Get the derived file lists. NodeList derivedFilesListNodes = rootElement.getElementsByTagName(TagNames.DERIVED_FILES_LIST_ELEM.toString()); for (int i = 0; i < derivedFilesListNodes.getLength(); ++i) { Element derivedFilesListElem = (Element) derivedFilesListNodes.item(i); // Get the derived files. NodeList derivedFileNodes = derivedFilesListElem.getElementsByTagName(TagNames.DERIVED_FILE_ELEM.toString()); for (int j = 0; j < derivedFileNodes.getLength(); ++j) { Element derivedFileElem = (Element) derivedFileNodes.item(j); // Get the local path of the derived file. String path = getChildElementContent(derivedFileElem, TagNames.LOCAL_PATH_ELEM.toString(), true); if (path.isEmpty()) { continue; } // Get the parent file of the derived file. String parentFile = getChildElementContent((Element) derivedFileNodes.item(j), TagNames.PARENT_FILE_ELEM.toString(), true); if (parentFile.isEmpty()) { continue; } this.resultsData.addDerivedFile(path, parentFile); } } } private void parseArtifacts(final Element root) { // Get the artifact lists. NodeList artifactsListNodes = root.getElementsByTagName(TagNames.ARTIFACTS_LIST_ELEM.toString()); for (int i = 0; i < artifactsListNodes.getLength(); ++i) { Element artifactsListElem = (Element) artifactsListNodes.item(i); // Get the artifacts. NodeList artifactNodes = artifactsListElem.getElementsByTagName(TagNames.ARTIFACT_ELEM.toString()); for (int j = 0; j < artifactNodes.getLength(); ++j) { Element artifactElem = (Element) artifactNodes.item(j); // Get the artifact type. final String type = getElementAttributeValue(artifactElem, AttributeNames.TYPE_ATTR.toString()); if (!type.isEmpty()) { // Get the source file of the artifact and the attributes, // if any. final String sourceFilePath = this.getChildElementContent(artifactElem, TagNames.SOURCE_FILE_ELEM.toString(), true); if (!sourceFilePath.isEmpty()) { ExternalResults.Artifact artifact = this.resultsData.addArtifact(type, sourceFilePath); parseArtifactAttributes(artifactElem, artifact); } } } } } private void parseArtifactAttributes(final Element artifactElem, ExternalResults.Artifact artifact) { // Get the artifact attributes. NodeList attributeNodesList = artifactElem.getElementsByTagName(TagNames.ATTRIBUTE_ELEM.toString()); for (int i = 0; i < attributeNodesList.getLength(); ++i) { Element attributeElem = (Element) attributeNodesList.item(i); final String type = getElementAttributeValue(attributeElem, AttributeNames.TYPE_ATTR.toString()); if (type.isEmpty()) { continue; } // Get the value of the artifact attribute. Element valueElem = this.getChildElement(attributeElem, TagNames.VALUE_ELEM.toString()); if (valueElem == null) { continue; } final String value = valueElem.getTextContent(); if (value.isEmpty()) { String errorMessage = NbBundle.getMessage(this.getClass(), "ExternalResultsXMLParser.parseArtifactAttributes.errMsg1.text", TagNames.VALUE_ELEM.toString(), this.resultsFilePath); recordError(errorMessage); continue; } // Get the value type. String valueType = parseArtifactAttributeValueType(valueElem); if (valueType.isEmpty()) { continue; } // Get the optional source module. String sourceModule = this.getChildElementContent(attributeElem, TagNames.SOURCE_MODULE_ELEM.toString(), false); // Add the attribute to the artifact. artifact.addAttribute(type, value, valueType, sourceModule); } } private String parseArtifactAttributeValueType(Element valueElem) { String valueType = valueElem.getAttribute(AttributeNames.TYPE_ATTR.toString()); if (valueType.isEmpty()) { // Default to text. valueType = AttributeValues.VALUE_TYPE_TEXT.toString(); } else if (!valueType.equals(AttributeValues.VALUE_TYPE_TEXT.toString()) && !valueType.equals(AttributeValues.VALUE_TYPE_DOUBLE.toString()) && !valueType.equals(AttributeValues.VALUE_TYPE_INT32.toString()) && !valueType.equals(AttributeValues.VALUE_TYPE_INT64.toString()) && !valueType.equals(AttributeValues.VALUE_TYPE_DATETIME.toString())) { String errorMessage = NbBundle.getMessage(this.getClass(), "ExternalResultsXMLParser.parseAttributeValueType.errMsg1.text", valueType, AttributeNames.TYPE_ATTR.toString(), TagNames.VALUE_ELEM.toString()); this.recordError(errorMessage); valueType = ""; } return valueType; } private void parseReports(Element root) { // Get the report lists. NodeList reportsListNodes = root.getElementsByTagName(TagNames.REPORTS_LIST_ELEM.toString()); for (int i = 0; i < reportsListNodes.getLength(); ++i) { Element reportsListElem = (Element) reportsListNodes.item(i); // Get the reports. NodeList reportNodes = reportsListElem.getElementsByTagName(TagNames.REPORT_ELEM.toString()); for (int j = 0; j < reportNodes.getLength(); ++j) { Element reportElem = (Element) reportNodes.item(j); // Get the local path. String path = getChildElementContent(reportElem, TagNames.LOCAL_PATH_ELEM.toString(), true); if (path.isEmpty()) { continue; } // Get the source module. String sourceModule = getChildElementContent(reportElem, TagNames.SOURCE_MODULE_ELEM.toString(), true); if (path.isEmpty()) { continue; } // Get the optional report name. String reportName = getChildElementContent(reportElem, TagNames.REPORT_NAME_ELEM.toString(), false); this.resultsData.addReport(path, sourceModule, reportName); } } } private String getElementAttributeValue(Element element, String attributeName) { final String attributeValue = element.getAttribute(attributeName); if (attributeValue.isEmpty()) { logger.log(Level.SEVERE, "Found {0} element missing {1} attribute in {2}", new Object[]{ //NON-NLS element.getTagName(), attributeName, this.resultsFilePath}); } return attributeValue; } private String getChildElementContent(Element parentElement, String childElementTagName, boolean required) { String content = ""; Element childElement = this.getChildElement(parentElement, childElementTagName); if (childElement != null) { content = childElement.getTextContent(); if (content.isEmpty()) { String errorMessage = NbBundle.getMessage(this.getClass(), "ExternalResultsXMLParser.getChildElementContent.errMsg1.text", parentElement.getTagName(), childElementTagName, this.resultsFilePath); this.recordError(errorMessage); } } else if (required) { String errorMessage = NbBundle.getMessage(this.getClass(), "ExternalResultsXMLParser.getChildElementContent.errMsg2.text", parentElement.getTagName(), childElementTagName, this.resultsFilePath); this.recordError(errorMessage); } return content; } private Element getChildElement(Element parentElement, String childElementTagName) { Element childElem = null; NodeList childNodes = parentElement.getElementsByTagName(childElementTagName); if (childNodes.getLength() > 0) { childElem = (Element) childNodes.item(0); if (childNodes.getLength() > 1) { String errorMessage = NbBundle.getMessage(this.getClass(), "ExternalResultsXMLParser.getChildElement.errMsg1.text", childElementTagName, parentElement.getTagName(), this.resultsFilePath); this.recordError(errorMessage); } } return childElem; } private void recordError(String errorMessage) { ExternalResultsXMLParser.logger.log(Level.SEVERE, errorMessage); this.errors.add(new ErrorInfo(this.getClass().getSimpleName(), errorMessage)); } private void recordError(String errorMessage, Exception ex) { ExternalResultsXMLParser.logger.log(Level.SEVERE, errorMessage, ex); this.errors.add(new ErrorInfo(this.getClass().getSimpleName(), errorMessage, ex)); } }