diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/cellxml/CellXMLDef.java b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/cellxml/CellXMLDef.java new file mode 100644 index 0000000000..903bf84bde --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/cellxml/CellXMLDef.java @@ -0,0 +1,110 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.experimental.cellex.cellxml; + +public class CellXMLDef { + + public static final String CLLXML_TYPE_ATTR = "type"; + public static final String CLLXML_UUID_ATTR = "UUID"; + public static final String CLLXML_DELETED_ATTR = "deleted"; + + public static final String CLLXML_NAME_TAG = "name"; + public static final String CLLXML_VALUE_TAG = "value"; + public static final String CLLXML_DOMAIN_TAG = "domain"; + public static final String CLLXML_PATH_TAG = "path"; + public static final String CLLXML_CREATED_TAG = "created"; + public static final String CLLXML_EXPIRES_TAG = "expires"; + public static final String CLLXML_DIRECTION_TAG = "direction"; + public static final String CLLXML_TIME_TAG = "time"; + public static final String CLLXML_TIMESTAMP_TAG = "timeStamp"; + public static final String CLLXML_DATETIME_TAG = "dateTime"; + public static final String CLLXML_DESCRIPTION_TAG = "description"; + public static final String CLLXML_ADDRESS_TAG = "address"; + public static final String CLLXML_START_TAG = "start"; + public static final String CLLXML_END_TAG = "end"; + public static final String CLLXML_URL_TAG = "url"; + public static final String CLLXML_URL_NAME_TAG = "urlName"; + public static final String CLLXML_URL_ADDRESS_TAG = "urlAddress"; + public static final String CLLXML_DEVICE_NAME_TAG = "deviceName"; + public static final String CLLXML_DEVICE_ADDRESS_TAG = "deviceAddress"; + public static final String CLLXML_USER_ID_TAG = "userID"; + public static final String CLLXML_PASSWORD_TAG = "password"; + + public static final String CLLXML_CONTACT_TAG = "contact"; + + public static final String CLLXML_DISPLAYNAME_TAG = "displayName"; + public static final String CLLXML_PHONE_NUMBER_TAG = "phoneNumber"; + public static final String CLLXML_PHONE_FORM_TAG = "phoneForm"; + public static final String CLLXML_EMAIL_ADDRESS_TAG = "emailAddress"; + public static final String CLLXML_RFC5322_FORM_TAG = "rfc5322Form"; + public static final String CLLXML_CALL_TAG = "call"; + + public static final String CLLXML_MESSAGE_TAG = "message"; + public static final String CLLXML_SOURCE_TAG = "source"; + public static final String CLLXML_DESTINATION_TAG = "destination"; + public static final String CLLXML_SUBJECT_TAG = "subject"; + public static final String CLLXML_BODY_TAG = "body"; + + public static final String CLLXML_TYPE_HOME = "home"; + public static final String CLLXML_TYPE_WORK = "work"; + public static final String CLLXML_TYPE_MOBILE = "mobile"; + + public static final String CLLXML_MSG_TYPE_SMS = "sms"; + public static final String CLLXML_MSG_TYPE_MMS = "mms"; + public static final String CLLXML_MSG_TYPE_EMAIL = "email"; + + public static final String CLLXML_CALENDAR_ENTRY_TAG = "calendarEntry"; + + public static final String CLLXML_INTERNET_BOOKMARK_TAG = "internetBookmark"; + public static final String CLLXML_INTERNET_HISTORY_TAG = "internetHistoryEntry"; + + public static final String CLLXML_COOKIE_TAG = "cookie"; + + public static final String CLLXML_SPEEDDIAL_ENTRY_TAG = "speedDialEntry"; + public static final String CLLXML_DIALCODE_TAG = "dialCode"; + + public static final String CLLXML_BLUETOOTH_PAIRING_TAG = "bluetoothPairing"; + + public static final String CLLXML_GEO_LOCATION_TAG = "geoLocation"; + public static final String CLLXML_LATITUDE_TAG = "latitude"; + public static final String CLLXML_LONGITUDE_TAG = "longitude"; + public static final String CLLXML_ALTITUDE_TAG = "altitude"; + + public static final String CLLXML_GPS_FAVORITES_TAG = "gpsFavorites"; + public static final String CLLXML_GPS_SEARCHES_TAG = "gpsSearches"; + public static final String CLLXML_GPS_LAST_KNOWN_LOCATION_TAG = "gpsLastKnownLocation"; + public static final String CLLXML_GPS_TRACKPOINTS_TAG = "gpsTrackpoints"; + + public static final String CLLXML_APPLICATION_ACCOUNT_TAG = "applicationAccount"; + public static final String CLLXML_USER_ACCOUNT_INFO_TAG = "userAccountInfo"; + public static final String CLLXML_APPLICATION_INFO_TAG = "applicationInfo"; + public static final String CLLXML_EMAIL_ACCOUNT_INFO_TAG = "emailAccountInfo"; + + public static final String CLLXML_APPLICATION_NAME_TAG = "applicationName"; + public static final String CLLXML_APPLICATION_URL_TAG = "applicationURL"; + public static final String CLLXML_INSTALL_PATH_TAG = "installPath"; + + public static final String CLLXML_MAILBOX_NAME_TAG = "mailboxName"; + public static final String CLLXML_REPLY_ADDRESS_TAG = "replyToAddress"; + public static final String CLLXML_MAILSERVER_TAG = "mailServer"; + + public static final String CLLXML_EVENTLOG_ENTRY_TAG = "eventLogEntry"; + public static final String CLLXML_EVENT_DESCRIPTION_TAG = "eventDescription"; + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/cellxml/CellXMLParser.java b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/cellxml/CellXMLParser.java new file mode 100644 index 0000000000..ef9414450d --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/cellxml/CellXMLParser.java @@ -0,0 +1,1700 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.experimental.cellex.cellxml; + +import java.io.FileInputStream; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.TimeZone; +import java.util.logging.Level; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.services.Blackboard; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.datamodel.AbstractContent; +import org.sleuthkit.datamodel.BlackboardArtifact; +import org.sleuthkit.datamodel.BlackboardAttribute; +import org.sleuthkit.datamodel.TskCoreException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +/** + * + * + */ +public class CellXMLParser { + + private static final Logger logger = Logger.getLogger(CellXMLParser.class.getName()); + private static CellXMLParser defaultInstance = null; + + private static int m_eventLogArtifactID = -1; + public static final String EVENT_LOG_ARTIFACT_NAME = "EVENT_LOG_ENTRY"; + public static final String EVENT_LOG_ARTIFACT_DISPLAY_NAME = "Event Log Entry"; + + public static final String DATETIME_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ssX"; // Note: the trailing X that parses the 8601 specification of timezone is available only from Java 7 onwards + + public CellXMLParser() { + + CreatePrivateArtifactsAttributes(); + } + + public static synchronized CellXMLParser getDefault() { + if (defaultInstance == null) { + defaultInstance = new CellXMLParser(); + } + return defaultInstance; + } + + private void CreatePrivateArtifactsAttributes() { + /** + * ** + * + * if (-1 == m_eventLogArtifactID) { + * + * try { m_eventLogArtifactID = + * services.getCurrentSleuthkitCaseDb().addArtifactType(EVENT_LOG_ARTIFACT_NAME, + * EVENT_LOG_ARTIFACT_DISPLAY_NAME); logger.log(Level.INFO, + * "CreatePrivateArtifactsAttributes: Private Artifact: " + + * EVENT_LOG_ARTIFACT_NAME + " added successfully!"); + * + * + * }catch (TskCoreException e) { //error reading file + * logger.log(Level.SEVERE, "CreatePrivateArtifactsAttributes: Failed to + * create private artifact: " + EVENT_LOG_ARTIFACT_NAME + " Error = ", + * e); + * + * } + * } + * else { logger.log(Level.INFO, "CreatePrivateArtifactsAttributes: + * artifact: " + EVENT_LOG_ARTIFACT_NAME + " already defined. ID = ", + * m_eventLogArtifactID); + * + * } + * + * return; *** + */ + + } + + public void Process(String cellXMLInputFilePath, AbstractContent content, String aModuleName) { + + if (cellXMLInputFilePath == null || cellXMLInputFilePath.isEmpty()) { + return; + } + + logger.log(Level.FINER, "Process(): Will process CellXML file: " + cellXMLInputFilePath); + + java.io.File cellxmlFile = new java.io.File(cellXMLInputFilePath); + if (cellxmlFile.exists()) { + Document cllxmlDoc = LoadCLLXML(cellXMLInputFilePath); + if (null != cllxmlDoc) { + ParseCellXML(cllxmlDoc, content, aModuleName); + } else { + logger.log(Level.SEVERE, "LoadCLLXML() failed."); + } + } else { + logger.log(Level.SEVERE, "CellXML file: " + cellXMLInputFilePath + " not found."); + } + + return; + + } + + // Loads the CLLXML file into a doc + private Document LoadCLLXML(String xmlFilePath) { + + Document doc = null; + + // Ideally we should use XMLUtil to read in the XML but currently + // it doesnt read files without needing a schema to validate against + DocumentBuilderFactory builderFactory + = DocumentBuilderFactory.newInstance(); + + try { + DocumentBuilder builder = builderFactory.newDocumentBuilder(); + doc = builder.parse(new FileInputStream(xmlFilePath)); + + doc.getDocumentElement().normalize(); + + } catch (ParserConfigurationException e) { + logger.log(Level.SEVERE, "Error loading XML file: " + xmlFilePath + " Can't initialize parser.", e); + + } catch (SAXException e) { + logger.log(Level.SEVERE, "Error loading XML file: " + xmlFilePath + " Can't parse XML.", e); + + } catch (IOException e) { + //error reading file + logger.log(Level.SEVERE, "Error loading XML file: " + xmlFilePath + " Can't read file.", e); + + } + + if (null != doc) { + logger.log(Level.INFO, "CLLXML file: " + xmlFilePath + " loaded successfully!!."); + } + + return doc; + } + + // Process the CLLXML Output produced by MPF and + // create BlackBoard entries from the data + private void ParseCellXML(Document aCLLXMLDoc, AbstractContent abstractContent, String aModuleName) { + + if (null == aCLLXMLDoc) { + return; + } + + ProcessContacts(aCLLXMLDoc, abstractContent, aModuleName); + ProcessCalls(aCLLXMLDoc, abstractContent, aModuleName); + ProcessMessages(aCLLXMLDoc, abstractContent, aModuleName); + ProcessCalendarEntries(aCLLXMLDoc, abstractContent, aModuleName); + + ProcessInternetBookmarks(aCLLXMLDoc, abstractContent, aModuleName); + ProcessInternetHistory(aCLLXMLDoc, abstractContent, aModuleName); + ProcessCookies(aCLLXMLDoc, abstractContent, aModuleName); + + ProcessSpeedDialEntries(aCLLXMLDoc, abstractContent, aModuleName); + + ProcessBluetoothEntries(aCLLXMLDoc, abstractContent, aModuleName); + + //ProcessEventLogEntries(aCLLXMLDoc, abstractContent); + ProcessGPSFavorites(aCLLXMLDoc, abstractContent, aModuleName); + ProcessGPSSearches(aCLLXMLDoc, abstractContent, aModuleName); + ProcessGPSLastKnownLocation(aCLLXMLDoc, abstractContent, aModuleName); + + ProcessApplicationAccounts(aCLLXMLDoc, abstractContent, aModuleName); + + } + + private long GetSecsSinceEpochFrom8601TimeStamp(String aTimeStamp) { + if (null == aTimeStamp) { + logger.log(Level.WARNING, "GetSecsSinceEpochFrom8601TimeStamp(): aTimeStamp is null!"); + return 0; + } + + SimpleDateFormat ISO8601DATEFORMAT = new SimpleDateFormat(DATETIME_FORMAT_STRING); + ISO8601DATEFORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); + try { + Date date = ISO8601DATEFORMAT.parse(aTimeStamp); + long millisecsEpoch = date.getTime(); + long secsEpoch = millisecsEpoch / 1000; + + return secsEpoch; + } catch (Exception ex) { + logger.log(Level.WARNING, "GetSecsSinceEpochFrom8601TimeStamp: Failed to parse 8601 timestamp string. (" + ex.getLocalizedMessage() + ")."); + return 0; + } + + } + + private static String GetXMLElemValue(String tag, Element element) { + + //logger.log(Level.INFO, "GetXMLElemValue(): Looking for tag : " + tag ); + String retStr = null; + NodeList nodeList = element.getElementsByTagName(tag); + + if (nodeList.getLength() > 0) { + Element node = (Element) nodeList.item(0); + //retStr = node.getNodeValue(); + retStr = node.getTextContent(); + if (null == retStr) { + logger.log(Level.FINER, "GetXMLElemValue(): getNodeValue() returned NULL for tag = " + tag); + } + } + + return retStr; + } + + /* + * If the given node has a "deleted" flag, create a TSK_ISDELETED attribute + * and append to the given set of attributes + */ + private boolean FlagDeletedContent(Element node, Collection attributes, String aModuleName) { + String deletedFlag = node.getAttribute(CellXMLDef.CLLXML_DELETED_ATTR); + if (null != deletedFlag) { + if (deletedFlag.equalsIgnoreCase("true")) { + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ISDELETED, aModuleName, "yes")); + return true; + } + } + + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ISDELETED, aModuleName, "")); + return false; + } + + private void ProcessContacts(Document aCLLXMLDoc, AbstractContent abstractContent, String aModuleName) { + + Element rootElem = aCLLXMLDoc.getDocumentElement(); + NodeList contactnodes = rootElem.getElementsByTagName(CellXMLDef.CLLXML_CONTACT_TAG); + + int len = contactnodes.getLength(); + logger.log(Level.INFO, "ProcessContacts(): Found " + Integer.toString(len) + " contacts !!."); + + for (int c = 0; c < len; c++) { + + Collection attributes = new ArrayList(); + + Element contactNode = (Element) contactnodes.item(c); + + String idValue = contactNode.getAttribute(CellXMLDef.CLLXML_UUID_ATTR); + + //logger.log(Level.FINER, "ProcessContacts(): Found Contact with UUID : " + idValue ); + // Add a TSK_ISDELETED attribute if the artifact is deleted + FlagDeletedContent(contactNode, attributes, aModuleName); + + // Get the name + String name = null; + NodeList nameNodes = contactNode.getElementsByTagName(CellXMLDef.CLLXML_NAME_TAG); + if (nameNodes.getLength() > 0) { + Element nameNode = (Element) nameNodes.item(0); + + name = GetXMLElemValue(CellXMLDef.CLLXML_DISPLAYNAME_TAG, nameNode); + if (null != name) { + logger.log(Level.FINER, "ProcessContacts(): Found Name = " + name); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, aModuleName, name)); + } else { + logger.log(Level.FINER, "ProcessContacts(): Failed to get Name."); + } + + } + + // Get phone numbers, there may be more than 1 + NodeList phoneNumberNodes = contactNode.getElementsByTagName(CellXMLDef.CLLXML_PHONE_NUMBER_TAG); + for (int p = 0; p < phoneNumberNodes.getLength(); p++) { + Element phNumberNode = (Element) phoneNumberNodes.item(p); + + String phNumType = phNumberNode.getAttribute(CellXMLDef.CLLXML_TYPE_ATTR); + String phNum = null; + + phNum = GetXMLElemValue(CellXMLDef.CLLXML_PHONE_FORM_TAG, phNumberNode); + if (null != phNum) { + logger.log(Level.FINER, "ProcessContacts(): Found Phone Num = " + phNum + " type = " + phNumType); + + // distinguish between different types - work, home, mobile + BlackboardAttribute.ATTRIBUTE_TYPE attrType; + + if (phNumType.equalsIgnoreCase(CellXMLDef.CLLXML_TYPE_HOME)) { + attrType = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_HOME; + } else if (phNumType.equalsIgnoreCase(CellXMLDef.CLLXML_TYPE_WORK)) { + attrType = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_OFFICE; + } else if (phNumType.equalsIgnoreCase(CellXMLDef.CLLXML_TYPE_MOBILE)) { + attrType = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_MOBILE; + } else { + attrType = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER; + } + + attributes.add(new BlackboardAttribute(attrType, aModuleName, phNum)); + + } else { + logger.log(Level.FINER, "ProcessContacts(): Failed to get phoneForm."); + } + } + + // Get email addresses, there may be more than 1 + NodeList emailAddrNodes = contactNode.getElementsByTagName(CellXMLDef.CLLXML_EMAIL_ADDRESS_TAG); + for (int e = 0; e < emailAddrNodes.getLength(); e++) { + Element emailAddrNode = (Element) emailAddrNodes.item(e); + + String emailType = emailAddrNode.getAttribute(CellXMLDef.CLLXML_TYPE_ATTR); + String email = null; + + email = GetXMLElemValue(CellXMLDef.CLLXML_RFC5322_FORM_TAG, emailAddrNode); + if (null != email) { + logger.log(Level.FINER, "ProcessContacts(): Found Email addr = " + email + " type = " + emailType); + + // distinguish between different types - work, home, mobile + BlackboardAttribute.ATTRIBUTE_TYPE attrType; + if (emailType.equalsIgnoreCase(CellXMLDef.CLLXML_TYPE_HOME)) { + attrType = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_HOME; + } else if (emailType.equalsIgnoreCase(CellXMLDef.CLLXML_TYPE_WORK)) { + attrType = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_OFFICE; + } else { + attrType = BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL; + } + + attributes.add(new BlackboardAttribute(attrType, aModuleName, email)); + + } else { + logger.log(Level.FINER, "ProcessContacts(): Failed to get Email."); + } + } + + try { + + // Create a CONTACT Artifact + // Add the attributes, if there are any + if (!attributes.isEmpty()) { + BlackboardArtifact bba = abstractContent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT); + bba.addAttributes(attributes); + indexArtifact(bba); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Failed to create blackboard artifact for Contact. (" + ex.getLocalizedMessage() + ")."); + } + + } // for each contact + + } + + private void ProcessSpeedDialEntries(Document aCLLXMLDoc, AbstractContent abstractContent, String aModuleName) { + + Element rootElem = aCLLXMLDoc.getDocumentElement(); + NodeList speedDialEntryNodes = rootElem.getElementsByTagName(CellXMLDef.CLLXML_SPEEDDIAL_ENTRY_TAG); + + int len = speedDialEntryNodes.getLength(); + logger.log(Level.INFO, "ProcessSpeedDialEntries(): Found " + Integer.toString(len) + " SpeedDial entries !!."); + + for (int c = 0; c < len; c++) { + + Collection attributes = new ArrayList(); + + Element speedDialEntryNode = (Element) speedDialEntryNodes.item(c); + + // Add a TSK_ISDELETED attribute if the artifact is deleted + FlagDeletedContent(speedDialEntryNode, attributes, aModuleName); + + // get dialCode + String dialCode = null; + dialCode = GetXMLElemValue(CellXMLDef.CLLXML_DIALCODE_TAG, speedDialEntryNode); + if (null != dialCode) { + logger.log(Level.FINER, "ProcessSpeedDialEntries(): Found dialCode = " + dialCode); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SHORTCUT, aModuleName, dialCode)); + } else { + logger.log(Level.FINER, "ProcessSpeedDialEntries(): Failed to get dialCode."); + } + + // Get the display name, if there is one + String name = null; + name = GetXMLElemValue(CellXMLDef.CLLXML_DISPLAYNAME_TAG, speedDialEntryNode); + if (null != name) { + logger.log(Level.FINER, "ProcessSpeedDialEntries(): Found Name = " + name); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME_PERSON, aModuleName, name)); + } else { + logger.log(Level.FINER, "ProcessSpeedDialEntries(): Failed to get Name."); + } + + // Get phone number + NodeList phoneNumberNodes = speedDialEntryNode.getElementsByTagName(CellXMLDef.CLLXML_PHONE_NUMBER_TAG); + if (phoneNumberNodes.getLength() > 0) { + String phNum = null; + + Element phNumberNode = (Element) phoneNumberNodes.item(0); + phNum = GetXMLElemValue(CellXMLDef.CLLXML_PHONE_FORM_TAG, phNumberNode); + if (null != phNum) { + logger.log(Level.FINER, "ProcessSpeedDialEntries(): Found Phone Num = " + phNum); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, aModuleName, phNum)); + + } else { + logger.log(Level.FINER, "ProcessSpeedDialEntries(): Failed to get phoneForm."); + } + } + + try { + // Create a SpeedDial Entry Artifact + // Add the attributes, if there are any + if (!attributes.isEmpty()) { + BlackboardArtifact bba = abstractContent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_SPEED_DIAL_ENTRY); + bba.addAttributes(attributes); + indexArtifact(bba); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "ProcessSpeedDialEntries(): Failed to create blackboard artifact for Cookie. (" + ex.getLocalizedMessage() + ")."); + } + + } // for each speed dial entry + + } + + private void ProcessCalls(Document aCLLXMLDoc, AbstractContent abstractContent, String aModuleName) { + + Element rootElem = aCLLXMLDoc.getDocumentElement(); + NodeList callnodes = rootElem.getElementsByTagName(CellXMLDef.CLLXML_CALL_TAG); + + int len = callnodes.getLength(); + logger.log(Level.INFO, "ProcessCalls(): Found " + Integer.toString(len) + " calls !!."); + + for (int c = 0; c < len; c++) { + + Collection attributes = new ArrayList(); + + Element callNode = (Element) callnodes.item(c); + + // String idValue = callNode.getAttribute(CellXMLDef.CLLXML_UUID_ATTR); + // Add a TSK_ISDELETED attribute if the artifact is deleted + FlagDeletedContent(callNode, attributes, aModuleName); + + // Get the display name, if there is one + String name = null; + name = GetXMLElemValue(CellXMLDef.CLLXML_DISPLAYNAME_TAG, callNode); + if (null != name) { + logger.log(Level.FINER, "ProcessCalls(): Found Name = " + name); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, aModuleName, name)); + } else { + logger.log(Level.FINER, "ProcessCalls(): Failed to get Name."); + } + + // Get phone number + NodeList phoneNumberNodes = callNode.getElementsByTagName(CellXMLDef.CLLXML_PHONE_NUMBER_TAG); + if (phoneNumberNodes.getLength() > 0) { + String phNum = null; + + Element phNumberNode = (Element) phoneNumberNodes.item(0); + phNum = GetXMLElemValue(CellXMLDef.CLLXML_PHONE_FORM_TAG, phNumberNode); + if (null != phNum) { + logger.log(Level.FINER, "ProcessCalls(): Found Phone Num = " + phNum); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, aModuleName, phNum)); + + } else { + logger.log(Level.FINER, "ProcessCalls(): Failed to get phoneForm."); + } + } + + // Get timestamp + NodeList timeNodes = callNode.getElementsByTagName(CellXMLDef.CLLXML_TIME_TAG); + if (timeNodes.getLength() > 0) { + String timeStampStr = null; + + Element timeNode = (Element) timeNodes.item(0); + timeStampStr = GetXMLElemValue(CellXMLDef.CLLXML_DATETIME_TAG, timeNode); + if (null != timeStampStr) { + logger.log(Level.FINER, "ProcessCalls(): Found timeStamp = " + timeStampStr); + + long secsEpoch = GetSecsSinceEpochFrom8601TimeStamp(timeStampStr); + if (secsEpoch > 0) { + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START, aModuleName, secsEpoch)); + } + + } else { + logger.log(Level.FINER, "ProcessCalls(): Failed to get timeStamp."); + } + } + + // get direction + String direction = null; + direction = GetXMLElemValue(CellXMLDef.CLLXML_DIRECTION_TAG, callNode); + if (null != direction) { + logger.log(Level.FINER, "ProcessCalls(): Found direction = " + direction); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, aModuleName, direction)); + } else { + logger.log(Level.FINER, "ProcessCalls(): Failed to get direction."); + } + + try { + + // Create a Calllog Artifact + // Add the attributes, if there are any + if (!attributes.isEmpty()) { + BlackboardArtifact bba = abstractContent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CALLLOG); + bba.addAttributes(attributes); + indexArtifact(bba); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "Failed to create blackboard artifact for Call. (" + ex.getLocalizedMessage() + ")."); + } + + } // for each call + + } + + private void ProcessMessages(Document aCLLXMLDoc, AbstractContent abstractContent, String aModuleName) { + + Element rootElem = aCLLXMLDoc.getDocumentElement(); + NodeList messagenodes = rootElem.getElementsByTagName(CellXMLDef.CLLXML_MESSAGE_TAG); + + int len = messagenodes.getLength(); + logger.log(Level.INFO, "ProcessMessages(): Found " + Integer.toString(len) + " messages !!."); + + for (int c = 0; c < len; c++) { + + Collection attributes = new ArrayList(); + + Element messageNode = (Element) messagenodes.item(c); + + // String idValue = callNode.getAttribute(CellXMLDef.CLLXML_UUID_ATTR); + // Add a TSK_ISDELETED attribute if the artifact is deleted + FlagDeletedContent(messageNode, attributes, aModuleName); + + String msgType = messageNode.getAttribute(CellXMLDef.CLLXML_TYPE_ATTR); + if (null != msgType) { + logger.log(Level.FINER, "ProcessMessages(): Found message of type = " + msgType); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_MESSAGE_TYPE, aModuleName, msgType)); + + } + + // get direction + String direction = null; + direction = GetXMLElemValue(CellXMLDef.CLLXML_DIRECTION_TAG, messageNode); + if (null != direction) { + logger.log(Level.FINER, "ProcessMessages(): Found direction = " + direction); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DIRECTION, aModuleName, direction)); + } else { + logger.log(Level.FINER, "ProcessMessages(): Failed to get direction."); + } + + // Get source phonenumber/email + NodeList sourceNodes = messageNode.getElementsByTagName(CellXMLDef.CLLXML_SOURCE_TAG); + if (sourceNodes.getLength() > 0) { + Element sourceNode = (Element) sourceNodes.item(0); + + // Get source phone number + NodeList phoneNumberNodes = sourceNode.getElementsByTagName(CellXMLDef.CLLXML_PHONE_NUMBER_TAG); + if (phoneNumberNodes.getLength() > 0) { + String phNum = null; + + Element phNumberNode = (Element) phoneNumberNodes.item(0); + phNum = GetXMLElemValue(CellXMLDef.CLLXML_PHONE_FORM_TAG, phNumberNode); + if (null != phNum) { + logger.log(Level.FINER, "ProcessMessages(): Found Phone Num = " + phNum); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_FROM, aModuleName, phNum)); + + } else { + logger.log(Level.FINER, "ProcessMessages(): Failed to get phoneForm."); + } + } + + // Get source email + NodeList emailNodes = sourceNode.getElementsByTagName(CellXMLDef.CLLXML_EMAIL_ADDRESS_TAG); + if (emailNodes.getLength() > 0) { + String emailAddr = null; + + Element emailNode = (Element) emailNodes.item(0); + emailAddr = GetXMLElemValue(CellXMLDef.CLLXML_RFC5322_FORM_TAG, emailNode); + if (null != emailAddr) { + logger.log(Level.FINER, "ProcessMessages(): Found Source email addr = " + emailAddr); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_FROM, aModuleName, emailAddr)); + + } else { + logger.log(Level.FINER, "ProcessMessages(): Failed to get source email."); + } + } + + } + + // Get desination phonenumber/email + NodeList destinationNodes = messageNode.getElementsByTagName(CellXMLDef.CLLXML_DESTINATION_TAG); + if (destinationNodes.getLength() > 0) { + Element destNode = (Element) destinationNodes.item(0); + + // Get destination phone number + NodeList phoneNumberNodes = destNode.getElementsByTagName(CellXMLDef.CLLXML_PHONE_NUMBER_TAG); + if (phoneNumberNodes.getLength() > 0) { + String phNum = null; + + Element phNumberNode = (Element) phoneNumberNodes.item(0); + phNum = GetXMLElemValue(CellXMLDef.CLLXML_PHONE_FORM_TAG, phNumberNode); + if (null != phNum) { + logger.log(Level.FINER, "ProcessMessages(): Found Phone Num = " + phNum); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER_TO, aModuleName, phNum)); + + } else { + logger.log(Level.FINER, "ProcessMessages(): Failed to get phoneForm."); + } + } + + // Get destination email + NodeList emailNodes = destNode.getElementsByTagName(CellXMLDef.CLLXML_EMAIL_ADDRESS_TAG); + if (emailNodes.getLength() > 0) { + String emailAddr = null; + + Element emailNode = (Element) emailNodes.item(0); + emailAddr = GetXMLElemValue(CellXMLDef.CLLXML_RFC5322_FORM_TAG, emailNode); + if (null != emailAddr) { + logger.log(Level.FINER, "ProcessMessages(): Found dest email addr = " + emailAddr); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_TO, aModuleName, emailAddr)); + + } else { + logger.log(Level.FINER, "ProcessMessages(): Failed to dest email."); + } + } + } + + // Get timestamp + NodeList timeNodes = messageNode.getElementsByTagName(CellXMLDef.CLLXML_TIME_TAG); + if (timeNodes.getLength() > 0) { + String timeStampStr = null; + + Element timeNode = (Element) timeNodes.item(0); + timeStampStr = GetXMLElemValue(CellXMLDef.CLLXML_DATETIME_TAG, timeNode); + if (null != timeStampStr) { + logger.log(Level.FINER, "ProcessMessages(): Found timeStamp = " + timeStampStr); + + long secsEpoch = GetSecsSinceEpochFrom8601TimeStamp(timeStampStr); + if (secsEpoch > 0) { + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, aModuleName, secsEpoch)); + } + + } else { + logger.log(Level.FINER, "ProcessMessages(): Failed to get timeStamp."); + } + } + + // get subject + String subject = null; + subject = GetXMLElemValue(CellXMLDef.CLLXML_SUBJECT_TAG, messageNode); + if (null != subject) { + logger.log(Level.FINER, "ProcessMessages(): Found subject = " + subject); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SUBJECT, aModuleName, subject)); + } else { + logger.log(Level.FINER, "ProcessMessages(): Failed to get subject."); + } + + // get body + String msgBody = null; + msgBody = GetXMLElemValue(CellXMLDef.CLLXML_BODY_TAG, messageNode); + if (null != msgBody) { + logger.log(Level.FINER, "ProcessMessages(): Found msgBody = " + msgBody); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_TEXT, aModuleName, msgBody)); + } else { + logger.log(Level.FINER, "ProcessMessages(): Failed to get msg body."); + } + + try { + + // Create a Message Artifact + // Add the attributes, if there are any + if (!attributes.isEmpty()) { + BlackboardArtifact bba = abstractContent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE); + bba.addAttributes(attributes); + indexArtifact(bba); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "ProcessMessages(): Failed to create blackboard artifact for Message. (" + ex.getLocalizedMessage() + ")."); + } + + } // for each message + + } + + private void ProcessCalendarEntries(Document aCLLXMLDoc, AbstractContent abstractContent, String aModuleName) { + + Element rootElem = aCLLXMLDoc.getDocumentElement(); + NodeList calendarNodes = rootElem.getElementsByTagName(CellXMLDef.CLLXML_CALENDAR_ENTRY_TAG); + + int len = calendarNodes.getLength(); + logger.log(Level.INFO, "ProcessCalendarEntries(): Found " + Integer.toString(len) + " calendar entries !!."); + + for (int c = 0; c < len; c++) { + + Collection attributes = new ArrayList(); + + Element calNode = (Element) calendarNodes.item(c); + + // Add a TSK_ISDELETED attribute if the artifact is deleted + FlagDeletedContent(calNode, attributes, aModuleName); + + // String idValue = callNode.getAttribute(CellXMLDef.CLLXML_UUID_ATTR); + String calType = calNode.getAttribute(CellXMLDef.CLLXML_TYPE_ATTR); + + if (null != calType) { + logger.log(Level.FINER, "ProcessCalendarEntries(): Found calendar entry of type = " + calType); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CALENDAR_ENTRY_TYPE, aModuleName, calType)); + + } + + // get description + String description = null; + description = GetXMLElemValue(CellXMLDef.CLLXML_DESCRIPTION_TAG, calNode); + if (null != description) { + logger.log(Level.FINER, "ProcessCalendarEntries(): Found direction = " + description); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION, aModuleName, description)); + } else { + logger.log(Level.WARNING, "ProcessCalendarEntries(): Failed to get direction."); + } + + // Get Start time + NodeList startTimeNodes = calNode.getElementsByTagName(CellXMLDef.CLLXML_START_TAG); + if (startTimeNodes.getLength() > 0) { + String startTimeStampStr = null; + + Element starttimeNode = (Element) startTimeNodes.item(0); + startTimeStampStr = GetXMLElemValue(CellXMLDef.CLLXML_DATETIME_TAG, starttimeNode); + if (null != startTimeStampStr) { + logger.log(Level.FINER, "ProcessCalendarEntries(): Found start timeStamp = " + startTimeStampStr); + + long secsEpoch = GetSecsSinceEpochFrom8601TimeStamp(startTimeStampStr); + if (secsEpoch > 0) { + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START, aModuleName, secsEpoch)); + } + + } else { + logger.log(Level.WARNING, "ProcessCalendarEntries(): Failed to get start timeStamp."); + } + } + + // Get end time + NodeList endTimeNodes = calNode.getElementsByTagName(CellXMLDef.CLLXML_END_TAG); + if (endTimeNodes.getLength() > 0) { + String endTimeStampStr = null; + + Element endtimeNode = (Element) endTimeNodes.item(0); + endTimeStampStr = GetXMLElemValue(CellXMLDef.CLLXML_DATETIME_TAG, endtimeNode); + if (null != endTimeStampStr) { + logger.log(Level.FINER, "ProcessCalendarEntries(): Found end timeStamp = " + endTimeStampStr); + + long secsEpoch = GetSecsSinceEpochFrom8601TimeStamp(endTimeStampStr); + if (secsEpoch > 0) { + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_END, aModuleName, secsEpoch)); + } + } else { + logger.log(Level.WARNING, "ProcessCalendarEntries(): Failed to get end timeStamp."); + } + } + + try { + + // Create a Calendar entry Artifact + // Add the attributes, if there are any + if (!attributes.isEmpty()) { + BlackboardArtifact bba = abstractContent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_CALENDAR_ENTRY); + bba.addAttributes(attributes); + indexArtifact(bba); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "ProcessCalendarEntries(): Failed to create blackboard artifact for Calendar Entry. (" + ex.getLocalizedMessage() + ")."); + } + + } // for each cal entry + + } + + private void ProcessBluetoothEntries(Document aCLLXMLDoc, AbstractContent abstractContent, String aModuleName) { + + Element rootElem = aCLLXMLDoc.getDocumentElement(); + NodeList bluetoothNodes = rootElem.getElementsByTagName(CellXMLDef.CLLXML_BLUETOOTH_PAIRING_TAG); + + int len = bluetoothNodes.getLength(); + logger.log(Level.INFO, "ProcessBluetoothEntries(): Found " + Integer.toString(len) + " Bluetooth entries !!."); + + for (int c = 0; c < len; c++) { + + Collection attributes = new ArrayList(); + + Element bluetoothEntryNode = (Element) bluetoothNodes.item(c); + + // Add a TSK_ISDELETED attribute if the artifact is deleted + FlagDeletedContent(bluetoothEntryNode, attributes, aModuleName); + + // get deviceName + String deviceName = null; + deviceName = GetXMLElemValue(CellXMLDef.CLLXML_DEVICE_NAME_TAG, bluetoothEntryNode); + if (null != deviceName) { + logger.log(Level.FINER, "ProcessBluetoothEntries(): Found device name = " + deviceName); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_NAME, aModuleName, deviceName)); + } else { + logger.log(Level.FINER, "ProcessBluetoothEntries(): Failed to get dialCode."); + } + + // Get the display name, if there is one + String deviceID = null; + deviceID = GetXMLElemValue(CellXMLDef.CLLXML_DEVICE_ADDRESS_TAG, bluetoothEntryNode); + if (null != deviceID) { + logger.log(Level.FINER, "ProcessBluetoothEntries(): Found device id = " + deviceID); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_ID, aModuleName, deviceID)); + } else { + logger.log(Level.FINER, "ProcessBluetoothEntries(): Failed to get deviceID."); + } + + // Get time + NodeList timeNodes = bluetoothEntryNode.getElementsByTagName(CellXMLDef.CLLXML_TIME_TAG); + if (timeNodes.getLength() > 0) { + String timeStampStr = null; + + Element timeNode = (Element) timeNodes.item(0); + timeStampStr = GetXMLElemValue(CellXMLDef.CLLXML_DATETIME_TAG, timeNode); + if (null != timeStampStr) { + logger.log(Level.FINER, "ProcessBluetoothEntries(): Found timeStamp = " + timeStampStr); + + long secsEpoch = GetSecsSinceEpochFrom8601TimeStamp(timeStampStr); + if (secsEpoch > 0) { + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, aModuleName, secsEpoch)); + } + } else { + logger.log(Level.FINER, "ProcessBluetoothEntries(): Failed to get create timeStamp."); + } + } + + try { + // Create a SpeedDial Entry Artifact + // Add the attributes, if there are any + if (!attributes.isEmpty()) { + BlackboardArtifact bba = abstractContent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_BLUETOOTH_PAIRING); + bba.addAttributes(attributes); + indexArtifact(bba); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "ProcessBluetoothEntries(): Failed to create blackboard artifact for Bluetooth entry. (" + ex.getLocalizedMessage() + ")."); + } + + } // for each cookie + + } + + private void ProcessEventLogEntries(Document aCLLXMLDoc, AbstractContent abstractContent, String aModuleName) { + + Element rootElem = aCLLXMLDoc.getDocumentElement(); + NodeList eventLogNodes = rootElem.getElementsByTagName(CellXMLDef.CLLXML_EVENTLOG_ENTRY_TAG); + + int len = eventLogNodes.getLength(); + logger.log(Level.INFO, "ProcessEventLogEntries(): Found " + Integer.toString(len) + " EventLog entries !!."); + + for (int c = 0; c < len; c++) { + + Collection attributes = new ArrayList(); + + Element eventLogNode = (Element) eventLogNodes.item(c); + + // Add a TSK_ISDELETED attribute if the artifact is deleted + FlagDeletedContent(eventLogNode, attributes, aModuleName); + + String eventType = eventLogNode.getAttributes().getNamedItem("type").getNodeValue(); + if (null != eventType) { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Found eventType = " + eventType); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY, aModuleName, eventType)); + + } else { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Failed to get userID."); + } + + // get description + String eventDescription = null; + eventDescription = GetXMLElemValue(CellXMLDef.CLLXML_EVENT_DESCRIPTION_TAG, eventLogNode); + if (null != eventDescription) { + logger.log(Level.FINER, "ProcessEventLogEntries(): Found event description = " + eventDescription); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION, aModuleName, eventDescription)); + } else { + logger.log(Level.FINER, "ProcessEventLogEntries(): Failed to get eventDescription."); + } + + // Get time + NodeList timeNodes = eventLogNode.getElementsByTagName(CellXMLDef.CLLXML_TIME_TAG); + if (timeNodes.getLength() > 0) { + String timeStampStr = null; + + Element timeNode = (Element) timeNodes.item(0); + timeStampStr = GetXMLElemValue(CellXMLDef.CLLXML_DATETIME_TAG, timeNode); + if (null != timeStampStr) { + logger.log(Level.FINER, "ProcessEventLogEntries(): Found timeStamp = " + timeStampStr); + + long secsEpoch = GetSecsSinceEpochFrom8601TimeStamp(timeStampStr); + if (secsEpoch > 0) { + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, aModuleName, secsEpoch)); + } + } else { + logger.log(Level.FINER, "ProcessEventLogEntries(): Failed to get create timeStamp."); + } + } + + try { + // Create a EventLog Entry Artifact + // Add the attributes, if there are any + if (!attributes.isEmpty()) { + // use the private artifact ID for EventLog Artifact + BlackboardArtifact bba = abstractContent.newArtifact(m_eventLogArtifactID); + bba.addAttributes(attributes); + indexArtifact(bba); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "ProcessEventLogEntries(): Failed to create blackboard artifact for EventLog entry. (" + ex.getLocalizedMessage() + ")."); + } + + } // for each event + + } + + private void ProcessApplicationAccounts(Document aCLLXMLDoc, AbstractContent abstractContent, String aModuleName) { + + Element rootElem = aCLLXMLDoc.getDocumentElement(); + NodeList appAccountsNodes = rootElem.getElementsByTagName(CellXMLDef.CLLXML_APPLICATION_ACCOUNT_TAG); + + int len = appAccountsNodes.getLength(); + logger.log(Level.INFO, "ProcessApplicationAccounts(): Found " + Integer.toString(len) + " application accounts."); + + for (int c = 0; c < len; c++) { + + Collection attributes = new ArrayList(); + BlackboardAttribute userId = null; + + Element appAccountNode = (Element) appAccountsNodes.item(c); + + // Add a TSK_ISDELETED attribute if the artifact is deleted + FlagDeletedContent(appAccountNode, attributes, aModuleName); + + // Get userAccountInfo + NodeList userAccountInfoNodes = appAccountNode.getElementsByTagName(CellXMLDef.CLLXML_USER_ACCOUNT_INFO_TAG); + if (userAccountInfoNodes.getLength() > 0) { + Element userAccountInfoNode = (Element) userAccountInfoNodes.item(0); + + // get userID + String userID = GetXMLElemValue(CellXMLDef.CLLXML_USER_ID_TAG, userAccountInfoNode); + if (null != userID) { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Found userID = " + userID); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_ID, aModuleName, userID)); + + userId = new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_ID, aModuleName, userID); + + } else { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Failed to get phoneForm."); + } + + // get password + String password = GetXMLElemValue(CellXMLDef.CLLXML_PASSWORD_TAG, userAccountInfoNode); + if (null != password) { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Found password = " + password); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PASSWORD, aModuleName, password)); + + } else { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Failed to get password."); + } + + // Get displayName + // get password + String displayName = GetXMLElemValue(CellXMLDef.CLLXML_DISPLAYNAME_TAG, userAccountInfoNode); + if (null != displayName) { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Found display name = " + displayName); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, aModuleName, displayName)); + + } else { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Failed to get display name"); + } + + } else { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Could not find element "); + } + + // Get applicationInfo + NodeList applicationInfoNodes = appAccountNode.getElementsByTagName(CellXMLDef.CLLXML_APPLICATION_INFO_TAG); + if (applicationInfoNodes.getLength() > 0) { + Element applicationInfoNode = (Element) applicationInfoNodes.item(0); + + // get application Type - appType is an attribute + String appType = applicationInfoNode.getAttributes().getNamedItem("type").getNodeValue(); + if (null != appType) { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Found appType = " + appType); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CATEGORY, aModuleName, appType)); + + } else { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Failed to get userID."); + } + + // get appName + String appName = GetXMLElemValue(CellXMLDef.CLLXML_APPLICATION_NAME_TAG, applicationInfoNode); + if (null != appName) { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Found appName = " + appName); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, aModuleName, appName)); + + } else { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Failed to get appName."); + } + + // get appURL + String appURL = GetXMLElemValue(CellXMLDef.CLLXML_URL_TAG, applicationInfoNode); + if (null != appURL) { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Found app URL = " + appURL); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL, aModuleName, appURL)); + + } else { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Failed to get app URL"); + } + + // get appPath + String appPath = GetXMLElemValue(CellXMLDef.CLLXML_INSTALL_PATH_TAG, applicationInfoNode); + if (null != appPath) { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Found app Path = " + appPath); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH, aModuleName, appPath)); + + } else { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Failed to get app Path"); + } + + } else { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Could not find element "); + } + + // Get emailAccountInfo + NodeList emailAccountInfoNodes = appAccountNode.getElementsByTagName(CellXMLDef.CLLXML_EMAIL_ACCOUNT_INFO_TAG); + if (applicationInfoNodes.getLength() > 0) { + Element emailAccountInfoNode = (Element) emailAccountInfoNodes.item(0); + + // get mailboxName + String mboxName = GetXMLElemValue(CellXMLDef.CLLXML_MAILBOX_NAME_TAG, emailAccountInfoNode); + if (null != mboxName) { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Found mboxName = " + mboxName); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION, aModuleName, mboxName)); + } else { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Failed to get mboxName."); + } + + // get replyToAddress + String replyToAddress = GetXMLElemValue(CellXMLDef.CLLXML_REPLY_ADDRESS_TAG, emailAccountInfoNode); + if (null != replyToAddress) { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Found replyToAddress = " + replyToAddress); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL_REPLYTO, aModuleName, replyToAddress)); + } else { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Failed to get replyToAddress"); + } + + // get mailServer + String serverName = GetXMLElemValue(CellXMLDef.CLLXML_MAILSERVER_TAG, emailAccountInfoNode); + if (null != serverName) { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Found app Path = " + serverName); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SERVER_NAME, aModuleName, serverName)); + } else { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Failed to get app Path"); + } + + } else { + logger.log(Level.FINER, "ProcessApplicationAccounts(): Could not find element "); + } + + try { + // Create a ApplicationAccount Search artifact + // Add the attributes, if there are any + if (!attributes.isEmpty()) { + BlackboardArtifact bba = abstractContent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT); + bba.addAttributes(attributes); + indexArtifact(bba); + } + if (userId != null) { + BlackboardArtifact bba = abstractContent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_ACCOUNT); + bba.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_ID, + aModuleName, userId.getValueString())); + indexArtifact(bba); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "ProcessApplicationAccounts(): Failed to create blackboard artifact for Application Account. (" + ex.getLocalizedMessage() + ")."); + } + + } // for application account + } + + private void ProcessCookies(Document aCLLXMLDoc, AbstractContent abstractContent, String aModuleName) { + + Element rootElem = aCLLXMLDoc.getDocumentElement(); + NodeList cookieNodes = rootElem.getElementsByTagName(CellXMLDef.CLLXML_COOKIE_TAG); + + int len = cookieNodes.getLength(); + logger.log(Level.INFO, "ProcessCookies(): Found " + Integer.toString(len) + " Cookie entries !!."); + + for (int c = 0; c < len; c++) { + + Collection attributes = new ArrayList(); + + Element cookieNode = (Element) cookieNodes.item(c); + + // Add a TSK_ISDELETED attribute if the artifact is deleted + FlagDeletedContent(cookieNode, attributes, aModuleName); + + // get name + String cookieName = null; + cookieName = GetXMLElemValue(CellXMLDef.CLLXML_NAME_TAG, cookieNode); + if (null != cookieName) { + logger.log(Level.FINER, "ProcessCookies(): Found cookieName = " + cookieName); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, aModuleName, cookieName)); + } else { + logger.log(Level.FINER, "ProcessCookies(): Failed to get urlName."); + } + + // Get value + String cookieValue = null; + cookieValue = GetXMLElemValue(CellXMLDef.CLLXML_VALUE_TAG, cookieNode); + if (null != cookieValue) { + logger.log(Level.FINER, "ProcessCookies(): Found cookieValue = " + cookieValue); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_VALUE, aModuleName, cookieValue)); + } else { + logger.log(Level.FINER, "ProcessCookies(): Failed to get cookieValue."); + } + + // Get domain + String domain = null; + domain = GetXMLElemValue(CellXMLDef.CLLXML_DOMAIN_TAG, cookieNode); + if (null != domain) { + logger.log(Level.FINER, "ProcessCookies(): Found cookieValue = " + domain); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DOMAIN, aModuleName, domain)); + } else { + logger.log(Level.FINER, "ProcessCookies(): Failed to get domain."); + } + + // Get path + String path = null; + path = GetXMLElemValue(CellXMLDef.CLLXML_PATH_TAG, cookieNode); + if (null != path) { + logger.log(Level.FINER, "ProcessCookies(): Found path = " + path); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH, aModuleName, path)); + } else { + logger.log(Level.FINER, "ProcessCookies(): Failed to get domain."); + } + + // Get created time + NodeList createdTimeNodes = cookieNode.getElementsByTagName(CellXMLDef.CLLXML_CREATED_TAG); + if (createdTimeNodes.getLength() > 0) { + + String createdTimeStampStr = null; + + Element starttimeNode = (Element) createdTimeNodes.item(0); + createdTimeStampStr = GetXMLElemValue(CellXMLDef.CLLXML_DATETIME_TAG, starttimeNode); + if (null != createdTimeStampStr) { + logger.log(Level.FINER, "ProcessCookies(): Found created timeStamp = " + createdTimeStampStr); + + long secsEpoch = GetSecsSinceEpochFrom8601TimeStamp(createdTimeStampStr); + if (secsEpoch > 0) { + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_START, aModuleName, secsEpoch)); + } + } else { + logger.log(Level.FINER, "ProcessCookies(): Failed to get created timeStamp."); + } + } + + // Get expires time + NodeList expiresTimeNodes = cookieNode.getElementsByTagName(CellXMLDef.CLLXML_EXPIRES_TAG); + if (expiresTimeNodes.getLength() > 0) { + + String expiresTimeStampStr = null; + + Element starttimeNode = (Element) createdTimeNodes.item(0); + expiresTimeStampStr = GetXMLElemValue(CellXMLDef.CLLXML_DATETIME_TAG, starttimeNode); + if (null != expiresTimeStampStr) { + logger.log(Level.FINER, "ProcessCookies(): Found expires timeStamp = " + expiresTimeStampStr); + + long secsEpoch = GetSecsSinceEpochFrom8601TimeStamp(expiresTimeStampStr); + if (secsEpoch > 0) { + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_END, aModuleName, secsEpoch)); + } + } else { + logger.log(Level.FINER, "ProcessCookies(): Failed to get expires timeStamp."); + } + } + + try { + // Create a Cookie Artifact + // Add the attributes, if there are any + if (!attributes.isEmpty()) { + BlackboardArtifact bba = abstractContent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE); + bba.addAttributes(attributes); + indexArtifact(bba); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "ProcessCookies(): Failed to create blackboard artifact for Cookie. (" + ex.getLocalizedMessage() + ")."); + } + + } // for each cookie + + } + + private void ProcessInternetBookmarks(Document aCLLXMLDoc, AbstractContent abstractContent, String aModuleName) { + + Element rootElem = aCLLXMLDoc.getDocumentElement(); + NodeList bookmarkNodes = rootElem.getElementsByTagName(CellXMLDef.CLLXML_INTERNET_BOOKMARK_TAG); + + int len = bookmarkNodes.getLength(); + logger.log(Level.INFO, "ProcessInternetBookmarks(): Found " + Integer.toString(len) + " Bookmark entries !!."); + + for (int c = 0; c < len; c++) { + + Collection attributes = new ArrayList(); + + Element bookmarkNode = (Element) bookmarkNodes.item(c); + + // Add a TSK_ISDELETED attribute if the artifact is deleted + FlagDeletedContent(bookmarkNode, attributes, aModuleName); + + // get name + String urlName = null; + urlName = GetXMLElemValue(CellXMLDef.CLLXML_URL_NAME_TAG, bookmarkNode); + if (null != urlName) { + logger.log(Level.FINER, "ProcessInternetBookmarks(): Found urlName = " + urlName); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, aModuleName, urlName)); + } else { + logger.log(Level.FINER, "ProcessInternetBookmarks(): Failed to get urlName."); + } + + // Get url address + String urlAddress = null; + urlAddress = GetXMLElemValue(CellXMLDef.CLLXML_URL_ADDRESS_TAG, bookmarkNode); + if (null != urlAddress) { + logger.log(Level.FINER, "ProcessInternetBookmarks(): Found urlAddress = " + urlAddress); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL, aModuleName, urlAddress)); + } else { + logger.log(Level.FINER, "ProcessInternetBookmarks(): Failed to get urlAddress."); + } + + // Get create time + NodeList startTimeNodes = bookmarkNode.getElementsByTagName(CellXMLDef.CLLXML_TIME_TAG); + if (startTimeNodes.getLength() > 0) { + String createTimeStampStr = null; + + Element starttimeNode = (Element) startTimeNodes.item(0); + createTimeStampStr = GetXMLElemValue(CellXMLDef.CLLXML_DATETIME_TAG, starttimeNode); + if (null != createTimeStampStr) { + logger.log(Level.FINER, "ProcessInternetBookmarks(): Found start timeStamp = " + createTimeStampStr); + + long secsEpoch = GetSecsSinceEpochFrom8601TimeStamp(createTimeStampStr); + if (secsEpoch > 0) { + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED, aModuleName, secsEpoch)); + } + } else { + logger.log(Level.FINER, "ProcessInternetBookmarks(): Failed to get create timeStamp."); + } + } + + try { + // Create a bookMark entry Artifact + // Add the attributes, if there are any + if (!attributes.isEmpty()) { + BlackboardArtifact bba = abstractContent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK); + bba.addAttributes(attributes); + indexArtifact(bba); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "ProcessInternetBookmarks(): Failed to create blackboard artifact for Bookmark. (" + ex.getLocalizedMessage() + ")."); + } + + } // for each Bookmark entry + + } + + private void ProcessInternetHistory(Document aCLLXMLDoc, AbstractContent abstractContent, String aModuleName) { + + Element rootElem = aCLLXMLDoc.getDocumentElement(); + NodeList historyNodes = rootElem.getElementsByTagName(CellXMLDef.CLLXML_INTERNET_HISTORY_TAG); + + int len = historyNodes.getLength(); + logger.log(Level.INFO, "ProcessInternetHistory(): Found " + Integer.toString(len) + " Internet History entries !!."); + + for (int c = 0; c < len; c++) { + + Collection attributes = new ArrayList(); + + Element historyNode = (Element) historyNodes.item(c); + + // Add a TSK_ISDELETED attribute if the artifact is deleted + FlagDeletedContent(historyNode, attributes, aModuleName); + + // get name + String urlName = null; + urlName = GetXMLElemValue(CellXMLDef.CLLXML_URL_NAME_TAG, historyNode); + if (null != urlName) { + logger.log(Level.FINER, "ProcessInternetHistory(): Found urlName = " + urlName); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, aModuleName, urlName)); + } else { + logger.log(Level.FINER, "ProcessInternetHistory(): Failed to get urlName."); + } + + // Get url address + String urlAddress = null; + urlAddress = GetXMLElemValue(CellXMLDef.CLLXML_URL_ADDRESS_TAG, historyNode); + if (null != urlAddress) { + logger.log(Level.FINER, "ProcessInternetHistory(): Found urlAddress = " + urlAddress); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL, aModuleName, urlAddress)); + } else { + logger.log(Level.FINER, "ProcessInternetHistory(): Failed to get urlAddress."); + } + + // Get access time + NodeList startTimeNodes = historyNode.getElementsByTagName(CellXMLDef.CLLXML_TIME_TAG); + if (startTimeNodes.getLength() > 0) { + String accessedTimeStampStr = null; + + Element starttimeNode = (Element) startTimeNodes.item(0); + accessedTimeStampStr = GetXMLElemValue(CellXMLDef.CLLXML_DATETIME_TAG, starttimeNode); + if (null != accessedTimeStampStr) { + logger.log(Level.FINER, "ProcessInternetHistory(): Found start timeStamp = " + accessedTimeStampStr); + + long secsEpoch = GetSecsSinceEpochFrom8601TimeStamp(accessedTimeStampStr); + if (secsEpoch > 0) { + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED, aModuleName, secsEpoch)); + } + } else { + logger.log(Level.FINER, "ProcessInternetHistory(): Failed to get access timeStamp."); + } + } + + try { + // Create a Web history entry Artifact + // Add the attributes, if there are any + if (!attributes.isEmpty()) { + BlackboardArtifact bba = abstractContent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY); + bba.addAttributes(attributes); + indexArtifact(bba); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "ProcessInternetHistory(): Failed to create blackboard artifact for Internet History. (" + ex.getLocalizedMessage() + ")."); + } + + } // for each history entry + + } + + private void ProcessGPSFavorites(Document aCLLXMLDoc, AbstractContent abstractContent, String aModuleName) { + + Element rootElem = aCLLXMLDoc.getDocumentElement(); + NodeList gpsFavNodes = rootElem.getElementsByTagName(CellXMLDef.CLLXML_GPS_FAVORITES_TAG); + + if (gpsFavNodes.getLength() > 0) { + + Element gpsFavNode = (Element) gpsFavNodes.item(0); + NodeList geoLocationNodes = gpsFavNode.getElementsByTagName(CellXMLDef.CLLXML_GEO_LOCATION_TAG); + + int len = geoLocationNodes.getLength(); + logger.log(Level.FINER, "ProcessGPSFavorites(): Found " + Integer.toString(len) + " GPS favorites entries !!."); + + for (int c = 0; c < len; c++) { + + Collection attributes = new ArrayList(); + Element geoLocationNode = (Element) geoLocationNodes.item(c); + + // Add a TSK_ISDELETED attribute if the artifact is deleted + FlagDeletedContent(geoLocationNode, attributes, aModuleName); + + // get latitude + String latitudeStr = null; + latitudeStr = GetXMLElemValue(CellXMLDef.CLLXML_LATITUDE_TAG, geoLocationNode); + if (null != latitudeStr) { + double latitude = Double.parseDouble(latitudeStr); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE, aModuleName, latitude)); + } else { + logger.log(Level.FINER, "ProcessGPSFavorites(): Failed to get latitude."); + } + + // get longitude + String longitudeStr = null; + longitudeStr = GetXMLElemValue(CellXMLDef.CLLXML_LONGITUDE_TAG, geoLocationNode); + if (null != longitudeStr) { + double longitude = Double.parseDouble(longitudeStr); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE, aModuleName, longitude)); + } else { + logger.log(Level.FINER, "ProcessGPSFavorites(): Failed to get longitude."); + } + + // get altitude + String altitudeStr = null; + altitudeStr = GetXMLElemValue(CellXMLDef.CLLXML_ALTITUDE_TAG, geoLocationNode); + if (null != altitudeStr) { + double altitude = Double.parseDouble(altitudeStr); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE, aModuleName, altitude)); + } else { + logger.log(Level.FINER, "ProcessGPSFavorites(): Failed to get longitude."); + } + + // Get the name, if there is one + String locationName = null; + locationName = GetXMLElemValue(CellXMLDef.CLLXML_NAME_TAG, geoLocationNode); + if (null != locationName) { + logger.log(Level.FINER, "ProcessGPSFavorites(): Found location name = " + locationName); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, aModuleName, locationName)); + } else { + logger.log(Level.FINER, "ProcessGPSFavorites(): Failed to get name."); + } + + // Get the locartion address, if there is one + String locationAddress = null; + locationAddress = GetXMLElemValue(CellXMLDef.CLLXML_ADDRESS_TAG, geoLocationNode); + if (null != locationAddress) { + logger.log(Level.FINER, "ProcessGPSFavorites(): Found location address = " + locationAddress); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION, aModuleName, locationAddress)); + } else { + logger.log(Level.FINER, "ProcessGPSFavorites(): Failed to get address."); + } + + // Get time - if there is any + NodeList timeNodes = geoLocationNode.getElementsByTagName(CellXMLDef.CLLXML_TIME_TAG); + if (timeNodes.getLength() > 0) { + + String timeStampStr = null; + + Element timeNode = (Element) timeNodes.item(0); + timeStampStr = GetXMLElemValue(CellXMLDef.CLLXML_DATETIME_TAG, timeNode); + if (null != timeStampStr) { + logger.log(Level.FINER, "ProcessGPSFavorites(): Found created timeStamp = " + timeStampStr); + + long secsEpoch = GetSecsSinceEpochFrom8601TimeStamp(timeStampStr); + if (secsEpoch > 0) { + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, aModuleName, secsEpoch)); + } + } else { + logger.log(Level.FINER, "ProcessGPSFavorites(): Failed to get created timeStamp."); + } + } + + try { + // Create a GPS Bookmark artifact + // Add the attributes, if there are any + if (!attributes.isEmpty()) { + BlackboardArtifact bba = abstractContent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK); + bba.addAttributes(attributes); + indexArtifact(bba); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "ProcessGPSFavorites(): Failed to create blackboard artifact for GPS Bookmark. (" + ex.getLocalizedMessage() + ")."); + } + + } // for each geoLocation + + } + + } + + private void ProcessGPSSearches(Document aCLLXMLDoc, AbstractContent abstractContent, String aModuleName) { + + Element rootElem = aCLLXMLDoc.getDocumentElement(); + NodeList gpsSearchesNodes = rootElem.getElementsByTagName(CellXMLDef.CLLXML_GPS_SEARCHES_TAG); + + if (gpsSearchesNodes.getLength() > 0) { + + Element gpsSearchesNode = (Element) gpsSearchesNodes.item(0); + + NodeList geoLocationNodes = gpsSearchesNode.getElementsByTagName(CellXMLDef.CLLXML_GEO_LOCATION_TAG); + + int len = geoLocationNodes.getLength(); + logger.log(Level.INFO, "ProcessGPSSearches(): Found " + Integer.toString(len) + " GPS Search entries !!."); + + for (int c = 0; c < len; c++) { + + Collection attributes = new ArrayList(); + + Element geoLocationNode = (Element) geoLocationNodes.item(c); + + // Add a TSK_ISDELETED attribute if the artifact is deleted + FlagDeletedContent(geoLocationNode, attributes, aModuleName); + + // get latitude + String latitudeStr = null; + latitudeStr = GetXMLElemValue(CellXMLDef.CLLXML_LATITUDE_TAG, geoLocationNode); + if (null != latitudeStr) { + double latitude = Double.parseDouble(latitudeStr); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE, aModuleName, latitude)); + } else { + logger.log(Level.FINER, "ProcessGPSSearches(): Failed to get latitude."); + } + + // get longitude + String longitudeStr = null; + longitudeStr = GetXMLElemValue(CellXMLDef.CLLXML_LONGITUDE_TAG, geoLocationNode); + if (null != longitudeStr) { + double longitude = Double.parseDouble(longitudeStr); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE, aModuleName, longitude)); + } else { + logger.log(Level.FINER, "ProcessGPSSearches(): Failed to get longitude."); + } + + // get altitude + String altitudeStr = null; + altitudeStr = GetXMLElemValue(CellXMLDef.CLLXML_ALTITUDE_TAG, geoLocationNode); + if (null != altitudeStr) { + double altitude = Double.parseDouble(altitudeStr); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE, aModuleName, altitude)); + } else { + logger.log(Level.FINER, "ProcessGPSSearches(): Failed to get longitude."); + } + + // Get the name, if there is one + String locationName = null; + locationName = GetXMLElemValue(CellXMLDef.CLLXML_NAME_TAG, geoLocationNode); + if (null != locationName) { + logger.log(Level.FINER, "ProcessGPSSearches(): Found location name = " + locationName); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, aModuleName, locationName)); + } else { + logger.log(Level.FINER, "ProcessGPSSearches(): Failed to get name."); + } + + // Get the locartion address, if there is one + String locationAddress = null; + locationAddress = GetXMLElemValue(CellXMLDef.CLLXML_ADDRESS_TAG, geoLocationNode); + if (null != locationAddress) { + logger.log(Level.FINER, "ProcessGPSSearches(): Found location address = " + locationAddress); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION, aModuleName, locationAddress)); + } else { + logger.log(Level.FINER, "ProcessGPSSearches(): Failed to get address."); + } + + // Get time - if there is any + NodeList timeNodes = geoLocationNode.getElementsByTagName(CellXMLDef.CLLXML_TIME_TAG); + if (timeNodes.getLength() > 0) { + + String timeStampStr = null; + + Element timeNode = (Element) timeNodes.item(0); + timeStampStr = GetXMLElemValue(CellXMLDef.CLLXML_DATETIME_TAG, timeNode); + if (null != timeStampStr) { + logger.log(Level.FINER, "ProcessGPSFavorites(): Found created timeStamp = " + timeStampStr); + + long secsEpoch = GetSecsSinceEpochFrom8601TimeStamp(timeStampStr); + if (secsEpoch > 0) { + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, aModuleName, secsEpoch)); + } + } else { + logger.log(Level.FINER, "ProcessGPSFavorites(): Failed to get created timeStamp."); + } + } + + try { + // Create a GPS Search artifact + // Add the attributes, if there are any + if (!attributes.isEmpty()) { + BlackboardArtifact bba = abstractContent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_SEARCH); + bba.addAttributes(attributes); + indexArtifact(bba); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "ProcessGPSSearches(): Failed to create blackboard artifact for GPS Search. (" + ex.getLocalizedMessage() + ")."); + } + + } // for each geoLocation + + } + + } + + private void ProcessGPSLastKnownLocation(Document aCLLXMLDoc, AbstractContent abstractContent, String aModuleName) { + + Element rootElem = aCLLXMLDoc.getDocumentElement(); + NodeList gpsSearchesNodes = rootElem.getElementsByTagName(CellXMLDef.CLLXML_GPS_LAST_KNOWN_LOCATION_TAG); + + if (gpsSearchesNodes.getLength() > 0) { + + Element gpsSearchesNode = (Element) gpsSearchesNodes.item(0); + + NodeList geoLocationNodes = gpsSearchesNode.getElementsByTagName(CellXMLDef.CLLXML_GEO_LOCATION_TAG); + + int len = geoLocationNodes.getLength(); + logger.log(Level.INFO, "ProcessGPSLastKnownLocation(): Found " + Integer.toString(len) + " GPS Last Known location !!."); + + for (int c = 0; c < len; c++) { + + Collection attributes = new ArrayList(); + + Element geoLocationNode = (Element) geoLocationNodes.item(c); + + // Add a TSK_ISDELETED attribute if the artifact is deleted + FlagDeletedContent(geoLocationNode, attributes, aModuleName); + + // get latitude + String latitudeStr = null; + latitudeStr = GetXMLElemValue(CellXMLDef.CLLXML_LATITUDE_TAG, geoLocationNode); + if (null != latitudeStr) { + double latitude = Double.parseDouble(latitudeStr); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE, aModuleName, latitude)); + } else { + logger.log(Level.FINER, "ProcessGPSLastKnownLocation(): Failed to get latitude."); + } + + // get longitude + String longitudeStr = null; + longitudeStr = GetXMLElemValue(CellXMLDef.CLLXML_LONGITUDE_TAG, geoLocationNode); + if (null != longitudeStr) { + double longitude = Double.parseDouble(longitudeStr); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE, aModuleName, longitude)); + } else { + logger.log(Level.FINER, "ProcessGPSLastKnownLocation(): Failed to get longitude."); + } + + // get altitude + String altitudeStr = null; + altitudeStr = GetXMLElemValue(CellXMLDef.CLLXML_ALTITUDE_TAG, geoLocationNode); + if (null != altitudeStr) { + double altitude = Double.parseDouble(altitudeStr); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE, aModuleName, altitude)); + } else { + logger.log(Level.FINER, "ProcessGPSLastKnownLocation(): Failed to get longitude."); + } + + // Get the name, if there is one + String locationName = null; + locationName = GetXMLElemValue(CellXMLDef.CLLXML_NAME_TAG, geoLocationNode); + if (null != locationName) { + logger.log(Level.FINER, "ProcessGPSLastKnownLocation(): Found location name = " + locationName); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME, aModuleName, locationName)); + } else { + logger.log(Level.FINER, "ProcessGPSLastKnownLocation(): Failed to get name."); + } + + // Get the locartion address, if there is one + String locationAddress = null; + locationAddress = GetXMLElemValue(CellXMLDef.CLLXML_ADDRESS_TAG, geoLocationNode); + if (null != locationAddress) { + logger.log(Level.FINER, "ProcessGPSLastKnownLocation(): Found location address = " + locationAddress); + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_LOCATION, aModuleName, locationAddress)); + } else { + logger.log(Level.FINER, "ProcessGPSLastKnownLocation(): Failed to get address."); + } + + // Get time - if there is any + NodeList timeNodes = geoLocationNode.getElementsByTagName(CellXMLDef.CLLXML_TIME_TAG); + if (timeNodes.getLength() > 0) { + + String timeStampStr = null; + + Element timeNode = (Element) timeNodes.item(0); + timeStampStr = GetXMLElemValue(CellXMLDef.CLLXML_DATETIME_TAG, timeNode); + if (null != timeStampStr) { + logger.log(Level.FINER, "ProcessGPSLastKnownLocation(): Found created timeStamp = " + timeStampStr); + + long secsEpoch = GetSecsSinceEpochFrom8601TimeStamp(timeStampStr); + if (secsEpoch > 0) { + attributes.add(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, aModuleName, secsEpoch)); + } + } else { + logger.log(Level.FINER, "ProcessGPSLastKnownLocation(): Failed to get created timeStamp."); + } + } + + try { + // Create a GPS Search artifact + // Add the attributes, if there are any + if (!attributes.isEmpty()) { + BlackboardArtifact bba = abstractContent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_LAST_KNOWN_LOCATION); + bba.addAttributes(attributes); + indexArtifact(bba); + } + } catch (TskCoreException ex) { + logger.log(Level.WARNING, "ProcessGPSLastKnownLocation(): Failed to create blackboard artifact for GPS Last Location. (" + ex.getLocalizedMessage() + ")."); + } + + } // for each geoLocation + + } + + } + + /** + * Index the text associated with the given artifact. + * + * @param artifact + */ + private void indexArtifact(BlackboardArtifact artifact) throws TskCoreException { + Blackboard blackboard = Case.getCurrentCase().getServices().getBlackboard(); + + try { + // index the artifact for keyword search + blackboard.indexArtifact(artifact); + } catch (Blackboard.BlackboardException ex) { + logger.log(Level.SEVERE, NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.error.msg", artifact.getDisplayName()), ex); //NON-NLS + MessageNotifyUtil.Notify.error( + NbBundle.getMessage(Blackboard.class, "Blackboard.unableToIndexArtifact.exception.msg"), artifact.getDisplayName()); + } + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/AddCellebriteAndroidImageTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/AddCellebriteAndroidImageTask.java new file mode 100644 index 0000000000..4d7a5725f4 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/AddCellebriteAndroidImageTask.java @@ -0,0 +1,241 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.experimental.cellex.datasourceprocessors; + +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.services.FileManager; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback.DataSourceProcessorResult; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.Image; +import org.sleuthkit.datamodel.LocalFilesDataSource; +import org.sleuthkit.datamodel.SleuthkitCase; +import org.sleuthkit.datamodel.SleuthkitJNI; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskDataException; + +/* + * A runnable that adds the image files from a Cellebrite UFED output folder to + * a case database. If SleuthKit fails to find a filesystem in any of input + * image files, the file is added to the case as a local/logical file instead. + */ +class AddCellebriteAndroidImageTask implements Runnable { + + private static final Logger logger = Logger.getLogger(AddCellebriteAndroidImageTask.class.getName()); + public static final String MODULE_NAME = "Cellebrite UFED Output Data Source Processor"; + public static final String TSK_FS_TYPE_UNKNOWN_ERR_MSG = "Cannot determine file system type"; + private final String deviceId; + private final List imageFilePaths; + private final String timeZone; + private final DataSourceProcessorProgressMonitor progressMonitor; + private final DataSourceProcessorCallback callback; + private final Case currentCase; + private boolean criticalErrorOccurred; + private volatile boolean cancelled; + + /** + * Constructs a runnable that adds the image files from a Cellebrite UFED + * output folder to a case database. If SleuthKit fails to find a filesystem + * in any of input image files, the file is added to the case as a + * local/logical file instead. + * + * @param deviceId An ASCII-printable identifier for the device + * associated with the data source that is intended + * to be unique across multiple cases (e.g., a UUID). + * @param imageFilePaths The paths of the Cellebrite output files. + * @param timeZone The time zone to use when processing dates and + * times for the image, obtained from + * java.util.TimeZone.getID. + * @param progressMonitor Progress monitor for reporting progress during + * processing. + * @param callback Callback to call when processing is done. + */ + AddCellebriteAndroidImageTask(String deviceId, List imageFilePaths, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + this.deviceId = deviceId; + this.imageFilePaths = imageFilePaths; + this.timeZone = timeZone; + this.callback = callback; + this.progressMonitor = progressMonitor; + currentCase = Case.getCurrentCase(); + } + + @Override + public void run() { + /* + * Try to add the input image files as images. + */ + List newDataSources = new ArrayList<>(); + List localFileDataSourcePaths = new ArrayList<>(); + List errorMessages = new ArrayList<>(); + currentCase.getSleuthkitCase().acquireExclusiveLock(); + try { + progressMonitor.setIndeterminate(true); + for (String imageFilePath : imageFilePaths) { + if (!cancelled) { + addImageToCase(imageFilePath, newDataSources, localFileDataSourcePaths, errorMessages); + } + } + } finally { + currentCase.getSleuthkitCase().releaseExclusiveLock(); + } + + /* + * Try to add any input image files that did not have file systems as a + * single local/logical files set with the device id as the root virtual + * directory name. + */ + if (!cancelled && localFileDataSourcePaths.size() > 0) { + FileManager fileManager = currentCase.getServices().getFileManager(); + FileManager.FileAddProgressUpdater progressUpdater = (final AbstractFile newFile) -> { + progressMonitor.setProgressText(String.format("Adding: %s as logical file", Paths.get(newFile.getParentPath(), newFile.getName()))); + }; + try { + LocalFilesDataSource localFilesDataSource = fileManager.addLocalFilesDataSource(deviceId, deviceId, timeZone, localFileDataSourcePaths, progressUpdater); + newDataSources.add(localFilesDataSource.getRootDirectory()); + } catch (TskCoreException | TskDataException ex) { + errorMessages.add(String.format("Error adding images without file systems for device %s: %s", deviceId, ex.getLocalizedMessage())); + criticalErrorOccurred = true; + } + } + + /* + * This appears to be the best that can be done to indicate completion + * with the DataSourceProcessorProgressMonitor in its current form. + */ + progressMonitor.setProgress(0); + progressMonitor.setProgress(100); + + /* + * Pass the results back via the callback. + */ + DataSourceProcessorResult result; + if (criticalErrorOccurred) { + result = DataSourceProcessorResult.CRITICAL_ERRORS; + } else if (!errorMessages.isEmpty()) { + result = DataSourceProcessorResult.NONCRITICAL_ERRORS; + } else { + result = DataSourceProcessorResult.NO_ERRORS; + } + callback.done(result, errorMessages, newDataSources); + criticalErrorOccurred = false; + } + + /** + * Attempts to cancel the processing of the input image files. May result in + * partial processing of the input. + */ + public void cancelTask() { + logger.log(Level.WARNING, "AddCellebriteAndroidImageTask cancelled, processing may be incomplete"); + cancelled = true; + } + + /** + * Attempts to add an input image to the case. + * + * @param imageFilePath The image file path. + * @param newDataSources If the image is added, a data source is + * added to this list for eventual return to + * the caller via the callback. + * @param localFileDataSourcePaths If the image cannot be added because + * SleuthKit cannot detect a filesystem, the + * image file path is added to this list for + * later addition as a part of a + * local/logical files data source. + * @param errorMessages If there are any error messages, the + * error messages are added to this list for + * eventual return to the caller via the + * callback. + */ + private void addImageToCase(String imageFilePath, List newDataSources, List localFileDataSourcePaths, List errorMessages) { + /* + * Try to add the image to the case database as a data source. + */ + progressMonitor.setProgressText(String.format("Adding: %s", imageFilePath)); + SleuthkitCase caseDatabase = currentCase.getSleuthkitCase(); + SleuthkitJNI.CaseDbHandle.AddImageProcess addImageProcess = caseDatabase.makeAddImageProcess(timeZone, false, false); + Thread progressReporterThread = new Thread(new AddImageProgressReportingTask(progressMonitor, addImageProcess)); + try { + progressReporterThread.start(); + addImageProcess.run(deviceId, new String[]{imageFilePath}); + } catch (TskCoreException ex) { + if (ex.getMessage().contains(TSK_FS_TYPE_UNKNOWN_ERR_MSG)) { + /* + * If SleuthKit failed to add the image because it did not find + * a file system, save the image path so it can be added to the + * case as part of a local/logical files data source. All other + * errors are critical. + */ + localFileDataSourcePaths.add(imageFilePath); + } else { + errorMessages.add(String.format("Critical error adding %s for %s:", imageFilePath, deviceId, ex.getLocalizedMessage())); + criticalErrorOccurred = true; + } + /* + * Either way, the add image process needs to be reverted. + */ + try { + addImageProcess.revert(); + } catch (TskCoreException e) { + errorMessages.add(String.format("Critical error reverting add image process for %s for %s: %s", imageFilePath, deviceId, e.getLocalizedMessage())); + criticalErrorOccurred = true; + } + return; + } catch (TskDataException ex) { + errorMessages.add(String.format("Non-critical error adding %s for %s: %s", imageFilePath, deviceId, ex.getLocalizedMessage())); + } finally { + progressReporterThread.interrupt(); + } + + /* + * Try to commit the results of the add image process, retrieve the new + * image from the case database, and add it to the list of new data + * sources to be returned via the callback. + */ + try { + long imageId = addImageProcess.commit(); + Image dataSource = caseDatabase.getImageById(imageId); + newDataSources.add(dataSource); + + /* + * Verify the size of the new image. Note that it may not be what is + * expected, but at least part of it was added to the case. + */ + String verificationError = dataSource.verifyImageSize(); + if (!verificationError.isEmpty()) { + errorMessages.add(String.format("Non-critical error adding %s for device %s: %s", imageFilePath, deviceId, verificationError)); + } + } catch (TskCoreException ex) { + /* + * The add image process commit failed or querying the case database + * for the newly added image failed. Either way, this is a critical + * error. + */ + errorMessages.add(String.format("Critical error adding %s for device %s: %s", imageFilePath, deviceId, ex.getLocalizedMessage())); + criticalErrorOccurred = true; + } + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/AddCellebriteXMLTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/AddCellebriteXMLTask.java new file mode 100644 index 0000000000..b0ffde46b4 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/AddCellebriteXMLTask.java @@ -0,0 +1,230 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.experimental.cellex.datasourceprocessors; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.logging.Level; +import org.apache.commons.io.FilenameUtils; +import org.openide.modules.InstalledFileLocator; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.casemodule.services.FileManager; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; +import org.sleuthkit.autopsy.coreutils.ExecUtil; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.LocalFilesDataSource; +import org.sleuthkit.datamodel.TskCoreException; +import org.sleuthkit.datamodel.TskDataException; +import org.sleuthkit.autopsy.experimental.cellex.cellxml.CellXMLParser; + +/* + * A runnable that adds a Cellebrite XML report a case as a local files data + * source. The Cellebrite XML report is converted to a DFXML/CellXML report and + * the CellXML report is parsed to generate artifacts. + */ +class AddCellebriteXMLTask implements Runnable { + + public enum CellebriteInputType { + + handset, + SIM, + }; + + private static final Logger logger = Logger.getLogger(CellebriteXMLProcessor.class.getName()); + private static final String MODULE_NAME = "Cellebrite XML Processor"; + private static final String CONVERTOR_EXE = "CellebriteToDFXMLConv.exe"; + private final String deviceId; + private final String rootVirtualDirectoryName; + private final String cellebriteXmlFilePath; + private final CellebriteInputType cellebriteXmlFileType; + private final DataSourceProcessorProgressMonitor progressMonitor; + private final DataSourceProcessorCallback callback; + private volatile boolean cancelled = false; + + /** + * Constructs a runnable that adds a Cellebrite XML report a case as a local + * files data source. The Cellebrite XML report is converted to a + * DFXML/CellXML report and the CellXML report is parsed to generate + * artifacts. + * + * @param deviceId An ASCII-printable identifier for the + * device associated with the data source + * that is intended to be unique across + * multiple cases (e.g., a UUID). + * @param rootVirtualDirectoryName The name to give to the virtual directory + * that will represent the data source. Pass + * the empty string to get a default name of + * the form: LogicalFileSet[N] + * @param cellebriteXmlFilePath Path to a Cellebrite report XML file. + * @param cellebriteXmlFileType Handset or SIM. + * @param progressMonitor Progress monitor for reporting + * progressMonitor during processing. + * @param callback Callback to call when processing is done. + */ + AddCellebriteXMLTask(String deviceId, String rootVirtualDirectoryName, String cellebriteXmlFilePath, CellebriteInputType cellebriteXmlFileType, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + this.deviceId = deviceId; + this.rootVirtualDirectoryName = rootVirtualDirectoryName; + this.cellebriteXmlFilePath = cellebriteXmlFilePath; + this.cellebriteXmlFileType = cellebriteXmlFileType; + this.callback = callback; + this.progressMonitor = progressMonitor; + } + + /** + * Adds a Cellebrite XML report a case as a local files data source. The + * Cellebrite XML report is converted to a DFXML/CellXML report and the + * CellXML report is parsed to generate artifacts. + */ + @Override + public void run() { + List newDataSources = new ArrayList<>(); + List errorMessages = new ArrayList<>(); + try { + progressMonitor.setIndeterminate(true); + progressMonitor.setProgressText("Processing: " + cellebriteXmlFilePath); + + /* + * Locate the Cellebrite XML to DFXML/CellXML converter. + */ + final File converterHome = InstalledFileLocator.getDefault().locate(FilenameUtils.removeExtension(CONVERTOR_EXE), AddCellebriteXMLTask.class.getPackage().getName(), false); + if (null == converterHome) { + errorMessages.add(String.format("Critical error adding %s for device %s: %s not found", cellebriteXmlFilePath, deviceId, CONVERTOR_EXE)); + return; + } + String converterExePath = Paths.get(converterHome.getAbsolutePath(), CONVERTOR_EXE).toString(); + + /* + * Get the file name of the image file sans extension and use it to + * create an converter output folder in the module output directory + * of the case, adding a time stamp suffix for uniqueness. + * + * NOTE: The input file name may not have a .xml extension. + */ + String cellebriteXmlFileNameWithoutExt = FilenameUtils.removeExtension(Paths.get(cellebriteXmlFilePath).getFileName().toString()); + Path converterOutputDirPath = Paths.get(Case.getCurrentCase().getModuleDirectory(), + MODULE_NAME, + cellebriteXmlFileNameWithoutExt + "_" + new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss-SSSS").format(new Date())); + try { + Files.createDirectories(converterOutputDirPath); + } catch (IOException ex) { + errorMessages.add(String.format("Critical error adding %s for device %s, cannot create converter output directory %s : %s", cellebriteXmlFilePath, deviceId, converterOutputDirPath, ex.getLocalizedMessage())); + return; + } + String cellXmlFilePath = Paths.get(converterOutputDirPath.toString(), cellebriteXmlFileNameWithoutExt + ".xml").toString(); + + /* + * Run the converter. + */ + ProcessBuilder processBuilder = new ProcessBuilder( + converterExePath, + "-t", + cellebriteXmlFileType.toString(), + "-i", + cellebriteXmlFilePath, + "-o", + cellXmlFilePath); + String logFileName = Paths.get(converterOutputDirPath.toString(), "c2c_stdout.txt").toString(); + File logFile = new File(logFileName); + Path errFileName = Paths.get(converterOutputDirPath.toString(), "c2c_errors.txt"); + File errFile = new File(errFileName.toString()); + processBuilder.redirectError(ProcessBuilder.Redirect.appendTo(errFile)); + processBuilder.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile)); + try { + int exitValue = ExecUtil.execute(processBuilder); + if (exitValue != 0) { + errorMessages.add(String.format("Critical error adding %s for device %s: %s returned failure code", cellebriteXmlFilePath, deviceId, CONVERTOR_EXE)); + return; + } + } catch (IOException | SecurityException ex) { + errorMessages.add(String.format("Critical error adding %s for device %s, %s execution exception: %s", cellebriteXmlFilePath, deviceId, CONVERTOR_EXE, ex.getMessage())); + return; + } + + if (cancelled) { + return; + } + + /* + * Add the Cellebrite XML file to the case as a local file data + * source. + */ + FileManager fileManager = Case.getCurrentCase().getServices().getFileManager(); + List localFilePaths = new ArrayList<>(); + localFilePaths.add(cellebriteXmlFilePath); + LocalFilesDataSource newDataSource = fileManager.addLocalFilesDataSource(deviceId, rootVirtualDirectoryName, "", localFilePaths, (final AbstractFile newFile) -> { + }); + newDataSources.add(newDataSource.getRootDirectory()); + + if (cancelled) { + return; + } + + /* + * Generate artifacts from the DFXML/CellXML file. + */ + java.io.File cellxmlFile = new java.io.File(cellXmlFilePath); + if (cellxmlFile.exists()) { + CellXMLParser.getDefault().Process(cellXmlFilePath, newDataSource.getRootDirectory(), MODULE_NAME); + } else { + errorMessages.add(String.format("Critical error adding %s for device %s: missing CellXML file", cellebriteXmlFilePath, deviceId)); + } + } catch (TskDataException | TskCoreException ex) { + errorMessages.add(String.format("Critical error adding %s for device %s: %s", cellebriteXmlFilePath, deviceId, ex.getLocalizedMessage())); + } finally { + /* + * This appears to be the best that can be done to indicate + * completion with the DataSourceProcessorProgressMonitor in its + * current form. + */ + progressMonitor.setProgress(0); + progressMonitor.setProgress(100); + + /* + * Pass the results back via the callback. + */ + DataSourceProcessorCallback.DataSourceProcessorResult result; + if (!errorMessages.isEmpty()) { + result = DataSourceProcessorCallback.DataSourceProcessorResult.CRITICAL_ERRORS; + } else { + result = DataSourceProcessorCallback.DataSourceProcessorResult.NO_ERRORS; + } + callback.done(result, errorMessages, newDataSources); + } + } + + /** + * Attempts to cancel the processing of the input image file. May result in + * partial processing of the input. + */ + public void cancelTask() { + logger.log(Level.WARNING, "AddMPFImageTask cancelled, processing may be incomplete"); + cancelled = true; + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/AddImageProgressReportingTask.java b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/AddImageProgressReportingTask.java new file mode 100644 index 0000000000..78de63b6d0 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/AddImageProgressReportingTask.java @@ -0,0 +1,74 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.experimental.cellex.datasourceprocessors; + +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; +import org.sleuthkit.datamodel.SleuthkitJNI; + +/* + * A Runnable that updates a data source processor progress monitor with the + * name of the directory currently being processed by a SleuthKit add image + * process. + * + * TODO (JIRA-1578): The sleep code in the run method should be removed. Clients + * should use a java.util.concurrent.ScheduledThreadPoolExecutor instead to be + * able to control update frequency and cancellation. + */ +class AddImageProgressReportingTask implements Runnable { + + DataSourceProcessorProgressMonitor progressMonitor; + SleuthkitJNI.CaseDbHandle.AddImageProcess addImageProcess; + + /** + * Constructs a Runnable that updates a data source processor progress + * monitor with the name of the directory currently being processed by a + * SleuthKit add image process. + * + * @param progressMonitor The progress monitor. + * @param addImageProcess An Sleuth add image process. + */ + AddImageProgressReportingTask(DataSourceProcessorProgressMonitor progressMonitor, SleuthkitJNI.CaseDbHandle.AddImageProcess addImageProcess) { + this.progressMonitor = progressMonitor; + this.addImageProcess = addImageProcess; + } + + /** + * Every two seconds, updates the progress monitor with the name of the + * directory currently being processed by the add image process. + */ + @Override + public void run() { + try { + while (!Thread.currentThread().isInterrupted()) { + String currDir = addImageProcess.currentDirectory(); + if (null != currDir && !currDir.isEmpty()) { + progressMonitor.setProgressText("Adding: " + currDir); + } + /* + * TODO (JIRA-1578): The sleep should be removed here. Clients + * should use a java.util.concurrent.ScheduledThreadPoolExecutor + * instead to be able to control update frequency and + * cancellation, + */ + Thread.sleep(2 * 1000); + } + } catch (InterruptedException expected) { + } + } +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/Bundle.properties b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/Bundle.properties new file mode 100644 index 0000000000..681f109ed5 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/Bundle.properties @@ -0,0 +1,27 @@ +OpenIDE-Module-Display-Category=Ingest Module +OpenIDE-Module-Long-Description=\ + Extracts application information, lists potentially suspicious databases and SD card folders, and parses known SQLite databases. +OpenIDE-Module-Name=Android Triage +MPFModuleSimplePanel.jSizeLabel.text=Min Size (MB): +MPFModuleSimplePanel.jImgExtCheckBox.text=.img +MPFModuleSimplePanel.jNoExtCheckBox.text=\ No extension +MPFModuleSimplePanel.jBinExtCheckBox.text=.bin +MPFModuleSimplePanel.jMinSizeSpinner.toolTipText=Minimum size of file to be processed by MPF. +MPFModuleSimplePanel.jAllFilesRadioButton.text=Yes (will take longer) +MPFModuleSimplePanel.jFilterFilesRadioButton.text=No +MPFModuleSimplePanel.jLabel2.text=File Extensions: +MPFModuleSimplePanel.jLabel3.text=Process files that meet the following criteria: +MPFModuleSimplePanel.jLabel4.text=Run on all files in set of logical files? +CellebriteXMLFilePanel.browseButton.text=Browse +CellebriteXMLFilePanel.pathLabel.text=Browse for a Cellebrite XML file: +CellebriteXMLFilePanel.pathTextField.text= +CellebriteXMLFilePanel.jLabel1.text=Input type: +CellebriteXMLFilePanel.jHandsetRadioButton.text=Handset +CellebriteXMLFilePanel.jSIMRadioButton.text=SIM +CellebriteAndroidInputPanel.browseButton.text=Browse +CellebriteAndroidInputPanel.pathTextField.text= +CellebriteAndroidInputPanel.timeZoneLabel.text=Please select the input timezone: +CellebriteAndroidInputPanel.pathLabel.text=Browse for a Android phone image folder: +OpenIDE-Module-Short-Description=Does triage of Android data. +CellebriteAndroidInputPanel.errorLabel.text=Error Label +CellebriteXMLFilePanel.errorLabel.text=Error Label diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/CellebriteAndroidImageProcessor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/CellebriteAndroidImageProcessor.java new file mode 100644 index 0000000000..5532c85e9f --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/CellebriteAndroidImageProcessor.java @@ -0,0 +1,193 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.experimental.cellex.datasourceprocessors; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.swing.JPanel; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; + +/** + * An Cellebrite UFED output folder data source processor that implements the + * DataSourceProcessor service provider interface to allow integration with the + * add data source wizard. It also provides a run method overload to allow it to + * be used independently of the wizard. + */ +@ServiceProvider(service = DataSourceProcessor.class) +public class CellebriteAndroidImageProcessor implements DataSourceProcessor { + + private static final String DATA_SOURCE_TYPE = "Cellebrite Android"; + private final CellebriteAndroidInputPanel configPanel; + private AddCellebriteAndroidImageTask addImagesTask; + + /** + * Contructs a Cellebrite UFED output folder data source processor that + * implements the DataSourceProcessor service provider interface to allow + * integration with the add data source wizard. It also provides a run + * method overload to allow it to be used independently of the wizard. + */ + public CellebriteAndroidImageProcessor() { + configPanel = CellebriteAndroidInputPanel.createInstance(CellebriteAndroidImageProcessor.class.getName()); + } + + /** + * Gets a string that describes the type of data sources this processor is + * able to add to the case database. The string is suitable for display in a + * type selection UI component (e.g., a combo box). + * + * @return A data source type display string for this data source processor. + */ + @Override + public String getDataSourceType() { + return DATA_SOURCE_TYPE; + } + + /** + * Gets the panel that allows a user to select a data source and do any + * configuration required by the data source. The panel is less than 544 + * pixels wide and less than 173 pixels high. + * + * @return A selection and configuration panel for this data source + * processor. + */ + @Override + public JPanel getPanel() { + configPanel.readSettings(); + configPanel.select(); + return configPanel; + } + + /** + * Indicates whether the settings in the selection and configuration panel + * are valid and complete. + * + * @return True if the settings are valid and complete and the processor is + * ready to have its run method called, false otherwise. + */ + @Override + public boolean isPanelValid() { + return configPanel.validatePanel(); + } + + /** + * Adds a data source to the case database using a background task in a + * separate thread and the settings provided by the selection and + * configuration panel. Returns as soon as the background task is started. + * The background task uses a callback object to signal task completion and + * return results. + * + * This method should not be called unless isPanelValid returns true. + * + * @param progressMonitor Progress monitor that will be used by the + * background task to report progress. + * @param callback Callback that will be used by the background task + * to return results. + */ + @Override + public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + configPanel.storeSettings(); + run(UUID.randomUUID().toString(), configPanel.getContentPaths(), configPanel.getTimeZone(), progressMonitor, callback); + } + + /** + * Adds a data source to the case database using a background task in a + * separate thread and the given settings instead of those provided by the + * selection and configuration panel. Returns as soon as the background task + * is started and uses the callback object to signal task completion and + * return results. + * + * This method should not be called unless isPanelValid returns true. + * + * @param progressMonitor Progress monitor that will be used by the + * background task to report progress. + * @param callback Callback that will be used by the background task + * to return results. + */ + public void run(String deviceId, String imageFolderPath, String timeZone, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + List imageFilePaths = getImageFilePaths(imageFolderPath); + addImagesTask = new AddCellebriteAndroidImageTask(deviceId, imageFilePaths, timeZone, progressMonitor, callback); + new Thread(addImagesTask).start(); + } + + /** + * Requests cancellation of the background task that adds a data source to + * the case database, after the task is started using the run method. This + * is a "best effort" cancellation, with no guarantees that the case + * database will be unchanged. If cancellation succeeded, the list of new + * data sources returned by the background task will be empty. + */ + @Override + public void cancel() { + addImagesTask.cancelTask(); + } + + /** + * Resets the selection and configuration panel for this data source + * processor. + */ + @Override + public void reset() { + configPanel.reset(); + } + + /** + * Gets the paths of the image files in a Cellebrite UFED output folder. + * + * @param folderPath The path to a Cellebrite UFED output folder + * + * @return A list of image file paths. + */ + private static List getImageFilePaths(String folderPath) { + List imageFilePaths = new ArrayList<>(); + File folder = new File(folderPath); + File[] listOfFiles = folder.listFiles(); + for (File file : listOfFiles) { + if (file.isFile()) { + String fName = file.getName().toLowerCase(); + int lastPeriod = fName.lastIndexOf('.'); + if (-1 == lastPeriod) { + continue; + } + String fNameNoExt = fName.substring(0, lastPeriod); + String ext = fName.substring(lastPeriod + 1); + String filePathName = folderPath + File.separator + fName; + if (ext.equalsIgnoreCase("bin")) { + // this needs to identify and handle different Cellebrite scenarios: + // i single image in a single file + // ii. Single image spilt over multiple files - just need to pass the first to TSK and it will combine the split image files. + // Note there may be more than than one split images in a single dir, + // e.g. blk0_mmcblk0.bin, blk0_mmcblk0(1).bin......, and blk24_mmcblk1.bin, blk24_mmcblk1(1).bin...... + //iii. Multiple image files - one per volume - need to handle each one separately + // e.g. blk0_mmcblk0.bin, mtd0_system.bin, mtd1_cache.bin, mtd2_userdata.bin + // if the file name ends with something like (001).bin then its part of a split image, + if (!fNameNoExt.matches("\\w+\\(\\d+\\)")) { + imageFilePaths.add(filePathName); + } + } + } + } + return imageFilePaths; + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/CellebriteAndroidInputPanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/CellebriteAndroidInputPanel.form new file mode 100644 index 0000000000..0f923d24b6 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/CellebriteAndroidInputPanel.form @@ -0,0 +1,115 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/CellebriteAndroidInputPanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/CellebriteAndroidInputPanel.java new file mode 100644 index 0000000000..d249940985 --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/CellebriteAndroidInputPanel.java @@ -0,0 +1,346 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.experimental.cellex.datasourceprocessors; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.io.File; +import java.util.Calendar; +import java.util.SimpleTimeZone; +import java.util.TimeZone; +import javax.swing.JFileChooser; +import javax.swing.JPanel; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.autopsy.coreutils.PathValidator; + +public class CellebriteAndroidInputPanel extends JPanel implements DocumentListener { + + private final String PROP_LASTIMAGE_PATH = "LBL_LastImage_PATH"; + + private PropertyChangeSupport pcs = null; + private JFileChooser fc = new JFileChooser(); + + // Externally supplied name is used to store settings + private String contextName; + + /** + * Creates new form CellebriteAndroidInputPanel + */ + public CellebriteAndroidInputPanel(String context) { + initComponents(); + + errorLabel.setVisible(false); + + fc.setDragEnabled(false); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + fc.setMultiSelectionEnabled(false); + + this.contextName = context; + pcs = new PropertyChangeSupport(this); + + createTimeZoneList(); + } + + /** + * Creates and returns an instance of a ImageFilePanel. + */ + public static synchronized CellebriteAndroidInputPanel createInstance(String context) { + CellebriteAndroidInputPanel instance = new CellebriteAndroidInputPanel(context); + instance.postInit(); + + return instance; + } + + //post-constructor initialization to properly initialize listener support + //without leaking references of uninitialized objects + private void postInit() { + pathTextField.getDocument().addDocumentListener(this); + } + + /** + * Creates the drop down list for the time zones and then makes the local + * machine time zone to be selected. + */ + public void createTimeZoneList() { + // load and add all timezone + String[] ids = SimpleTimeZone.getAvailableIDs(); + for (String id : ids) { + TimeZone zone = TimeZone.getTimeZone(id); + int offset = zone.getRawOffset() / 1000; + int hour = offset / 3600; + int minutes = (offset % 3600) / 60; + String item = String.format("(GMT%+d:%02d) %s", hour, minutes, id); + + /* + * DateFormat dfm = new SimpleDateFormat("z"); + * dfm.setTimeZone(zone); boolean hasDaylight = + * zone.useDaylightTime(); String first = dfm.format(new Date(2010, + * 1, 1)); String second = dfm.format(new Date(2011, 6, 6)); int mid + * = hour * -1; String result = first + Integer.toString(mid); + * if(hasDaylight){ result = result + second; } + * timeZoneComboBox.addItem(item + " (" + result + ")"); + */ + timeZoneComboBox.addItem(item); + } + // get the current timezone + TimeZone thisTimeZone = Calendar.getInstance().getTimeZone(); + int thisOffset = thisTimeZone.getRawOffset() / 1000; + int thisHour = thisOffset / 3600; + int thisMinutes = (thisOffset % 3600) / 60; + String formatted = String.format("(GMT%+d:%02d) %s", thisHour, thisMinutes, thisTimeZone.getID()); + + // set the selected timezone + timeZoneComboBox.setSelectedItem(formatted); + } + + /** + * Get the path of the user selected folder. + * + * @return the image path + */ + public String getContentPaths() { + return pathTextField.getText(); + } + + /** + * Set the path of the images folder. + */ + public void setContentPath(String s) { + pathTextField.setText(s); + } + + public String getTimeZone() { + String tz = timeZoneComboBox.getSelectedItem().toString(); + return tz.substring(tz.indexOf(")") + 2).trim(); + } + + /** + * 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() { + + pathLabel = new javax.swing.JLabel(); + pathTextField = new javax.swing.JTextField(); + browseButton = new javax.swing.JButton(); + timeZoneLabel = new javax.swing.JLabel(); + timeZoneComboBox = new javax.swing.JComboBox<>(); + errorLabel = new javax.swing.JLabel(); + + org.openide.awt.Mnemonics.setLocalizedText(pathLabel, org.openide.util.NbBundle.getMessage(CellebriteAndroidInputPanel.class, "CellebriteAndroidInputPanel.pathLabel.text")); // NOI18N + + pathTextField.setText(org.openide.util.NbBundle.getMessage(CellebriteAndroidInputPanel.class, "CellebriteAndroidInputPanel.pathTextField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(CellebriteAndroidInputPanel.class, "CellebriteAndroidInputPanel.browseButton.text")); // NOI18N + browseButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + browseButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(timeZoneLabel, org.openide.util.NbBundle.getMessage(CellebriteAndroidInputPanel.class, "CellebriteAndroidInputPanel.timeZoneLabel.text")); // NOI18N + + timeZoneComboBox.setMaximumRowCount(30); + + errorLabel.setForeground(new java.awt.Color(255, 0, 0)); + org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(CellebriteAndroidInputPanel.class, "CellebriteAndroidInputPanel.errorLabel.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) + .addComponent(pathLabel) + .addGroup(layout.createSequentialGroup() + .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 286, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(browseButton)) + .addGroup(layout.createSequentialGroup() + .addComponent(timeZoneLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 215, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(errorLabel)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(pathLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(browseButton)) + .addGap(8, 8, 8) + .addComponent(errorLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(timeZoneLabel) + .addComponent(timeZoneComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(198, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + @SuppressWarnings("deprecation") + private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed + String oldText = pathTextField.getText(); + + // set the current directory of the FileChooser if the ImagePath Field is valid + File currentDir = new File(oldText); + if (currentDir.exists()) { + fc.setCurrentDirectory(currentDir); + } + + int retval = fc.showOpenDialog(this); + if (retval == JFileChooser.APPROVE_OPTION) { + String path = fc.getSelectedFile().getPath(); + pathTextField.setText(path); + } + + pcs.firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.FOCUS_NEXT.toString(), false, true); + }//GEN-LAST:event_browseButtonActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton browseButton; + private javax.swing.JLabel errorLabel; + private javax.swing.JLabel pathLabel; + private javax.swing.JTextField pathTextField; + private javax.swing.JComboBox timeZoneComboBox; + private javax.swing.JLabel timeZoneLabel; + // End of variables declaration//GEN-END:variables + + /** + * Update functions are called by the pathTextField which has this set as + * it's DocumentEventListener. Each update function fires a property change + * to be caught by the parent panel. + * + * @param e the event, which is ignored + */ + @Override + public void insertUpdate(DocumentEvent e) { + pcs.firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); + } + + @Override + public void removeUpdate(DocumentEvent e) { + pcs.firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); + } + + @Override + public void changedUpdate(DocumentEvent e) { + pcs.firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); + } + + /** + * Set the focus to the pathTextField. + */ + public void select() { + pathTextField.requestFocusInWindow(); + } + + @Override + public synchronized void addPropertyChangeListener(PropertyChangeListener pcl) { + super.addPropertyChangeListener(pcl); + + if (pcs == null) { + pcs = new PropertyChangeSupport(this); + } + + pcs.addPropertyChangeListener(pcl); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener pcl) { + super.removePropertyChangeListener(pcl); + + pcs.removePropertyChangeListener(pcl); + } + + /** + * Should we enable the next button of the wizard? + * + * @return true if a proper image has been selected, false otherwise + */ + public boolean validatePanel() { + errorLabel.setVisible(false); + String path = getContentPaths(); + if (path == null || path.isEmpty()) { + return false; + } + + // display warning if there is one (but don't disable "next" button) + warnIfPathIsInvalid(path); + + // check if a folder exists by this name. + File f = new File(path); + boolean isValid = (f.exists() && f.isDirectory()); + + return isValid; + } + + /** + * Validates path to selected data source and displays warning if it is + * invalid. + * + * @param path Absolute path to the selected data source + */ + private void warnIfPathIsInvalid(String path) { + if (!PathValidator.isValid(path, Case.getCurrentCase().getCaseType())) { + errorLabel.setVisible(true); + errorLabel.setText("Path to multi-user data source is on \"C:\" drive"); + } + } + + public void reset() { + //reset the UI elements to default + pathTextField.setText(null); + } + + /* + * Store the last used settings + */ + public void storeSettings() { + String imagePathName = getContentPaths(); + if (null != imagePathName) { + String imagePath = imagePathName.substring(0, imagePathName.lastIndexOf(File.separator) + 1); + ModuleSettings.setConfigSetting(contextName, PROP_LASTIMAGE_PATH, imagePath); + } + } + + /* + * Read and load the last used settings + */ + public void readSettings() { + String lastImagePath = ModuleSettings.getConfigSetting(contextName, PROP_LASTIMAGE_PATH); + if (null != lastImagePath) { + if (!lastImagePath.isEmpty()) { + pathTextField.setText(lastImagePath); + } + } + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/CellebriteXMLFilePanel.form b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/CellebriteXMLFilePanel.form new file mode 100644 index 0000000000..81e541cbbf --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/CellebriteXMLFilePanel.form @@ -0,0 +1,138 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/CellebriteXMLFilePanel.java b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/CellebriteXMLFilePanel.java new file mode 100644 index 0000000000..58533d500a --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/CellebriteXMLFilePanel.java @@ -0,0 +1,330 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.experimental.cellex.datasourceprocessors; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import java.util.List; +import java.io.File; +import javax.swing.JFileChooser; +import javax.swing.JPanel; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.filechooser.FileFilter; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; +import org.sleuthkit.autopsy.coreutils.ModuleSettings; +import org.sleuthkit.autopsy.coreutils.PathValidator; + +public class CellebriteXMLFilePanel extends JPanel implements DocumentListener { + + private final String PROP_LASTINPUT_PATH = "LBL_LastInputFile_PATH"; + + private PropertyChangeSupport pcs = null; + private JFileChooser fc = new JFileChooser(); + + // Externally supplied name is used to store settings + private String contextName; + + /** + * Creates new form CellebriteXMLFilePanel + */ + private CellebriteXMLFilePanel(String context, List fileChooserFilters) { + initComponents(); + + errorLabel.setVisible(false); + + fc.setDragEnabled(false); + fc.setFileSelectionMode(JFileChooser.FILES_ONLY); + fc.setMultiSelectionEnabled(false); + + boolean firstFilter = true; + for (FileFilter filter : fileChooserFilters) { + if (firstFilter) { // set the first on the list as the default selection + fc.setFileFilter(filter); + firstFilter = false; + } else { + fc.addChoosableFileFilter(filter); + } + } + + this.contextName = context; + pcs = new PropertyChangeSupport(this); + } + + /** + * Creates and returns an instance of a CellebriteXMLFilePanel. + */ + public static synchronized CellebriteXMLFilePanel createInstance(String context, List fileChooserFilters) { + CellebriteXMLFilePanel instance = new CellebriteXMLFilePanel(context, fileChooserFilters); + + instance.postInit(); + + return instance; + } + + //post-constructor initialization to properly initialize listener support + //without leaking references of uninitialized objects + private void postInit() { + pathTextField.getDocument().addDocumentListener(this); + } + + /** + * 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() { + + infileTypeButtonGroup = new javax.swing.ButtonGroup(); + pathLabel = new javax.swing.JLabel(); + pathTextField = new javax.swing.JTextField(); + browseButton = new javax.swing.JButton(); + jHandsetRadioButton = new javax.swing.JRadioButton(); + jLabel1 = new javax.swing.JLabel(); + jSIMRadioButton = new javax.swing.JRadioButton(); + errorLabel = new javax.swing.JLabel(); + + org.openide.awt.Mnemonics.setLocalizedText(pathLabel, org.openide.util.NbBundle.getMessage(CellebriteXMLFilePanel.class, "CellebriteXMLFilePanel.pathLabel.text")); // NOI18N + + pathTextField.setText(org.openide.util.NbBundle.getMessage(CellebriteXMLFilePanel.class, "CellebriteXMLFilePanel.pathTextField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(browseButton, org.openide.util.NbBundle.getMessage(CellebriteXMLFilePanel.class, "CellebriteXMLFilePanel.browseButton.text")); // NOI18N + browseButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + browseButtonActionPerformed(evt); + } + }); + + infileTypeButtonGroup.add(jHandsetRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(jHandsetRadioButton, org.openide.util.NbBundle.getMessage(CellebriteXMLFilePanel.class, "CellebriteXMLFilePanel.jHandsetRadioButton.text")); // NOI18N + jHandsetRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jHandsetRadioButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(CellebriteXMLFilePanel.class, "CellebriteXMLFilePanel.jLabel1.text")); // NOI18N + + infileTypeButtonGroup.add(jSIMRadioButton); + org.openide.awt.Mnemonics.setLocalizedText(jSIMRadioButton, org.openide.util.NbBundle.getMessage(CellebriteXMLFilePanel.class, "CellebriteXMLFilePanel.jSIMRadioButton.text")); // NOI18N + jSIMRadioButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + jSIMRadioButtonActionPerformed(evt); + } + }); + + errorLabel.setForeground(new java.awt.Color(255, 0, 0)); + org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(CellebriteXMLFilePanel.class, "CellebriteXMLFilePanel.errorLabel.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(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 286, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(browseButton)) + .addComponent(pathLabel) + .addGroup(layout.createSequentialGroup() + .addGap(16, 16, 16) + .addComponent(jHandsetRadioButton) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jSIMRadioButton)) + .addComponent(jLabel1) + .addComponent(errorLabel)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(pathLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(browseButton)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(errorLabel) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jSIMRadioButton) + .addComponent(jHandsetRadioButton)) + .addContainerGap(26, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + @SuppressWarnings("deprecation") + private void browseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseButtonActionPerformed + String oldText = pathTextField.getText(); + // set the current directory of the FileChooser if the ImagePath Field is valid + File currentDir = new File(oldText); + if (currentDir.exists()) { + fc.setCurrentDirectory(currentDir); + } + + int retval = fc.showOpenDialog(this); + if (retval == JFileChooser.APPROVE_OPTION) { + String path = fc.getSelectedFile().getPath(); + pathTextField.setText(path); + } + pcs.firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.FOCUS_NEXT.toString(), false, true); + }//GEN-LAST:event_browseButtonActionPerformed + + private void jHandsetRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jHandsetRadioButtonActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_jHandsetRadioButtonActionPerformed + + private void jSIMRadioButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jSIMRadioButtonActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_jSIMRadioButtonActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton browseButton; + private javax.swing.JLabel errorLabel; + private javax.swing.ButtonGroup infileTypeButtonGroup; + private javax.swing.JRadioButton jHandsetRadioButton; + private javax.swing.JLabel jLabel1; + private javax.swing.JRadioButton jSIMRadioButton; + private javax.swing.JLabel pathLabel; + private javax.swing.JTextField pathTextField; + // End of variables declaration//GEN-END:variables +/** + * Get the path of the user selected image. + * + * @return the image path + */ + public String getImageFilePath() { + return pathTextField.getText(); + } + + public void reset() { + //reset the UI elements to default + pathTextField.setText(null); + jHandsetRadioButton.setSelected(true); + } + + public boolean isHandsetFile() { + return jHandsetRadioButton.isSelected(); + } + + /** + * Should we enable the next button of the wizard? + * + * @return true if a proper image has been selected, false otherwise + */ + public boolean validatePanel() { + errorLabel.setVisible(false); + String path = getImageFilePath(); + if (path == null || path.isEmpty()) { + return false; + } + + // display warning if there is one (but don't disable "next" button) + warnIfPathIsInvalid(path); + + boolean isExist = new File(path).exists(); + + return (isExist); + } + + /** + * Validates path to selected data source and displays warning if it is + * invalid. + * + * @param path Absolute path to the selected data source + */ + private void warnIfPathIsInvalid(String path) { + if (!PathValidator.isValid(path, Case.getCurrentCase().getCaseType())) { + errorLabel.setVisible(true); + errorLabel.setText("Path to multi-user data source is on \"C:\" drive"); + } + } + + public void storeSettings() { + String inFilePath = getImageFilePath(); + if (null != inFilePath) { + String imagePath = inFilePath.substring(0, inFilePath.lastIndexOf(File.separator) + 1); + ModuleSettings.setConfigSetting(contextName, PROP_LASTINPUT_PATH, imagePath); + } + } + + public void readSettings() { + String inFilePath = ModuleSettings.getConfigSetting(contextName, PROP_LASTINPUT_PATH); + if (null != inFilePath) { + if (!inFilePath.isEmpty()) { + pathTextField.setText(inFilePath); + } + } + } + + /** + * Update functions are called by the pathTextField which has this set as + * it's DocumentEventListener. Each update function fires a property change + * to be caught by the parent panel. + * + * @param e the event, which is ignored + */ + @Override + public void insertUpdate(DocumentEvent e) { + pcs.firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); + } + + @Override + public void removeUpdate(DocumentEvent e) { + pcs.firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); + } + + @Override + public void changedUpdate(DocumentEvent e) { + pcs.firePropertyChange(DataSourceProcessor.DSP_PANEL_EVENT.UPDATE_UI.toString(), false, true); + } + + /** + * Set the focus to the pathTextField. + */ + public void select() { + pathTextField.requestFocusInWindow(); + } + + @Override + public synchronized void addPropertyChangeListener(PropertyChangeListener pcl) { + super.addPropertyChangeListener(pcl); + + if (pcs == null) { + pcs = new PropertyChangeSupport(this); + } + + pcs.addPropertyChangeListener(pcl); + } + + @Override + public void removePropertyChangeListener(PropertyChangeListener pcl) { + super.removePropertyChangeListener(pcl); + + pcs.removePropertyChangeListener(pcl); + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/CellebriteXMLProcessor.java b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/CellebriteXMLProcessor.java new file mode 100644 index 0000000000..7f0497accd --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/CellebriteXMLProcessor.java @@ -0,0 +1,188 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.experimental.cellex.datasourceprocessors; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import javax.swing.JPanel; +import javax.swing.filechooser.FileFilter; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.casemodule.GeneralFilter; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorProgressMonitor; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessorCallback; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataSourceProcessor; + +/** + * A Cellebrite XML report file data source processor that implements the + * DataSourceProcessor service provider interface to allow integration with the + * add data source wizard. It also provides a run method overload to allow it to + * be used independently of the wizard. + */ +@ServiceProvider(service = DataSourceProcessor.class) +public class CellebriteXMLProcessor implements DataSourceProcessor { + + private static final String DATA_SOURCE_TYPE = "Cellebrite XML"; + private static final List CELLEBRITE_EXTS = Arrays.asList(new String[]{".xml"}); + private static final String CELLEBRITE_DESC = "Cellebrite XML Files (*.xml)"; + private static final GeneralFilter xmlFilter = new GeneralFilter(CELLEBRITE_EXTS, CELLEBRITE_DESC); + private static final List filtersList = new ArrayList<>(); + private final CellebriteXMLFilePanel configPanel; + private AddCellebriteXMLTask addCellebriteXMLTask; + + static { + filtersList.add(xmlFilter); + } + + /** + * Gets the file extensions supported by this data source processor as a + * list of file filters. + * + * @return List List of FileFilter objects + */ + public static final List getFileFilterList() { + return filtersList; + } + + /* + * Constructs a Cellebrite XML report file data source processor that + * implements the DataSourceProcessor service provider interface to allow + * integration with the add data source wizard. It also provides a run + * method overload to allow it to be used independently of the wizard. + */ + public CellebriteXMLProcessor() { + configPanel = CellebriteXMLFilePanel.createInstance(CellebriteXMLProcessor.class.getName(), filtersList); + } + + /** + * Gets a string that describes the type of data sources this processor is + * able to add to the case database. The string is suitable for display in a + * type selection UI component (e.g., a combo box). + * + * @return A data source type display string for this data source processor. + */ + @Override + public String getDataSourceType() { + return DATA_SOURCE_TYPE; + } + + /** + * Gets the panel that allows a user to select a data source and do any + * configuration required by the data source. The panel is less than 544 + * pixels wide and less than 173 pixels high. + * + * @return A selection and configuration panel for this data source + * processor. + */ + @Override + public JPanel getPanel() { + configPanel.readSettings(); + configPanel.select(); + return configPanel; + } + + /** + * Indicates whether the settings in the selection and configuration panel + * are valid and complete. + * + * @return True if the settings are valid and complete and the processor is + * ready to have its run method called, false otherwise. + */ + @Override + public boolean isPanelValid() { + return configPanel.validatePanel(); + } + + /** + * Adds a data source to the case database using a background task in a + * separate thread and the settings provided by the selection and + * configuration panel. Returns as soon as the background task is started. + * The background task uses a callback object to signal task completion and + * return results. + * + * This method should not be called unless isPanelValid returns true. + * + * @param progressMonitor Progress monitor that will be used by the + * background task to report progress. + * @param callback Callback that will be used by the background task + * to return results. + */ + @Override + public void run(DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + configPanel.storeSettings(); + String deviceId = UUID.randomUUID().toString(); + run(deviceId, deviceId, configPanel.getImageFilePath(), configPanel.isHandsetFile(), progressMonitor, callback); + } + + /** + * Adds a data source to the case database using a background task in a + * separate thread and the given settings instead of those provided by the + * selection and configuration panel. Returns as soon as the background task + * is started and uses the callback object to signal task completion and + * return results. + * + * @param deviceId An ASCII-printable identifier for the + * device associated with the data source + * that is intended to be unique across + * multiple cases (e.g., a UUID). + * @param rootVirtualDirectoryName The name to give to the virtual directory + * that will represent the data source. Pass + * the empty string to get a default name of + * the form: LogicalFileSet[N] + * @param cellebriteXmlFilePath Path to a Cellebrite report XML file. + * @param isHandsetFile Indicates whether the XML file is for a + * handset or a SIM. + * @param progressMonitor Progress monitor for reporting progress + * during processing. + * @param callback Callback to call when processing is done. + */ + public void run(String deviceId, String rootVirtualDirectoryName, String cellebriteXmlFilePath, boolean isHandsetFile, DataSourceProcessorProgressMonitor progressMonitor, DataSourceProcessorCallback callback) { + AddCellebriteXMLTask.CellebriteInputType inputType; + if (isHandsetFile) { + inputType = AddCellebriteXMLTask.CellebriteInputType.handset; + } else { + inputType = AddCellebriteXMLTask.CellebriteInputType.SIM; + } + addCellebriteXMLTask = new AddCellebriteXMLTask(deviceId, rootVirtualDirectoryName, cellebriteXmlFilePath, inputType, progressMonitor, callback); + new Thread(addCellebriteXMLTask).start(); + } + + /** + * Requests cancellation of the background task that adds a data source to + * the case database, after the task is started using the run method. This + * is a "best effort" cancellation, with no guarantees that the case + * database will be unchanged. If cancellation succeeded, the list of new + * data sources returned by the background task will be empty. + */ + @Override + public void cancel() { + addCellebriteXMLTask.cancelTask(); + } + + /** + * Resets the selection and configuration panel for this data source + * processor. + */ + @Override + public void reset() { + configPanel.reset(); + } + +} diff --git a/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/Util.java b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/Util.java new file mode 100644 index 0000000000..fe550b291a --- /dev/null +++ b/Experimental/src/org/sleuthkit/autopsy/experimental/cellex/datasourceprocessors/Util.java @@ -0,0 +1,132 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2015 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.experimental.cellex.datasourceprocessors; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import static java.lang.Math.min; +import java.util.Collection; +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.TskCoreException; + +/** + * + * @author flynn + */ +public class Util { + + public interface Mapper { + + public T2 apply(T1 x); + } + + public static void copyToFile(AbstractFile inputFile, String destPath) + throws IOException, TskCoreException { + long inputSize = inputFile.getSize(); + long bufSize = min(inputSize, 65536L); + + byte[] buffer = new byte[(int) bufSize]; + + FileOutputStream output = new FileOutputStream(destPath); + + long offset = 0; + long bytesLeft = inputSize; + + try { + while (bytesLeft > 0) { + int bytesRead = inputFile.read(buffer, offset, bufSize); + + if (bytesRead < 0) { + throw new IOException("I/O error (rc " + bytesRead + ")"); + } + + output.write(buffer); + + bytesLeft -= bytesRead; + offset += bytesRead; + } + } finally { + // Always always always close output, no matter what. + output.close(); + } + } + + public static String getBasename(String path) { + String baseName = path; + int lastSlash = baseName.lastIndexOf(File.separator); + + if (lastSlash >= 0) { + baseName = baseName.substring(lastSlash + 1); + } + + return baseName; + } + + public static String stripExtension(String path) { + String noExt = path; + + int lastPeriod = path.lastIndexOf('.'); + + if (lastPeriod > 0) { // Not >=, >. A single "." should be preserved. + noExt = path.substring(0, lastPeriod); + } + + return noExt; + } + + public static String joinPath(String... elements) { + return join(File.separator, elements); + } + + public static String join(String delim, Mapper mapFunc, + String... elements) { + String joined = ""; + + for (String element : elements) { + if (mapFunc != null) { + element = mapFunc.apply(element); + } + + if ((element != null) && (element.length() > 0)) { + if (joined.length() > 0) { + joined += delim; + } + + joined += element; + } + } + + return joined; + } + + public static String join(String delim, Mapper mapFunc, + Collection elements) { + return join(delim, mapFunc, + elements.toArray(new String[elements.size()])); + } + + public static String join(String delim, String... elements) { + return join(delim, null, elements); + } + + public static String join(String delim, Collection elements) { + return join(delim, null, elements); + } +}