diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedMatchesSource.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedMatchesSource.java index dbd3e41f80..a1eba61173 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedMatchesSource.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/HighlightedMatchesSource.java @@ -57,11 +57,11 @@ class HighlightedMatchesSource implements MarkupSource { public String getMarkup() { SolrQuery q = new SolrQuery(); - final String queryEscaped = KeywordSearchUtil.escapeLuceneQuery(solrQuery, true); + final String queryEscaped = KeywordSearchUtil.escapeLuceneQuery(solrQuery, true, false); q.setQuery(queryEscaped); q.addFilterQuery("id:" + content.getId()); - q.addHighlightField("content"); + q.addHighlightField("content"); //for exact highlighting, try content_ws field (with stored="true" in Solr schema) q.setHighlightSimplePre(HIGHLIGHT_PRE); q.setHighlightSimplePost(HIGHLIGHT_POST); q.setHighlightFragsize(0); // don't fragment the highlight diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchListTopComponent.form b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchListTopComponent.form index f8b1dc40ec..a952affca6 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchListTopComponent.form +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchListTopComponent.form @@ -155,6 +155,9 @@ + + + diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchListTopComponent.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchListTopComponent.java index 2cc28b5460..876985025b 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchListTopComponent.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchListTopComponent.java @@ -21,9 +21,12 @@ package org.sleuthkit.autopsy.keywordsearch; import java.awt.Component; import java.awt.event.ActionListener; import java.util.ArrayList; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Pattern; @@ -75,9 +78,6 @@ public final class KeywordSearchListTopComponent extends TopComponent implements searchButton.setToolTipText("Execute the keyword list search using the current list"); deleteWordButton.setToolTipText("Delete selected keyword(s) from the list"); deleteAllWordsButton.setToolTipText("Delete all keywords from the list (clear it)"); - - loadListButton.setEnabled(false); - saveListButton.setEnabled(false); keywordTable.setAutoscrolls(true); keywordTable.setTableHeader(null); @@ -85,15 +85,15 @@ public final class KeywordSearchListTopComponent extends TopComponent implements keywordTable.setShowVerticalLines(false); keywordTable.getParent().setBackground(keywordTable.getBackground()); - + //customize column witdhs - keywordTable.setSize(260,200); + keywordTable.setSize(260, 200); final int width = keywordTable.getSize().width; TableColumn column = null; for (int i = 0; i < 2; i++) { column = keywordTable.getColumnModel().getColumn(i); if (i == 1) { - column.setPreferredWidth(((int) (width *0.2))); + column.setPreferredWidth(((int) (width * 0.2))); //column.setCellRenderer(new CellTooltipRenderer()); } else { column.setCellRenderer(new CellTooltipRenderer()); @@ -104,10 +104,10 @@ public final class KeywordSearchListTopComponent extends TopComponent implements loadDefaultKeywords(); } - + private void loadDefaultKeywords() { //some hardcoded keywords for testing - + //phone number tableModel.addKeyword("\\d\\d\\d[\\.-]\\d\\d\\d[\\.-]\\d\\d\\d\\d"); tableModel.addKeyword("\\d{8,10}"); @@ -174,6 +174,11 @@ public final class KeywordSearchListTopComponent extends TopComponent implements }); org.openide.awt.Mnemonics.setLocalizedText(loadListButton, org.openide.util.NbBundle.getMessage(KeywordSearchListTopComponent.class, "KeywordSearchListTopComponent.loadListButton.text")); // NOI18N + loadListButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + loadListButtonActionPerformed(evt); + } + }); org.openide.awt.Mnemonics.setLocalizedText(deleteWordButton, org.openide.util.NbBundle.getMessage(KeywordSearchListTopComponent.class, "KeywordSearchListTopComponent.deleteWordButton.text")); // NOI18N deleteWordButton.addActionListener(new java.awt.event.ActionListener() { @@ -274,7 +279,6 @@ public final class KeywordSearchListTopComponent extends TopComponent implements }// //GEN-END:initComponents private void searchButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_searchButtonActionPerformed - // TODO add your handling code here: }//GEN-LAST:event_searchButtonActionPerformed private void addWordButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_addWordButtonActionPerformed @@ -318,11 +322,39 @@ public final class KeywordSearchListTopComponent extends TopComponent implements }//GEN-LAST:event_addWordButtonActionPerformed private void saveListButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveListButtonActionPerformed - + KeywordSearchListsXML writer = KeywordSearchListsXML.getInstance(); + + //TODO popup with name / check if overwrite, then save + + String listName = "initial"; + + List keywords = tableModel.getAllKeywords(); + + boolean shouldWrite = false; + boolean written = false; + if (writer.listExists(listName)) { + boolean replace = KeywordSearchUtil.displayConfirmDialog("Save Keyword List", "Keyword List <" + listName + "> already exists, do you want to replace it?", + KeywordSearchUtil.DIALOG_MESSAGE_TYPE.WARN); + if (replace) { + shouldWrite = true; + } + + } else { + shouldWrite = true; + } + + if (shouldWrite) { + writer.addList(listName, keywords); + written = writer.save(); + } + + if (written) { + KeywordSearchUtil.displayDialog("Save Keyword List", "Keyword List <" + listName + "> saved", KeywordSearchUtil.DIALOG_MESSAGE_TYPE.INFO); + } + }//GEN-LAST:event_saveListButtonActionPerformed private void chLiteralWordActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_chLiteralWordActionPerformed - }//GEN-LAST:event_chLiteralWordActionPerformed private void deleteWordButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteWordButtonActionPerformed @@ -332,6 +364,24 @@ public final class KeywordSearchListTopComponent extends TopComponent implements private void deleteAllWordsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteAllWordsButtonActionPerformed tableModel.deleteAll(); }//GEN-LAST:event_deleteAllWordsButtonActionPerformed + + private void loadListButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_loadListButtonActionPerformed + KeywordSearchListsXML loader = KeywordSearchListsXML.getInstance(); + + //TODO popup widget with all lists in a a table, user picks name, then load into the model + String listName = "initial"; + + KeywordSearchList list = loader.getList(listName); + if (list != null) { + List keywords = list.getKeywords(); + + //TODO clear/append option ? + tableModel.deleteAll(); + tableModel.addKeywords(keywords); + KeywordSearchUtil.displayDialog("Save Keyword List", "Keyword List <" + listName + "> loaded", KeywordSearchUtil.DIALOG_MESSAGE_TYPE.INFO); + } + + }//GEN-LAST:event_loadListButtonActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton addWordButton; private javax.swing.JTextField addWordField; @@ -352,12 +402,10 @@ public final class KeywordSearchListTopComponent extends TopComponent implements @Override public void componentOpened() { - } @Override public void componentClosed() { - } void writeProperties(java.util.Properties p) { @@ -441,7 +489,7 @@ public final class KeywordSearchListTopComponent extends TopComponent implements private static Logger logger = Logger.getLogger(KeywordTableModel.class.getName()); //data - private List keywordData = new ArrayList(); + private Set keywordData = new TreeSet(); @Override public int getColumnCount() { @@ -456,12 +504,18 @@ public final class KeywordSearchListTopComponent extends TopComponent implements @Override public Object getValueAt(int rowIndex, int columnIndex) { Object ret = null; + TableEntry entry = null; + //iterate until row + Iterator it = keywordData.iterator(); + for (int i = 0; i <= rowIndex; ++i) { + entry = it.next(); + } switch (columnIndex) { case 0: - ret = (Object) keywordData.get(rowIndex).keyword; + ret = (Object) entry.keyword; break; case 1: - ret = (Object) keywordData.get(rowIndex).isActive; + ret = (Object) entry.isActive; break; default: logger.log(Level.SEVERE, "Invalid table column index: " + columnIndex); @@ -478,7 +532,13 @@ public final class KeywordSearchListTopComponent extends TopComponent implements @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { if (columnIndex == 1) { - keywordData.get(rowIndex).isActive = (Boolean) aValue; + TableEntry entry = null; + //iterate until row + Iterator it = keywordData.iterator(); + for (int i = 0; i <= rowIndex; ++i) { + entry = it.next(); + } + entry.isActive = (Boolean) aValue; } } @@ -511,13 +571,24 @@ public final class KeywordSearchListTopComponent extends TopComponent implements } void addKeyword(String keyword) { - keywordData.add(0, new TableEntry(keyword)); - this.fireTableRowsInserted(keywordData.size() - 1, keywordData.size()); + if (!keywordExists(keyword)) { + keywordData.add(new TableEntry(keyword)); + } + fireTableDataChanged(); + } + + void addKeywords(List keywords) { + for (String keyword : keywords) { + if (!keywordExists(keyword)) { + keywordData.add(new TableEntry(keyword)); + } + } + fireTableDataChanged(); } void deleteAll() { keywordData.clear(); - initEmpty(); + fireTableDataChanged(); } void deleteSelected() { @@ -535,14 +606,7 @@ public final class KeywordSearchListTopComponent extends TopComponent implements } - void initEmpty() { - for (int i = 0; i < 10; ++i) { - keywordData.add(0, new TableEntry("", false)); - } - fireTableDataChanged(); - } - - class TableEntry { + class TableEntry implements Comparable { String keyword; Boolean isActive; @@ -556,6 +620,11 @@ public final class KeywordSearchListTopComponent extends TopComponent implements this.keyword = keyword; this.isActive = false; } + + @Override + public int compareTo(Object o) { + return this.keyword.compareTo(((TableEntry) o).keyword); + } } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchListsXML.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchListsXML.java new file mode 100644 index 0000000000..d6e5e2ff3f --- /dev/null +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchListsXML.java @@ -0,0 +1,334 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2011 Basis Technology Corp. + * Contact: carrier sleuthkit org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.sleuthkit.autopsy.keywordsearch; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Result; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import org.sleuthkit.autopsy.coreutils.AutopsyPropFile; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +/** + * Manages reading and writing of keyword lists to user settings XML file keywords.xml + */ +public class KeywordSearchListsXML { + + private static final String ROOT_EL = "keyword_lists"; + private static final String LIST_EL = "keyword_list"; + private static final String LIST_NAME_ATTR = "name"; + private static final String LIST_CREATE_ATTR = "created"; + private static final String LIST_MOD_ATTR = "modified"; + private static final String KEYWORD_EL = "keyword"; + private static final String LISTS_FILE_NAME = "keywords.xml"; + private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; + private static final String ENCODING = "UTF-8"; + private String LISTS_FILE = AutopsyPropFile.getUserDirPath() + File.separator + LISTS_FILE_NAME; + private static final Logger logger = Logger.getLogger(KeywordSearchListsXML.class.getName()); + + Map theLists; //the keyword data + + static KeywordSearchListsXML theInstance = null; + + private KeywordSearchListsXML() { + } + + static KeywordSearchListsXML getInstance() { + if (theInstance == null) { + theInstance = new KeywordSearchListsXML(); + theInstance.reload(); + } + return theInstance; + } + + /** + * load the file or create new + */ + public void reload() { + boolean created = false; + theLists = new LinkedHashMap(); + if (!this.listFileExists()) { + //create new if it doesn't exist + save(); + created = true; + } + + if (!load() && !created) { + //create new if failed to load + save(); + } + + } + + /** + * get all loaded keyword lists + * @return List of keyword list objects + */ + Map getLists() { + return theLists; + } + + /** + * get list by name or null + * @param name id of the list + * @return keyword list representation + */ + KeywordSearchList getList(String name) { + return theLists.get(name); + } + + /** + * check if list with given name id exists + * @param name id to check + * @return true if list already exists or false otherwise + */ + boolean listExists(String name) { + return getList(name) != null; + } + + /** + * adds the new word list using name id + * replacing old one if exists with the same name + * requires following call to save() to make permanent changes + * @param name the name of the new list or list to replace + * @param newList list of keywords + * @return true if old list was replaced + */ + boolean addList(String name, List newList) { + boolean replaced = false; + KeywordSearchList curList = getList(name); + final Date now = new Date(); + if (curList == null) { + theLists.put(name, new KeywordSearchList(name, now, now, newList)); + } else { + theLists.put(name, new KeywordSearchList(name, curList.getDateCreated(), now, newList)); + replaced = true; + } + return replaced; + } + + /** + * writes out current list replacing the last lists file + */ + boolean save() { + boolean success = false; + DateFormat dateFormatter = new SimpleDateFormat(DATE_FORMAT); + DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance(); + + try { + DocumentBuilder docBuilder = dbfac.newDocumentBuilder(); + Document doc = docBuilder.newDocument(); + + Element rootEl = doc.createElement(ROOT_EL); + doc.appendChild(rootEl); + + for (String listName : theLists.keySet()) { + KeywordSearchList list = theLists.get(listName); + String created = dateFormatter.format(list.getDateCreated()); + String modified = dateFormatter.format(list.getDateModified()); + List keywords = list.getKeywords(); + + Element listEl = doc.createElement(LIST_EL); + listEl.setAttribute(LIST_NAME_ATTR, listName); + listEl.setAttribute(LIST_CREATE_ATTR, created); + listEl.setAttribute(LIST_MOD_ATTR, modified); + + for (String keyword : keywords) { + Element keywordEl = doc.createElement(KEYWORD_EL); + keywordEl.setTextContent(keyword); + listEl.appendChild(keywordEl); + } + rootEl.appendChild(listEl); + } + + success = saveDoc(doc); + } catch (ParserConfigurationException e) { + logger.log(Level.SEVERE, "Error saving keyword list: can't initialize parser.", e); + } + return success; + } + + + + /** + * load and parse XML, then dispose + */ + private boolean load() { + final Document doc = loadDoc(); + if (doc == null) { + return false; + } + DateFormat dateFormatter = new SimpleDateFormat(DATE_FORMAT); + + + Element root = doc.getDocumentElement(); + if (root == null) { + logger.log(Level.SEVERE, "Error loading keyword list: invalid file format."); + return false; + } + try { + NodeList listsNList = root.getElementsByTagName(LIST_EL); + int numLists = listsNList.getLength(); + for (int i = 0; i < numLists; ++i) { + Element listEl = (Element) listsNList.item(i); + final String name = listEl.getAttribute(LIST_NAME_ATTR); + final String created = listEl.getAttribute(LIST_CREATE_ATTR); + final String modified = listEl.getAttribute(LIST_MOD_ATTR); + Date createdDate = dateFormatter.parse(created); + Date modDate = dateFormatter.parse(modified); + List words = new ArrayList(); + KeywordSearchList list = new KeywordSearchList(name, createdDate, modDate, words); + + //parse all words + NodeList wordsNList = listEl.getElementsByTagName(KEYWORD_EL); + final int numKeywords = wordsNList.getLength(); + for (int j = 0; j < numKeywords; ++j) { + Element wordEl = (Element) wordsNList.item(j); + words.add(wordEl.getTextContent()); + + } + theLists.put(name, list); + } + } catch (ParseException e) { + //error parsing dates + logger.log(Level.SEVERE, "Error loading keyword list: can't parse dates.", e); + return false; + } + return true; + } + + private boolean listFileExists() { + File f = new File(LISTS_FILE); + return f.exists() && f.canRead() && f.canWrite(); + } + + private Document loadDoc() { + DocumentBuilderFactory builderFactory = + DocumentBuilderFactory.newInstance(); + + Document ret = null; + + + try { + DocumentBuilder builder = builderFactory.newDocumentBuilder(); + ret = builder.parse( + new FileInputStream(LISTS_FILE)); + } catch (ParserConfigurationException e) { + logger.log(Level.SEVERE, "Error loading keyword list: can't initialize parser.", e); + + } catch (SAXException e) { + logger.log(Level.SEVERE, "Error loading keyword list: can't parse XML.", e); + + } catch (IOException e) { + //error reading file + logger.log(Level.SEVERE, "Error loading keyword list: can't read file.", e); + + } + return ret; + + } + + private boolean saveDoc(final Document doc) { + TransformerFactory xf = TransformerFactory.newInstance(); + xf.setAttribute("indent-number", new Integer(1)); + boolean success = false; + try { + Transformer xformer = xf.newTransformer(); + xformer.setOutputProperty(OutputKeys.METHOD, "xml"); + xformer.setOutputProperty(OutputKeys.INDENT, "yes"); + xformer.setOutputProperty(OutputKeys.ENCODING, ENCODING); + xformer.setOutputProperty(OutputKeys.STANDALONE, "yes"); + xformer.setOutputProperty(OutputKeys.VERSION, "1.0"); + Result out = new StreamResult(new OutputStreamWriter(new FileOutputStream(new File(LISTS_FILE)), ENCODING)); + xformer.transform(new DOMSource(doc), out); + success = true; + } catch (UnsupportedEncodingException e) { + logger.log(Level.SEVERE, "Should not happen", e); + } catch (TransformerConfigurationException e) { + logger.log(Level.SEVERE, "Error writing keyword lists XML", e); + } catch (TransformerException e) { + logger.log(Level.SEVERE, "Error writing keyword lists XML", e); + } catch (FileNotFoundException e) { + logger.log(Level.SEVERE, "Error writing keyword lists XML: cannot write to file: " + LISTS_FILE, e); + } + return success; + } +} + +/** + * a representation of a single keyword list + * created or loaded + */ +class KeywordSearchList { + + private String name; + private Date created; + private Date modified; + private List keywords; + + KeywordSearchList(String name, Date created, Date modified, List keywords) { + this.name = name; + this.created = created; + this.modified = modified; + this.keywords = keywords; + } + + String getName() { + return name; + } + + Date getDateCreated() { + return created; + } + + Date getDateModified() { + return modified; + } + + List getKeywords() { + return keywords; + } +} diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java index 77494d1640..8be35ba8a0 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchResultFactory.java @@ -70,8 +70,7 @@ public class KeywordSearchResultFactory extends ChildFactory { public String toString() { return "Match"; } - }, - } + },} private Presentation presentation; private Collection queries; private Collection things; @@ -149,6 +148,7 @@ public class KeywordSearchResultFactory extends ChildFactory { childFactory = new ResultCollapsedChildFactory(thing); final Node ret = new KeyValueNode(thing, Children.create(childFactory, true)); SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { //DataResultViewerTable view = Utilities.actionsGlobalContext().lookup(DataResultViewerTable.class); @@ -199,11 +199,11 @@ public class KeywordSearchResultFactory extends ChildFactory { final int lastTerm = terms.size() - 1; int curTerm = 0; for (Term term : terms) { - final String termS = KeywordSearchUtil.escapeLuceneQuery(term.getTerm(), true); + final String termS = KeywordSearchUtil.escapeLuceneQuery(term.getTerm(), true, false); if (!termS.contains("*")) { highlightQuery.append(termS); if (lastTerm != curTerm) { - highlightQuery.append(" "); + highlightQuery.append(" "); //acts as OR || } } } @@ -304,25 +304,27 @@ public class KeywordSearchResultFactory extends ChildFactory { final String contentStr = KeywordSearch.getServer().getCore().getSolrContent(content); - //make sure the file contains a match (this gets rid of large number of false positives) - //TODO option in GUI to include approximate matches (faster) - boolean matchFound = false; - if (contentStr != null) {//if not null, some error getting from Solr, handle it by not filtering out - //perform java regex to validate match from Solr - String origQuery = thingContent.getQuery(); + //postprocess + //make sure Solr result contains a match (this gets rid of large number of false positives) + boolean postprocess = true; + boolean matchFound = true; + if (postprocess) { + if (contentStr != null) {//if not null, some error getting from Solr, handle it by not filtering out + //perform java regex to validate match from Solr + String origQuery = thingContent.getQuery(); + + //since query is a match result, we can assume literal pattern + origQuery = Pattern.quote(origQuery); + Pattern p = Pattern.compile(origQuery, Pattern.CASE_INSENSITIVE | Pattern.DOTALL); - //escape the regex query because it may contain special characters from the previous match - //since it's a match result, we can assume literal pattern - origQuery = Pattern.quote(origQuery); - Pattern p = Pattern.compile(origQuery, Pattern.CASE_INSENSITIVE | Pattern.DOTALL); - - Matcher m = p.matcher(contentStr); - matchFound = m.find(); + Matcher m = p.matcher(contentStr); + matchFound = m.find(); + } } if (matchFound) { Node kvNode = new KeyValueNode(thingContent, Children.LEAF); - //wrap in KeywordSearchFilterNode for the markup content, might need to override FilterNode for more customization + //wrap in KeywordSearchFilterNode for the markup content HighlightedMatchesSource highlights = new HighlightedMatchesSource(content, query); return new KeywordSearchFilterNode(highlights, kvNode, query); } else { diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchUtil.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchUtil.java index 9b8afa65a1..2940696605 100755 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchUtil.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/KeywordSearchUtil.java @@ -16,7 +16,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.sleuthkit.autopsy.keywordsearch; import java.awt.Component; @@ -31,8 +30,10 @@ import org.sleuthkit.datamodel.TskException; public class KeywordSearchUtil { - public enum DIALOG_MESSAGE_TYPE {ERROR, WARN, INFO}; - + public enum DIALOG_MESSAGE_TYPE { + + ERROR, WARN, INFO + }; private static final Logger logger = Logger.getLogger(KeywordSearchUtil.class.getName()); public static String buildDirName(FsContent f) { @@ -65,42 +66,44 @@ public class KeywordSearchUtil { * such as /+-&|!(){}[]^"~*?:\ and treat the whole query as literal word * @return encoded query */ - public static String escapeLuceneQuery(String query, boolean escapeLuceneChars) { + public static String escapeLuceneQuery(String query, boolean escapeLuceneChars, boolean encode) { String queryEscaped = null; String inputString = query; - + if (escapeLuceneChars == true) { final String ESCAPE_CHARS = "/+-&|!(){}[]^\"~*?:\\"; StringBuilder sb = new StringBuilder(); - for (int i = 0; i< inputString.length(); ++i) { + for (int i = 0; i < inputString.length(); ++i) { char c = inputString.charAt(i); - if (ESCAPE_CHARS.contains(Character.toString(c)) ) { + if (ESCAPE_CHARS.contains(Character.toString(c))) { sb.append("\\"); } sb.append(c); } - inputString = sb.toString(); + queryEscaped = inputString = sb.toString(); } - - try { - queryEscaped = URLEncoder.encode(inputString, "UTF-8"); - } - catch (UnsupportedEncodingException ex) { - logger.log(Level.SEVERE, "Error escaping URL query, should not happen.", ex); - queryEscaped = query; + + if (encode) { + try { + queryEscaped = URLEncoder.encode(inputString, "UTF-8"); + } catch (UnsupportedEncodingException ex) { + logger.log(Level.SEVERE, "Error escaping URL query, should not happen.", ex); + queryEscaped = query; + } } return queryEscaped; } - - + public static void displayDialog(final String title, final String message, final DIALOG_MESSAGE_TYPE type) { int messageType; - if (type == DIALOG_MESSAGE_TYPE.ERROR) + if (type == DIALOG_MESSAGE_TYPE.ERROR) { messageType = JOptionPane.ERROR_MESSAGE; - else if (type == DIALOG_MESSAGE_TYPE.WARN) + } else if (type == DIALOG_MESSAGE_TYPE.WARN) { messageType = JOptionPane.WARNING_MESSAGE; - else messageType = JOptionPane.INFORMATION_MESSAGE; - + } else { + messageType = JOptionPane.INFORMATION_MESSAGE; + } + final Component parentComponent = null; // Use default window frame. JOptionPane.showMessageDialog( parentComponent, @@ -108,4 +111,20 @@ public class KeywordSearchUtil { title, messageType); } + + public static boolean displayConfirmDialog(final String title, final String message, final DIALOG_MESSAGE_TYPE type) { + int messageType; + if (type == DIALOG_MESSAGE_TYPE.ERROR) { + messageType = JOptionPane.ERROR_MESSAGE; + } else if (type == DIALOG_MESSAGE_TYPE.WARN) { + messageType = JOptionPane.WARNING_MESSAGE; + } else { + messageType = JOptionPane.INFORMATION_MESSAGE; + } + if (JOptionPane.showConfirmDialog(null, message, title, JOptionPane.YES_NO_OPTION, messageType) == JOptionPane.YES_OPTION) { + return true; + } else { + return false; + } + } } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java index 82d3799523..29e199b9e4 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/LuceneQuery.java @@ -56,7 +56,7 @@ public class LuceneQuery implements KeywordSearchQuery { @Override public void escape() { - queryEscaped = KeywordSearchUtil.escapeLuceneQuery(query, true); + queryEscaped = KeywordSearchUtil.escapeLuceneQuery(query, true, true); isEscaped = true; } diff --git a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java index db9f995bdd..83492307d0 100644 --- a/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java +++ b/KeywordSearch/src/org/sleuthkit/autopsy/keywordsearch/TermComponentQuery.java @@ -49,7 +49,7 @@ import org.sleuthkit.autopsy.keywordsearch.KeywordSearchQueryManager.Presentatio import org.sleuthkit.datamodel.FsContent; public class TermComponentQuery implements KeywordSearchQuery { - + private static final int TERMS_UNLIMITED = -1; //corresponds to field in Solr schema, analyzed with white-space tokenizer only private static final String TERMS_SEARCH_FIELD = "content_ws"; @@ -60,14 +60,14 @@ public class TermComponentQuery implements KeywordSearchQuery { private String queryEscaped; private boolean isEscaped; private List terms; - + public TermComponentQuery(String query) { this.termsQuery = query; this.queryEscaped = query; isEscaped = false; terms = null; } - + @Override public void escape() { //treat as literal @@ -77,13 +77,13 @@ public class TermComponentQuery implements KeywordSearchQuery { queryEscaped = Pattern.quote(termsQuery); isEscaped = true; } - + @Override public boolean validate() { if (queryEscaped.equals("")) { return false; } - + boolean valid = true; try { Pattern.compile(queryEscaped); @@ -110,9 +110,9 @@ public class TermComponentQuery implements KeywordSearchQuery { q.setTermsRegex(queryEscaped); q.addTermsField(TERMS_SEARCH_FIELD); q.setTimeAllowed(TERMS_TIMEOUT); - + return q; - + } /* @@ -120,7 +120,7 @@ public class TermComponentQuery implements KeywordSearchQuery { */ protected List executeQuery(SolrQuery q) { Server.Core solrCore = KeywordSearch.getServer().getCore(); - + List termsCol = null; try { TermsResponse tr = solrCore.queryTerms(q); @@ -131,17 +131,17 @@ public class TermComponentQuery implements KeywordSearchQuery { return null; //no need to create result view, just display error dialog } } - + @Override public String getEscapedQueryString() { return this.queryEscaped; } - + @Override public String getQueryString() { return this.termsQuery; } - + @Override public Collection getTerms() { return terms; @@ -154,7 +154,7 @@ public class TermComponentQuery implements KeywordSearchQuery { @Override public List performQuery() { List results = new ArrayList(); - + final SolrQuery q = createQuery(); terms = executeQuery(q); @@ -168,20 +168,21 @@ public class TermComponentQuery implements KeywordSearchQuery { final int lastTerm = terms.size() - 1; int curTerm = 0; for (Term term : terms) { - final String termS = term.getTerm(); + final String termS = KeywordSearchUtil.escapeLuceneQuery(term.getTerm(), true, false); + //final String termS = term.getTerm(); if (!termS.contains("*")) { - filesQueryB.append(termS); + filesQueryB.append(TERMS_SEARCH_FIELD).append(":").append(termS); if (curTerm != lastTerm) { - filesQueryB.append(" "); + filesQueryB.append(" "); //acts as OR || } } ++curTerm; } List uniqueMatches = new ArrayList(); - + if (!terms.isEmpty()) { LuceneQuery filesQuery = new LuceneQuery(filesQueryB.toString()); - filesQuery.escape(); + //filesQuery.escape(); try { uniqueMatches = filesQuery.performQuery(); } catch (RuntimeException e) { @@ -190,28 +191,33 @@ public class TermComponentQuery implements KeywordSearchQuery { } + //result postprocessing //filter out non-matching files using the original query (whether literal or not) - //TODO this could be costly, for now just testing how it performs - for (FsContent f : uniqueMatches) { - Pattern p = Pattern.compile(queryEscaped, Pattern.CASE_INSENSITIVE | Pattern.DOTALL); - final String contentStr = KeywordSearch.getServer().getCore().getSolrContent(f); - Matcher m = p.matcher(contentStr); - if (m.find()) { - results.add(f); + boolean postprocess = false; + if (postprocess) { + for (FsContent f : uniqueMatches) { + Pattern p = Pattern.compile(queryEscaped, Pattern.CASE_INSENSITIVE | Pattern.DOTALL); + final String contentStr = KeywordSearch.getServer().getCore().getSolrContent(f); + Matcher m = p.matcher(contentStr); + if (m.find()) { + results.add(f); + } } + } else { + results.addAll(uniqueMatches); } - - - + + + return results; } - + @Override public void execute() { SolrQuery q = createQuery(); - + logger.log(Level.INFO, "Executing TermsComponent query: " + q.toString()); - + final SwingWorker worker = new TermsQueryWorker(q); worker.execute(); } @@ -221,9 +227,9 @@ public class TermComponentQuery implements KeywordSearchQuery { * @param terms */ private void publishNodes(List terms) { - + Collection things = new ArrayList(); - + Iterator it = terms.iterator(); int termID = 0; //long totalMatches = 0; @@ -237,17 +243,17 @@ public class TermComponentQuery implements KeywordSearchQuery { things.add(new KeyValueThing(match, kvs, ++termID)); //totalMatches += matches; } - + Node rootNode = null; if (things.size() > 0) { Children childThingNodes = Children.create(new KeywordSearchResultFactory(termsQuery, things, Presentation.DETAIL), true); - + rootNode = new AbstractNode(childThingNodes); } else { rootNode = Node.EMPTY; } - + final String pathText = "Term query"; // String pathText = "RegEx query: " + termsQuery //+ " Files with exact matches: " + Long.toString(totalMatches) + " (also listing approximate matches)"; @@ -256,29 +262,29 @@ public class TermComponentQuery implements KeywordSearchQuery { searchResultWin.requestActive(); // make it the active top component } - + class TermsQueryWorker extends SwingWorker, Void> { - + private SolrQuery q; private ProgressHandle progress; - + TermsQueryWorker(SolrQuery q) { this.q = q; } - + @Override protected List doInBackground() throws Exception { progress = ProgressHandleFactory.createHandle("Terms query task"); progress.start(); progress.progress("Running Terms query."); - + terms = executeQuery(q); - + progress.progress("Terms query completed."); - + return terms; } - + @Override protected void done() { if (!this.isCancelled()) { @@ -287,7 +293,7 @@ public class TermComponentQuery implements KeywordSearchQuery { publishNodes(terms); } catch (InterruptedException e) { logger.log(Level.INFO, "Exception while executing regex query,", e); - + } catch (ExecutionException e) { logger.log(Level.INFO, "Exception while executing regex query,", e); } finally {