/*
* Autopsy Forensic Browser
*
* Copyright 2011-2016 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.modules.filetypeid;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import javax.xml.bind.DatatypeConverter;
import javax.xml.parsers.ParserConfigurationException;
import org.openide.util.io.NbObjectInputStream;
import org.openide.util.io.NbObjectOutputStream;
import org.sleuthkit.autopsy.coreutils.PlatformUtil;
import org.sleuthkit.autopsy.coreutils.XMLUtil;
import org.sleuthkit.autopsy.modules.filetypeid.FileType.Signature;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
/**
* A singleton manager for the custom file types defined by Autopsy and by
* users.
*/
final class CustomFileTypesManager {
private static final String SERIALIZED_SETTINGS_FILE = "UserFileTypeDefinitions.settings"; //NON-NLS
private static final String XML_SETTINGS_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 RELATIVE_ATTRIBUTE = "RelativeToStart"; //NON-NLS
private static CustomFileTypesManager instance;
private final List<FileType> autopsyDefinedFileTypes = new ArrayList<>();
private List<FileType> userDefinedFileTypes = new ArrayList<>();
/**
* Gets the singleton manager of the custom file types defined by Autopsy
* and by users.
*
* @return The custom file types manager singleton.
*
* @throws CustomFileTypesException if there is a problem loading the custom
* file types.
*/
synchronized static CustomFileTypesManager getInstance() throws CustomFileTypesException {
if (null == instance) {
instance = new CustomFileTypesManager();
try {
instance.loadUserDefinedFileTypes();
instance.createAutopsyDefinedFileTypes();
} catch (CustomFileTypesException ex) {
instance = null;
throw ex;
}
}
return instance;
}
/**
* Constructs a manager for the custom file types defined by Autopsy and by
* users.
*/
private CustomFileTypesManager() {
}
/**
* Gets the custom file types defined by Autopsy and by users.
*
* @return A list of custom file types, possibly empty.
*/
synchronized List<FileType> getFileTypes() {
/**
* It is safe to return references instead of copies in this snapshot
* because FileType objects are immutable.
*/
List<FileType> customTypes = new ArrayList<>(userDefinedFileTypes);
customTypes.addAll(autopsyDefinedFileTypes);
return customTypes;
}
/**
* Gets the custom file types defined by Autopsy.
*
* @return A list of custom file types, possibly empty.
*/
synchronized List<FileType> getAutopsyDefinedFileTypes() {
/**
* It is safe to return references instead of copies in this snapshot
* because FileType objects are immutable.
*/
return new ArrayList<>(autopsyDefinedFileTypes);
}
/**
* Gets the user-defined custom file types.
*
* @return A list of file types, possibly empty.
*/
synchronized List<FileType> getUserDefinedFileTypes() {
/**
* It is safe to return references instead of copies in this snapshot
* because FileType objects are immutable.
*/
return new ArrayList<>(userDefinedFileTypes);
}
/**
* Sets the user-defined custom file types.
*
* @param newFileTypes A list of user-defined file types.
*
* @throws CustomFileTypesException if there is a problem setting the file
* types.
*/
synchronized void setUserDefinedFileTypes(List<FileType> newFileTypes) throws CustomFileTypesException {
String filePath = getFileTypeDefinitionsFilePath(SERIALIZED_SETTINGS_FILE);
writeSerializedFileTypes(newFileTypes, filePath);
userDefinedFileTypes = newFileTypes;
}
/**
* Creates the custom file types defined by Autopsy.
*
* @throws CustomFileTypesException if there is a problem creating the file
* types.
*/
private void createAutopsyDefinedFileTypes() throws CustomFileTypesException {
byte[] byteArray;
FileType fileType;
try {
/*
* Add type for xml.
*/
List<Signature> signatureList;
signatureList = new ArrayList<>();
signatureList.add(new Signature("<?xml", 0L)); //NON-NLS
fileType = new FileType("text/xml", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for gzip.
*/
byteArray = DatatypeConverter.parseHexBinary("1F8B"); //NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 0L));
fileType = new FileType("application/x-gzip", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for wk1.
*/
byteArray = DatatypeConverter.parseHexBinary("0000020006040600080000000000"); //NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 0L));
fileType = new FileType("application/x-123", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for Radiance images.
*/
byteArray = DatatypeConverter.parseHexBinary("233F52414449414E43450A");//NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 0L));
fileType = new FileType("image/vnd.radiance", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for dcx images.
*/
byteArray = DatatypeConverter.parseHexBinary("B168DE3A"); //NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 0L));
fileType = new FileType("image/x-dcx", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for ics images.
*/
signatureList.clear();
signatureList.add(new Signature("icns", 0L)); //NON-NLS
fileType = new FileType("image/x-icns", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for pict images.
*/
byteArray = DatatypeConverter.parseHexBinary("001102FF"); //NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 522L));
fileType = new FileType("image/x-pict", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
byteArray = DatatypeConverter.parseHexBinary("1100"); //NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 522L));
fileType = new FileType("image/x-pict", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for pam.
*/
signatureList.clear();
signatureList.add(new Signature("P7", 0L)); //NON-NLS
fileType = new FileType("image/x-portable-arbitrarymap", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for pfm.
*/
signatureList.clear();
signatureList.add(new Signature("PF", 0L)); //NON-NLS
fileType = new FileType("image/x-portable-floatmap", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
signatureList.clear();
signatureList.add(new Signature("Pf", 0L)); //NON-NLS
fileType = new FileType("image/x-portable-floatmap", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for tga.
*/
byteArray = DatatypeConverter.parseHexBinary("54525545564953494F4E2D5846494C452E00"); //NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 17, false));
fileType = new FileType("image/x-tga", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for ilbm.
*/
signatureList.clear();
signatureList.add(new Signature("FORM", 0L)); //NON-NLS
signatureList.add(new Signature("ILBM", 8L)); //NON-NLS
fileType = new FileType("image/x-ilbm", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
signatureList.clear();
signatureList.add(new Signature("FORM", 0L)); //NON-NLS
signatureList.add(new Signature("PBM", 8L)); //NON-NLS
fileType = new FileType("image/x-ilbm", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for webp.
*/
signatureList.clear();
signatureList.add(new Signature("RIFF", 0L)); //NON-NLS
signatureList.add(new Signature("WEBP", 8L)); //NON-NLS
fileType = new FileType("image/webp", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for aiff.
*/
signatureList.clear();
signatureList.add(new Signature("FORM", 0L)); //NON-NLS
signatureList.add(new Signature("AIFF", 8L)); //NON-NLS
fileType = new FileType("audio/aiff", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
signatureList.clear();
signatureList.add(new Signature("FORM", 0L)); //NON-NLS
signatureList.add(new Signature("AIFC", 8L)); //NON-NLS
fileType = new FileType("audio/aiff", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
signatureList.clear();
signatureList.add(new Signature("FORM", 0L)); //NON-NLS
signatureList.add(new Signature("8SVX", 8L)); //NON-NLS
fileType = new FileType("audio/aiff", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for iff.
*/
signatureList.clear();
signatureList.add(new Signature("FORM", 0L)); //NON-NLS
fileType = new FileType("application/x-iff", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
/*
* Add type for .tec files with leading End Of Image marker (JFIF JPEG)
*/
byteArray = DatatypeConverter.parseHexBinary("FFD9FFD8"); //NON-NLS
signatureList.clear();
signatureList.add(new Signature(byteArray, 0L));
fileType = new FileType("image/jpeg", signatureList); //NON-NLS
autopsyDefinedFileTypes.add(fileType);
} catch (IllegalArgumentException ex) {
/*
* parseHexBinary() throws this if the argument passed in is not hex
*/
throw new CustomFileTypesException("Error creating Autopsy defined custom file types", ex); //NON-NLS
}
}
/**
* Loads the custom file types defined by users.
*
* @throws CustomFileTypesException if there is a problem loading the file
* types.
*/
private void loadUserDefinedFileTypes() throws CustomFileTypesException {
userDefinedFileTypes.clear();
String filePath = getFileTypeDefinitionsFilePath(SERIALIZED_SETTINGS_FILE);
if (new File(filePath).exists()) {
userDefinedFileTypes = readSerializedFileTypes(filePath);
} else {
filePath = getFileTypeDefinitionsFilePath(XML_SETTINGS_FILE);
if (new File(filePath).exists()) {
userDefinedFileTypes = readFileTypesXML(filePath);
}
}
}
/**
* Writes serialized custom file types to a file.
*
* @param fileTypes A collection of file types.
* @param filePath The path to the file.
*
* @throws CustomFileTypesException if there is a problem writing the file
* types.
*/
private static void writeSerializedFileTypes(List<FileType> fileTypes, String filePath) throws CustomFileTypesException {
try (NbObjectOutputStream out = new NbObjectOutputStream(new FileOutputStream(filePath))) {
UserDefinedFileTypesSettings settings = new UserDefinedFileTypesSettings(fileTypes);
out.writeObject(settings);
} catch (IOException ex) {
throw new CustomFileTypesException(String.format("Failed to write settings to %s", filePath), ex); //NON-NLS
}
}
/**
* Reads serialized custom file types from a file.
*
* @param filePath The path to the file.
*
* @return The custom file types.
*
* @throws CustomFileTypesException if there is a problem reading the file
* types.
*/
private static List<FileType> readSerializedFileTypes(String filePath) throws CustomFileTypesException {
File serializedDefs = new File(filePath);
try {
try (NbObjectInputStream in = new NbObjectInputStream(new FileInputStream(serializedDefs))) {
UserDefinedFileTypesSettings filesSetsSettings = (UserDefinedFileTypesSettings) in.readObject();
return filesSetsSettings.getUserDefinedFileTypes();
}
} catch (IOException | ClassNotFoundException ex) {
throw new CustomFileTypesException(String.format("Failed to read settings from %s", filePath), ex); //NON-NLS
}
}
/**
* Reads custom file type definitions from an XML file.
*
* @param filePath The path to the file.
*
* @return A collection of custom file types read from the XML file.
*
* @throws IOException if there is problem reading the XML
* file.
* @throws SAXException if there is a problem parsing the
* XML file.
* @throws ParserConfigurationException if there is a problem parsing the
* XML file.
*/
private static List<FileType> readFileTypesXML(String filePath) throws CustomFileTypesException {
try {
List<FileType> fileTypes = new ArrayList<>();
Document doc = XMLUtil.loadDocument(filePath);
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 = parseFileType(fileTypeElem);
fileTypes.add(fileType);
}
}
}
return fileTypes;
} catch (IOException | ParserConfigurationException | SAXException ex) {
throw new CustomFileTypesException(String.format("Failed to read ssettings from %s", filePath), ex); //NON-NLS
}
}
/**
* Gets a custom file type definition from a file type XML element.
*
* @param fileTypeElem The XML element.
*
* @return A file type object.
*
* @throws IllegalArgumentException if there is a problem parsing the file
* type.
* @throws NumberFormatException if there is a problem parsing the file
* type.
*/
private static FileType parseFileType(Element fileTypeElem) throws IllegalArgumentException, NumberFormatException {
String mimeType = parseMimeType(fileTypeElem);
Signature signature = parseSignature(fileTypeElem);
// File type definitions in the XML file were written prior to the
// implementation of multiple signatures per type.
List<Signature> sigList = new ArrayList<>();
sigList.add(signature);
return new FileType(mimeType, sigList);
}
/**
* 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);
Element offsetElem = (Element) signatureElem.getElementsByTagName(OFFSET_TAG_NAME).item(0);
String offsetString = offsetElem.getTextContent();
long offset = DatatypeConverter.parseLong(offsetString);
boolean isRelativeToStart;
String relativeString = offsetElem.getAttribute(RELATIVE_ATTRIBUTE);
if (null == relativeString || relativeString.equals("")) {
isRelativeToStart = true;
} else {
isRelativeToStart = DatatypeConverter.parseBoolean(relativeString);
}
return new Signature(signatureBytes, offset, signatureType, isRelativeToStart);
}
/**
* 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 or null if the tag doesn't exist.
*/
private static String getChildElementTextContent(Element elem, String tagName) {
NodeList childElems = elem.getElementsByTagName(tagName);
Node childNode = childElems.item(0);
if (childNode == null) {
return null;
}
Element childElem = (Element) childNode;
return childElem.getTextContent();
}
/**
* 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();
}
/**
* .An exception thrown by the custom file types manager.
*/
static class CustomFileTypesException extends Exception {
private static final long serialVersionUID = 1L;
CustomFileTypesException(String message) {
super(message);
}
CustomFileTypesException(String message, Throwable throwable) {
super(message, throwable);
}
}
}