diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java index a5c01c9d42..da0423735b 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/PlatformUtil.java @@ -30,6 +30,8 @@ import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.lang.management.MemoryUsage; import java.nio.charset.Charset; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -211,37 +213,31 @@ public class PlatformUtil { * * @param resourceClass class in the same package as the resourceFile to * extract - * @param resourceFile resource file name to extract + * @param resourceFileName Name of the resource file to extract * @param overWrite true to overwrite an existing resource * @return true if extracted, false otherwise (if file already exists) * @throws IOException exception thrown if extract the file failed for IO * reasons */ - public static boolean extractResourceToUserConfigDir(final Class resourceClass, final String resourceFile, boolean overWrite) throws IOException { - final File userDir = new File(getUserConfigDirectory()); - - final File resourceFileF = new File(userDir + File.separator + resourceFile); - if (resourceFileF.exists() && !overWrite) { + public static boolean extractResourceToUserConfigDir(final Class resourceClass, final String resourceFileName, boolean overWrite) throws IOException { + Path resourceFilePath = Paths.get(getUserConfigDirectory(), resourceFileName); + final File resourceFile = resourceFilePath.toFile(); + if (resourceFile.exists() && !overWrite) { return false; } - resourceFileF.getParentFile().mkdirs(); + InputStream inputStream = resourceClass.getResourceAsStream(resourceFileName); + if (null == inputStream) { + return false; + } - InputStream inputStream = resourceClass.getResourceAsStream(resourceFile); - - OutputStream out = null; + resourceFile.getParentFile().mkdirs(); try (InputStream in = new BufferedInputStream(inputStream)) { - - OutputStream outFile = new FileOutputStream(resourceFileF); - out = new BufferedOutputStream(outFile); - int readBytes; - while ((readBytes = in.read()) != -1) { - out.write(readBytes); - } - } finally { - if (out != null) { - out.flush(); - out.close(); + try (OutputStream out = new BufferedOutputStream(new FileOutputStream(resourceFile))) { + int readBytes; + while ((readBytes = in.read()) != -1) { + out.write(readBytes); + } } } return true; diff --git a/Core/src/org/sleuthkit/autopsy/coreutils/XMLUtil.java b/Core/src/org/sleuthkit/autopsy/coreutils/XMLUtil.java index a997f01dd5..cc055638d7 100644 --- a/Core/src/org/sleuthkit/autopsy/coreutils/XMLUtil.java +++ b/Core/src/org/sleuthkit/autopsy/coreutils/XMLUtil.java @@ -25,6 +25,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; +import java.nio.file.Paths; import java.util.logging.Level; import javax.xml.XMLConstants; import javax.xml.parsers.DocumentBuilder; @@ -54,6 +55,101 @@ import org.xml.sax.SAXException; */ public class XMLUtil { + /** + * Creates a W3C DOM. + * + * @return The document object. + * @throws ParserConfigurationException + */ + public static Document createDocument() throws ParserConfigurationException { + DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = builderFactory.newDocumentBuilder(); + return builder.newDocument(); + } + + /** + * Loads an XML document into a WC3 DOM and validates it using a schema + * packaged as a class resource. + * + * @param The name of the class associated with the resource. + * @param docPath The full path to the XML document. + * @param clazz The class associated with the schema resource. + * @param schemaResourceName The name of the schema resource. + * @return The WC3 DOM document object. + * @throws IOException + * @throws ParserConfigurationException + * @throws SAXException + */ + public static Document loadDocument(String docPath, Class clazz, String schemaResourceName) throws IOException, ParserConfigurationException, SAXException { + Document doc = loadDocument(docPath); + validateDocument(doc, clazz, schemaResourceName); + return doc; + } + + /** + * Loads an XML document into a WC3 DOM. + * + * @param docPath The full path to the XML document. + * @return The WC3 DOM document object. + * @throws ParserConfigurationException + * @throws SAXException + * @throws IOException + */ + public static Document loadDocument(String docPath) throws ParserConfigurationException, SAXException, IOException { + DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = builderFactory.newDocumentBuilder(); + Document doc = builder.parse(new FileInputStream(docPath)); + return doc; + } + + /** + * Validates a WC3 DOM using a schema packaged as a class resource. + * + * @param doc + * @param clazz + * @param schemaResourceName + * @throws SAXException + * @throws IOException + */ + public static void validateDocument(final Document doc, Class clazz, String schemaResourceName) throws SAXException, IOException { + PlatformUtil.extractResourceToUserConfigDir(clazz, schemaResourceName, false); + File schemaFile = new File(Paths.get(PlatformUtil.getUserConfigDirectory(), schemaResourceName).toAbsolutePath().toString()); + SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + Schema schema = schemaFactory.newSchema(schemaFile); + Validator validator = schema.newValidator(); + validator.validate(new DOMSource(doc), new DOMResult()); + } + + /** + * Saves a WC3 DOM by writing it to an XML document. + * + * @param doc The WC3 DOM document object. + * @param docPath The full path to the XML document. + * @param encoding Encoding scheme to use for the XML document, e.g., + * "UTF-8." + * @throws TransformerConfigurationException + * @throws FileNotFoundException + * @throws UnsupportedEncodingException + * @throws TransformerException + * @throws IOException + */ + public static void saveDocument(final Document doc, String encoding, String docPath) throws TransformerConfigurationException, FileNotFoundException, UnsupportedEncodingException, TransformerException, IOException { + TransformerFactory xf = TransformerFactory.newInstance(); + xf.setAttribute("indent-number", 1); //NON-NLS + Transformer xformer = xf.newTransformer(); + xformer.setOutputProperty(OutputKeys.METHOD, "xml"); //NON-NLS + xformer.setOutputProperty(OutputKeys.INDENT, "yes"); //NON-NLS + xformer.setOutputProperty(OutputKeys.ENCODING, encoding); + xformer.setOutputProperty(OutputKeys.STANDALONE, "yes"); //NON-NLS + xformer.setOutputProperty(OutputKeys.VERSION, "1.0"); + File file = new File(docPath); + try (FileOutputStream stream = new FileOutputStream(file)) { + Result out = new StreamResult(new OutputStreamWriter(stream, encoding)); + xformer.transform(new DOMSource(doc), out); + stream.flush(); + } + } + /** * Utility to validate XML files against pre-defined schema files. * @@ -71,6 +167,7 @@ public class XMLUtil { * IngestModuleLoader. * */ + // TODO: Deprecate. public static boolean xmlIsValid(DOMSource xmlfile, Class clazz, String schemaFile) { try { PlatformUtil.extractResourceToUserConfigDir(clazz, schemaFile, false); @@ -108,6 +205,7 @@ public class XMLUtil { * IngestModuleLoader. * */ + // TODO: Deprecate. public static boolean xmlIsValid(Document doc, Class clazz, String type) { DOMSource dms = new DOMSource(doc); return xmlIsValid(dms, clazz, type); @@ -119,6 +217,7 @@ public class XMLUtil { * @param clazz the class this method is invoked from * @param xmlPath the full path to the file to load */ + // TODO: Deprecate. public static Document loadDoc(Class clazz, String xmlPath) { DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); Document ret = null; @@ -143,6 +242,7 @@ public class XMLUtil { * @param xmlPath the full path to the file to load * @param xsdPath the full path to the file to validate against */ + // TODO: Deprecate public static Document loadDoc(Class clazz, String xmlPath, String xsdPath) { Document ret = loadDoc(clazz, xmlPath); if (!XMLUtil.xmlIsValid(ret, clazz, xsdPath)) { @@ -159,9 +259,10 @@ public class XMLUtil { * @param encoding to encoding, such as "UTF-8", to encode the file with * @param doc the document to save */ + // TODO: Deprecate. public static boolean saveDoc(Class clazz, String xmlPath, String encoding, final Document doc) { TransformerFactory xf = TransformerFactory.newInstance(); - xf.setAttribute("indent-number", new Integer(1)); //NON-NLS + xf.setAttribute("indent-number", 1); //NON-NLS boolean success = false; try { Transformer xformer = xf.newTransformer(); @@ -191,4 +292,5 @@ public class XMLUtil { } return success; } + } diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle.properties b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle.properties index 1369d3d332..ef33f85fc6 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle.properties @@ -1,5 +1,4 @@ OpenIDE-Module-Name=FileTypeId -FileTypeIdModuleSettingsPanel.skipKnownCheckBox.toolTipText=Depending on how many files have known hashes, checking this box will improve the speed of file type identification. FileTypeIdIngestModule.moduleName.text=File Type Identification FileTypeIdIngestModule.moduleDesc.text=Matches file types based on binary signatures. FileTypeIdIngestModule.complete.totalProcTime=Total Processing Time @@ -7,4 +6,38 @@ FileTypeIdIngestModule.complete.totalFiles=Total Files Processed FileTypeIdIngestModule.complete.srvMsg.text=File Type Id Results FileTypeIdModuleFactory.getIngestJobSettingsPanel.exception.msg=Expected settings argument to be instanceof FileTypeIdModuleSettings FileTypeIdModuleFactory.createFileIngestModule.exception.msg=Expected settings argument to be instanceof FileTypeIdModuleSettings -FileTypeIdModuleSettingsPanel.skipKnownCheckBox.text=Skip known files (NSRL) +FileTypeIdIngestJobSettingsPanel.skipKnownCheckBox.toolTipText=Depending on how many files have known hashes, checking this box will improve the speed of file type identification. +FileTypeIdIngestJobSettingsPanel.skipKnownCheckBox.text=Skip known files (NSRL) +FileTypeIdGlobalSettingsPanel.hexPrefixLabel.text=0x +FileTypeIdGlobalSettingsPanel.deleteTypeButton.text=DeleteType +FileTypeIdGlobalSettingsPanel.offsetTextField.text= +FileTypeIdGlobalSettingsPanel.offsetLabel.text=Offset +FileTypeIdGlobalSettingsPanel.postHitCheckBox.text=Post interesting file hit when found +FileTypeIdGlobalSettingsPanel.signatureTextField.text= +FileTypeIdGlobalSettingsPanel.signatureTypeLabel.text=Signature Type +FileTypeIdGlobalSettingsPanel.mimeTypeTextField.text= +FileTypeIdGlobalSettingsPanel.signatureLabel.text=Signature +FileTypeIdGlobalSettingsPanel.mimeTypeLabel.text=MIME Type +FileTypeIdGlobalSettingsPanel.saveTypeButton.text=Save Type +FileTypeIdGlobalSettingsPanel.signatureComboBox.rawItem=Bytes (Hex) +FileTypeIdGlobalSettingsPanel.signatureComboBox.asciiItem=String (ASCII) +FileTypeIdGlobalSettingsPanel.JOptionPane.invalidMIMEType.message=MIME type is required. +FileTypeIdGlobalSettingsPanel.JOptionPane.invalidMIMEType.title=Missing MIME Type +FileTypeIdGlobalSettingsPanel.JOptionPane.invalidSignature.message=Signature is required. +FileTypeIdGlobalSettingsPanel.JOptionPane.invalidSignature.title=Missing Signature +FileTypeIdGlobalSettingsPanel.JOptionPane.invalidOffset.message=Offset must be a positive integer. +FileTypeIdGlobalSettingsPanel.JOptionPane.invalidOffset.title=Invalid Offset +FileTypeIdGlobalSettingsPanel.JOptionPane.invalidRawSignatureBytes.message=The signature has one or more invalid hexadecimal digits. +FileTypeIdGlobalSettingsPanel.JOptionPane.invalidSignatureBytes.title=Invalid Signature +FileTypeIdGlobalSettingsPanel.JOptionPane.invalidInterestingFilesSetName.message=Interesting files set name is required if alert is requests. +FileTypeIdGlobalSettingsPanel.JOptionPane.invalidInterestingFilesSetName.title=Missing Interesting Files Set Name +FileTypeIdGlobalSettingsPanel.filesSetNameLabel.text=Files Set Name +FileTypeIdGlobalSettingsPanel.filesSetNameTextField.text= +FileTypeIdGlobalSettingsPanel.JOptionPane.storeFailed.title=Save Failed +FileTypeIdGlobalSettingsPanel.JOptionPane.loadFailed.title=Load Failed +FileTypeIdGlobalSettingsPanel.hintTextArea.text=Enter a MIME type and signature to be used to identify files of that type. If the signature is a byte sequence, enter the sequence using two hex values for each byte, e.g., EEF0 is a two byte signature. +FileTypeIdGlobalSettingsPanel.ingestRunningWarningLabel.text=Cannot make changes to file type definitions when ingest is running! +UserDefinedFileTypesManager.loadFileTypes.errorMessage=Failed to load existing file type definitions. +UserDefinedFileTypesManager.saveFileTypes.errorMessage=Failed to save file type definitions. +FileTypeIdIngestJobSettingsPanel.skipSmallFilesCheckBox.text=Skip files smaller than {0} bytes +FileTypeIdGlobalSettingsPanel.newTypeButton.text=New Type diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle_ja.properties b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle_ja.properties index a12d0e7cd8..ee53301d77 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle_ja.properties +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/Bundle_ja.properties @@ -1,10 +1,10 @@ -OpenIDE-Module-Name=\u30D5\u30A1\u30A4\u30EB\u30BF\u30A4\u30D7\u306E\u7279\u5B9A -FileTypeIdModuleSettingsPanel.skipKnownCheckBox.toolTipText=\u65E2\u77E5\u306E\u30CF\u30C3\u30B7\u30E5\u5024\u3092\u6301\u3064\u30D5\u30A1\u30A4\u30EB\u6570\u306B\u3088\u3063\u3066\u306F\u3001\u3053\u306E\u30DC\u30C3\u30AF\u30B9\u3092\u9078\u629E\u3059\u308B\u306E\u306B\u3088\u308A\u3001\u30D5\u30A1\u30A4\u30EB\u30BF\u30A4\u30D7\u306E\u7279\u5B9A\u3092\u52A0\u901F\u3057\u307E\u3059\u3002 -FileTypeIdIngestModule.moduleName.text=\u30D5\u30A1\u30A4\u30EB\u30BF\u30A4\u30D7\u306E\u7279\u5B9A -FileTypeIdIngestModule.moduleDesc.text=\u30D0\u30A4\u30CA\u30EA\u7F72\u540D\u306B\u57FA\u3065\u3044\u3066\u30D5\u30A1\u30A4\u30EB\u30BF\u30A4\u30D7\u3092\u4E00\u81F4\u3059\u308B\u3002 -FileTypeIdIngestModule.complete.totalProcTime=\u5408\u8A08\u51E6\u7406\u6642\u9593 -FileTypeIdIngestModule.complete.totalFiles=\u5408\u8A08\u51E6\u7406\u30D5\u30A1\u30A4\u30EB\u6570 -FileTypeIdIngestModule.complete.srvMsg.text=\u30D5\u30A1\u30A4\u30EB\u30BF\u30A4\u30D7\u7279\u5B9A\u306E\u7D50\u679C -FileTypeIdModuleSettingsPanel.skipKnownCheckBox.text=\u65E2\u77E5\u30D5\u30A1\u30A4\u30EB\uFF08NSRL\uFF09\u3092\u30B9\u30AD\u30C3\u30D7 -FileTypeIdModuleFactory.getIngestJobSettingsPanel.exception.msg=\u8A2D\u5B9A\u3092\u884C\u3046\u70BA\u306E\u60F3\u5B9A\u3055\u308C\u308B\u5F15\u6570\u306Finstanceof FileTypeIdModuleSettings\u3067\u3059\u3002 -FileTypeIdModuleFactory.createFileIngestModule.exception.msg=\u8A2D\u5B9A\u3092\u884C\u3046\u70BA\u306E\u60F3\u5B9A\u3055\u308C\u308B\u5F15\u6570\u306Finstanceof FileTypeIdModuleSettings\u3067\u3059\u3002 \ No newline at end of file +OpenIDE-Module-Name=\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u306e\u7279\u5b9a +FileTypeIdIngestModule.moduleName.text=\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u306e\u7279\u5b9a +FileTypeIdIngestModule.moduleDesc.text=\u30d0\u30a4\u30ca\u30ea\u7f72\u540d\u306b\u57fa\u3065\u3044\u3066\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u3092\u4e00\u81f4\u3059\u308b\u3002 +FileTypeIdIngestModule.complete.totalProcTime=\u5408\u8a08\u51e6\u7406\u6642\u9593 +FileTypeIdIngestModule.complete.totalFiles=\u5408\u8a08\u51e6\u7406\u30d5\u30a1\u30a4\u30eb\u6570 +FileTypeIdIngestModule.complete.srvMsg.text=\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u7279\u5b9a\u306e\u7d50\u679c +FileTypeIdModuleFactory.getIngestJobSettingsPanel.exception.msg=\u8a2d\u5b9a\u3092\u884c\u3046\u70ba\u306e\u60f3\u5b9a\u3055\u308c\u308b\u5f15\u6570\u306finstanceof FileTypeIdModuleSettings\u3067\u3059\u3002 +FileTypeIdModuleFactory.createFileIngestModule.exception.msg=\u8a2d\u5b9a\u3092\u884c\u3046\u70ba\u306e\u60f3\u5b9a\u3055\u308c\u308b\u5f15\u6570\u306finstanceof FileTypeIdModuleSettings\u3067\u3059\u3002 +FileTypeIdIngestJobSettingsPanel.skipKnownCheckBox.toolTipText=\u65e2\u77e5\u306e\u30cf\u30c3\u30b7\u30e5\u5024\u3092\u6301\u3064\u30d5\u30a1\u30a4\u30eb\u6570\u306b\u3088\u3063\u3066\u306f\u3001\u3053\u306e\u30dc\u30c3\u30af\u30b9\u3092\u9078\u629e\u3059\u308b\u306e\u306b\u3088\u308a\u3001\u30d5\u30a1\u30a4\u30eb\u30bf\u30a4\u30d7\u306e\u7279\u5b9a\u3092\u52a0\u901f\u3057\u307e\u3059\u3002 +FileTypeIdIngestJobSettingsPanel.skipKnownCheckBox.text=\u65e2\u77e5\u30d5\u30a1\u30a4\u30eb\uff08NSRL\uff09\u3092\u30b9\u30ad\u30c3\u30d7 diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileType.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileType.java new file mode 100644 index 0000000000..e3903ffc30 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileType.java @@ -0,0 +1,193 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 Basis Technology Corp. + * Contact: carrier sleuthkit 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.modules.filetypeid; + +import java.util.Arrays; +import java.util.logging.Level; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * Represents a file type characterized by a file signature. + *

+ * Thread-safe (immutable). + */ +class FileType { + + private final String mimeType; + private final Signature signature; + private final String interestingFilesSetName; + private final boolean alert; + + /** + * Creates a representation of a file type characterized by a file + * signature. + * + * @param mimeType The mime type to associate with this file type. + * @param signature The signature that characterizes this file type. + * @param filesSetName The name of an interesting files set that includes + * files of this type, may be the empty string. + * @param alert Whether the user wishes to be alerted when a file matching + * this type is encountered. + */ + FileType(String mimeType, final Signature signature, String filesSetName, boolean alert) { + this.mimeType = mimeType; + this.signature = new Signature(signature.getSignatureBytes(), signature.getOffset(), signature.getType()); + this.interestingFilesSetName = filesSetName; + this.alert = alert; + } + + /** + * Gets the MIME type associated with this file type. + * + * @return The MIME type. + */ + String getMimeType() { + return mimeType; + } + + /** + * Gets the signature associated with this file type. + * + * @return The signature. + */ + Signature getSignature() { + return new Signature(signature.getSignatureBytes(), signature.getOffset(), signature.getType()); + } + + /** + * Determines whether or not a file is an instance of this file type. + * + * @param file The file to test. + * @return True or false. + */ + boolean matches(final AbstractFile file) { + return signature.containedIn(file); + } + + /** + * Indicates whether or not an alert is desired if a file of this type is + * encountered. + * + * @return True or false. + */ + boolean alertOnMatch() { + return alert; + } + + /** + * Gets the name of an interesting files set that includes files of this + * type. + * + * @return The interesting files set name, possibly empty. + */ + String getFilesSetName() { + return interestingFilesSetName; + } + + /** + * A file signature consisting of a sequence of bytes at a specific offset + * within a file. + *

+ * Thread-safe (immutable). + */ + static class Signature { + + private static final Logger logger = Logger.getLogger(Signature.class.getName()); + + /** + * The way the signature byte sequence should be interpreted. + */ + enum Type { + + RAW, ASCII + }; + + private final byte[] signatureBytes; + private final long offset; + private final Type type; + + /** + * Creates a file signature consisting of a sequence of bytes at a + * specific offset within a file. + * + * @param signatureBytes The signature bytes. + * @param offset The offset of the signature bytes. + * @param type The interpretation of the signature bytes (e.g., raw + * bytes, an ASCII string). + */ + Signature(final byte[] signatureBytes, long offset, Type type) { + this.signatureBytes = Arrays.copyOf(signatureBytes, signatureBytes.length); + this.offset = offset; + this.type = type; + } + + /** + * Gets the byte sequence of the signature. + * + * @return The byte sequence as an array of bytes. + */ + byte[] getSignatureBytes() { + return Arrays.copyOf(signatureBytes, signatureBytes.length); + } + + /** + * Gets the offset of the signature. + * + * @return The offset. + */ + long getOffset() { + return offset; + } + + /** + * Gets the interpretation of the byte sequence for the signature. + * + * @return The signature type. + */ + Type getType() { + return type; + } + + /** + * Determines whether or not the signature is contained within a given + * file. + * + * @param file The file to test + * @return True or false. + */ + boolean containedIn(final AbstractFile file) { + try { + byte[] buffer = new byte[signatureBytes.length]; + int bytesRead = file.read(buffer, offset, signatureBytes.length); + return ((bytesRead == signatureBytes.length) && (Arrays.equals(buffer, signatureBytes))); + } catch (TskCoreException ex) { + /** + * This exception is caught rather than propagated because files + * in images are not always consistent with their file system + * meta data making for read errors. + */ + Signature.logger.log(Level.WARNING, "Error reading from file with objId = " + file.getId(), ex); + return false; + } + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.form new file mode 100644 index 0000000000..c168205dad --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.form @@ -0,0 +1,346 @@ + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java new file mode 100644 index 0000000000..d0847fc63f --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdGlobalSettingsPanel.java @@ -0,0 +1,672 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 Basis Technology Corp. + * Contact: carrier sleuthkit 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.modules.filetypeid; + +import java.awt.EventQueue; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Map; +import javax.swing.DefaultComboBoxModel; +import javax.swing.DefaultListModel; +import javax.swing.JOptionPane; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.xml.bind.DatatypeConverter; +import org.openide.util.Exceptions; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.corecomponents.OptionsPanel; +import org.sleuthkit.autopsy.ingest.IngestManager; +import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel; +import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature; +import org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException; + +/** + * A panel to allow a user to make custom file type definitions. In addition to + * being an ingest module global settings panel, an instance of this class also + * appears in the NetBeans options dialog as an options panel. + */ +final class FileTypeIdGlobalSettingsPanel extends IngestModuleGlobalSettingsPanel implements OptionsPanel { + + private static final String RAW_SIGNATURE_TYPE_COMBO_BOX_ITEM = NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.signatureComboBox.rawItem"); + private static final String ASCII_SIGNATURE_TYPE_COMBO_BOX_ITEM = NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.signatureComboBox.asciiItem"); + + /** + * The list model for the file types list component of this panel is the set + * of MIME types associated with the user-defined file types. A mapping of + * the MIME types to file type objects lies behind the list model. This map + * is obtained from the user-defined types manager. + */ + private DefaultListModel typesListModel; + private Map fileTypes; + + /** + * This panel implements a property change listener that listens to ingest + * job events so it can disable the buttons on the panel if ingest is + * running. This is done to prevent changes to user-defined types while the + * type definitions are in use. + */ + // TODO: Disabling during ingest would not be necessary if the file ingest + // modules obtained and shared a per data source ingest job snapshot of the + // file type definitions. + IngestJobEventPropertyChangeListener ingestJobEventsListener; + + /** + * Creates a panel to allow a user to make custom file type definitions. + */ + FileTypeIdGlobalSettingsPanel() { + initComponents(); + customizeComponents(); + addIngestJobEventsListener(); + } + + /** + * Does child component initialization in addition to that done by the + * Matisse generated code. + */ + private void customizeComponents() { + setFileTypesListModel(); + setSignatureTypeComboBoxModel(); + clearTypeDetailsComponents(); + addTypeListSelectionListener(); + addTextFieldListeners(); + } + + /** + * Sets the list model for the list of file types. + */ + private void setFileTypesListModel() { + typesListModel = new DefaultListModel<>(); + typesList.setModel(typesListModel); + } + + /** + * Sets the model for the signature type combo box. + */ + private void setSignatureTypeComboBoxModel() { + DefaultComboBoxModel sigTypeComboBoxModel = new DefaultComboBoxModel<>(); + sigTypeComboBoxModel.addElement(FileTypeIdGlobalSettingsPanel.RAW_SIGNATURE_TYPE_COMBO_BOX_ITEM); + sigTypeComboBoxModel.addElement(FileTypeIdGlobalSettingsPanel.ASCII_SIGNATURE_TYPE_COMBO_BOX_ITEM); + signatureTypeComboBox.setModel(sigTypeComboBoxModel); + signatureTypeComboBox.setSelectedItem(FileTypeIdGlobalSettingsPanel.RAW_SIGNATURE_TYPE_COMBO_BOX_ITEM); + } + + /** + * Adds a listener to the types list component so that the components in the + * file type details section of the panel can be populated and cleared based + * on the selection in the list. + */ + private void addTypeListSelectionListener() { + typesList.addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + if (e.getValueIsAdjusting() == false) { + if (typesList.getSelectedIndex() == -1) { + clearTypeDetailsComponents(); + } else { + populateTypeDetailsComponents(); + } + } + } + }); + } + + /** + * Adds listeners to the text fields that enable and disable the buttons on + * the panel. + */ + private void addTextFieldListeners() { + DocumentListener listener = new DocumentListener() { + @Override + public void changedUpdate(DocumentEvent e) { + enableButtons(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + enableButtons(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + enableButtons(); + } + }; + + mimeTypeTextField.getDocument().addDocumentListener(listener); + offsetTextField.getDocument().addDocumentListener(listener); + signatureTextField.getDocument().addDocumentListener(listener); + filesSetNameTextField.getDocument().addDocumentListener(listener); + } + + /** + * Add a property change listener that listens to ingest job events to + * disable the buttons on the panel if ingest is running. This is done to + * prevent changes to user-defined types while the type definitions are in + * use. + */ + // TODO: Disabling during ingest would not be necessary if the file ingest + // modules obtained and shared a per data source ingest job snapshot of the + // file type definitions. + private void addIngestJobEventsListener() { + ingestJobEventsListener = new IngestJobEventPropertyChangeListener(); + IngestManager.getInstance().addIngestJobEventListener(ingestJobEventsListener); + } + + /** + * A property change listener that listens to ingest job events. + */ + private class IngestJobEventPropertyChangeListener implements PropertyChangeListener { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + enableButtons(); + } + }); + } + } + + /** + * Enables or disables the panel buttons based on the state of the panel and + * the application. + */ + private void enableButtons() { + boolean ingestIsRunning = IngestManager.getInstance().isIngestRunning(); + newTypeButton.setEnabled(!ingestIsRunning); + + boolean fileTypeIsSelected = typesList.getSelectedIndex() != -1; + deleteTypeButton.setEnabled(!ingestIsRunning && fileTypeIsSelected); + + boolean requiredFieldsPopulated + = !mimeTypeTextField.getText().isEmpty() + && !offsetTextField.getText().isEmpty() + && !signatureTextField.getText().isEmpty() + && (postHitCheckBox.isSelected() ? !filesSetNameTextField.getText().isEmpty() : true); + saveTypeButton.setEnabled(!ingestIsRunning && requiredFieldsPopulated); + + ingestRunningWarningLabel.setVisible(ingestIsRunning); + } + + /** + * @inheritDoc + */ + @Override + public void load() { + try { + fileTypes = UserDefinedFileTypesManager.getInstance().getUserDefinedFileTypes(); + updateFileTypesListModel(); + if (!typesListModel.isEmpty()) { + typesList.setSelectedIndex(0); + } + } catch (UserDefinedFileTypesException ex) { + JOptionPane.showMessageDialog(null, + ex.getLocalizedMessage(), + NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.JOptionPane.loadFailed.title"), + JOptionPane.ERROR_MESSAGE); + fileTypes = Collections.emptyMap(); + } + enableButtons(); + } + + /** + * Sets the list model for the file types list component. + */ + private void updateFileTypesListModel() { + ArrayList mimeTypes = new ArrayList<>(fileTypes.keySet()); + Collections.sort(mimeTypes); + typesListModel.clear(); + for (String mimeType : mimeTypes) { + typesListModel.addElement(mimeType); + } + } + + /** + * Populates all of the components in the file type details portion of the + * panel based on the current selection in the file types list. + */ + private void populateTypeDetailsComponents() { + String mimeType = typesList.getSelectedValue(); + FileType fileType = fileTypes.get(mimeType); + if (null != fileType) { + mimeTypeTextField.setText(fileType.getMimeType()); + Signature signature = fileType.getSignature(); + FileType.Signature.Type sigType = signature.getType(); + signatureTypeComboBox.setSelectedItem(sigType == FileType.Signature.Type.RAW ? FileTypeIdGlobalSettingsPanel.RAW_SIGNATURE_TYPE_COMBO_BOX_ITEM : FileTypeIdGlobalSettingsPanel.ASCII_SIGNATURE_TYPE_COMBO_BOX_ITEM); + String signatureBytes; + if (Signature.Type.RAW == signature.getType()) { + signatureBytes = DatatypeConverter.printHexBinary(signature.getSignatureBytes()); + } else { + try { + signatureBytes = new String(signature.getSignatureBytes(), "UTF-8"); + } catch (UnsupportedEncodingException ex) { + JOptionPane.showMessageDialog(null, + ex.getLocalizedMessage(), + NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.JOptionPane.storeFailed.title"), + JOptionPane.ERROR_MESSAGE); + signatureBytes = ""; + } + } + signatureTextField.setText(signatureBytes); + offsetTextField.setText(Long.toString(signature.getOffset())); + postHitCheckBox.setSelected(fileType.alertOnMatch()); + filesSetNameTextField.setEnabled(postHitCheckBox.isSelected()); + filesSetNameTextField.setText(fileType.getFilesSetName()); + } + enableButtons(); + } + + /** + * Clears all of the components in the individual type details portion of + * the panel. + */ + private void clearTypeDetailsComponents() { + typesList.clearSelection(); + mimeTypeTextField.setText(""); //NON-NLS + signatureTypeComboBox.setSelectedItem(FileTypeIdGlobalSettingsPanel.RAW_SIGNATURE_TYPE_COMBO_BOX_ITEM); + hexPrefixLabel.setVisible(true); + signatureTextField.setText(""); //NON-NLS + offsetTextField.setText(""); //NON-NLS + postHitCheckBox.setSelected(false); + filesSetNameTextField.setText(""); //NON-NLS + filesSetNameTextField.setEnabled(false); + enableButtons(); + } + + /** + * @inheritDoc + */ + @Override + public void store() { + try { + UserDefinedFileTypesManager.getInstance().setUserDefinedFileTypes(fileTypes); + } catch (UserDefinedFileTypesManager.UserDefinedFileTypesException ex) { + JOptionPane.showMessageDialog(null, + ex.getLocalizedMessage(), + NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.JOptionPane.storeFailed.title"), + JOptionPane.ERROR_MESSAGE); + } + } + + /** + * @inheritDoc + */ + @Override + public void saveSettings() { + store(); + } + + /** + * @inheritDoc + */ + @Override + @SuppressWarnings("FinalizeDeclaration") + protected void finalize() throws Throwable { + IngestManager.getInstance().removeIngestJobEventListener(ingestJobEventsListener); + super.finalize(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + typesScrollPane = new javax.swing.JScrollPane(); + typesList = new javax.swing.JList(); + separator = new javax.swing.JSeparator(); + mimeTypeLabel = new javax.swing.JLabel(); + mimeTypeTextField = new javax.swing.JTextField(); + signatureTypeLabel = new javax.swing.JLabel(); + signatureTextField = new javax.swing.JTextField(); + offsetLabel = new javax.swing.JLabel(); + offsetTextField = new javax.swing.JTextField(); + newTypeButton = new javax.swing.JButton(); + deleteTypeButton = new javax.swing.JButton(); + saveTypeButton = new javax.swing.JButton(); + hexPrefixLabel = new javax.swing.JLabel(); + signatureTypeComboBox = new javax.swing.JComboBox(); + signatureLabel = new javax.swing.JLabel(); + hintScrollPane = new javax.swing.JScrollPane(); + hintTextArea = new javax.swing.JTextArea(); + postHitCheckBox = new javax.swing.JCheckBox(); + filesSetNameLabel = new javax.swing.JLabel(); + filesSetNameTextField = new javax.swing.JTextField(); + ingestRunningWarningLabel = new javax.swing.JLabel(); + + setMaximumSize(new java.awt.Dimension(500, 300)); + setPreferredSize(new java.awt.Dimension(500, 300)); + + typesList.setMinimumSize(new java.awt.Dimension(200, 0)); + typesScrollPane.setViewportView(typesList); + + separator.setOrientation(javax.swing.SwingConstants.VERTICAL); + + org.openide.awt.Mnemonics.setLocalizedText(mimeTypeLabel, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.mimeTypeLabel.text")); // NOI18N + + mimeTypeTextField.setText(org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.mimeTypeTextField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(signatureTypeLabel, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.signatureTypeLabel.text")); // NOI18N + + signatureTextField.setText(org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.signatureTextField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(offsetLabel, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.offsetLabel.text")); // NOI18N + + offsetTextField.setText(org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.offsetTextField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(newTypeButton, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.newTypeButton.text")); // NOI18N + newTypeButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + newTypeButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(deleteTypeButton, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.deleteTypeButton.text")); // NOI18N + deleteTypeButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + deleteTypeButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(saveTypeButton, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.saveTypeButton.text")); // NOI18N + saveTypeButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + saveTypeButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(hexPrefixLabel, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.hexPrefixLabel.text")); // NOI18N + + signatureTypeComboBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + signatureTypeComboBoxActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(signatureLabel, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.signatureLabel.text")); // NOI18N + + hintTextArea.setEditable(false); + hintTextArea.setColumns(20); + hintTextArea.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N + hintTextArea.setLineWrap(true); + hintTextArea.setRows(5); + hintTextArea.setText(org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.hintTextArea.text")); // NOI18N + hintTextArea.setWrapStyleWord(true); + hintTextArea.setPreferredSize(new java.awt.Dimension(164, 70)); + hintScrollPane.setViewportView(hintTextArea); + + org.openide.awt.Mnemonics.setLocalizedText(postHitCheckBox, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.postHitCheckBox.text")); // NOI18N + postHitCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + postHitCheckBoxActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(filesSetNameLabel, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.filesSetNameLabel.text")); // NOI18N + + filesSetNameTextField.setText(org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.filesSetNameTextField.text")); // NOI18N + + ingestRunningWarningLabel.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/modules/filetypeid/warning16.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(ingestRunningWarningLabel, org.openide.util.NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.ingestRunningWarningLabel.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(ingestRunningWarningLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGap(30, 30, 30)) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(typesScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addComponent(deleteTypeButton) + .addGap(0, 97, Short.MAX_VALUE))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(separator, javax.swing.GroupLayout.PREFERRED_SIZE, 13, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(layout.createSequentialGroup() + .addComponent(offsetLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 71, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(offsetTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 178, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) + .addComponent(signatureLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(signatureTypeLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(javax.swing.GroupLayout.Alignment.LEADING, layout.createSequentialGroup() + .addGap(2, 2, 2) + .addComponent(mimeTypeLabel))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(mimeTypeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 176, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(signatureTypeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 176, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(hexPrefixLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(signatureTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 160, javax.swing.GroupLayout.PREFERRED_SIZE))))) + .addComponent(hintScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 260, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(postHitCheckBox) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(filesSetNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 179, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(newTypeButton, javax.swing.GroupLayout.PREFERRED_SIZE, 71, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(saveTypeButton)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() + .addComponent(filesSetNameLabel) + .addGap(188, 188, 188))))) + .addGap(29, 29, 29)))) + ); + + layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {deleteTypeButton, newTypeButton, saveTypeButton}); + + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(hintScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 76, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(mimeTypeLabel) + .addComponent(mimeTypeTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(signatureTypeLabel) + .addComponent(signatureTypeComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(signatureTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(hexPrefixLabel) + .addComponent(signatureLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(offsetTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(offsetLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(postHitCheckBox) + .addGap(1, 1, 1) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(filesSetNameLabel) + .addComponent(filesSetNameTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addComponent(typesScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 219, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(newTypeButton) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(deleteTypeButton) + .addComponent(saveTypeButton)))) + .addGroup(layout.createSequentialGroup() + .addGap(11, 11, 11) + .addComponent(separator, javax.swing.GroupLayout.PREFERRED_SIZE, 257, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(ingestRunningWarningLabel) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + layout.linkSize(javax.swing.SwingConstants.VERTICAL, new java.awt.Component[] {deleteTypeButton, newTypeButton, saveTypeButton}); + + }// //GEN-END:initComponents + + private void newTypeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newTypeButtonActionPerformed + clearTypeDetailsComponents(); + }//GEN-LAST:event_newTypeButtonActionPerformed + + private void deleteTypeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteTypeButtonActionPerformed + String typeName = typesList.getSelectedValue(); + fileTypes.remove(typeName); + updateFileTypesListModel(); + if (!typesListModel.isEmpty()) { + typesList.setSelectedIndex(0); + } + }//GEN-LAST:event_deleteTypeButtonActionPerformed + + private void saveTypeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveTypeButtonActionPerformed + /** + * Get the MIME type. + */ + String typeName = mimeTypeTextField.getText(); + if (typeName.isEmpty()) { + JOptionPane.showMessageDialog(null, + NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.JOptionPane.invalidMIMEType.message"), + NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.JOptionPane.invalidMIMEType.title"), + JOptionPane.ERROR_MESSAGE); + return; + } + + /** + * Get the signature type. + */ + FileType.Signature.Type sigType = signatureTypeComboBox.getSelectedItem() == FileTypeIdGlobalSettingsPanel.RAW_SIGNATURE_TYPE_COMBO_BOX_ITEM ? FileType.Signature.Type.RAW : FileType.Signature.Type.ASCII; + + /** + * Get the signature bytes. + */ + String sigString = signatureTextField.getText(); + if (sigString.isEmpty()) { + JOptionPane.showMessageDialog(null, + NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.JOptionPane.invalidSignature.message"), + NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.JOptionPane.invalidSignature.title"), + JOptionPane.ERROR_MESSAGE); + return; + } + byte[] signatureBytes; + if (FileType.Signature.Type.RAW == sigType) { + try { + signatureBytes = DatatypeConverter.parseHexBinary(sigString); + } catch (IllegalArgumentException ex) { + JOptionPane.showMessageDialog(null, + NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.JOptionPane.invalidRawSignatureBytes.message"), + NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.JOptionPane.invalidSignatureBytes.title"), + JOptionPane.ERROR_MESSAGE); + return; + } + } else { + signatureBytes = sigString.getBytes(Charset.forName("UTF-8")); + } + + /** + * Get the offset. + */ + long offset; + try { + offset = Long.parseUnsignedLong(offsetTextField.getText()); + } catch (NumberFormatException ex) { + JOptionPane.showMessageDialog(null, + NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.JOptionPane.invalidOffset.message"), + NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.JOptionPane.invalidOffset.title"), + JOptionPane.ERROR_MESSAGE); + return; + } + + /** + * Get the interesting files set details. + */ + String filesSetName = filesSetNameTextField.getText(); + if (postHitCheckBox.isSelected() && filesSetName.isEmpty()) { + JOptionPane.showMessageDialog(null, + NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.JOptionPane.invalidInterestingFilesSetName.message"), + NbBundle.getMessage(FileTypeIdGlobalSettingsPanel.class, "FileTypeIdGlobalSettingsPanel.JOptionPane.invalidInterestingFilesSetName.title"), + JOptionPane.ERROR_MESSAGE); + return; + } + + /** + * Put it all together and reset the file types list component. + */ + FileType.Signature signature = new FileType.Signature(signatureBytes, offset, sigType); + FileType fileType = new FileType(typeName, signature, filesSetName, postHitCheckBox.isSelected()); + fileTypes.put(typeName, fileType); + updateFileTypesListModel(); + typesList.setSelectedValue(fileType.getMimeType(), true); + }//GEN-LAST:event_saveTypeButtonActionPerformed + + private void postHitCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_postHitCheckBoxActionPerformed + filesSetNameTextField.setEnabled(postHitCheckBox.isSelected()); + enableButtons(); + }//GEN-LAST:event_postHitCheckBoxActionPerformed + + private void signatureTypeComboBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_signatureTypeComboBoxActionPerformed + hexPrefixLabel.setVisible(signatureTypeComboBox.getSelectedItem() == FileTypeIdGlobalSettingsPanel.RAW_SIGNATURE_TYPE_COMBO_BOX_ITEM); + }//GEN-LAST:event_signatureTypeComboBoxActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton deleteTypeButton; + private javax.swing.JLabel filesSetNameLabel; + private javax.swing.JTextField filesSetNameTextField; + private javax.swing.JLabel hexPrefixLabel; + private javax.swing.JScrollPane hintScrollPane; + private javax.swing.JTextArea hintTextArea; + private javax.swing.JLabel ingestRunningWarningLabel; + private javax.swing.JLabel mimeTypeLabel; + private javax.swing.JTextField mimeTypeTextField; + private javax.swing.JButton newTypeButton; + private javax.swing.JLabel offsetLabel; + private javax.swing.JTextField offsetTextField; + private javax.swing.JCheckBox postHitCheckBox; + private javax.swing.JButton saveTypeButton; + private javax.swing.JSeparator separator; + private javax.swing.JLabel signatureLabel; + private javax.swing.JTextField signatureTextField; + private javax.swing.JComboBox signatureTypeComboBox; + private javax.swing.JLabel signatureTypeLabel; + private javax.swing.JList typesList; + private javax.swing.JScrollPane typesScrollPane; + // End of variables declaration//GEN-END:variables + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdModuleSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestJobSettingsPanel.form similarity index 61% rename from Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdModuleSettingsPanel.form rename to Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestJobSettingsPanel.form index 60dfbc7cd3..f03a25070c 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdModuleSettingsPanel.form +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestJobSettingsPanel.form @@ -18,7 +18,10 @@ - + + + + @@ -28,7 +31,9 @@ - + + + @@ -38,15 +43,25 @@ - + - + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdModuleSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestJobSettingsPanel.java similarity index 61% rename from Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdModuleSettingsPanel.java rename to Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestJobSettingsPanel.java index 3839d2c711..87dd8dd53e 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdModuleSettingsPanel.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestJobSettingsPanel.java @@ -18,6 +18,7 @@ */ package org.sleuthkit.autopsy.modules.filetypeid; +import org.openide.util.NbBundle; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; @@ -25,25 +26,34 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; * UI component used to set ingest job options for file type identifier ingest * modules. */ -final class FileTypeIdModuleSettingsPanel extends IngestModuleIngestJobSettingsPanel { +class FileTypeIdIngestJobSettingsPanel extends IngestModuleIngestJobSettingsPanel { private final FileTypeIdModuleSettings settings; - public FileTypeIdModuleSettingsPanel(FileTypeIdModuleSettings settings) { + FileTypeIdIngestJobSettingsPanel(FileTypeIdModuleSettings settings) { this.settings = settings; initComponents(); customizeComponents(); } - private void customizeComponents() { - skipKnownCheckBox.setSelected(settings.skipKnownFiles()); - } - + /** + * @inheritDoc + */ @Override public IngestModuleIngestJobSettings getSettings() { return settings; } + /** + * Does child component initialization in addition to that done by the + * Matisse generated code. + */ + private void customizeComponents() { + skipKnownCheckBox.setSelected(settings.skipKnownFiles()); + skipSmallFilesCheckBox.setSelected(settings.skipSmallFiles()); + skipSmallFilesCheckBox.setText(NbBundle.getMessage(FileTypeIdIngestJobSettingsPanel.class, "FileTypeIdIngestJobSettingsPanel.skipSmallFilesCheckBox.text", settings.minFileSizeInBytes())); + } + /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always @@ -54,23 +64,33 @@ final class FileTypeIdModuleSettingsPanel extends IngestModuleIngestJobSettingsP private void initComponents() { skipKnownCheckBox = new javax.swing.JCheckBox(); + skipSmallFilesCheckBox = new javax.swing.JCheckBox(); skipKnownCheckBox.setSelected(true); - skipKnownCheckBox.setText(org.openide.util.NbBundle.getMessage(FileTypeIdModuleSettingsPanel.class, "FileTypeIdModuleSettingsPanel.skipKnownCheckBox.text")); // NOI18N - skipKnownCheckBox.setToolTipText(org.openide.util.NbBundle.getMessage(FileTypeIdModuleSettingsPanel.class, "FileTypeIdModuleSettingsPanel.skipKnownCheckBox.toolTipText")); // NOI18N + skipKnownCheckBox.setText(org.openide.util.NbBundle.getMessage(FileTypeIdIngestJobSettingsPanel.class, "FileTypeIdIngestJobSettingsPanel.skipKnownCheckBox.text")); // NOI18N + skipKnownCheckBox.setToolTipText(org.openide.util.NbBundle.getMessage(FileTypeIdIngestJobSettingsPanel.class, "FileTypeIdIngestJobSettingsPanel.skipKnownCheckBox.toolTipText")); // NOI18N skipKnownCheckBox.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { skipKnownCheckBoxActionPerformed(evt); } }); + skipSmallFilesCheckBox.setText(org.openide.util.NbBundle.getMessage(FileTypeIdIngestJobSettingsPanel.class, "FileTypeIdIngestJobSettingsPanel.skipSmallFilesCheckBox.text")); // NOI18N + skipSmallFilesCheckBox.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + skipSmallFilesCheckBoxActionPerformed(evt); + } + }); + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGap(10, 10, 10) - .addComponent(skipKnownCheckBox) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(skipSmallFilesCheckBox) + .addComponent(skipKnownCheckBox)) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); layout.setVerticalGroup( @@ -78,14 +98,22 @@ final class FileTypeIdModuleSettingsPanel extends IngestModuleIngestJobSettingsP .addGroup(layout.createSequentialGroup() .addGap(11, 11, 11) .addComponent(skipKnownCheckBox) - .addContainerGap(47, Short.MAX_VALUE)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(skipSmallFilesCheckBox) + .addContainerGap(60, Short.MAX_VALUE)) ); }// //GEN-END:initComponents private void skipKnownCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_skipKnownCheckBoxActionPerformed settings.setSkipKnownFiles(skipKnownCheckBox.isSelected()); }//GEN-LAST:event_skipKnownCheckBoxActionPerformed + + private void skipSmallFilesCheckBoxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_skipSmallFilesCheckBoxActionPerformed + settings.setSkipSmallFiles(skipSmallFilesCheckBox.isSelected()); + }//GEN-LAST:event_skipSmallFilesCheckBoxActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JCheckBox skipKnownCheckBox; + private javax.swing.JCheckBox skipSmallFilesCheckBox; // End of variables declaration//GEN-END:variables } diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java index 2abb69974a..c3c6bbcb5b 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdIngestModule.java @@ -20,127 +20,38 @@ package org.sleuthkit.autopsy.modules.filetypeid; import java.util.HashMap; import java.util.logging.Level; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.ingest.FileIngestModule; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.autopsy.ingest.IngestMessage; import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.datamodel.AbstractFile; -import org.sleuthkit.datamodel.BlackboardArtifact; -import org.sleuthkit.datamodel.BlackboardAttribute; -import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData.FileKnown; import org.sleuthkit.datamodel.TskException; import org.sleuthkit.autopsy.ingest.IngestModule.ProcessResult; import org.sleuthkit.autopsy.ingest.IngestModuleReferenceCounter; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; /** * Detects the type of a file based on signature (magic) values. Posts results * to the blackboard. */ +// TODO: This class does not need to be public. public class FileTypeIdIngestModule implements FileIngestModule { private static final Logger logger = Logger.getLogger(FileTypeIdIngestModule.class.getName()); - private static final long MIN_FILE_SIZE = 512; private final FileTypeIdModuleSettings settings; - private long jobId; - + private long jobId; private static final HashMap totalsForIngestJobs = new HashMap<>(); private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); - private TikaFileTypeDetector tikaDetector = new TikaFileTypeDetector(); + private final UserDefinedFileTypeIdentifier userDefinedFileTypeIdentifier; + private final TikaFileTypeDetector tikaDetector = new TikaFileTypeDetector(); - private static class IngestJobTotals { - long matchTime = 0; - long numFiles = 0; - } - - /** - * Update the match time total and increment num of files for this job - * @param ingestJobId - * @param matchTimeInc amount of time to add - */ - private static synchronized void addToTotals(long ingestJobId, long matchTimeInc) { - IngestJobTotals ingestJobTotals = totalsForIngestJobs.get(ingestJobId); - if (ingestJobTotals == null) { - ingestJobTotals = new IngestJobTotals(); - totalsForIngestJobs.put(ingestJobId, ingestJobTotals); - } - - ingestJobTotals.matchTime += matchTimeInc; - ingestJobTotals.numFiles++; - totalsForIngestJobs.put(ingestJobId, ingestJobTotals); - } - - FileTypeIdIngestModule(FileTypeIdModuleSettings settings) { - this.settings = settings; - } - - @Override - public void startUp(IngestJobContext context) throws IngestModuleException { - jobId = context.getJobId(); - refCounter.incrementAndGet(jobId); - } - - @Override - public ProcessResult process(AbstractFile abstractFile) { - // skip non-files - if ((abstractFile.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) - || (abstractFile.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)) { - - return ProcessResult.OK; - } - - if (settings.skipKnownFiles() && (abstractFile.getKnown() == FileKnown.KNOWN)) { - return ProcessResult.OK; - } - - if (abstractFile.getSize() < MIN_FILE_SIZE) { - return ProcessResult.OK; - } - - try { - long startTime = System.currentTimeMillis(); - tikaDetector.detectAndSave(abstractFile); - addToTotals(jobId, (System.currentTimeMillis() - startTime)); //add match time - return ProcessResult.OK; - } catch (TskException ex) { - logger.log(Level.WARNING, "Error matching file signature", ex); //NON-NLS - return ProcessResult.ERROR; - } catch (Exception e) { - logger.log(Level.WARNING, "Error matching file signature", e); //NON-NLS - return ProcessResult.ERROR; - } - } - - @Override - public void shutDown() { - // We only need to post the summary msg from the last module per job - if (refCounter.decrementAndGet(jobId) == 0) { - IngestJobTotals jobTotals; - synchronized(this) { - jobTotals = totalsForIngestJobs.remove(jobId); - } - if (jobTotals != null) { - StringBuilder detailsSb = new StringBuilder(); - detailsSb.append(""); //NON-NLS - detailsSb.append(""); //NON-NLS - detailsSb.append("\n"); //NON-NLS - detailsSb.append("\n"); //NON-NLS - detailsSb.append("
").append(FileTypeIdModuleFactory.getModuleName()).append("
") //NON-NLS - .append(NbBundle.getMessage(this.getClass(), "FileTypeIdIngestModule.complete.totalProcTime")) - .append("").append(jobTotals.matchTime).append("
") //NON-NLS - .append(NbBundle.getMessage(this.getClass(), "FileTypeIdIngestModule.complete.totalFiles")) - .append("").append(jobTotals.numFiles).append("
"); //NON-NLS - IngestServices.getInstance().postMessage(IngestMessage.createMessage(IngestMessage.MessageType.INFO, FileTypeIdModuleFactory.getModuleName(), - NbBundle.getMessage(this.getClass(), - "FileTypeIdIngestModule.complete.srvMsg.text"), - detailsSb.toString())); - } - } - } - /** * Validate if a given mime type is in the detector's registry. * @@ -157,4 +68,157 @@ public class FileTypeIdIngestModule implements FileIngestModule { TikaFileTypeDetector detector = new TikaFileTypeDetector(); return detector.isMimeTypeDetectable(mimeType); } -} \ No newline at end of file + + /** + * Creates an ingest module that detects the type of a file based on + * signature (magic) values. Posts results to the blackboard. + * + * @param settings The ingest module settings. + */ + FileTypeIdIngestModule(FileTypeIdModuleSettings settings) { + this.settings = settings; + userDefinedFileTypeIdentifier = new UserDefinedFileTypeIdentifier(); + try { + userDefinedFileTypeIdentifier.loadFileTypes(); + } catch (UserDefinedFileTypesManager.UserDefinedFileTypesException ex) { + logger.log(Level.SEVERE, "Failed to load file types", ex); + MessageNotifyUtil.Notify.error(FileTypeIdModuleFactory.getModuleName(), ex.getMessage()); + } + } + + /** + * @inheritDoc + */ + @Override + public void startUp(IngestJobContext context) throws IngestModuleException { + jobId = context.getJobId(); + refCounter.incrementAndGet(jobId); + } + + /** + * @inheritDoc + */ + @Override + public ProcessResult process(AbstractFile file) { + + String name = file.getName(); + + /** + * Skip unallocated space and unused blocks files. + */ + if ((file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) + || (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)) { + + return ProcessResult.OK; + } + + /** + * Skip known files if configured to do so. + */ + if (settings.skipKnownFiles() && (file.getKnown() == FileKnown.KNOWN)) { + return ProcessResult.OK; + } + + /** + * Filter out very small files to minimize false positives. + */ + if (settings.skipSmallFiles() && file.getSize() < settings.minFileSizeInBytes()) { + return ProcessResult.OK; + } + + try { + long startTime = System.currentTimeMillis(); + FileType fileType = this.userDefinedFileTypeIdentifier.identify(file); + if (null != fileType) { + String moduleName = FileTypeIdModuleFactory.getModuleName(); + BlackboardArtifact getInfoArtifact = file.getGenInfoArtifact(); + BlackboardAttribute typeAttr = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FILE_TYPE_SIG.getTypeID(), moduleName, fileType.getMimeType()); + getInfoArtifact.addAttribute(typeAttr); + + if (fileType.alertOnMatch()) { + BlackboardArtifact artifact = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT); + BlackboardAttribute setNameAttribute = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME.getTypeID(), moduleName, fileType.getFilesSetName()); + artifact.addAttribute(setNameAttribute); + + /** + * Use the MIME type as the category, i.e., the rule that + * determined this file belongs to the interesting files + * set. + */ + BlackboardAttribute ruleNameAttribute = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY.getTypeID(), moduleName, fileType.getMimeType()); + artifact.addAttribute(ruleNameAttribute); + } + + } else { + tikaDetector.detectAndSave(file); + } + addToTotals(jobId, (System.currentTimeMillis() - startTime)); + return ProcessResult.OK; + } catch (TskException ex) { + logger.log(Level.WARNING, "Error matching file signature", ex); //NON-NLS + return ProcessResult.ERROR; + } catch (Exception e) { + logger.log(Level.WARNING, "Error matching file signature", e); //NON-NLS + return ProcessResult.ERROR; + } + } + + /** + * @inheritDoc + */ + @Override + public void shutDown() { + /** + * If this is the instance of this module for this ingest job, post a + * summary message to the ingest messages box. + */ + if (refCounter.decrementAndGet(jobId) == 0) { + IngestJobTotals jobTotals; + synchronized (this) { + jobTotals = totalsForIngestJobs.remove(jobId); + } + if (jobTotals != null) { + StringBuilder detailsSb = new StringBuilder(); + detailsSb.append(""); //NON-NLS + detailsSb.append(""); //NON-NLS + detailsSb.append("\n"); //NON-NLS + detailsSb.append("\n"); //NON-NLS + detailsSb.append("
").append(FileTypeIdModuleFactory.getModuleName()).append("
") //NON-NLS + .append(NbBundle.getMessage(this.getClass(), "FileTypeIdIngestModule.complete.totalProcTime")) + .append("").append(jobTotals.matchTime).append("
") //NON-NLS + .append(NbBundle.getMessage(this.getClass(), "FileTypeIdIngestModule.complete.totalFiles")) + .append("").append(jobTotals.numFiles).append("
"); //NON-NLS + IngestServices.getInstance().postMessage(IngestMessage.createMessage(IngestMessage.MessageType.INFO, FileTypeIdModuleFactory.getModuleName(), + NbBundle.getMessage(this.getClass(), + "FileTypeIdIngestModule.complete.srvMsg.text"), + detailsSb.toString())); + } + } + } + + /** + * Update the match time total and increment number of files processed for + * this ingest job. + * + * @param jobId The ingest job identifier. + * @param matchTimeInc Amount of time to add. + */ + private static synchronized void addToTotals(long jobId, long matchTimeInc) { + IngestJobTotals ingestJobTotals = totalsForIngestJobs.get(jobId); + if (ingestJobTotals == null) { + ingestJobTotals = new IngestJobTotals(); + totalsForIngestJobs.put(jobId, ingestJobTotals); + } + + ingestJobTotals.matchTime += matchTimeInc; + ingestJobTotals.numFiles++; + totalsForIngestJobs.put(jobId, ingestJobTotals); + } + + private static class IngestJobTotals { + + long matchTime = 0; + long numFiles = 0; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdModuleFactory.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdModuleFactory.java index 6688fd8027..38823ca03d 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdModuleFactory.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdModuleFactory.java @@ -24,69 +24,121 @@ import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.autopsy.ingest.FileIngestModule; import org.sleuthkit.autopsy.ingest.IngestModuleFactory; import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter; +import org.sleuthkit.autopsy.ingest.IngestModuleGlobalSettingsPanel; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; /** - * An factory that creates file ingest modules that determine the types of - * files. + * A factory that creates file ingest modules that determine the types of files. */ +// TODO: This class does not need to be public. @ServiceProvider(service = IngestModuleFactory.class) public class FileTypeIdModuleFactory extends IngestModuleFactoryAdapter { + FileTypeIdGlobalSettingsPanel globalSettingsPanel; + + /** + * @inheritDoc + */ @Override public String getModuleDisplayName() { return getModuleName(); } + /** + * Gets the module display name. + * + * @return The name string. + */ static String getModuleName() { return NbBundle.getMessage(FileTypeIdIngestModule.class, "FileTypeIdIngestModule.moduleName.text"); } + /** + * @inheritDoc + */ @Override public String getModuleDescription() { return NbBundle.getMessage(FileTypeIdIngestModule.class, "FileTypeIdIngestModule.moduleDesc.text"); } + /** + * @inheritDoc + */ @Override public String getModuleVersionNumber() { return Version.getVersion(); } + /** + * @inheritDoc + */ + @Override + public boolean hasGlobalSettingsPanel() { + return true; + } + + /** + * @inheritDoc + */ + @Override + public IngestModuleGlobalSettingsPanel getGlobalSettingsPanel() { + if (null == globalSettingsPanel) { + globalSettingsPanel = new FileTypeIdGlobalSettingsPanel(); + } + globalSettingsPanel.load(); + return globalSettingsPanel; + } + + /** + * @inheritDoc + */ @Override public IngestModuleIngestJobSettings getDefaultIngestJobSettings() { return new FileTypeIdModuleSettings(); } + /** + * @inheritDoc + */ @Override public boolean hasIngestJobSettingsPanel() { return true; } + /** + * @inheritDoc + */ @Override public IngestModuleIngestJobSettingsPanel getIngestJobSettingsPanel(IngestModuleIngestJobSettings settings) { assert settings instanceof FileTypeIdModuleSettings; if (!(settings instanceof FileTypeIdModuleSettings)) { throw new IllegalArgumentException(NbBundle.getMessage(this.getClass(), - "FileTypeIdModuleFactory.getIngestJobSettingsPanel.exception.msg")); - } - return new FileTypeIdModuleSettingsPanel((FileTypeIdModuleSettings) settings); + "FileTypeIdModuleFactory.getIngestJobSettingsPanel.exception.msg")); + } + return new FileTypeIdIngestJobSettingsPanel((FileTypeIdModuleSettings) settings); } + /** + * @inheritDoc + */ @Override public boolean isFileIngestModuleFactory() { return true; } + /** + * @inheritDoc + */ @Override public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings settings) { assert settings instanceof FileTypeIdModuleSettings; if (!(settings instanceof FileTypeIdModuleSettings)) { throw new IllegalArgumentException( NbBundle.getMessage(this.getClass(), "FileTypeIdModuleFactory.createFileIngestModule.exception.msg")); - } + } return new FileTypeIdIngestModule((FileTypeIdModuleSettings) settings); } } diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdModuleSettings.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdModuleSettings.java index c472596be4..270c604d25 100755 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdModuleSettings.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdModuleSettings.java @@ -23,23 +23,30 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; /** * Ingest job options for the file type identifier ingest module instances. */ +// TODO: This class does not need to be public. public class FileTypeIdModuleSettings implements IngestModuleIngestJobSettings { private static final long serialVersionUID = 1L; - private volatile boolean skipKnownFiles = true; + private static final long MIN_FILE_SIZE_IN_BYTES = 512; + private boolean skipKnownFiles = true; + private boolean skipSmallFiles = true; FileTypeIdModuleSettings() { } - FileTypeIdModuleSettings(boolean skipKnownFiles) { + FileTypeIdModuleSettings(boolean skipKnownFiles, boolean skipSmallFiles) { this.skipKnownFiles = skipKnownFiles; + this.skipSmallFiles = skipSmallFiles; } - + + /** + * @inheritDoc + */ @Override public long getVersionNumber() { return serialVersionUID; - } - + } + void setSkipKnownFiles(boolean enabled) { skipKnownFiles = enabled; } @@ -47,4 +54,17 @@ public class FileTypeIdModuleSettings implements IngestModuleIngestJobSettings { boolean skipKnownFiles() { return skipKnownFiles; } -} \ No newline at end of file + + void setSkipSmallFiles(boolean enabled) { + this.skipSmallFiles = enabled; + } + + boolean skipSmallFiles() { + return skipSmallFiles; + } + + long minFileSizeInBytes() { + return MIN_FILE_SIZE_IN_BYTES; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdOptionsPanelController.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdOptionsPanelController.java new file mode 100644 index 0000000000..8f9aa7b500 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeIdOptionsPanelController.java @@ -0,0 +1,89 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.modules.filetypeid; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import javax.swing.JComponent; +import org.netbeans.spi.options.OptionsPanelController; +import org.openide.util.HelpCtx; +import org.openide.util.Lookup; + +@OptionsPanelController.TopLevelRegistration( + categoryName = "#OptionsCategory_Name_FileTypeId", + iconBase = "org/sleuthkit/autopsy/modules/filetypeid/user-defined-file-types-settings.png", + keywords = "#OptionsCategory_Keywords_FileTypeId", + keywordsCategory = "FileTypeId" +) +@org.openide.util.NbBundle.Messages({"OptionsCategory_Name_FileTypeId=FileTypeId", "OptionsCategory_Keywords_FileTypeId=FileTypeId"}) +public final class FileTypeIdOptionsPanelController extends OptionsPanelController { + + private FileTypeIdGlobalSettingsPanel panel; + private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); + private boolean changed; + + @Override + public void update() { + getPanel().load(); + changed = false; + } + + @Override + public void applyChanges() { + getPanel().store(); + changed = false; + } + + @Override + public void cancel() { + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public boolean isChanged() { + return changed; + } + + @Override + public HelpCtx getHelpCtx() { + return null; + } + + @Override + public JComponent getComponent(Lookup masterLookup) { + return getPanel(); + } + + @Override + public void addPropertyChangeListener(PropertyChangeListener l) { + pcs.addPropertyChangeListener(l); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener l) { + pcs.removePropertyChangeListener(l); + } + + private FileTypeIdGlobalSettingsPanel getPanel() { + if (panel == null) { + panel = new FileTypeIdGlobalSettingsPanel(); + } + return panel; + } + + void changed() { + if (!changed) { + changed = true; + pcs.firePropertyChange(OptionsPanelController.PROP_CHANGED, false, true); + } + pcs.firePropertyChange(OptionsPanelController.PROP_VALID, null, null); + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypes.xsd b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypes.xsd new file mode 100644 index 0000000000..26aa720ff1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypes.xsd @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/TikaFileTypeDetector.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/TikaFileTypeDetector.java index a6ba0e5483..84d1434644 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/TikaFileTypeDetector.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/TikaFileTypeDetector.java @@ -69,18 +69,6 @@ public class TikaFileTypeDetector { buf = buffer; } - // the xml detection in Tika tries to parse the entire file and throws exceptions - // for files that are not valid XML - try { - String tagHeader = new String(buf, 0, 5); - if (tagHeader.equals(" sleuthkit 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.modules.filetypeid; + +import java.util.HashMap; +import java.util.Map; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * Does file type identification for user-defined file types. + */ +final class UserDefinedFileTypeIdentifier { + + private Map fileTypes; + + /** + * Creates an object that can do file type identification for user-defined + * file types. + */ + UserDefinedFileTypeIdentifier() { + fileTypes = new HashMap<>(); + } + + /** + * Gets the user-defined file types from the user-defined file types + * manager. + * + * @throws + * org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException + */ + void loadFileTypes() throws UserDefinedFileTypesManager.UserDefinedFileTypesException { + fileTypes = UserDefinedFileTypesManager.getInstance().getFileTypes(); + } + + /** + * Attempts to identify a file using the set of user-defined file type file + * types. + * + * @param file The file to type. + * @return A FileType object or null if identification fails. + */ + FileType identify(final AbstractFile file) { + FileType type = null; + for (FileType fileType : this.fileTypes.values()) { + if (fileType.matches(file)) { + type = fileType; + break; + } + } + return type; + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/UserDefinedFileTypesManager.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/UserDefinedFileTypesManager.java new file mode 100644 index 0000000000..1884330707 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/UserDefinedFileTypesManager.java @@ -0,0 +1,518 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2014 Basis Technology Corp. + * Contact: carrier sleuthkit 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.modules.filetypeid; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import javax.xml.parsers.ParserConfigurationException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import javax.xml.bind.DatatypeConverter; +import javax.xml.transform.TransformerException; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.coreutils.XMLUtil; +import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature; +import org.xml.sax.SAXException; + +/** + * Manages user-defined file types characterized by MIME type, signature, and + * optional membership in an interesting files set. + *

+ * Note that this class exposes a very simple get/set API that operates on the + * user-defined file types as a complete set - there is no concept of adding, + * editing or deleting file types singly. This works because this class is not + * exposed outside of this ingest module package and is ONLY used in a very + * specific paradigm. In this paradigm, there is a single modal writer of file + * types in the form of a global settings panel that disables itself when ingest + * is running so that multiple readers in the form of file ingest modules get a + * consistent set of file type definitions. + *

+ * Thread-safe. + */ +final class UserDefinedFileTypesManager { + + private static final Logger logger = Logger.getLogger(UserDefinedFileTypesManager.class.getName()); + private static final String FILE_TYPE_DEFINITIONS_SCHEMA_FILE = "FileTypes.xsd"; //NON-NLS + private static final String USER_DEFINED_TYPE_DEFINITIONS_FILE = "UserFileTypeDefinitions.xml"; //NON-NLS + private static final String FILE_TYPES_TAG_NAME = "FileTypes"; //NON-NLS + private static final String FILE_TYPE_TAG_NAME = "FileType"; //NON-NLS + private static final String MIME_TYPE_TAG_NAME = "MimeType"; //NON-NLS + private static final String SIGNATURE_TAG_NAME = "Signature"; //NON-NLS + private static final String SIGNATURE_TYPE_ATTRIBUTE = "type"; //NON-NLS + private static final String BYTES_TAG_NAME = "Bytes"; //NON-NLS + private static final String OFFSET_TAG_NAME = "Offset"; //NON-NLS + private static final String INTERESTING_FILES_SET_TAG_NAME = "InterestingFileSset"; //NON-NLS + private static final String ALERT_ATTRIBUTE = "alert"; //NON-NLS + private static final String ENCODING_FOR_XML_FILE = "UTF-8"; //NON-NLS + private static final String ASCII_ENCODING = "US-ASCII"; //NON-NLS + private static UserDefinedFileTypesManager instance; + + /** + * File types to be persisted to the user-defined file type definitions file + * are stored in this mapping of MIME types to file types. Access to this + * map is guarded by the intrinsic lock of the user-defined file types + * manager for thread-safety. + */ + private final Map userDefinedFileTypes = new HashMap<>(); + + /** + * The combined set of user-defined file types and file types predefined by + * Autopsy are stored in this mapping of MIME types to file types. This is + * the current working set of file types. Access to this map is guarded by + * the intrinsic lock of the user-defined file types manager for + * thread-safety. + */ + private final Map fileTypes = new HashMap<>(); + + /** + * Gets the singleton manager of user-defined file types characterized by + * MIME type, signature, and optional membership in an interesting files + * set. + * + * @return The user-defined file types manager singleton. + */ + synchronized static UserDefinedFileTypesManager getInstance() { + if (instance == null) { + instance = new UserDefinedFileTypesManager(); + } + return instance; + } + + /** + * Creates a manager of user-defined file types characterized by MIME type, + * signature, and optional membership in an interesting files set. + */ + private UserDefinedFileTypesManager() { + } + + /** + * Gets both the predefined and the user-defined file types. + * + * @return A mapping of file type names to file types, possibly empty. + * @throws + * org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException + */ + synchronized Map getFileTypes() throws UserDefinedFileTypesException { + loadFileTypes(); + + /** + * It is safe to return references to the internal file type objects + * because they are immutable. Note that + * Collections.unmodifiableCollection() is not used here because this + * view of the file types is a snapshot. + */ + return new HashMap<>(fileTypes); + } + + /** + * Gets the user-defined file types. + * + * @return A mapping of file type names to file types, possibly empty. + * @throws + * org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException + */ + synchronized Map getUserDefinedFileTypes() throws UserDefinedFileTypesException { + loadFileTypes(); + + /** + * It is safe to return references to the internal file type objects + * because they are immutable. Note that + * Collections.unmodifiableCollection() is not used here because this + * view of the file types is a snapshot. + */ + return new HashMap<>(userDefinedFileTypes); + } + + /** + * Loads the MIME type to file type mappings with predefined and + * user-defined types. + * + * @throws + * org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException + */ + private void loadFileTypes() throws UserDefinedFileTypesException { + fileTypes.clear(); + userDefinedFileTypes.clear(); + /** + * Load the predefined types first so that they can be overwritten by + * any user-defined types with the same names. + */ + loadPredefinedFileTypes(); + loadUserDefinedFileTypes(); + } + + /** + * Adds the predefined file types to the in-memory mappings of MIME types to + * file types. + * + * @throws + * org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException + */ + private void loadPredefinedFileTypes() throws UserDefinedFileTypesException { + try { + FileType fileType = new FileType("text/xml", new Signature(" newFileTypes) throws UserDefinedFileTypesException { + try { + String filePath = getFileTypeDefinitionsFilePath(USER_DEFINED_TYPE_DEFINITIONS_FILE); + XmlWriter.writeFileTypes(newFileTypes.values(), filePath); + } catch (ParserConfigurationException | FileNotFoundException | UnsupportedEncodingException | TransformerException ex) { + throwUserDefinedFileTypesException(ex, "UserDefinedFileTypesManager.saveFileTypes.errorMessage"); + } catch (IOException ex) { + throwUserDefinedFileTypesException(ex, "UserDefinedFileTypesManager.saveFileTypes.errorMessage"); + } + } + + /** + * Gets the absolute path of a file type definitions file. + * + * @param fileName The name of the file. + * @return The absolute path to the file. + */ + private static String getFileTypeDefinitionsFilePath(String fileName) { + Path filePath = Paths.get(PlatformUtil.getUserConfigDirectory(), fileName); + return filePath.toAbsolutePath().toString(); + } + + /** + * Provides a mechanism for writing a set of file type definitions to an XML + * file. + */ + private static class XmlWriter { + + /** + * Writes a set of file type definitions to an XML file. + * + * @param fileTypes A collection of file types. + * @param filePath The path to the destination file. + * @throws ParserConfigurationException + * @throws IOException + * @throws FileNotFoundException + * @throws UnsupportedEncodingException + * @throws TransformerException + */ + private static void writeFileTypes(Collection fileTypes, String filePath) throws ParserConfigurationException, IOException, FileNotFoundException, UnsupportedEncodingException, TransformerException { + Document doc = XMLUtil.createDocument(); + Element fileTypesElem = doc.createElement(FILE_TYPES_TAG_NAME); + doc.appendChild(fileTypesElem); + for (FileType fileType : fileTypes) { + Element fileTypeElem = XmlWriter.createFileTypeElement(fileType, doc); + fileTypesElem.appendChild(fileTypeElem); + } + XMLUtil.saveDocument(doc, ENCODING_FOR_XML_FILE, filePath); + } + + /** + * Creates an XML representation of a file type. + * + * @param fileType The file type object. + * @param doc The WC3 DOM object to use to create the XML. + * @return An XML element. + */ + private static Element createFileTypeElement(FileType fileType, Document doc) { + Element fileTypeElem = doc.createElement(FILE_TYPE_TAG_NAME); + XmlWriter.addMimeTypeElement(fileType, fileTypeElem, doc); + XmlWriter.addSignatureElement(fileType, fileTypeElem, doc); + XmlWriter.addInterestingFilesSetElement(fileType, fileTypeElem, doc); + XmlWriter.addAlertAttribute(fileType, fileTypeElem); + return fileTypeElem; + } + + /** + * Add a MIME type child element to a file type XML element. + * + * @param fileType The file type to use as a content source. + * @param fileTypeElem The parent file type element. + * @param doc The WC3 DOM object to use to create the XML. + */ + private static void addMimeTypeElement(FileType fileType, Element fileTypeElem, Document doc) { + Element typeNameElem = doc.createElement(MIME_TYPE_TAG_NAME); + typeNameElem.setTextContent(fileType.getMimeType()); + fileTypeElem.appendChild(typeNameElem); + } + + /** + * Add a signature child element to a file type XML element. + * + * @param fileType The file type to use as a content source. + * @param fileTypeElem The parent file type element. + * @param doc The WC3 DOM object to use to create the XML. + */ + private static void addSignatureElement(FileType fileType, Element fileTypeElem, Document doc) { + Signature signature = fileType.getSignature(); + Element signatureElem = doc.createElement(SIGNATURE_TAG_NAME); + + Element bytesElem = doc.createElement(BYTES_TAG_NAME); + bytesElem.setTextContent(DatatypeConverter.printHexBinary(signature.getSignatureBytes())); + signatureElem.appendChild(bytesElem); + + Element offsetElem = doc.createElement(OFFSET_TAG_NAME); + offsetElem.setTextContent(DatatypeConverter.printLong(signature.getOffset())); + signatureElem.appendChild(offsetElem); + + signatureElem.setAttribute(SIGNATURE_TYPE_ATTRIBUTE, signature.getType().toString()); + fileTypeElem.appendChild(signatureElem); + } + + /** + * Add an interesting files set element to a file type XML element. + * + * @param fileType The file type to use as a content source. + * @param fileTypeElem The parent file type element. + * @param doc The WC3 DOM object to use to create the XML. + */ + private static void addInterestingFilesSetElement(FileType fileType, Element fileTypeElem, Document doc) { + if (!fileType.getFilesSetName().isEmpty()) { + Element filesSetElem = doc.createElement(INTERESTING_FILES_SET_TAG_NAME); + filesSetElem.setTextContent(fileType.getFilesSetName()); + fileTypeElem.appendChild(filesSetElem); + } + } + + /** + * Add an alert attribute to a file type XML element. + * + * @param fileType The file type to use as a content source. + * @param fileTypeElem The parent file type element. + */ + private static void addAlertAttribute(FileType fileType, Element fileTypeElem) { + fileTypeElem.setAttribute(ALERT_ATTRIBUTE, Boolean.toString(fileType.alertOnMatch())); + } + + } + + /** + * Provides a mechanism for reading a set of file type definitions from an + * XML file. + */ + private static class XmlReader { + + /** + * Reads a set of file type definitions from an XML file. + * + * @param filePath The path to the XML file. + * @return A collection of file types read from the XML file. + */ + private static List readFileTypes(String filePath) throws IOException, ParserConfigurationException, SAXException { + List fileTypes = new ArrayList<>(); + Document doc = XMLUtil.loadDocument(filePath, UserDefinedFileTypesManager.class, FILE_TYPE_DEFINITIONS_SCHEMA_FILE); + if (doc != null) { + Element fileTypesElem = doc.getDocumentElement(); + if (fileTypesElem != null && fileTypesElem.getNodeName().equals(FILE_TYPES_TAG_NAME)) { + NodeList fileTypeElems = fileTypesElem.getElementsByTagName(FILE_TYPE_TAG_NAME); + for (int i = 0; i < fileTypeElems.getLength(); ++i) { + Element fileTypeElem = (Element) fileTypeElems.item(i); + FileType fileType = XmlReader.parseFileType(fileTypeElem); + fileTypes.add(fileType); + } + } + } + return fileTypes; + } + + /** + * Gets a file type definition from a file type XML element. + * + * @param fileTypeElem The XML element. + * @return A file type object. + * @throws IllegalArgumentException + * @throws NumberFormatException + */ + private static FileType parseFileType(Element fileTypeElem) throws IllegalArgumentException, NumberFormatException { + String mimeType = XmlReader.parseMimeType(fileTypeElem); + Signature signature = XmlReader.parseSignature(fileTypeElem); + String filesSetName = XmlReader.parseInterestingFilesSet(fileTypeElem); + boolean alert = XmlReader.parseAlert(fileTypeElem); + return new FileType(mimeType, signature, filesSetName, alert); + } + + /** + * Gets the MIME type from a file type XML element. + * + * @param fileTypeElem The element + * @return A MIME type string. + */ + private static String parseMimeType(Element fileTypeElem) { + return getChildElementTextContent(fileTypeElem, MIME_TYPE_TAG_NAME); + } + + /** + * Gets the signature from a file type XML element. + * + * @param fileTypeElem The XML element. + * @return The signature. + */ + private static Signature parseSignature(Element fileTypeElem) throws IllegalArgumentException, NumberFormatException { + NodeList signatureElems = fileTypeElem.getElementsByTagName(SIGNATURE_TAG_NAME); + Element signatureElem = (Element) signatureElems.item(0); + + String sigTypeAttribute = signatureElem.getAttribute(SIGNATURE_TYPE_ATTRIBUTE); + Signature.Type signatureType = Signature.Type.valueOf(sigTypeAttribute); + + String sigBytesString = getChildElementTextContent(signatureElem, BYTES_TAG_NAME); + byte[] signatureBytes = DatatypeConverter.parseHexBinary(sigBytesString); + + String offsetString = getChildElementTextContent(signatureElem, OFFSET_TAG_NAME); + long offset = DatatypeConverter.parseLong(offsetString); + + return new Signature(signatureBytes, offset, signatureType); + } + + /** + * Gets the interesting files set name from a file type XML element. + * + * @param fileTypeElem The XML element. + * @return The files set name, possibly empty. + */ + private static String parseInterestingFilesSet(Element fileTypeElem) { + String filesSetName = ""; + NodeList filesSetElems = fileTypeElem.getElementsByTagName(INTERESTING_FILES_SET_TAG_NAME); + if (filesSetElems.getLength() > 0) { + Element filesSetElem = (Element) filesSetElems.item(0); + filesSetName = filesSetElem.getTextContent(); + } + return filesSetName; + } + + /** + * Gets the alert attribute from a file type XML element. + * + * @param fileTypeElem The XML element. + * @return True or false; + */ + private static boolean parseAlert(Element fileTypeElem) { + String alertAttribute = fileTypeElem.getAttribute(ALERT_ATTRIBUTE); + return Boolean.parseBoolean(alertAttribute); + } + + /** + * Gets the text content of a single child element. + * + * @param elem The parent element. + * @param tagName The tag name of the child element. + * @return The text content. + */ + private static String getChildElementTextContent(Element elem, String tagName) { + NodeList childElems = elem.getElementsByTagName(tagName); + Element childElem = (Element) childElems.item(0); + return childElem.getTextContent(); + } + + } + + /** + * Logs an exception, bundles the exception with a simple message in a + * uniform exception type, and throws the wrapper exception. + * + * @param ex The exception to wrap. + * @param messageKey A key into the bundle file that maps to the desired + * message. + * @throws + * org.sleuthkit.autopsy.modules.filetypeid.UserDefinedFileTypesManager.UserDefinedFileTypesException + */ + private void throwUserDefinedFileTypesException(Exception ex, String messageKey) throws UserDefinedFileTypesException { + String message = NbBundle.getMessage(UserDefinedFileTypesManager.class, messageKey); + logger.log(Level.SEVERE, message, ex); + throw new UserDefinedFileTypesException(message, ex); + } + + /** + * Used to translate more implementation-details-specific exceptions (which + * are logged by this class) into more generic exceptions for propagation to + * clients of the user-defined file types manager. + */ + static class UserDefinedFileTypesException extends Exception { + + UserDefinedFileTypesException(String message) { + super(message); + } + + UserDefinedFileTypesException(String message, Throwable throwable) { + super(message, throwable); + } + } + +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/user-defined-file-types-settings.png b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/user-defined-file-types-settings.png new file mode 100644 index 0000000000..4dc0c47b17 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/user-defined-file-types-settings.png differ diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/warning16.png b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/warning16.png new file mode 100644 index 0000000000..f5ba881738 Binary files /dev/null and b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/warning16.png differ