diff --git a/.gitignore b/.gitignore
index ba7f27a0ff..06530b55b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
/Case/build/
+/Case/nbproject/private/
/CoreComponentInterfaces/build/
/DirectoryTree/build/
/CoreComponents/build/
@@ -15,3 +16,4 @@
/DataModel/release/modules/lib/libewf.dll
/DataModel/release/modules/lib/libtsk_jni.dll
/DataModel/release/modules/lib/zlib1.dll
+/KeywordSearch/build/
\ No newline at end of file
diff --git a/KeywordSearch/build.xml b/KeywordSearch/build.xml
new file mode 100644
index 0000000000..3e4314aec7
--- /dev/null
+++ b/KeywordSearch/build.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+ Builds, tests, and runs the project org.sleuthkit.autopsy.keywordsearch.
+
+
diff --git a/KeywordSearch/manifest.mf b/KeywordSearch/manifest.mf
new file mode 100644
index 0000000000..cc92feb905
--- /dev/null
+++ b/KeywordSearch/manifest.mf
@@ -0,0 +1,6 @@
+Manifest-Version: 1.0
+OpenIDE-Module: org.sleuthkit.autopsy.keywordsearch/0
+OpenIDE-Module-Implementation-Version: 1
+OpenIDE-Module-Layer: org/sleuthkit/autopsy/keywordsearch/layer.xml
+OpenIDE-Module-Localizing-Bundle: org/sleuthkit/autopsy/keywordsearch/Bundle.properties
+
diff --git a/KeywordSearch/nbproject/build-impl.xml b/KeywordSearch/nbproject/build-impl.xml
new file mode 100644
index 0000000000..a0f2871dd4
--- /dev/null
+++ b/KeywordSearch/nbproject/build-impl.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ You must set 'suite.dir' to point to your containing module suite
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/KeywordSearch/nbproject/genfiles.properties b/KeywordSearch/nbproject/genfiles.properties
new file mode 100644
index 0000000000..8a936e1b8d
--- /dev/null
+++ b/KeywordSearch/nbproject/genfiles.properties
@@ -0,0 +1,8 @@
+build.xml.data.CRC32=71d4527d
+build.xml.script.CRC32=87b97b04
+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.
+# 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=71d4527d
+nbproject/build-impl.xml.script.CRC32=fe1f48d2
+nbproject/build-impl.xml.stylesheet.CRC32=238281d1@1.46.2
diff --git a/KeywordSearch/nbproject/project.properties b/KeywordSearch/nbproject/project.properties
new file mode 100644
index 0000000000..c51692cafc
--- /dev/null
+++ b/KeywordSearch/nbproject/project.properties
@@ -0,0 +1,3 @@
+javac.source=1.6
+javac.compilerargs=-Xlint -Xlint:-serial
+spec.version.base=0.0
diff --git a/KeywordSearch/nbproject/project.xml b/KeywordSearch/nbproject/project.xml
new file mode 100644
index 0000000000..600354b4e6
--- /dev/null
+++ b/KeywordSearch/nbproject/project.xml
@@ -0,0 +1,26 @@
+
+
+ org.netbeans.modules.apisupport.project
+
+
+ org.sleuthkit.autopsy.keywordsearch
+
+
+
+ org.sleuthkit.autopsy.datamodel
+
+
+
+ 1
+ 1.0
+
+
+
+
+
+ ext/apache-solr-solrj-3.4.0.jar
+ release/modules/ext/apache-solr-solrj-3.4.0.jar
+
+
+
+
diff --git a/KeywordSearch/nbproject/suite.properties b/KeywordSearch/nbproject/suite.properties
new file mode 100644
index 0000000000..29d7cc9bd6
--- /dev/null
+++ b/KeywordSearch/nbproject/suite.properties
@@ -0,0 +1 @@
+suite.dir=${basedir}/..
diff --git a/KeywordSearch/release/modules/ext/apache-solr-solrj-3.4.0.jar b/KeywordSearch/release/modules/ext/apache-solr-solrj-3.4.0.jar
new file mode 100644
index 0000000000..f615d94370
Binary files /dev/null and b/KeywordSearch/release/modules/ext/apache-solr-solrj-3.4.0.jar differ
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties
new file mode 100644
index 0000000000..d0f3334216
--- /dev/null
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Bundle.properties
@@ -0,0 +1 @@
+OpenIDE-Module-Name=KeywordSearch
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java
new file mode 100644
index 0000000000..5820e787f5
--- /dev/null
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/Ingester.java
@@ -0,0 +1,139 @@
+package org.sleuthkit.autopsy.keywordsearch;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.net.MalformedURLException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.Logger;
+import org.apache.solr.client.solrj.SolrServer;
+import org.apache.solr.client.solrj.SolrServerException;
+import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer;
+import org.apache.solr.client.solrj.request.AbstractUpdateRequest;
+import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
+import org.apache.solr.common.util.ContentStream;
+import org.sleuthkit.datamodel.File;
+import org.sleuthkit.datamodel.TskException;
+
+/**
+ * Handles ingesting files to a Solr server, given the url string for it
+ */
+class Ingester {
+
+
+ private SolrServer solr;
+ private boolean uncommitedIngests = false;
+
+ /**
+ * New Ingester connected to the server at given url
+ * @param url Should be something like "http://localhost:8983/solr"
+ */
+ Ingester(String url) {
+ try {
+ this.solr = new CommonsHttpSolrServer(url);
+ } catch (MalformedURLException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ super.finalize();
+ if (uncommitedIngests) {
+ Logger.getLogger(Ingester.class.getName()).warning("Ingester was used to add files that it never committed!");
+ }
+ }
+
+ void ingest(File f) throws IngesterException {
+ Map fields = new HashMap();
+ fields.put("id", Long.toString(f.getId()));
+ fields.put("file_name", f.getName());
+ fields.put("ctime", f.getCtimeAsDate());
+ fields.put("atime", f.getAtimeAsDate());
+ fields.put("mtime", f.getMtimeAsDate());
+ fields.put("crtime", f.getMtimeAsDate());
+
+ ContentStreamUpdateRequest up = new ContentStreamUpdateRequest("/update/extract");
+ up.addContentStream(new FileContentStream(f));
+ setFields(up, fields);
+ up.setAction(AbstractUpdateRequest.ACTION.COMMIT, true, true);
+
+ try {
+ solr.request(up);
+ } catch (IOException ex) {
+ throw new IngesterException("Problem posting file contents to Solr", ex);
+ } catch (SolrServerException ex) {
+ throw new IngesterException("Problem posting file contents to Solr", ex);
+ }
+ uncommitedIngests = true;
+ }
+
+ void commit() throws IngesterException {
+ uncommitedIngests = false;
+ try {
+ solr.commit();
+ } catch (IOException ex) {
+ throw new IngesterException("Problem making Solr commit", ex);
+ } catch (SolrServerException ex) {
+ throw new IngesterException("Problem making Solr commit", ex);
+ }
+ }
+
+ private static void setFields(ContentStreamUpdateRequest up, Map fields) {
+ for (Entry field : fields.entrySet()) {
+ up.setParam("literal." + field.getKey(), field.getValue());
+ }
+ }
+
+ private static class FileContentStream implements ContentStream {
+
+ File f;
+
+ FileContentStream(File f) {
+ this.f = f;
+ }
+
+ @Override
+ public String getName() {
+ return f.getName();
+ }
+
+ @Override
+ public String getSourceInfo() {
+ return "File:" + f.getId();
+ }
+
+ @Override
+ public String getContentType() {
+ return null;
+ }
+
+ @Override
+ public Long getSize() {
+ return f.getSize();
+ }
+
+ @Override
+ public InputStream getStream() throws IOException {
+ try {
+ return new ByteArrayInputStream(f.read(0, f.getSize()));
+ } catch (TskException ex) {
+ throw new IOException(ex);
+ }
+ }
+
+ @Override
+ public Reader getReader() throws IOException {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+ }
+
+ static class IngesterException extends Exception {
+ IngesterException(String message, Throwable ex) {
+ super(message, ex);
+ }
+ }
+}
diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/layer.xml b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/layer.xml
new file mode 100644
index 0000000000..40f6e5e00b
--- /dev/null
+++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/layer.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/nbproject/project.properties b/nbproject/project.properties
index d78ab488e8..6024f5383f 100644
--- a/nbproject/project.properties
+++ b/nbproject/project.properties
@@ -17,12 +17,14 @@ modules=\
${project.org.sleuthkit.autopsy.filesearch}:\
${project.org.sleuthkit.autopsy.datamodel}:\
${project.org.sleuthkit.autopsy.logging}:\
- ${project.org.sleuthkit.autopsy.casemodule}
+ ${project.org.sleuthkit.autopsy.casemodule}:\
+ ${project.org.sleuthkit.autopsy.keywordsearch}
project.org.sleuthkit.autopsy.casemodule=Case
project.org.sleuthkit.autopsy.corecomponentinterfaces=CoreComponentInterfaces
project.org.sleuthkit.autopsy.corecomponents=CoreComponents
project.org.sleuthkit.autopsy.directorytree=DirectoryTree
project.org.sleuthkit.autopsy.filesearch=FileSearch
+project.org.sleuthkit.autopsy.keywordsearch=KeywordSearch
project.org.sleuthkit.autopsy.logging=Logging
project.org.sleuthkit.autopsy.menuactions=MenuActions
project.org.sleuthkit.autopsy.datamodel=DataModel