/* * Autopsy Forensic Browser * * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sleuthkit.autopsy.keywordsearch; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.File; 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 org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.datamodel.BlackboardAttribute; /** * Keyword list saving, loading, and editing abstract class. */ abstract class KeywordSearchList { protected static final Logger LOGGER = Logger.getLogger(KeywordSearchList.class.getName()); private static final String PHONE_NUMBER_REGEX = "[(]{0,1}\\d\\d\\d[)]{0,1}[\\.-]\\d\\d\\d[\\.-]\\d\\d\\d\\d"; //NON-NLS private static final String IP_ADDRESS_REGEX = "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])"; //NON-NLS private static final String EMAIL_ADDRESS_REGEX = "(?=.{8})[a-z0-9%+_-]+(?:\\.[a-z0-9%+_-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z]{2,4}(?<!\\.txt|\\.exe|\\.dll|\\.jpg|\\.xml)"; //NON-NLS private static final String URL_REGEX = "((((ht|f)tp(s?))\\://)|www\\.)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,5})(\\:[0-9]+)*(/($|[a-zA-Z0-9\\.\\,\\;\\?\\'\\\\+&%\\$#\\=~_\\-]+))*"; //NON-NLS private static final String CCN_REGEX = ".*[3456]([ -]?\\d){11,18}.*"; //12-19 digits, with possible single spaces or dashes in between. first digit is 3,4,5, or 6 //NON-NLS protected String filePath; Map<String, KeywordList> theLists; //the keyword data PropertyChangeSupport changeSupport; protected List<String> lockedLists; KeywordSearchList(String filePath) { this.filePath = filePath; theLists = new LinkedHashMap<>(); lockedLists = new ArrayList<>(); changeSupport = new PropertyChangeSupport(this); } /** * Property change event support In events: For all of these enums, the old * value should be null, and the new value should be the keyword list name * string. */ enum ListsEvt { LIST_ADDED, LIST_DELETED, LIST_UPDATED }; enum LanguagesEvent { LANGUAGES_CHANGED, ENCODINGS_CHANGED } void fireLanguagesEvent(LanguagesEvent event) { try { changeSupport.firePropertyChange(event.toString(), null, null); } catch (Exception e) { LOGGER.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS } } public void addPropertyChangeListener(PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(listener); } private void prepopulateLists() { if (!theLists.isEmpty()) { return; } //phone number List<Keyword> phones = new ArrayList<>(); phones.add(new Keyword(PHONE_NUMBER_REGEX, false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PHONE_NUMBER)); lockedLists.add("Phone Numbers"); addList("Phone Numbers", phones, false, false, true); //IP address List<Keyword> ips = new ArrayList<>(); ips.add(new Keyword(IP_ADDRESS_REGEX, false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_IP_ADDRESS)); lockedLists.add("IP Addresses"); addList("IP Addresses", ips, false, false, true); //email List<Keyword> emails = new ArrayList<>(); emails.add(new Keyword(EMAIL_ADDRESS_REGEX, false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_EMAIL)); lockedLists.add("Email Addresses"); addList("Email Addresses", emails, true, false, true); //URL List<Keyword> urls = new ArrayList<>(); urls.add(new Keyword(URL_REGEX, false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_URL)); lockedLists.add("URLs"); addList("URLs", urls, false, false, true); //CCN List<Keyword> ccns = new ArrayList<>(); ccns.add(new Keyword(CCN_REGEX, false, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER)); lockedLists.add("Credit Card Numbers"); addList("Credit Card Numbers", ccns, false, false, true); } /** * load the file or create new */ public void reload() { boolean created = false; //theLists.clear(); //populate only the first time prepopulateLists(); //reset all the lists other than locked lists (we don't save them to XML) //we want to preserve state of locked lists List<String> toClear = new ArrayList<>(); for (String list : theLists.keySet()) { if (theLists.get(list).isEditable() == false) { toClear.add(list); } } for (String clearList : toClear) { theLists.remove(clearList); } if (!listFileExists()) { //create new if it doesn't exist save(); created = true; } //load, if fails to load create new if (!load() && !created) { //create new if failed to load save(); } } public List<KeywordList> getListsL() { List<KeywordList> ret = new ArrayList<>(); for (KeywordList list : theLists.values()) { ret.add(list); } return ret; } public List<KeywordList> getListsL(boolean locked) { List<KeywordList> ret = new ArrayList<>(); for (KeywordList list : theLists.values()) { if (list.isEditable().equals(locked)) { ret.add(list); } } return ret; } /** * Get list names of all loaded keyword list names * * @return List of keyword list names */ public List<String> getListNames() { return new ArrayList<>(theLists.keySet()); } /** * Get list names of all locked or unlocked loaded keyword list names * * @param locked true if look for locked lists, false otherwise * * @return List of keyword list names */ public List<String> getListNames(boolean locked) { ArrayList<String> lists = new ArrayList<>(); for (String listName : theLists.keySet()) { KeywordList list = theLists.get(listName); if (locked == list.isEditable()) { lists.add(listName); } } return lists; } /** * return first list that contains the keyword * * @param keyword * * @return found list or null */ public KeywordList getListWithKeyword(String keyword) { KeywordList found = null; for (KeywordList list : theLists.values()) { if (list.hasSearchTerm(keyword)) { found = list; break; } } return found; } /** * get number of lists currently stored * * @return number of lists currently stored */ int getNumberLists() { return theLists.size(); } /** * get number of unlocked or locked lists currently stored * * @param locked true if look for locked lists, false otherwise * * @return number of unlocked lists currently stored */ public int getNumberLists(boolean locked) { int numLists = 0; for (String listName : theLists.keySet()) { KeywordList list = theLists.get(listName); if (locked == list.isEditable()) { ++numLists; } } return numLists; } /** * get list by name or null * * @param name id of the list * * @return keyword list representation */ public KeywordList 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 * * @param name the name of the new list or list to replace * @param newList list of keywords * @param useForIngest should this list be used for ingest * * @return true if old list was replaced */ boolean addList(String name, List<Keyword> newList, boolean useForIngest, boolean ingestMessages, boolean locked) { boolean replaced = false; KeywordList curList = getList(name); final Date now = new Date(); if (curList == null) { theLists.put(name, new KeywordList(name, now, now, useForIngest, ingestMessages, newList, locked)); try { changeSupport.firePropertyChange(ListsEvt.LIST_ADDED.toString(), null, name); } catch (Exception e) { LOGGER.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS MessageNotifyUtil.Notify.show( NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.moduleErr"), NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.addList.errMsg1.msg"), MessageNotifyUtil.MessageType.ERROR); } } else { theLists.put(name, new KeywordList(name, curList.getDateCreated(), now, useForIngest, ingestMessages, newList, locked)); replaced = true; try { changeSupport.firePropertyChange(ListsEvt.LIST_UPDATED.toString(), null, name); } catch (Exception e) { LOGGER.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS MessageNotifyUtil.Notify.show( NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.moduleErr"), NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.addList.errMsg2.msg"), MessageNotifyUtil.MessageType.ERROR); } } return replaced; } boolean addList(String name, List<Keyword> newList, boolean useForIngest, boolean ingestMessages) { //make sure that the list is readded as a locked/built in list boolean isLocked = this.lockedLists.contains(name); return addList(name, newList, useForIngest, ingestMessages, isLocked); } boolean addList(String name, List<Keyword> newList) { return addList(name, newList, true, true); } boolean addList(KeywordList list) { return addList(list.getName(), list.getKeywords(), list.getUseForIngest(), list.getIngestMessages(), list.isEditable()); } /** * save multiple lists * * @param lists * * @return */ boolean saveLists(List<KeywordList> lists) { List<KeywordList> overwritten = new ArrayList<>(); List<KeywordList> newLists = new ArrayList<>(); for (KeywordList list : lists) { if (this.listExists(list.getName())) { overwritten.add(list); } else { newLists.add(list); } theLists.put(list.getName(), list); } boolean saved = save(true); if (saved) { for (KeywordList list : newLists) { try { changeSupport.firePropertyChange(ListsEvt.LIST_ADDED.toString(), null, list.getName()); } catch (Exception e) { LOGGER.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS MessageNotifyUtil.Notify.show( NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.moduleErr"), NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.saveList.errMsg1.msg"), MessageNotifyUtil.MessageType.ERROR); } } for (KeywordList over : overwritten) { try { changeSupport.firePropertyChange(ListsEvt.LIST_UPDATED.toString(), null, over.getName()); } catch (Exception e) { LOGGER.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS MessageNotifyUtil.Notify.show( NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.moduleErr"), NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.saveList.errMsg2.msg"), MessageNotifyUtil.MessageType.ERROR); } } } return saved; } /** * write out multiple lists * * @param lists * * @return */ boolean writeLists(List<KeywordList> lists) { List<KeywordList> overwritten = new ArrayList<>(); List<KeywordList> newLists = new ArrayList<>(); for (KeywordList list : lists) { if (this.listExists(list.getName())) { overwritten.add(list); } else { newLists.add(list); } theLists.put(list.getName(), list); } for (KeywordList list : newLists) { try { changeSupport.firePropertyChange(ListsEvt.LIST_ADDED.toString(), null, list.getName()); } catch (Exception e) { LOGGER.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS MessageNotifyUtil.Notify.show( NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.moduleErr"), NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.writeLists.errMsg1.msg"), MessageNotifyUtil.MessageType.ERROR); } } for (KeywordList over : overwritten) { try { changeSupport.firePropertyChange(ListsEvt.LIST_UPDATED.toString(), null, over.getName()); } catch (Exception e) { LOGGER.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS MessageNotifyUtil.Notify.show( NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.moduleErr"), NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.writeLists.errMsg2.msg"), MessageNotifyUtil.MessageType.ERROR); } } return true; } /** * delete list if exists and save new list * * @param name of list to delete * * @return true if deleted */ boolean deleteList(String name) { KeywordList delList = getList(name); if (delList != null && !delList.isEditable()) { theLists.remove(name); } try { changeSupport.firePropertyChange(ListsEvt.LIST_DELETED.toString(), null, name); } catch (Exception e) { LOGGER.log(Level.SEVERE, "KeywordSearchListsAbstract listener threw exception", e); //NON-NLS MessageNotifyUtil.Notify.show( NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.moduleErr"), NbBundle.getMessage(this.getClass(), "KeywordSearchListsAbstract.deleteList.errMsg1.msg"), MessageNotifyUtil.MessageType.ERROR); } return true; } /** * writes out current list replacing the last lists file */ public abstract boolean save(); /** * writes out current list replacing the last lists file * * @param isExport true is this save operation is an export and not a 'Save * As' */ public abstract boolean save(boolean isExport); /** * load and parse List, then dispose */ public abstract boolean load(); private boolean listFileExists() { File f = new File(filePath); return f.exists() && f.canRead() && f.canWrite(); } public void setUseForIngest(String key, boolean flag) { theLists.get(key).setUseForIngest(flag); } }