Rename of mboxemail and additions of thunderbirdparser class.

This commit is contained in:
Alex Ebadirad 2012-06-18 14:55:23 -07:00
parent 050e48bd5f
commit 88b61d9981
33 changed files with 1158 additions and 1782 deletions

View File

@ -1,8 +1,8 @@
#Updated by build script #Updated by build script
#Thu, 07 Jun 2012 13:38:12 -0700 #Mon, 18 Jun 2012 14:35:03 -0700
OpenIDE-Module-Name=CoreUtils OpenIDE-Module-Name=CoreUtils
app.name=Autopsy app.name=Autopsy
app.version=20120607 app.version=20120618
build.type=DEVELOPMENT build.type=DEVELOPMENT

View File

@ -1,6 +0,0 @@
Manifest-Version: 1.0
OpenIDE-Module: org.sleuthkit.autopsy.mboxparser
OpenIDE-Module-Layer: org/sleuthkit/autopsy/mboxparser/layer.xml
OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/mboxparser/Bundle.properties
OpenIDE-Module-Specification-Version: 1.0

View File

@ -1,57 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://www.netbeans.org/ns/project/1">
<type>org.netbeans.modules.apisupport.project</type>
<configuration>
<data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
<code-name-base>org.sleuthkit.autopsy.mboxparser</code-name-base>
<suite-component/>
<module-dependencies>
<dependency>
<code-name-base>org.sleuthkit.autopsy.casemodule</code-name-base>
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
<specification-version>1.0</specification-version>
</run-dependency>
</dependency>
<dependency>
<code-name-base>org.sleuthkit.autopsy.coreutils</code-name-base>
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
<release-version>0-1</release-version>
<specification-version>0.0</specification-version>
</run-dependency>
</dependency>
<dependency>
<code-name-base>org.sleuthkit.autopsy.datamodel</code-name-base>
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
<specification-version>1.0</specification-version>
</run-dependency>
</dependency>
<dependency>
<code-name-base>org.sleuthkit.autopsy.ingest</code-name-base>
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
<release-version>0-1</release-version>
<specification-version>1.0</specification-version>
</run-dependency>
</dependency>
</module-dependencies>
<public-packages/>
<class-path-extension>
<runtime-relative-path>ext/tika-core-1.1.jar</runtime-relative-path>
<binary-origin>release/modules/ext/tika-core-1.1.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/tika-parsers-1.1.jar</runtime-relative-path>
<binary-origin>release/modules/ext/tika-parsers-1.1.jar</binary-origin>
</class-path-extension>
</data>
</configuration>
</project>

View File

@ -1 +0,0 @@
OpenIDE-Module-Name=MboxEmailModule

View File

@ -1,206 +0,0 @@
package org.sleuthkit.autopsy.mboxparser;
import java.io.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.tika.Tika;
import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.mime.MimeTypes;
import org.apache.tika.mime.MediaType;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.parser.mbox.MboxParser;
import org.apache.tika.sax.BodyContentHandler;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
public class MboxEmailParser {
private InputStream stream;
//Tika object
private Tika tika;
private Metadata metadata;
private ContentHandler contentHandler;
private String mimeType;
private Parser parser;
private ParseContext context;
private static ArrayList<String> tikaMimeTypes;
static
{
tikaMimeTypes = new ArrayList<String>();
tikaMimeTypes.add(MimeTypes.OCTET_STREAM);
tikaMimeTypes.add(MimeTypes.PLAIN_TEXT);
tikaMimeTypes.add(MimeTypes.XML);
}
public MboxEmailParser()
{
this.tika = new Tika();
}
public MboxEmailParser(InputStream inStream)
{
this.tika = new Tika();
this.stream = inStream;
}
public MboxEmailParser(String filepath)
{
this.tika = new Tika();
this.stream = this.getClass().getResourceAsStream(filepath);
}
private void init() throws IOException
{
this.tika.setMaxStringLength(10*1024*1024);
this.metadata = new Metadata();
//Set MIME Type
this.mimeType = tika.detect(this.stream);
this.parser = new MboxParser();
this.context = new ParseContext();
this.contentHandler = new BodyContentHandler(-1);
//Seems like setting this causes the metadata not to output all of it.
// this.metadata.set(Metadata.CONTENT_TYPE, this.mimeType);
}
public void parse() throws FileNotFoundException, IOException, SAXException, TikaException
{
init();
// this.metadata = new Metadata();
//String mimeType = tika.detect(this.stream);
parser.parse(this.stream,this.contentHandler, this.metadata, context);
}
public void parse(InputStream inStream) throws FileNotFoundException, IOException, SAXException, TikaException
{
init();
parser.parse(inStream,this.contentHandler, this.metadata, context);
String blbha = "stop";
}
public Metadata getMetadata()
{
return this.metadata;
}
//Returns message content, i.e. plain text or html
public String getContent()
{
return this.contentHandler.toString();
}
public String detectEmailFileFormat(String filepath) throws IOException
{
return this.tika.detect(filepath);
}
//Detects the mime type from the first few bytes of the document
public String detectMediaTypeFromBytes(byte[] firstFewBytes, String inDocName)
{
return this.tika.detect(firstFewBytes, inDocName);
}
public boolean isValidMimeTypeMbox(byte[] buffer)
{
return (new String(buffer)).startsWith("From ");
}
//This assumes the file/stream was parsed since we are looking at the metadata
public boolean isValidMboxType()
{
return this.metadata.get(Metadata.CONTENT_TYPE).equals("application/mbox");
}
//Get email subject
public String getSubject()
{
return this.metadata.get(Metadata.SUBJECT);
}
public String getTitle()
{
return this.metadata.get(Metadata.TITLE);
}
public Long getDateCreated()
{
Long epochtime;
Long ftime = (long) 0;
try {
String datetime = this.metadata.get(Metadata.DATE);
epochtime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(datetime).getTime();
ftime = epochtime.longValue();
ftime = ftime / 1000;
} catch (ParseException ex) {
Logger.getLogger(MboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex);
}
return ftime;
}
public String getApplication()
{
String client;
String userAgent = "";
userAgent = this.metadata.get("MboxParser-user-agent");
if(userAgent.matches("(?i).*Thunderbird.*"))
{
client = "Thunderbird";
}
else{
client = "Unknown";
}
return client;
}
public String getContenType()
{
return this.metadata.get(Metadata.CONTENT_TYPE);
}
public String getContenEncoding()
{
return this.metadata.get(Metadata.CONTENT_ENCODING);
}
public String getFrom()
{
return this.metadata.get(Metadata.AUTHOR);
}
public String getTo()
{
return this.metadata.get(Metadata.MESSAGE_TO);
}
public String getCC()
{
return this.metadata.get(Metadata.MESSAGE_CC);
}
public String getBCC()
{
return this.metadata.get(Metadata.MESSAGE_BCC);
}
public String getRecipientAddress()
{
return this.metadata.get(Metadata.MESSAGE_RECIPIENT_ADDRESS);
}
public String getMboxSupportedMediaType()
{
return MediaType.application("mbox").getType();
}
}

View File

@ -1,195 +0,0 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2011 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.mboxparser;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.tika.exception.TikaException;
import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.IngestManagerProxy;
import org.sleuthkit.autopsy.ingest.IngestMessage;
import org.sleuthkit.autopsy.ingest.IngestMessage.MessageType;
import org.sleuthkit.autopsy.ingest.IngestServiceAbstract.*;
import org.sleuthkit.autopsy.ingest.IngestServiceAbstractFile;
import org.sleuthkit.autopsy.ingest.ServiceDataEvent;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskException;
import org.xml.sax.SAXException;
public class MboxFileIngestService implements IngestServiceAbstractFile {
private static final Logger logger = Logger.getLogger(MboxFileIngestService.class.getName());
private static MboxFileIngestService instance = null;
private IngestManagerProxy managerProxy;
private static int messageId = 0;
private static final String classname = "Mbox Parser";
public static synchronized MboxFileIngestService getDefault() {
if (instance == null) {
instance = new MboxFileIngestService();
}
return instance;
}
@Override
public ProcessResult process(AbstractFile fsContent) {
MboxEmailParser mbox = new MboxEmailParser();
boolean isMbox = false;
try {
byte[] t = new byte[(int) 128];
int byteRead = fsContent.read(t, 0, 128);
isMbox = mbox.isValidMimeTypeMbox(t);
} catch (TskException ex) {
Logger.getLogger(MboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex);
}
if (isMbox) {
managerProxy.postMessage(IngestMessage.createMessage(++messageId, MessageType.INFO, this, "Processing " + fsContent.getName()));
try {
ReadContentInputStream contentStream = new ReadContentInputStream(fsContent);
mbox.parse(contentStream);
String content = mbox.getContent();
String client = mbox.getApplication();
String from = mbox.getFrom();
String to = mbox.getTo();
Long date = mbox.getDateCreated();
String subject = mbox.getSubject();
String cc = mbox.getCC();
String bcc = mbox.getBCC();
Collection<BlackboardAttribute> bbattributes = new ArrayList<BlackboardAttribute>();
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_TO.getTypeID(), classname, "", to));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_CC.getTypeID(), classname, "", cc));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_BCC.getTypeID(), classname, "", bcc));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_FROM.getTypeID(), classname, "", from));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_PLAIN.getTypeID(), classname, "", content));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_HTML.getTypeID(), classname, "", content));
//bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_MSG_ID.getTypeID(), classname, "",));
//bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_MSG_REPLY_ID.getTypeID(), classname, "",));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_RCVD.getTypeID(), classname, "", date));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_SENT.getTypeID(), classname, "", date));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SUBJECT.getTypeID(), classname, "", subject));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID(), classname, "", client));
BlackboardArtifact bbart;
try {
bbart = fsContent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG);
bbart.addAttributes(bbattributes);
} catch (TskCoreException ex) {
Logger.getLogger(MboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex);
}
IngestManager.fireServiceDataEvent(new ServiceDataEvent(classname, BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG));
} catch (FileNotFoundException ex) {
Logger.getLogger(MboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(MboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex);
} catch (SAXException ex) {
Logger.getLogger(MboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex);
} catch (TikaException ex) {
Logger.getLogger(MboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex);
}
}
return ProcessResult.OK;
}
@Override
public void complete() {
logger.log(Level.INFO, "complete()");
managerProxy.postMessage(IngestMessage.createMessage(++messageId, MessageType.INFO, this, "COMPLETE"));
//service specific cleanup due completion here
}
@Override
public String getName() {
return "Mbox Parser";
}
@Override
public String getDescription() {
return "This class parses through a file to determine if it is an mbox file and if so, populates an email artifact for it in the blackboard.";
}
@Override
public void init(IngestManagerProxy managerProxy) {
logger.log(Level.INFO, "init()");
this.managerProxy = managerProxy;
//service specific initialization here
}
@Override
public void stop() {
logger.log(Level.INFO, "stop()");
//service specific cleanup due interruption here
}
@Override
public ServiceType getType() {
return ServiceType.AbstractFile;
}
@Override
public boolean hasSimpleConfiguration() {
return false;
}
@Override
public boolean hasAdvancedConfiguration() {
return false;
}
@Override
public javax.swing.JPanel getSimpleConfiguration() {
return null;
}
@Override
public javax.swing.JPanel getAdvancedConfiguration() {
return null;
}
@Override
public boolean hasBackgroundJobsRunning() {
return false;
}
@Override
public void saveAdvancedConfiguration() {
}
@Override
public void saveSimpleConfiguration() {
}
}

View File

@ -1,8 +1,8 @@
build.xml.data.CRC32=d23c11ef build.xml.data.CRC32=d1b02431
build.xml.script.CRC32=bbb1c310 build.xml.script.CRC32=bbb1c310
build.xml.stylesheet.CRC32=a56c6a5b@1.46.2 build.xml.stylesheet.CRC32=a56c6a5b@1.46.2
# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. # This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. # Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
nbproject/build-impl.xml.data.CRC32=d23c11ef nbproject/build-impl.xml.data.CRC32=d1b02431
nbproject/build-impl.xml.script.CRC32=1562aec2 nbproject/build-impl.xml.script.CRC32=1562aec2
nbproject/build-impl.xml.stylesheet.CRC32=238281d1@1.46.2 nbproject/build-impl.xml.stylesheet.CRC32=238281d1@1.46.2

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- You may freely edit this file. See harness/README in the NetBeans platform -->
<!-- for some information on what you could do (e.g. targets to override). -->
<!-- If you delete this file and reopen the project it will be recreated. -->
<project name="org.sleuthkit.autopsy.mboxparser" default="netbeans" basedir=".">
<description>Builds, tests, and runs the project org.sleuthkit.autopsy.mboxparser.</description>
<import file="nbproject/build-impl.xml"/>
</project>

View File

@ -1,6 +0,0 @@
Manifest-Version: 1.0
OpenIDE-Module: org.sleuthkit.autopsy.mboxparser
OpenIDE-Module-Layer: org/sleuthkit/autopsy/mboxparser/layer.xml
OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/mboxparser/Bundle.properties
OpenIDE-Module-Specification-Version: 1.0

View File

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
*** GENERATED FROM project.xml - DO NOT EDIT ***
*** EDIT ../build.xml INSTEAD ***
-->
<project name="org.sleuthkit.autopsy.mboxparser-impl" basedir="..">
<fail message="Please build using Ant 1.7.1 or higher.">
<condition>
<not>
<antversion atleast="1.7.1"/>
</not>
</condition>
</fail>
<property file="nbproject/private/suite-private.properties"/>
<property file="nbproject/suite.properties"/>
<fail unless="suite.dir">You must set 'suite.dir' to point to your containing module suite</fail>
<property file="${suite.dir}/nbproject/private/platform-private.properties"/>
<property file="${suite.dir}/nbproject/platform.properties"/>
<macrodef name="property" uri="http://www.netbeans.org/ns/nb-module-project/2">
<attribute name="name"/>
<attribute name="value"/>
<sequential>
<property name="@{name}" value="${@{value}}"/>
</sequential>
</macrodef>
<macrodef name="evalprops" uri="http://www.netbeans.org/ns/nb-module-project/2">
<attribute name="property"/>
<attribute name="value"/>
<sequential>
<property name="@{property}" value="@{value}"/>
</sequential>
</macrodef>
<property file="${user.properties.file}"/>
<nbmproject2:property name="harness.dir" value="nbplatform.${nbplatform.active}.harness.dir" xmlns:nbmproject2="http://www.netbeans.org/ns/nb-module-project/2"/>
<nbmproject2:property name="nbplatform.active.dir" value="nbplatform.${nbplatform.active}.netbeans.dest.dir" xmlns:nbmproject2="http://www.netbeans.org/ns/nb-module-project/2"/>
<nbmproject2:evalprops property="cluster.path.evaluated" value="${cluster.path}" xmlns:nbmproject2="http://www.netbeans.org/ns/nb-module-project/2"/>
<fail message="Path to 'platform' cluster missing in $${cluster.path} property or using corrupt Netbeans Platform (missing harness).">
<condition>
<not>
<contains string="${cluster.path.evaluated}" substring="platform"/>
</not>
</condition>
</fail>
<import file="${harness.dir}/build.xml"/>
</project>

View File

@ -1,100 +0,0 @@
cluster.path=\
${nbplatform.active.dir}/harness:\
${nbplatform.active.dir}/java:\
${nbplatform.active.dir}/platform
disabled.modules=\
org.apache.tools.ant.module,\
org.netbeans.api.debugger.jpda,\
org.netbeans.api.java,\
org.netbeans.libs.cglib,\
org.netbeans.libs.javacapi,\
org.netbeans.libs.javacimpl,\
org.netbeans.libs.jsr223,\
org.netbeans.libs.springframework,\
org.netbeans.modules.ant.browsetask,\
org.netbeans.modules.ant.debugger,\
org.netbeans.modules.ant.freeform,\
org.netbeans.modules.ant.grammar,\
org.netbeans.modules.ant.kit,\
org.netbeans.modules.beans,\
org.netbeans.modules.classfile,\
org.netbeans.modules.dbschema,\
org.netbeans.modules.debugger.jpda,\
org.netbeans.modules.debugger.jpda.ant,\
org.netbeans.modules.debugger.jpda.projects,\
org.netbeans.modules.debugger.jpda.ui,\
org.netbeans.modules.form,\
org.netbeans.modules.form.j2ee,\
org.netbeans.modules.form.kit,\
org.netbeans.modules.hibernate,\
org.netbeans.modules.hibernatelib,\
org.netbeans.modules.hudson.ant,\
org.netbeans.modules.hudson.maven,\
org.netbeans.modules.i18n,\
org.netbeans.modules.i18n.form,\
org.netbeans.modules.j2ee.core.utilities,\
org.netbeans.modules.j2ee.eclipselink,\
org.netbeans.modules.j2ee.eclipselinkmodelgen,\
org.netbeans.modules.j2ee.jpa.refactoring,\
org.netbeans.modules.j2ee.jpa.verification,\
org.netbeans.modules.j2ee.metadata,\
org.netbeans.modules.j2ee.metadata.model.support,\
org.netbeans.modules.j2ee.persistence,\
org.netbeans.modules.j2ee.persistence.kit,\
org.netbeans.modules.j2ee.persistenceapi,\
org.netbeans.modules.j2ee.toplinklib,\
org.netbeans.modules.java.api.common,\
org.netbeans.modules.java.debug,\
org.netbeans.modules.java.editor,\
org.netbeans.modules.java.editor.lib,\
org.netbeans.modules.java.examples,\
org.netbeans.modules.java.freeform,\
org.netbeans.modules.java.guards,\
org.netbeans.modules.java.helpset,\
org.netbeans.modules.java.hints,\
org.netbeans.modules.java.hints.processor,\
org.netbeans.modules.java.j2seplatform,\
org.netbeans.modules.java.j2seproject,\
org.netbeans.modules.java.kit,\
org.netbeans.modules.java.lexer,\
org.netbeans.modules.java.navigation,\
org.netbeans.modules.java.platform,\
org.netbeans.modules.java.preprocessorbridge,\
org.netbeans.modules.java.project,\
org.netbeans.modules.java.source,\
org.netbeans.modules.java.source.ant,\
org.netbeans.modules.java.sourceui,\
org.netbeans.modules.javadoc,\
org.netbeans.modules.javawebstart,\
org.netbeans.modules.jellytools,\
org.netbeans.modules.jellytools.java,\
org.netbeans.modules.junit,\
org.netbeans.modules.maven,\
org.netbeans.modules.maven.coverage,\
org.netbeans.modules.maven.embedder,\
org.netbeans.modules.maven.grammar,\
org.netbeans.modules.maven.graph,\
org.netbeans.modules.maven.hints,\
org.netbeans.modules.maven.indexer,\
org.netbeans.modules.maven.junit,\
org.netbeans.modules.maven.kit,\
org.netbeans.modules.maven.model,\
org.netbeans.modules.maven.osgi,\
org.netbeans.modules.maven.persistence,\
org.netbeans.modules.maven.repository,\
org.netbeans.modules.maven.search,\
org.netbeans.modules.maven.spring,\
org.netbeans.modules.projectimport.eclipse.core,\
org.netbeans.modules.projectimport.eclipse.j2se,\
org.netbeans.modules.refactoring.java,\
org.netbeans.modules.spellchecker.bindings.java,\
org.netbeans.modules.spring.beans,\
org.netbeans.modules.swingapp,\
org.netbeans.modules.websvc.jaxws21,\
org.netbeans.modules.websvc.jaxws21api,\
org.netbeans.modules.websvc.saas.codegen.java,\
org.netbeans.modules.xml.jaxb,\
org.netbeans.modules.xml.tools.java,\
org.openide.compat,\
org.openide.util.enumerations
nbplatform.active=default

View File

@ -1,2 +0,0 @@
javac.source=1.6
javac.compilerargs=-Xlint -Xlint:-serial

View File

@ -1 +0,0 @@
suite.dir=${basedir}/..

View File

@ -1 +0,0 @@
OpenIDE-Module-Name=ThunderbirdMboxEmailModule

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.2//EN" "http://www.netbeans.org/dtds/filesystem-1_2.dtd">
<filesystem>
<folder name="Services">
<file name="org-sleuthkit-autopsy-mboxparser-MboxFileIngestService.instance">
<attr name="instanceOf" stringvalue="org.sleuthkit.autopsy.ingest.IngestServiceAbstractFile"/>
<attr name="instanceCreate" methodvalue="org.sleuthkit.autopsy.mboxparser.MboxFileIngestService.getDefault"/>
<attr name="position" intvalue="1100"/>
</file>
</folder>
</filesystem>

View File

@ -30,7 +30,7 @@ modules=\
${project.org.sleuthkit.autopsy.recentactivity}:\ ${project.org.sleuthkit.autopsy.recentactivity}:\
${project.org.sleuthkit.autopsy.report}:\ ${project.org.sleuthkit.autopsy.report}:\
${project.org.sleuthkit.autopsy.testing}:\ ${project.org.sleuthkit.autopsy.testing}:\
${project.org.sleuthkit.autopsy.mboxparser} ${project.org.sleuthkit.autopsy.thunderbirdparser}
project.org.sleuthkit.autopsy.casemodule=Case project.org.sleuthkit.autopsy.casemodule=Case
project.org.sleuthkit.autopsy.corecomponentinterfaces=CoreComponentInterfaces project.org.sleuthkit.autopsy.corecomponentinterfaces=CoreComponentInterfaces
project.org.sleuthkit.autopsy.corecomponents=CoreComponents project.org.sleuthkit.autopsy.corecomponents=CoreComponents
@ -40,10 +40,10 @@ project.org.sleuthkit.autopsy.filesearch=FileSearch
project.org.sleuthkit.autopsy.hashdatabase=HashDatabase project.org.sleuthkit.autopsy.hashdatabase=HashDatabase
project.org.sleuthkit.autopsy.ingest=Ingest project.org.sleuthkit.autopsy.ingest=Ingest
project.org.sleuthkit.autopsy.keywordsearch=KeywordSearch project.org.sleuthkit.autopsy.keywordsearch=KeywordSearch
project.org.sleuthkit.autopsy.mboxparser=MboxEmailModule
project.org.sleuthkit.autopsy.menuactions=MenuActions project.org.sleuthkit.autopsy.menuactions=MenuActions
project.org.sleuthkit.autopsy.datamodel=DataModel project.org.sleuthkit.autopsy.datamodel=DataModel
project.org.sleuthkit.autopsy.recentactivity=RecentActivity project.org.sleuthkit.autopsy.recentactivity=RecentActivity
project.org.sleuthkit.autopsy.report=Report project.org.sleuthkit.autopsy.report=Report
project.org.sleuthkit.autopsy.testing=Testing project.org.sleuthkit.autopsy.testing=Testing
project.org.sleuthkit.autopsy.thunderbirdparser=thunderbirdparser

View File

@ -2,7 +2,7 @@
<!-- You may freely edit this file. See harness/README in the NetBeans platform --> <!-- You may freely edit this file. See harness/README in the NetBeans platform -->
<!-- for some information on what you could do (e.g. targets to override). --> <!-- for some information on what you could do (e.g. targets to override). -->
<!-- If you delete this file and reopen the project it will be recreated. --> <!-- If you delete this file and reopen the project it will be recreated. -->
<project name="org.sleuthkit.autopsy.mboxparser" default="netbeans" basedir="."> <project name="org.sleuthkit.autopsy.thunderbirdparser" default="netbeans" basedir=".">
<description>Builds, tests, and runs the project org.sleuthkit.autopsy.mboxparser.</description> <description>Builds, tests, and runs the project org.sleuthkit.autopsy.thunderbirdparser.</description>
<import file="nbproject/build-impl.xml"/> <import file="nbproject/build-impl.xml"/>
</project> </project>

View File

@ -0,0 +1,6 @@
Manifest-Version: 1.0
OpenIDE-Module: org.sleuthkit.autopsy.thunderbirdparser
OpenIDE-Module-Layer: org/sleuthkit/autopsy/thunderbirdparser/layer.xml
OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/thunderbirdparser/Bundle.properties
OpenIDE-Module-Specification-Version: 1.0

View File

@ -3,7 +3,7 @@
*** GENERATED FROM project.xml - DO NOT EDIT *** *** GENERATED FROM project.xml - DO NOT EDIT ***
*** EDIT ../build.xml INSTEAD *** *** EDIT ../build.xml INSTEAD ***
--> -->
<project name="org.sleuthkit.autopsy.mboxparser-impl" basedir=".."> <project name="org.sleuthkit.autopsy.thunderbirdparser-impl" basedir="..">
<fail message="Please build using Ant 1.7.1 or higher."> <fail message="Please build using Ant 1.7.1 or higher.">
<condition> <condition>
<not> <not>

View File

@ -1,57 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://www.netbeans.org/ns/project/1"> <project xmlns="http://www.netbeans.org/ns/project/1">
<type>org.netbeans.modules.apisupport.project</type> <type>org.netbeans.modules.apisupport.project</type>
<configuration> <configuration>
<data xmlns="http://www.netbeans.org/ns/nb-module-project/3"> <data xmlns="http://www.netbeans.org/ns/nb-module-project/3">
<code-name-base>org.sleuthkit.autopsy.mboxparser</code-name-base> <code-name-base>org.sleuthkit.autopsy.thunderbirdparser</code-name-base>
<suite-component/> <suite-component/>
<module-dependencies> <module-dependencies>
<dependency> <dependency>
<code-name-base>org.sleuthkit.autopsy.casemodule</code-name-base> <code-name-base>org.sleuthkit.autopsy.casemodule</code-name-base>
<build-prerequisite/> <build-prerequisite/>
<compile-dependency/> <compile-dependency/>
<run-dependency> <run-dependency>
<release-version>1</release-version> <release-version>1</release-version>
<specification-version>1.0</specification-version> <specification-version>1.0</specification-version>
</run-dependency> </run-dependency>
</dependency> </dependency>
<dependency> <dependency>
<code-name-base>org.sleuthkit.autopsy.coreutils</code-name-base> <code-name-base>org.sleuthkit.autopsy.coreutils</code-name-base>
<build-prerequisite/> <build-prerequisite/>
<compile-dependency/> <compile-dependency/>
<run-dependency> <run-dependency>
<release-version>0-1</release-version> <release-version>0-1</release-version>
<specification-version>0.0</specification-version> <specification-version>0.0</specification-version>
</run-dependency> </run-dependency>
</dependency> </dependency>
<dependency> <dependency>
<code-name-base>org.sleuthkit.autopsy.datamodel</code-name-base> <code-name-base>org.sleuthkit.autopsy.datamodel</code-name-base>
<build-prerequisite/> <build-prerequisite/>
<compile-dependency/> <compile-dependency/>
<run-dependency> <run-dependency>
<release-version>1</release-version> <release-version>1</release-version>
<specification-version>1.0</specification-version> <specification-version>1.0</specification-version>
</run-dependency> </run-dependency>
</dependency> </dependency>
<dependency> <dependency>
<code-name-base>org.sleuthkit.autopsy.ingest</code-name-base> <code-name-base>org.sleuthkit.autopsy.ingest</code-name-base>
<build-prerequisite/> <build-prerequisite/>
<compile-dependency/> <compile-dependency/>
<run-dependency> <run-dependency>
<release-version>0-1</release-version> <release-version>0-1</release-version>
<specification-version>1.0</specification-version> <specification-version>1.0</specification-version>
</run-dependency> </run-dependency>
</dependency> </dependency>
</module-dependencies> <dependency>
<public-packages/> <code-name-base>org.sleuthkit.autopsy.recentactivity</code-name-base>
<class-path-extension> <build-prerequisite/>
<runtime-relative-path>ext/tika-core-1.1.jar</runtime-relative-path> <compile-dependency/>
<binary-origin>release/modules/ext/tika-core-1.1.jar</binary-origin> <run-dependency>
</class-path-extension> <specification-version>1.0</specification-version>
<class-path-extension> </run-dependency>
<runtime-relative-path>ext/tika-parsers-1.1.jar</runtime-relative-path> </dependency>
<binary-origin>release/modules/ext/tika-parsers-1.1.jar</binary-origin> </module-dependencies>
</class-path-extension> <public-packages/>
</data> <class-path-extension>
</configuration> <runtime-relative-path>ext/tika-core-1.1.jar</runtime-relative-path>
</project> <binary-origin>release/modules/ext/tika-core-1.1.jar</binary-origin>
</class-path-extension>
<class-path-extension>
<runtime-relative-path>ext/tika-parsers-1.1.jar</runtime-relative-path>
<binary-origin>release/modules/ext/tika-parsers-1.1.jar</binary-origin>
</class-path-extension>
</data>
</configuration>
</project>

View File

@ -0,0 +1 @@
OpenIDE-Module-Name=ThunderbirdParser

View File

@ -1,183 +1,183 @@
package org.sleuthkit.autopsy.thunderbirdparser; package org.sleuthkit.autopsy.thunderbirdparser;
import java.io.*; import java.io.*;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.apache.tika.Tika; import org.apache.tika.Tika;
import org.apache.tika.exception.TikaException; import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata; import org.apache.tika.metadata.Metadata;
import org.apache.tika.mime.MimeTypes; import org.apache.tika.mime.MimeTypes;
import org.apache.tika.mime.MediaType; import org.apache.tika.mime.MediaType;
import org.apache.tika.parser.ParseContext; import org.apache.tika.parser.ParseContext;
import org.apache.tika.sax.BodyContentHandler; import org.apache.tika.sax.BodyContentHandler;
import org.xml.sax.ContentHandler; import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
public class ThunderbirdEmailParser { public class ThunderbirdEmailParser {
private InputStream stream; private InputStream stream;
//Tika object //Tika object
private Tika tika; private Tika tika;
private ThunderbirdMetadata metadata; private ThunderbirdMetadata metadata;
private ContentHandler contentHandler; private ContentHandler contentHandler;
private String mimeType; private String mimeType;
private ThunderbirdMboxParser parser; private ThunderbirdMboxParser parser;
private ParseContext context; private ParseContext context;
private static ArrayList<String> tikaMimeTypes; private static ArrayList<String> tikaMimeTypes;
static static
{ {
tikaMimeTypes = new ArrayList<String>(); tikaMimeTypes = new ArrayList<String>();
tikaMimeTypes.add(MimeTypes.OCTET_STREAM); tikaMimeTypes.add(MimeTypes.OCTET_STREAM);
tikaMimeTypes.add(MimeTypes.PLAIN_TEXT); tikaMimeTypes.add(MimeTypes.PLAIN_TEXT);
tikaMimeTypes.add(MimeTypes.XML); tikaMimeTypes.add(MimeTypes.XML);
} }
public ThunderbirdEmailParser() public ThunderbirdEmailParser()
{ {
this.tika = new Tika(); this.tika = new Tika();
} }
public ThunderbirdEmailParser(InputStream inStream) public ThunderbirdEmailParser(InputStream inStream)
{ {
this.tika = new Tika(); this.tika = new Tika();
this.stream = inStream; this.stream = inStream;
} }
public ThunderbirdEmailParser(String filepath) public ThunderbirdEmailParser(String filepath)
{ {
this.tika = new Tika(); this.tika = new Tika();
this.stream = this.getClass().getResourceAsStream(filepath); this.stream = this.getClass().getResourceAsStream(filepath);
} }
private void init() throws IOException private void init() throws IOException
{ {
this.tika.setMaxStringLength(10*1024*1024); this.tika.setMaxStringLength(10*1024*1024);
this.metadata = new ThunderbirdMetadata(); this.metadata = new ThunderbirdMetadata();
//Set MIME Type //Set MIME Type
//this.mimeType = tika.detect(this.stream); //this.mimeType = tika.detect(this.stream);
this.parser = new ThunderbirdMboxParser(); this.parser = new ThunderbirdMboxParser();
this.context = new ParseContext(); this.context = new ParseContext();
this.contentHandler = new BodyContentHandler(10*1024*1024); this.contentHandler = new BodyContentHandler(10*1024*1024);
} }
public void parse() throws FileNotFoundException, IOException, SAXException, TikaException public void parse() throws FileNotFoundException, IOException, SAXException, TikaException
{ {
init(); init();
parser.parse(this.stream,this.contentHandler, this.metadata, context); parser.parse(this.stream,this.contentHandler, this.metadata, context);
} }
public void parse(InputStream inStream) throws FileNotFoundException, IOException, SAXException, TikaException public void parse(InputStream inStream) throws FileNotFoundException, IOException, SAXException, TikaException
{ {
init(); init();
parser.parse(inStream,this.contentHandler, this.metadata, context); parser.parse(inStream,this.contentHandler, this.metadata, context);
} }
public ThunderbirdMetadata getMetadata() public ThunderbirdMetadata getMetadata()
{ {
return this.metadata; return this.metadata;
} }
//Returns message content, i.e. plain text or html //Returns message content, i.e. plain text or html
public String getContent() public String getContent()
{ {
return this.contentHandler.toString(); return this.contentHandler.toString();
} }
public String detectEmailFileFormat(String filepath) throws IOException public String detectEmailFileFormat(String filepath) throws IOException
{ {
return this.tika.detect(filepath); return this.tika.detect(filepath);
} }
//Detects the mime type from the first few bytes of the document //Detects the mime type from the first few bytes of the document
public String detectMediaTypeFromBytes(byte[] firstFewBytes, String inDocName) public String detectMediaTypeFromBytes(byte[] firstFewBytes, String inDocName)
{ {
return this.tika.detect(firstFewBytes, inDocName); return this.tika.detect(firstFewBytes, inDocName);
} }
public boolean isValidMimeTypeMbox(byte[] buffer) public boolean isValidMimeTypeMbox(byte[] buffer)
{ {
return (new String(buffer)).startsWith("From "); return (new String(buffer)).startsWith("From ");
} }
//This assumes the file/stream was parsed since we are looking at the metadata //This assumes the file/stream was parsed since we are looking at the metadata
public boolean isValidMboxType() public boolean isValidMboxType()
{ {
return this.metadata.get(Metadata.CONTENT_TYPE).equals("application/mbox"); return this.metadata.get(Metadata.CONTENT_TYPE).equals("application/mbox");
} }
//Get email subject //Get email subject
public String getSubject() public String getSubject()
{ {
return this.metadata.get(Metadata.SUBJECT); return this.metadata.get(Metadata.SUBJECT);
} }
public String getTitle() public String getTitle()
{ {
return this.metadata.get(Metadata.TITLE); return this.metadata.get(Metadata.TITLE);
} }
public Long getDateCreated() public Long getDateCreated()
{ {
Long epochtime; Long epochtime;
Long ftime = 0L; Long ftime = 0L;
try { try {
String datetime = this.metadata.get(Metadata.DATE); String datetime = this.metadata.get(Metadata.DATE);
epochtime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(datetime).getTime(); epochtime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'").parse(datetime).getTime();
ftime = epochtime.longValue(); ftime = epochtime.longValue();
ftime = ftime / 1000; ftime = ftime / 1000;
} catch (ParseException ex) { } catch (ParseException ex) {
Logger.getLogger(ThunderbirdMboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(ThunderbirdMboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex);
} }
return ftime; return ftime;
} }
public String getContenType() public String getContenType()
{ {
return this.metadata.get(Metadata.CONTENT_TYPE); return this.metadata.get(Metadata.CONTENT_TYPE);
} }
public String getContenEncoding() public String getContenEncoding()
{ {
return this.metadata.get(Metadata.CONTENT_ENCODING); return this.metadata.get(Metadata.CONTENT_ENCODING);
} }
public String getFrom() public String getFrom()
{ {
return this.metadata.get(Metadata.MESSAGE_FROM); return this.metadata.get(Metadata.AUTHOR);
} }
public String getTo() public String getTo()
{ {
return this.metadata.get(Metadata.MESSAGE_TO); return this.metadata.get(Metadata.MESSAGE_TO);
} }
public String getCC() public String getCC()
{ {
return this.metadata.get(Metadata.MESSAGE_CC); return this.metadata.get(Metadata.MESSAGE_CC);
} }
public String getBCC() public String getBCC()
{ {
return this.metadata.get(Metadata.MESSAGE_BCC); return this.metadata.get(Metadata.MESSAGE_BCC);
} }
public String getRecipientAddress() public String getRecipientAddress()
{ {
return this.metadata.get(Metadata.MESSAGE_RECIPIENT_ADDRESS); return this.metadata.get(Metadata.MESSAGE_RECIPIENT_ADDRESS);
} }
public String getMboxSupportedMediaType() public String getMboxSupportedMediaType()
{ {
return MediaType.application("mbox").getType(); return MediaType.application("mbox").getType();
} }
} }

View File

@ -1,193 +1,193 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2011 Basis Technology Corp. * Copyright 2011 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.sleuthkit.autopsy.thunderbirdparser; package org.sleuthkit.autopsy.thunderbirdparser;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.apache.tika.exception.TikaException; import org.apache.tika.exception.TikaException;
import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.IngestManagerProxy; import org.sleuthkit.autopsy.ingest.IngestManagerProxy;
import org.sleuthkit.autopsy.ingest.IngestMessage; import org.sleuthkit.autopsy.ingest.IngestMessage;
import org.sleuthkit.autopsy.ingest.IngestMessage.MessageType; import org.sleuthkit.autopsy.ingest.IngestMessage.MessageType;
import org.sleuthkit.autopsy.ingest.IngestServiceAbstract.*; import org.sleuthkit.autopsy.ingest.IngestServiceAbstract.*;
import org.sleuthkit.autopsy.ingest.IngestServiceAbstractFile; import org.sleuthkit.autopsy.ingest.IngestServiceAbstractFile;
import org.sleuthkit.autopsy.ingest.ServiceDataEvent; import org.sleuthkit.autopsy.ingest.ServiceDataEvent;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskException; import org.sleuthkit.datamodel.TskException;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
public class ThunderbirdMboxFileIngestService implements IngestServiceAbstractFile { public class ThunderbirdMboxFileIngestService implements IngestServiceAbstractFile {
private static final Logger logger = Logger.getLogger(ThunderbirdMboxFileIngestService.class.getName()); private static final Logger logger = Logger.getLogger(ThunderbirdMboxFileIngestService.class.getName());
private static ThunderbirdMboxFileIngestService instance = null; private static ThunderbirdMboxFileIngestService instance = null;
private IngestManagerProxy managerProxy; private IngestManagerProxy managerProxy;
private static int messageId = 0; private static int messageId = 0;
private static final String classname = "Mbox Parser"; private static final String classname = "Thunderbird Parser";
public static synchronized ThunderbirdMboxFileIngestService getDefault() { public static synchronized ThunderbirdMboxFileIngestService getDefault() {
if (instance == null) { if (instance == null) {
instance = new ThunderbirdMboxFileIngestService(); instance = new ThunderbirdMboxFileIngestService();
} }
return instance; return instance;
} }
@Override @Override
public ProcessResult process(AbstractFile fsContent) { public ProcessResult process(AbstractFile fsContent) {
ThunderbirdEmailParser mbox = new ThunderbirdEmailParser(); ThunderbirdEmailParser mbox = new ThunderbirdEmailParser();
boolean isMbox = false; boolean isMbox = false;
try { try {
byte[] t = new byte[(int) 128]; byte[] t = new byte[(int) 128];
int byteRead = fsContent.read(t, 0, 128); int byteRead = fsContent.read(t, 0, 128);
isMbox = mbox.isValidMimeTypeMbox(t); isMbox = mbox.isValidMimeTypeMbox(t);
} catch (TskException ex) { } catch (TskException ex) {
Logger.getLogger(ThunderbirdMboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(ThunderbirdMboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex);
} }
if (isMbox) { if (isMbox) {
managerProxy.postMessage(IngestMessage.createMessage(++messageId, MessageType.INFO, this, "Processing " + fsContent.getName())); managerProxy.postMessage(IngestMessage.createMessage(++messageId, MessageType.INFO, this, "Processing " + fsContent.getName()));
try { try {
ReadContentInputStream contentStream = new ReadContentInputStream(fsContent); ReadContentInputStream contentStream = new ReadContentInputStream(fsContent);
mbox.parse(contentStream); mbox.parse(contentStream);
String content = mbox.getContent(); String content = mbox.getContent();
String from = mbox.getFrom(); String from = mbox.getFrom();
String to = mbox.getTo(); String to = mbox.getTo();
Long date = mbox.getDateCreated(); Long date = mbox.getDateCreated();
String subject = mbox.getSubject(); String subject = mbox.getSubject();
String cc = mbox.getCC(); String cc = mbox.getCC();
String bcc = mbox.getBCC(); String bcc = mbox.getBCC();
Collection<BlackboardAttribute> bbattributes = new ArrayList<BlackboardAttribute>(); Collection<BlackboardAttribute> bbattributes = new ArrayList<BlackboardAttribute>();
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_TO.getTypeID(), classname, "", to)); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_TO.getTypeID(), classname, "", to));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_CC.getTypeID(), classname, "", cc)); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_CC.getTypeID(), classname, "", cc));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_BCC.getTypeID(), classname, "", bcc)); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_BCC.getTypeID(), classname, "", bcc));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_FROM.getTypeID(), classname, "", from)); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_FROM.getTypeID(), classname, "", from));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_PLAIN.getTypeID(), classname, "", content)); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_PLAIN.getTypeID(), classname, "", content));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_HTML.getTypeID(), classname, "", content)); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_HTML.getTypeID(), classname, "", content));
//bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_MSG_ID.getTypeID(), classname, "",)); //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_MSG_ID.getTypeID(), classname, "",));
//bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_MSG_REPLY_ID.getTypeID(), classname, "",)); //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_MSG_REPLY_ID.getTypeID(), classname, "",));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_RCVD.getTypeID(), classname, "", date)); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_RCVD.getTypeID(), classname, "", date));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_SENT.getTypeID(), classname, "", date)); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_SENT.getTypeID(), classname, "", date));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SUBJECT.getTypeID(), classname, "", subject)); bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SUBJECT.getTypeID(), classname, "", subject));
BlackboardArtifact bbart; BlackboardArtifact bbart;
try { try {
bbart = fsContent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG); bbart = fsContent.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG);
bbart.addAttributes(bbattributes); bbart.addAttributes(bbattributes);
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
Logger.getLogger(ThunderbirdMboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(ThunderbirdMboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex);
} }
IngestManager.fireServiceDataEvent(new ServiceDataEvent(classname, BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG)); IngestManager.fireServiceDataEvent(new ServiceDataEvent(classname, BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG));
} catch (FileNotFoundException ex) { } catch (FileNotFoundException ex) {
Logger.getLogger(ThunderbirdMboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(ThunderbirdMboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) { } catch (IOException ex) {
Logger.getLogger(ThunderbirdMboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(ThunderbirdMboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex);
} catch (SAXException ex) { } catch (SAXException ex) {
Logger.getLogger(ThunderbirdMboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(ThunderbirdMboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex);
} catch (TikaException ex) { } catch (TikaException ex) {
Logger.getLogger(ThunderbirdMboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex); Logger.getLogger(ThunderbirdMboxFileIngestService.class.getName()).log(Level.SEVERE, null, ex);
} }
} }
return ProcessResult.OK; return ProcessResult.OK;
} }
@Override @Override
public void complete() { public void complete() {
logger.log(Level.INFO, "complete()"); logger.log(Level.INFO, "complete()");
managerProxy.postMessage(IngestMessage.createMessage(++messageId, MessageType.INFO, this, "COMPLETE")); managerProxy.postMessage(IngestMessage.createMessage(++messageId, MessageType.INFO, this, "COMPLETE"));
//service specific cleanup due completion here //service specific cleanup due completion here
} }
@Override @Override
public String getName() { public String getName() {
return "Mbox Parser"; return "Mbox Parser";
} }
@Override @Override
public String getDescription() { public String getDescription() {
return "This class parses through a file to determine if it is an mbox file and if so, populates an email artifact for it in the blackboard."; return "This class parses through a file to determine if it is an mbox file and if so, populates an email artifact for it in the blackboard.";
} }
@Override @Override
public void init(IngestManagerProxy managerProxy) { public void init(IngestManagerProxy managerProxy) {
logger.log(Level.INFO, "init()"); logger.log(Level.INFO, "init()");
this.managerProxy = managerProxy; this.managerProxy = managerProxy;
//service specific initialization here //service specific initialization here
} }
@Override @Override
public void stop() { public void stop() {
logger.log(Level.INFO, "stop()"); logger.log(Level.INFO, "stop()");
//service specific cleanup due interruption here //service specific cleanup due interruption here
} }
@Override @Override
public ServiceType getType() { public ServiceType getType() {
return ServiceType.AbstractFile; return ServiceType.AbstractFile;
} }
@Override @Override
public boolean hasSimpleConfiguration() { public boolean hasSimpleConfiguration() {
return false; return false;
} }
@Override @Override
public boolean hasAdvancedConfiguration() { public boolean hasAdvancedConfiguration() {
return false; return false;
} }
@Override @Override
public javax.swing.JPanel getSimpleConfiguration() { public javax.swing.JPanel getSimpleConfiguration() {
return null; return null;
} }
@Override @Override
public javax.swing.JPanel getAdvancedConfiguration() { public javax.swing.JPanel getAdvancedConfiguration() {
return null; return null;
} }
@Override @Override
public boolean hasBackgroundJobsRunning() { public boolean hasBackgroundJobsRunning() {
return false; return false;
} }
@Override @Override
public void saveAdvancedConfiguration() { public void saveAdvancedConfiguration() {
} }
@Override @Override
public void saveSimpleConfiguration() { public void saveSimpleConfiguration() {
} }
} }

View File

@ -1,253 +1,253 @@
/* /*
* To change this template, choose Tools | Templates * To change this template, choose Tools | Templates
* and open the template in the editor. * and open the template in the editor.
*/ */
package org.sleuthkit.autopsy.thunderbirdparser; package org.sleuthkit.autopsy.thunderbirdparser;
import java.io.*; import java.io.*;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.apache.tika.exception.TikaException; import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata; import org.apache.tika.metadata.Metadata;
import org.apache.tika.mime.MediaType; import org.apache.tika.mime.MediaType;
import org.apache.tika.parser.AbstractParser; import org.apache.tika.parser.AbstractParser;
import org.apache.tika.parser.ParseContext; import org.apache.tika.parser.ParseContext;
import org.xml.sax.ContentHandler; import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
/** /**
* *
* @author arivera * @author arivera
*/ */
public class ThunderbirdMboxParser { public class ThunderbirdMboxParser {
/** Serial version UID */ /** Serial version UID */
private static final long serialVersionUID = -1762689436731160661L; private static final long serialVersionUID = -1762689436731160661L;
private static final Set<MediaType> SUPPORTED_TYPES = private static final Set<MediaType> SUPPORTED_TYPES =
Collections.singleton(MediaType.application("mbox")); Collections.singleton(MediaType.application("mbox"));
public static final String MBOX_MIME_TYPE = "application/mbox"; public static final String MBOX_MIME_TYPE = "application/mbox";
public static final String MBOX_RECORD_DIVIDER = "From "; public static final String MBOX_RECORD_DIVIDER = "From ";
private static final Pattern EMAIL_HEADER_PATTERN = Pattern.compile("([^ ]+):[ \t]*(.*)"); private static final Pattern EMAIL_HEADER_PATTERN = Pattern.compile("([^ ]+):[ \t]*(.*)");
private static final Pattern EMAIL_ADDRESS_PATTERN = Pattern.compile("<(.*@.*)>"); private static final Pattern EMAIL_ADDRESS_PATTERN = Pattern.compile("<(.*@.*)>");
private static final String EMAIL_HEADER_METADATA_PREFIX = "MboxParser-"; private static final String EMAIL_HEADER_METADATA_PREFIX = "MboxParser-";
private static final String EMAIL_FROMLINE_METADATA = EMAIL_HEADER_METADATA_PREFIX + "from"; private static final String EMAIL_FROMLINE_METADATA = EMAIL_HEADER_METADATA_PREFIX + "from";
private ThunderbirdXHTMLContentHandler xhtml = null; private ThunderbirdXHTMLContentHandler xhtml = null;
private enum ParseStates { private enum ParseStates {
START, IN_HEADER, IN_CONTENT START, IN_HEADER, IN_CONTENT
} }
public Set<MediaType> getSupportedTypes(ParseContext context) { public Set<MediaType> getSupportedTypes(ParseContext context) {
return SUPPORTED_TYPES; return SUPPORTED_TYPES;
} }
public void parse( public void parse(
InputStream stream, ContentHandler handler, InputStream stream, ContentHandler handler,
ThunderbirdMetadata metadata, ParseContext context) ThunderbirdMetadata metadata, ParseContext context)
throws IOException, TikaException, SAXException { throws IOException, TikaException, SAXException {
InputStreamReader isr; InputStreamReader isr;
try { try {
// Headers are going to be 7-bit ascii // Headers are going to be 7-bit ascii
isr = new InputStreamReader(stream, "US-ASCII"); isr = new InputStreamReader(stream, "US-ASCII");
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new TikaException("US-ASCII is not supported!", e); throw new TikaException("US-ASCII is not supported!", e);
} }
BufferedReader reader = new BufferedReader(isr); BufferedReader reader = new BufferedReader(isr);
metadata.set(Metadata.CONTENT_TYPE, MBOX_MIME_TYPE); metadata.set(Metadata.CONTENT_TYPE, MBOX_MIME_TYPE);
metadata.set(Metadata.CONTENT_ENCODING, "us-ascii"); metadata.set(Metadata.CONTENT_ENCODING, "us-ascii");
xhtml = new ThunderbirdXHTMLContentHandler(handler, metadata); xhtml = new ThunderbirdXHTMLContentHandler(handler, metadata);
xhtml.startDocument(); xhtml.startDocument();
ThunderbirdMboxParser.ParseStates parseState = ThunderbirdMboxParser.ParseStates.START; ThunderbirdMboxParser.ParseStates parseState = ThunderbirdMboxParser.ParseStates.START;
String multiLine = null; String multiLine = null;
boolean inQuote = false; boolean inQuote = false;
int numEmails = 0; int numEmails = 0;
// We're going to scan, line-by-line, for a line that starts with // We're going to scan, line-by-line, for a line that starts with
// "From " // "From "
for (String curLine = reader.readLine(); curLine != null; curLine = reader.readLine()) for (String curLine = reader.readLine(); curLine != null; curLine = reader.readLine())
{ {
boolean newMessage = curLine.startsWith(MBOX_RECORD_DIVIDER); boolean newMessage = curLine.startsWith(MBOX_RECORD_DIVIDER);
if (newMessage) { if (newMessage) {
numEmails += 1; numEmails += 1;
} }
switch (parseState) { switch (parseState) {
case START: case START:
if (newMessage) { if (newMessage) {
parseState = ThunderbirdMboxParser.ParseStates.IN_HEADER; parseState = ThunderbirdMboxParser.ParseStates.IN_HEADER;
newMessage = false; newMessage = false;
// Fall through to IN_HEADER // Fall through to IN_HEADER
} else { } else {
break; break;
} }
case IN_HEADER: case IN_HEADER:
if (newMessage) { if (newMessage) {
saveHeaderInMetadata(numEmails, metadata, multiLine); saveHeaderInMetadata(numEmails, metadata, multiLine);
//saveHeaderInMetadata(numEmails, metadata, curLine); //saveHeaderInMetadata(numEmails, metadata, curLine);
multiLine = curLine; multiLine = curLine;
} }
//I think this is never going to be true //I think this is never going to be true
else if (curLine.length() == 0) else if (curLine.length() == 0)
{ {
// Blank line is signal that we're transitioning to the content. // Blank line is signal that we're transitioning to the content.
saveHeaderInMetadata(numEmails, metadata, multiLine); saveHeaderInMetadata(numEmails, metadata, multiLine);
parseState = ThunderbirdMboxParser.ParseStates.IN_CONTENT; parseState = ThunderbirdMboxParser.ParseStates.IN_CONTENT;
// Mimic what PackageParser does between entries. // Mimic what PackageParser does between entries.
xhtml.startElement("div", "class", "email-entry"); xhtml.startElement("div", "class", "email-entry");
xhtml.startElement("p"); xhtml.startElement("p");
inQuote = false; inQuote = false;
} }
else if ((curLine.startsWith(" ") || curLine.startsWith("\t")) ) else if ((curLine.startsWith(" ") || curLine.startsWith("\t")) )
{ {
multiLine += " " + curLine.trim(); multiLine += " " + curLine.trim();
} }
else else
{ {
saveHeaderInMetadata(numEmails, metadata, multiLine); saveHeaderInMetadata(numEmails, metadata, multiLine);
multiLine = curLine; multiLine = curLine;
} }
break; break;
// TODO - use real email parsing support so we can correctly handle // TODO - use real email parsing support so we can correctly handle
// things like multipart messages and quoted-printable encoding. // things like multipart messages and quoted-printable encoding.
// We'd also want this for charset handling, where content isn't 7-bit // We'd also want this for charset handling, where content isn't 7-bit
// ascii. // ascii.
case IN_CONTENT: case IN_CONTENT:
if (newMessage) { if (newMessage) {
endMessage(inQuote); endMessage(inQuote);
parseState = ThunderbirdMboxParser.ParseStates.IN_HEADER; parseState = ThunderbirdMboxParser.ParseStates.IN_HEADER;
multiLine = curLine; multiLine = curLine;
} else { } else {
boolean quoted = curLine.startsWith(">"); boolean quoted = curLine.startsWith(">");
if (inQuote) { if (inQuote) {
if (!quoted) { if (!quoted) {
xhtml.endElement("q"); xhtml.endElement("q");
inQuote = false; inQuote = false;
} }
} else if (quoted) { } else if (quoted) {
xhtml.startElement("q"); xhtml.startElement("q");
inQuote = true; inQuote = true;
} }
xhtml.characters(curLine); xhtml.characters(curLine);
// For plain text email, each line is a real break position. // For plain text email, each line is a real break position.
xhtml.element("br", ""); xhtml.element("br", "");
} }
} }
} }
if (parseState == ThunderbirdMboxParser.ParseStates.IN_HEADER) { if (parseState == ThunderbirdMboxParser.ParseStates.IN_HEADER) {
saveHeaderInMetadata(numEmails, metadata, multiLine); saveHeaderInMetadata(numEmails, metadata, multiLine);
} else if (parseState == ThunderbirdMboxParser.ParseStates.IN_CONTENT) { } else if (parseState == ThunderbirdMboxParser.ParseStates.IN_CONTENT) {
endMessage(inQuote); endMessage(inQuote);
} }
xhtml.endDocument(); xhtml.endDocument();
} }
private void endMessage(boolean inQuote) throws SAXException { private void endMessage(boolean inQuote) throws SAXException {
if (inQuote) { if (inQuote) {
xhtml.endElement("q"); xhtml.endElement("q");
} }
xhtml.endElement("p"); xhtml.endElement("p");
xhtml.endElement("div"); xhtml.endElement("div");
} }
private void saveHeaderInMetadata(int numEmails, ThunderbirdMetadata metadata, String curLine) private void saveHeaderInMetadata(int numEmails, ThunderbirdMetadata metadata, String curLine)
{ {
//if ((curLine != null) && curLine.startsWith(MBOX_RECORD_DIVIDER) && (numEmails >= 1)) n //if ((curLine != null) && curLine.startsWith(MBOX_RECORD_DIVIDER) && (numEmails >= 1)) n
//At this point, the current line we are feeding should never be null!!! //At this point, the current line we are feeding should never be null!!!
if ((curLine != null) && curLine.startsWith(MBOX_RECORD_DIVIDER)) if ((curLine != null) && curLine.startsWith(MBOX_RECORD_DIVIDER))
{ {
metadata.add(EMAIL_FROMLINE_METADATA, curLine.substring(MBOX_RECORD_DIVIDER.length())); metadata.add(EMAIL_FROMLINE_METADATA, curLine.substring(MBOX_RECORD_DIVIDER.length()));
return; return;
} }
else if ((curLine == null)) { else if ((curLine == null)) {
return; return;
} }
Matcher headerMatcher = EMAIL_HEADER_PATTERN.matcher(curLine); Matcher headerMatcher = EMAIL_HEADER_PATTERN.matcher(curLine);
if (!headerMatcher.matches()) { if (!headerMatcher.matches()) {
return; // ignore malformed header lines return; // ignore malformed header lines
} }
String headerTag = headerMatcher.group(1).toLowerCase(); String headerTag = headerMatcher.group(1).toLowerCase();
String headerContent = headerMatcher.group(2); String headerContent = headerMatcher.group(2);
if (headerTag.equalsIgnoreCase("From")) { if (headerTag.equalsIgnoreCase("From")) {
metadata.add(ThunderbirdMetadata.AUTHOR, headerContent); metadata.add(ThunderbirdMetadata.AUTHOR, headerContent);
metadata.add(ThunderbirdMetadata.CREATOR, headerContent); metadata.add(ThunderbirdMetadata.CREATOR, headerContent);
} else if (headerTag.equalsIgnoreCase("To") || } else if (headerTag.equalsIgnoreCase("To") ||
headerTag.equalsIgnoreCase("Cc") || headerTag.equalsIgnoreCase("Cc") ||
headerTag.equalsIgnoreCase("Bcc")) { headerTag.equalsIgnoreCase("Bcc")) {
Matcher address = EMAIL_ADDRESS_PATTERN.matcher(headerContent); Matcher address = EMAIL_ADDRESS_PATTERN.matcher(headerContent);
if(address.find()) { if(address.find()) {
metadata.add(ThunderbirdMetadata.MESSAGE_RECIPIENT_ADDRESS, address.group(1)); metadata.add(ThunderbirdMetadata.MESSAGE_RECIPIENT_ADDRESS, address.group(1));
} else if(headerContent.indexOf('@') > -1) { } else if(headerContent.indexOf('@') > -1) {
metadata.add(ThunderbirdMetadata.MESSAGE_RECIPIENT_ADDRESS, headerContent); metadata.add(ThunderbirdMetadata.MESSAGE_RECIPIENT_ADDRESS, headerContent);
} }
String property = ThunderbirdMetadata.MESSAGE_TO; String property = ThunderbirdMetadata.MESSAGE_TO;
if (headerTag.equalsIgnoreCase("Cc")) { if (headerTag.equalsIgnoreCase("Cc")) {
property = ThunderbirdMetadata.MESSAGE_CC; property = ThunderbirdMetadata.MESSAGE_CC;
} else if (headerTag.equalsIgnoreCase("Bcc")) { } else if (headerTag.equalsIgnoreCase("Bcc")) {
property = ThunderbirdMetadata.MESSAGE_BCC; property = ThunderbirdMetadata.MESSAGE_BCC;
} }
metadata.add(property, headerContent); metadata.add(property, headerContent);
} else if (headerTag.equalsIgnoreCase("Subject")) { } else if (headerTag.equalsIgnoreCase("Subject")) {
metadata.add(ThunderbirdMetadata.SUBJECT, headerContent); metadata.add(ThunderbirdMetadata.SUBJECT, headerContent);
metadata.add(ThunderbirdMetadata.TITLE, headerContent); metadata.add(ThunderbirdMetadata.TITLE, headerContent);
} else if (headerTag.equalsIgnoreCase("Date")) { } else if (headerTag.equalsIgnoreCase("Date")) {
try { try {
Date date = parseDate(headerContent); Date date = parseDate(headerContent);
metadata.set(ThunderbirdMetadata.DATE, date); metadata.set(ThunderbirdMetadata.DATE, date);
metadata.set(ThunderbirdMetadata.CREATION_DATE, date); metadata.set(ThunderbirdMetadata.CREATION_DATE, date);
} catch (ParseException e) { } catch (ParseException e) {
// ignoring date because format was not understood // ignoring date because format was not understood
} }
} else if (headerTag.equalsIgnoreCase("Message-Id")) { } else if (headerTag.equalsIgnoreCase("Message-Id")) {
metadata.add(ThunderbirdMetadata.IDENTIFIER, headerContent); metadata.add(ThunderbirdMetadata.IDENTIFIER, headerContent);
} else if (headerTag.equalsIgnoreCase("In-Reply-To")) { } else if (headerTag.equalsIgnoreCase("In-Reply-To")) {
metadata.add(ThunderbirdMetadata.RELATION, headerContent); metadata.add(ThunderbirdMetadata.RELATION, headerContent);
} else if (headerTag.equalsIgnoreCase("Content-Type")) { } else if (headerTag.equalsIgnoreCase("Content-Type")) {
// TODO - key off content-type in headers to // TODO - key off content-type in headers to
// set mapping to use for content and convert if necessary. // set mapping to use for content and convert if necessary.
metadata.add(ThunderbirdMetadata.CONTENT_TYPE, headerContent); metadata.add(ThunderbirdMetadata.CONTENT_TYPE, headerContent);
metadata.add(ThunderbirdMetadata.FORMAT, headerContent); metadata.add(ThunderbirdMetadata.FORMAT, headerContent);
} else { } else {
metadata.add(EMAIL_HEADER_METADATA_PREFIX + headerTag, headerContent); metadata.add(EMAIL_HEADER_METADATA_PREFIX + headerTag, headerContent);
} }
} }
public static Date parseDate(String headerContent) throws ParseException { public static Date parseDate(String headerContent) throws ParseException {
SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.US); SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z", Locale.US);
return dateFormat.parse(headerContent); return dateFormat.parse(headerContent);
} }
} }

View File

@ -1,447 +1,447 @@
/* /*
* To change this template, choose Tools | Templates * To change this template, choose Tools | Templates
* and open the template in the editor. * and open the template in the editor.
*/ */
package org.sleuthkit.autopsy.thunderbirdparser; package org.sleuthkit.autopsy.thunderbirdparser;
import java.io.Serializable; import java.io.Serializable;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.DateFormatSymbols; import java.text.DateFormatSymbols;
import java.text.ParseException; import java.text.ParseException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.*; import java.util.*;
import org.apache.tika.metadata.*; import org.apache.tika.metadata.*;
/** /**
* *
* @author arivera * @author arivera
*/ */
public class ThunderbirdMetadata implements CreativeCommons, DublinCore, Geographic, HttpHeaders, public class ThunderbirdMetadata implements CreativeCommons, DublinCore, Geographic, HttpHeaders,
IPTC, Message, MSOffice, ClimateForcast, TIFF, TikaMetadataKeys, TikaMimeKeys, IPTC, Message, MSOffice, ClimateForcast, TIFF, TikaMetadataKeys, TikaMimeKeys,
Serializable { Serializable {
private int strArrCount = 0; private int strArrCount = 0;
/** Serial version UID */ /** Serial version UID */
private static final long serialVersionUID = 5623926545693153182L; private static final long serialVersionUID = 5623926545693153182L;
/** /**
* A map of all metadata attributes. * A map of all metadata attributes.
*/ */
private Map<String, ArrayList<String>> metadata = null; private Map<String, ArrayList<String>> metadata = null;
/** /**
* The UTC time zone. Not sure if {@link TimeZone#getTimeZone(String)} * The UTC time zone. Not sure if {@link TimeZone#getTimeZone(String)}
* understands "UTC" in all environments, but it'll fall back to GMT * understands "UTC" in all environments, but it'll fall back to GMT
* in such cases, which is in practice equivalent to UTC. * in such cases, which is in practice equivalent to UTC.
*/ */
private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
/** /**
* Custom time zone used to interpret date values without a time * Custom time zone used to interpret date values without a time
* component in a way that most likely falls within the same day * component in a way that most likely falls within the same day
* regardless of in which time zone it is later interpreted. For * regardless of in which time zone it is later interpreted. For
* example, the "2012-02-17" date would map to "2012-02-17T12:00:00Z" * example, the "2012-02-17" date would map to "2012-02-17T12:00:00Z"
* (instead of the default "2012-02-17T00:00:00Z"), which would still * (instead of the default "2012-02-17T00:00:00Z"), which would still
* map to "2012-02-17" if interpreted in say Pacific time (while the * map to "2012-02-17" if interpreted in say Pacific time (while the
* default mapping would result in "2012-02-16" for UTC-8). * default mapping would result in "2012-02-16" for UTC-8).
*/ */
private static final TimeZone MIDDAY = TimeZone.getTimeZone("GMT-12:00"); private static final TimeZone MIDDAY = TimeZone.getTimeZone("GMT-12:00");
/** /**
* Some parsers will have the date as a ISO-8601 string * Some parsers will have the date as a ISO-8601 string
* already, and will set that into the Metadata object. * already, and will set that into the Metadata object.
* So we can return Date objects for these, this is the * So we can return Date objects for these, this is the
* list (in preference order) of the various ISO-8601 * list (in preference order) of the various ISO-8601
* variants that we try when processing a date based * variants that we try when processing a date based
* property. * property.
*/ */
private static final DateFormat[] iso8601InputFormats = new DateFormat[] { private static final DateFormat[] iso8601InputFormats = new DateFormat[] {
// yyyy-mm-ddThh... // yyyy-mm-ddThh...
createDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", UTC), // UTC/Zulu createDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", UTC), // UTC/Zulu
createDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", null), // With timezone createDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", null), // With timezone
createDateFormat("yyyy-MM-dd'T'HH:mm:ss", null), // Without timezone createDateFormat("yyyy-MM-dd'T'HH:mm:ss", null), // Without timezone
// yyyy-mm-dd hh... // yyyy-mm-dd hh...
createDateFormat("yyyy-MM-dd' 'HH:mm:ss'Z'", UTC), // UTC/Zulu createDateFormat("yyyy-MM-dd' 'HH:mm:ss'Z'", UTC), // UTC/Zulu
createDateFormat("yyyy-MM-dd' 'HH:mm:ssZ", null), // With timezone createDateFormat("yyyy-MM-dd' 'HH:mm:ssZ", null), // With timezone
createDateFormat("yyyy-MM-dd' 'HH:mm:ss", null), // Without timezone createDateFormat("yyyy-MM-dd' 'HH:mm:ss", null), // Without timezone
// Date without time, set to Midday UTC // Date without time, set to Midday UTC
createDateFormat("yyyy-MM-dd", MIDDAY), // Normal date format createDateFormat("yyyy-MM-dd", MIDDAY), // Normal date format
createDateFormat("yyyy:MM:dd", MIDDAY), // Image (IPTC/EXIF) format createDateFormat("yyyy:MM:dd", MIDDAY), // Image (IPTC/EXIF) format
}; };
private static DateFormat createDateFormat(String format, TimeZone timezone) { private static DateFormat createDateFormat(String format, TimeZone timezone) {
SimpleDateFormat sdf = SimpleDateFormat sdf =
new SimpleDateFormat(format, new DateFormatSymbols(Locale.US)); new SimpleDateFormat(format, new DateFormatSymbols(Locale.US));
if (timezone != null) { if (timezone != null) {
sdf.setTimeZone(timezone); sdf.setTimeZone(timezone);
} }
return sdf; return sdf;
} }
/** /**
* Parses the given date string. This method is synchronized to prevent * Parses the given date string. This method is synchronized to prevent
* concurrent access to the thread-unsafe date formats. * concurrent access to the thread-unsafe date formats.
* *
* @see <a href="https://issues.apache.org/jira/browse/TIKA-495">TIKA-495</a> * @see <a href="https://issues.apache.org/jira/browse/TIKA-495">TIKA-495</a>
* @param date date string * @param date date string
* @return parsed date, or <code>null</code> if the date can't be parsed * @return parsed date, or <code>null</code> if the date can't be parsed
*/ */
private static synchronized Date parseDate(String date) { private static synchronized Date parseDate(String date) {
// Java doesn't like timezones in the form ss+hh:mm // Java doesn't like timezones in the form ss+hh:mm
// It only likes the hhmm form, without the colon // It only likes the hhmm form, without the colon
int n = date.length(); int n = date.length();
if (date.charAt(n - 3) == ':' if (date.charAt(n - 3) == ':'
&& (date.charAt(n - 6) == '+' || date.charAt(n - 6) == '-')) { && (date.charAt(n - 6) == '+' || date.charAt(n - 6) == '-')) {
date = date.substring(0, n - 3) + date.substring(n - 2); date = date.substring(0, n - 3) + date.substring(n - 2);
} }
// Try several different ISO-8601 variants // Try several different ISO-8601 variants
for (DateFormat format : iso8601InputFormats) { for (DateFormat format : iso8601InputFormats) {
try { try {
return format.parse(date); return format.parse(date);
} catch (ParseException ignore) { } catch (ParseException ignore) {
} }
} }
return null; return null;
} }
/** /**
* Returns a ISO 8601 representation of the given date. This method is * Returns a ISO 8601 representation of the given date. This method is
* synchronized to prevent concurrent access to the thread-unsafe date * synchronized to prevent concurrent access to the thread-unsafe date
* formats. * formats.
* *
* @see <a href="https://issues.apache.org/jira/browse/TIKA-495">TIKA-495</a> * @see <a href="https://issues.apache.org/jira/browse/TIKA-495">TIKA-495</a>
* @param date given date * @param date given date
* @return ISO 8601 date string * @return ISO 8601 date string
*/ */
private static String formatDate(Date date) { private static String formatDate(Date date) {
Calendar calendar = GregorianCalendar.getInstance(UTC, Locale.US); Calendar calendar = GregorianCalendar.getInstance(UTC, Locale.US);
calendar.setTime(date); calendar.setTime(date);
return String.format( return String.format(
"%04d-%02d-%02dT%02d:%02d:%02dZ", "%04d-%02d-%02dT%02d:%02d:%02dZ",
calendar.get(Calendar.YEAR), calendar.get(Calendar.YEAR),
calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.MONTH) + 1,
calendar.get(Calendar.DAY_OF_MONTH), calendar.get(Calendar.DAY_OF_MONTH),
calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.HOUR_OF_DAY),
calendar.get(Calendar.MINUTE), calendar.get(Calendar.MINUTE),
calendar.get(Calendar.SECOND)); calendar.get(Calendar.SECOND));
} }
/** /**
* Constructs a new, empty metadata. * Constructs a new, empty metadata.
*/ */
public ThunderbirdMetadata() { public ThunderbirdMetadata() {
metadata = new HashMap<String, ArrayList<String>>(); metadata = new HashMap<String, ArrayList<String>>();
} }
/** /**
* Returns true if named value is multivalued. * Returns true if named value is multivalued.
* *
* @param name * @param name
* name of metadata * name of metadata
* @return true is named value is multivalued, false if single value or null * @return true is named value is multivalued, false if single value or null
*/ */
public boolean isMultiValued(final String name) { public boolean isMultiValued(final String name) {
return metadata.get(name) != null && metadata.get(name).size() > 1; return metadata.get(name) != null && metadata.get(name).size() > 1;
} }
/** /**
* Returns an array of the names contained in the metadata. * Returns an array of the names contained in the metadata.
* *
* @return Metadata names * @return Metadata names
*/ */
public ArrayList<String> names() { public ArrayList<String> names() {
return new ArrayList<String>(metadata.keySet());//.toArray(new String[metadata.keySet().size()]); return new ArrayList<String>(metadata.keySet());//.toArray(new String[metadata.keySet().size()]);
} }
/** /**
* Get the value associated to a metadata name. If many values are assiociated * Get the value associated to a metadata name. If many values are assiociated
* to the specified name, then the first one is returned. * to the specified name, then the first one is returned.
* *
* @param name * @param name
* of the metadata. * of the metadata.
* @return the value associated to the specified metadata name. * @return the value associated to the specified metadata name.
*/ */
public String get(final String name) { public String get(final String name) {
ArrayList<String> values = metadata.get(name); ArrayList<String> values = metadata.get(name);
if (values == null) { if (values == null) {
return null; return null;
} else { } else {
return values.get(0); return values.get(0);
} }
} }
/** /**
* Returns the value (if any) of the identified metadata property. * Returns the value (if any) of the identified metadata property.
* *
* @since Apache Tika 0.7 * @since Apache Tika 0.7
* @param property property definition * @param property property definition
* @return property value, or <code>null</code> if the property is not set * @return property value, or <code>null</code> if the property is not set
*/ */
public String get(Property property) { public String get(Property property) {
return get(property.getName()); return get(property.getName());
} }
/** /**
* Returns the value of the identified Integer based metadata property. * Returns the value of the identified Integer based metadata property.
* *
* @since Apache Tika 0.8 * @since Apache Tika 0.8
* @param property simple integer property definition * @param property simple integer property definition
* @return property value as a Integer, or <code>null</code> if the property is not set, or not a valid Integer * @return property value as a Integer, or <code>null</code> if the property is not set, or not a valid Integer
*/ */
public Integer getInt(Property property) { public Integer getInt(Property property) {
if(property.getPropertyType() != Property.PropertyType.SIMPLE) { if(property.getPropertyType() != Property.PropertyType.SIMPLE) {
return null; return null;
} }
if(property.getValueType() != Property.ValueType.INTEGER) { if(property.getValueType() != Property.ValueType.INTEGER) {
return null; return null;
} }
String v = get(property); String v = get(property);
if(v == null) { if(v == null) {
return null; return null;
} }
try { try {
return Integer.valueOf(v); return Integer.valueOf(v);
} catch(NumberFormatException e) { } catch(NumberFormatException e) {
return null; return null;
} }
} }
/** /**
* Returns the value of the identified Date based metadata property. * Returns the value of the identified Date based metadata property.
* *
* @since Apache Tika 0.8 * @since Apache Tika 0.8
* @param property simple date property definition * @param property simple date property definition
* @return property value as a Date, or <code>null</code> if the property is not set, or not a valid Date * @return property value as a Date, or <code>null</code> if the property is not set, or not a valid Date
*/ */
public Date getDate(Property property) { public Date getDate(Property property) {
if(property.getPropertyType() != Property.PropertyType.SIMPLE) { if(property.getPropertyType() != Property.PropertyType.SIMPLE) {
return null; return null;
} }
if(property.getValueType() != Property.ValueType.DATE) { if(property.getValueType() != Property.ValueType.DATE) {
return null; return null;
} }
String v = get(property); String v = get(property);
if (v != null) { if (v != null) {
return parseDate(v); return parseDate(v);
} else { } else {
return null; return null;
} }
} }
/** /**
* Get the values associated to a metadata name. * Get the values associated to a metadata name.
* *
* @param name * @param name
* of the metadata. * of the metadata.
* @return the values associated to a metadata name. * @return the values associated to a metadata name.
*/ */
public ArrayList<String> getValues(final String name) { public ArrayList<String> getValues(final String name) {
return _getValues(name); return _getValues(name);
} }
private ArrayList<String> _getValues(final String name) { private ArrayList<String> _getValues(final String name) {
ArrayList<String> values = metadata.get(name); ArrayList<String> values = metadata.get(name);
if (values == null) { if (values == null) {
values = new ArrayList<String>(); values = new ArrayList<String>();
} }
return values; return values;
} }
/** /**
* Add a metadata name/value mapping. Add the specified value to the list of * Add a metadata name/value mapping. Add the specified value to the list of
* values associated to the specified metadata name. * values associated to the specified metadata name.
* *
* @param name * @param name
* the metadata name. * the metadata name.
* @param value * @param value
* the metadata value. * the metadata value.
*/ */
public void add(final String name, final String value) { public void add(final String name, final String value) {
ArrayList<String> values = metadata.get(name); ArrayList<String> values = metadata.get(name);
if (values == null) { if (values == null) {
set(name, value); set(name, value);
} else { } else {
//ArrayList<String> newValues = new ArrayList<String>();//new String[values.size() + 1]; //ArrayList<String> newValues = new ArrayList<String>();//new String[values.size() + 1];
//System.arraycopy(values, 0, newValues, 0, values.size()); //System.arraycopy(values, 0, newValues, 0, values.size());
//newValues[newValues.length - 1] = value; //newValues[newValues.length - 1] = value;
values.add(value); values.add(value);
metadata.put(name, values); metadata.put(name, values);
} }
} }
/** /**
* Copy All key-value pairs from properties. * Copy All key-value pairs from properties.
* *
* @param properties * @param properties
* properties to copy from * properties to copy from
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void setAll(Properties properties) { public void setAll(Properties properties) {
ArrayList<String> propArr = new ArrayList<String>(); ArrayList<String> propArr = new ArrayList<String>();
Enumeration<String> names = Enumeration<String> names =
(Enumeration<String>) properties.propertyNames(); (Enumeration<String>) properties.propertyNames();
while (names.hasMoreElements()) { while (names.hasMoreElements()) {
String name = names.nextElement(); String name = names.nextElement();
propArr.add(properties.getProperty(name) ); propArr.add(properties.getProperty(name) );
metadata.put(name, propArr); metadata.put(name, propArr);
} }
} }
/** /**
* Set metadata name/value. Associate the specified value to the specified * Set metadata name/value. Associate the specified value to the specified
* metadata name. If some previous values were associated to this name, they * metadata name. If some previous values were associated to this name, they
* are removed. * are removed.
* *
* @param name * @param name
* the metadata name. * the metadata name.
* @param value * @param value
* the metadata value. * the metadata value.
*/ */
public void set(String name, String value) { public void set(String name, String value) {
ArrayList<String> strArr = this.metadata.get(name); ArrayList<String> strArr = this.metadata.get(name);
if(strArr != null) if(strArr != null)
{ {
metadata.put(name, strArr); metadata.put(name, strArr);
} }
else else
{ {
strArr = new ArrayList<String>(); strArr = new ArrayList<String>();
strArr.add(value); strArr.add(value);
metadata.put(name,strArr); metadata.put(name,strArr);
} }
++strArrCount; ++strArrCount;
} }
/** /**
* Sets the value of the identified metadata property. * Sets the value of the identified metadata property.
* *
* @since Apache Tika 0.7 * @since Apache Tika 0.7
* @param property property definition * @param property property definition
* @param value property value * @param value property value
*/ */
public void set(Property property, String value) { public void set(Property property, String value) {
set(property.getName(), value); set(property.getName(), value);
} }
/** /**
* Sets the integer value of the identified metadata property. * Sets the integer value of the identified metadata property.
* *
* @since Apache Tika 0.8 * @since Apache Tika 0.8
* @param property simple integer property definition * @param property simple integer property definition
* @param value property value * @param value property value
*/ */
public void set(Property property, int value) { public void set(Property property, int value) {
if(property.getPropertyType() != Property.PropertyType.SIMPLE) { if(property.getPropertyType() != Property.PropertyType.SIMPLE) {
throw new PropertyTypeException(Property.PropertyType.SIMPLE, property.getPropertyType()); throw new PropertyTypeException(Property.PropertyType.SIMPLE, property.getPropertyType());
} }
if(property.getValueType() != Property.ValueType.INTEGER) { if(property.getValueType() != Property.ValueType.INTEGER) {
throw new PropertyTypeException(Property.ValueType.INTEGER, property.getValueType()); throw new PropertyTypeException(Property.ValueType.INTEGER, property.getValueType());
} }
set(property.getName(), Integer.toString(value)); set(property.getName(), Integer.toString(value));
} }
/** /**
* Sets the real or rational value of the identified metadata property. * Sets the real or rational value of the identified metadata property.
* *
* @since Apache Tika 0.8 * @since Apache Tika 0.8
* @param property simple real or simple rational property definition * @param property simple real or simple rational property definition
* @param value property value * @param value property value
*/ */
public void set(Property property, double value) { public void set(Property property, double value) {
if(property.getPropertyType() != Property.PropertyType.SIMPLE) { if(property.getPropertyType() != Property.PropertyType.SIMPLE) {
throw new PropertyTypeException(Property.PropertyType.SIMPLE, property.getPropertyType()); throw new PropertyTypeException(Property.PropertyType.SIMPLE, property.getPropertyType());
} }
if(property.getValueType() != Property.ValueType.REAL && if(property.getValueType() != Property.ValueType.REAL &&
property.getValueType() != Property.ValueType.RATIONAL) { property.getValueType() != Property.ValueType.RATIONAL) {
throw new PropertyTypeException(Property.ValueType.REAL, property.getValueType()); throw new PropertyTypeException(Property.ValueType.REAL, property.getValueType());
} }
set(property.getName(), Double.toString(value)); set(property.getName(), Double.toString(value));
} }
/** /**
* Sets the date value of the identified metadata property. * Sets the date value of the identified metadata property.
* *
* @since Apache Tika 0.8 * @since Apache Tika 0.8
* @param property simple integer property definition * @param property simple integer property definition
* @param date property value * @param date property value
*/ */
public void set(Property property, Date date) { public void set(Property property, Date date) {
if(property.getPropertyType() != Property.PropertyType.SIMPLE) { if(property.getPropertyType() != Property.PropertyType.SIMPLE) {
throw new PropertyTypeException(Property.PropertyType.SIMPLE, property.getPropertyType()); throw new PropertyTypeException(Property.PropertyType.SIMPLE, property.getPropertyType());
} }
if(property.getValueType() != Property.ValueType.DATE) { if(property.getValueType() != Property.ValueType.DATE) {
throw new PropertyTypeException(Property.ValueType.DATE, property.getValueType()); throw new PropertyTypeException(Property.ValueType.DATE, property.getValueType());
} }
set(property.getName(), formatDate(date)); set(property.getName(), formatDate(date));
} }
/** /**
* Remove a metadata and all its associated values. * Remove a metadata and all its associated values.
* *
* @param name * @param name
* metadata name to remove * metadata name to remove
*/ */
public void remove(String name) { public void remove(String name) {
metadata.remove(name); metadata.remove(name);
} }
/** /**
* Returns the number of metadata names in this metadata. * Returns the number of metadata names in this metadata.
* *
* @return number of metadata names * @return number of metadata names
*/ */
public int size() { public int size() {
return metadata.size(); return metadata.size();
} }
public boolean equals(Object o) { public boolean equals(Object o) {
if (o == null) { if (o == null) {
return false; return false;
} }
ThunderbirdMetadata other = null; ThunderbirdMetadata other = null;
try { try {
other = (ThunderbirdMetadata) o; other = (ThunderbirdMetadata) o;
} catch (ClassCastException cce) { } catch (ClassCastException cce) {
return false; return false;
} }
if (other.size() != size()) { if (other.size() != size()) {
return false; return false;
} }
ArrayList<String> names = names(); ArrayList<String> names = names();
for (String str : names) for (String str : names)
{//int i = 0; i < names.length; i++) { {//int i = 0; i < names.length; i++) {
ArrayList<String> otherValues = other._getValues(str); ArrayList<String> otherValues = other._getValues(str);
ArrayList<String> thisValues = _getValues(str); ArrayList<String> thisValues = _getValues(str);
if (otherValues.size() != thisValues.size()) { if (otherValues.size() != thisValues.size()) {
return false; return false;
} }
for (int j = 0; j < otherValues.size(); j++) { for (int j = 0; j < otherValues.size(); j++) {
if (!otherValues.get(j).equals(thisValues.get(j))) { if (!otherValues.get(j).equals(thisValues.get(j))) {
return false; return false;
} }
} }
} }
return true; return true;
} }
public String toString() { public String toString() {
StringBuffer buf = new StringBuffer(); StringBuffer buf = new StringBuffer();
ArrayList<String> names = names(); ArrayList<String> names = names();
for (int i = 0; i < names.size(); i++) { for (int i = 0; i < names.size(); i++) {
ArrayList<String> values = _getValues(names.get(i)); ArrayList<String> values = _getValues(names.get(i));
for (int j = 0; j < values.size(); j++) { for (int j = 0; j < values.size(); j++) {
buf.append(names.get(i)).append("=").append(values.get(j)).append(" "); buf.append(names.get(i)).append("=").append(values.get(j)).append(" ");
} }
} }
return buf.toString(); return buf.toString();
} }
} }

View File

@ -2,9 +2,9 @@
<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.2//EN" "http://www.netbeans.org/dtds/filesystem-1_2.dtd"> <!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.2//EN" "http://www.netbeans.org/dtds/filesystem-1_2.dtd">
<filesystem> <filesystem>
<folder name="Services"> <folder name="Services">
<file name="org-sleuthkit-autopsy-mboxparser-MboxFileIngestService.instance"> <file name="org-sleuthkit-autopsy-thunderbirdparser-ThunderbirdMboxFilervice.instance">
<attr name="instanceOf" stringvalue="org.sleuthkit.autopsy.ingest.IngestServiceAbstractFile"/> <attr name="instanceOf" stringvalue="org.sleuthkit.autopsy.ingest.IngestServiceAbstractFile"/>
<attr name="instanceCreate" methodvalue="org.sleuthkit.autopsy.mboxparser.MboxFileIngestService.getDefault"/> <attr name="instanceCreate" methodvalue="org.sleuthkit.autopsy.thunderbirdparser.ThunderbirdMboxFileIngestService.getDefault"/>
<attr name="position" intvalue="1100"/> <attr name="position" intvalue="1100"/>
</file> </file>
</folder> </folder>