19 package org.sleuthkit.autopsy.keywordsearch;
 
   21 import com.google.common.base.CharMatcher;
 
   22 import java.util.ArrayList;
 
   23 import java.util.Collection;
 
   24 import java.util.HashMap;
 
   25 import java.util.List;
 
   27 import java.util.logging.Level;
 
   28 import java.util.regex.Matcher;
 
   29 import java.util.regex.Pattern;
 
   30 import org.apache.commons.lang3.StringUtils;
 
   31 import org.apache.commons.lang3.math.NumberUtils;
 
   32 import org.apache.commons.validator.routines.DomainValidator;
 
   33 import org.apache.solr.client.solrj.SolrQuery;
 
   34 import org.apache.solr.client.solrj.SolrQuery.SortClause;
 
   35 import org.apache.solr.client.solrj.SolrRequest;
 
   36 import org.apache.solr.client.solrj.response.QueryResponse;
 
   37 import org.apache.solr.common.SolrDocument;
 
   38 import org.apache.solr.common.SolrDocumentList;
 
   39 import org.apache.solr.common.params.CursorMarkParams;
 
   40 import org.openide.util.NbBundle;
 
   51 import org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
 
   53 import org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
 
   72 final class RegexQuery 
implements KeywordSearchQuery {
 
   74     public static final Logger LOGGER = Logger.getLogger(RegexQuery.class.getName());
 
   86     private static final CharSequence[] UNSUPPORTED_CHARS = {
"\\d", 
"\\D", 
"\\w", 
"\\W", 
"\\s", 
"\\S", 
"\\n",
 
   87         "\\t", 
"\\r", 
"\\f", 
"\\a", 
"\\e", 
"\\v", 
"\\V", 
"\\h", 
"\\H", 
"\\p"}; 
 
   89     private static final int MAX_RESULTS_PER_CURSOR_MARK = 512;
 
   90     private static final int MIN_EMAIL_ADDR_LENGTH = 8;
 
   91     private static final String SNIPPET_DELIMITER = String.valueOf(Character.toChars(171));
 
  103     static final Pattern CREDIT_CARD_NUM_PATTERN
 
  104             = Pattern.compile(
"(?<ccn>[2-6]([ -]?[0-9]){11,18})");
 
  105     static final Pattern CREDIT_CARD_TRACK1_PATTERN = Pattern.compile(
 
  117             + 
"(?<accountNumber>[2-6]([ -]?[0-9]){11,18})"  
  119             + 
"(?<name>[^^]{2,26})"  
  121             + 
"(?:(?:\\^|(?<expiration>\\d{4}))"  
  122             + 
"(?:(?:\\^|(?<serviceCode>\\d{3}))" 
  123             + 
"(?:(?<discretionary>[^?]*)"  
  127     static final Pattern CREDIT_CARD_TRACK2_PATTERN = Pattern.compile(
 
  138             + 
"(?<accountNumber>[2-6]([ -]?[0-9]){11,18})"  
  140             + 
"(?:(?<expiration>\\d{4})"  
  141             + 
"(?:(?<serviceCode>\\d{3})"  
  142             + 
"(?:(?<discretionary>[^:;<=>?]*)"  
  146     static final BlackboardAttribute.Type KEYWORD_SEARCH_DOCUMENT_ID = 
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID);
 
  148     private final List<KeywordQueryFilter> filters = 
new ArrayList<>();
 
  149     private final KeywordList keywordList;
 
  150     private final Keyword originalKeyword; 
 
  151     private final String keywordString;
 
  152     private final boolean queryStringContainsWildcardPrefix;
 
  153     private final boolean queryStringContainsWildcardSuffix;
 
  155     private boolean escaped;
 
  156     private String escapedQuery;
 
  157     private String field = Server.Schema.CONTENT_STR.toString();
 
  165     RegexQuery(KeywordList keywordList, Keyword keyword) {
 
  166         this.keywordList = keywordList;
 
  167         this.originalKeyword = keyword;
 
  168         this.keywordString = keyword.getSearchTerm();
 
  170         this.queryStringContainsWildcardPrefix = this.keywordString.startsWith(
".*");
 
  171         this.queryStringContainsWildcardSuffix = this.keywordString.endsWith(
".*");
 
  175     public KeywordList getKeywordList() {
 
  180     public boolean validate() {
 
  181         if (keywordString.isEmpty()) {
 
  186             Pattern.compile(keywordString, Pattern.UNICODE_CHARACTER_CLASS);
 
  192             for (CharSequence c : UNSUPPORTED_CHARS) {
 
  193                 if (keywordString.contains(c)) {
 
  198         } 
catch (IllegalArgumentException ex) {
 
  204     public QueryResults performQuery() throws NoOpenCoreException {
 
  206         final Server solrServer = KeywordSearch.getServer();
 
  207         SolrQuery solrQuery = 
new SolrQuery();
 
  226         boolean skipWildcardPrefix = queryStringContainsWildcardPrefix || getQueryString().startsWith(
"^");
 
  227         boolean skipWildcardSuffix = queryStringContainsWildcardSuffix
 
  228                 || (getQueryString().endsWith(
"$") && (!getQueryString().endsWith(
"\\$")));
 
  244         String queryString = (originalKeyword.searchTermIsLiteral() ? getEscapedQueryString() : getQueryString());
 
  245         double indexSchemaVersion = NumberUtils.toDouble(solrServer.getIndexInfo().getSchemaVersion());
 
  246         if (indexSchemaVersion >= 2.1) {
 
  247             queryString = queryString.toLowerCase();
 
  250         solrQuery.setQuery((field == null ? Server.Schema.CONTENT_STR.toString() : field) + 
":/" 
  251                 + (skipWildcardPrefix ? 
"" : 
".*")
 
  255                 + (skipWildcardSuffix ? 
"" : 
".*") + 
"/");
 
  258         solrQuery.setFields(Server.Schema.CONTENT_STR.toString(), Server.Schema.ID.toString(), Server.Schema.CHUNK_SIZE.toString());
 
  261                 .map(KeywordQueryFilter::toString)
 
  262                 .forEach(solrQuery::addFilterQuery);
 
  264         solrQuery.setRows(MAX_RESULTS_PER_CURSOR_MARK);
 
  266         solrQuery.setSort(SortClause.asc(Server.Schema.ID.toString()));
 
  268         String cursorMark = CursorMarkParams.CURSOR_MARK_START;
 
  269         SolrDocumentList resultList;
 
  270         boolean allResultsProcessed = 
false;
 
  271         QueryResults results = 
new QueryResults(
this);
 
  273         while (!allResultsProcessed) {
 
  275                 solrQuery.set(CursorMarkParams.CURSOR_MARK_PARAM, cursorMark);
 
  276                 QueryResponse response = solrServer.query(solrQuery, SolrRequest.METHOD.POST);
 
  277                 resultList = response.getResults();
 
  279                 for (SolrDocument resultDoc : resultList) {
 
  281                         List<KeywordHit> keywordHits = createKeywordHits(resultDoc);
 
  282                         for (KeywordHit hit : keywordHits) {
 
  283                             Keyword keywordInstance = 
new Keyword(hit.getHit(), 
true, 
true, originalKeyword.getListName(), originalKeyword.getOriginalTerm());
 
  284                             List<KeywordHit> hitsForKeyword = results.getResults(keywordInstance);
 
  285                             if (hitsForKeyword == null) {
 
  286                                 hitsForKeyword = 
new ArrayList<>();
 
  287                                 results.addResult(keywordInstance, hitsForKeyword);
 
  289                             hitsForKeyword.add(hit);
 
  291                     } 
catch (TskCoreException ex) {
 
  292                         LOGGER.log(Level.SEVERE, 
"Error creating keyword hits", ex); 
 
  296                 String nextCursorMark = response.getNextCursorMark();
 
  297                 if (cursorMark.equals(nextCursorMark)) {
 
  298                     allResultsProcessed = 
true;
 
  300                 cursorMark = nextCursorMark;
 
  301             } 
catch (KeywordSearchModuleException ex) {
 
  302                 LOGGER.log(Level.SEVERE, 
"Error executing Regex Solr Query: " + keywordString, ex); 
 
  303                 MessageNotifyUtil.Notify.error(NbBundle.getMessage(Server.class, 
"Server.query.exception.msg", keywordString), ex.getCause().getMessage());
 
  310     private List<KeywordHit> createKeywordHits(SolrDocument solrDoc) 
throws TskCoreException {
 
  312         final HashMap<String, String> keywordsFoundInThisDocument = 
new HashMap<>();
 
  314         List<KeywordHit> hits = 
new ArrayList<>();
 
  315         final String docId = solrDoc.getFieldValue(Server.Schema.ID.toString()).toString();
 
  316         final Integer chunkSize = (Integer) solrDoc.getFieldValue(Server.Schema.CHUNK_SIZE.toString());
 
  318         final Collection<Object> content_str = solrDoc.getFieldValues(Server.Schema.CONTENT_STR.toString());
 
  320         String searchPattern;
 
  321         if (originalKeyword.searchTermIsLiteral()) {
 
  335             searchPattern = 
"[\\w[\\.']]*" + Pattern.quote(keywordString.toLowerCase()) + 
"[\\w[\\.']]*";
 
  337             searchPattern = keywordString;
 
  340         final Pattern pattern = Pattern.compile(searchPattern, Pattern.CASE_INSENSITIVE);
 
  343             for (Object content_obj : content_str) {
 
  344                 String content = (String) content_obj;
 
  345                 Matcher hitMatcher = pattern.matcher(content);
 
  348                 while (hitMatcher.find(offset)) {
 
  353                     if (chunkSize != null && hitMatcher.start() >= chunkSize) {
 
  357                     String hit = hitMatcher.group();
 
  362                     if (
"".equals(hit)) {
 
  366                     offset = hitMatcher.end();
 
  367                     final ATTRIBUTE_TYPE artifactAttributeType = originalKeyword.getArtifactAttributeType();
 
  375                     if (!queryStringContainsWildcardSuffix
 
  376                             && (artifactAttributeType == ATTRIBUTE_TYPE.TSK_PHONE_NUMBER
 
  377                             || artifactAttributeType == ATTRIBUTE_TYPE.TSK_IP_ADDRESS)) {
 
  378                         if (artifactAttributeType == ATTRIBUTE_TYPE.TSK_PHONE_NUMBER) {
 
  380                             hit = hit.replaceAll(
"^[^0-9\\(]", 
"");
 
  383                             hit = hit.replaceAll(
"^[^0-9]", 
"");
 
  386                         hit = hit.replaceAll(
"[^0-9]$", 
"");
 
  397                     if (originalKeyword.searchTermIsLiteral()) {
 
  398                         hit = hit.replaceAll(
"^" + KeywordSearchList.BOUNDARY_CHARACTERS + 
"*", 
"");
 
  399                         hit = hit.replaceAll(KeywordSearchList.BOUNDARY_CHARACTERS + 
"*$", 
"");
 
  409                         if (hit.length() > 255) {
 
  426                     if (keywordsFoundInThisDocument.containsKey(hit)) {
 
  429                     keywordsFoundInThisDocument.put(hit, hit);
 
  431                     if (artifactAttributeType == null) {
 
  432                         hits.add(
new KeywordHit(docId, makeSnippet(content, hitMatcher, hit), hit));
 
  434                         switch (artifactAttributeType) {
 
  441                                 if (hit.length() >= MIN_EMAIL_ADDR_LENGTH
 
  442                                         && DomainValidator.getInstance(
true).isValidTld(hit.substring(hit.lastIndexOf(
'.')))) {
 
  443                                     hits.add(
new KeywordHit(docId, makeSnippet(content, hitMatcher, hit), hit));
 
  447                             case TSK_CARD_NUMBER:
 
  453                                 Matcher ccnMatcher = CREDIT_CARD_NUM_PATTERN.matcher(hit);
 
  455                                 for (
int rLength = hit.length(); rLength >= 12; rLength--) {
 
  456                                     ccnMatcher.region(0, rLength);
 
  457                                     if (ccnMatcher.find()) {
 
  458                                         final String group = ccnMatcher.group(
"ccn");
 
  459                                         if (CreditCardValidator.isValidCCN(group)) {
 
  460                                             hits.add(
new KeywordHit(docId, makeSnippet(content, hitMatcher, hit), hit));
 
  467                                 hits.add(
new KeywordHit(docId, makeSnippet(content, hitMatcher, hit), hit));
 
  473         } 
catch (Throwable error) {
 
  482             throw new TskCoreException(
"Failed to create keyword hits for Solr document id " + docId + 
" due to " + error.getMessage());
 
  500     private String makeSnippet(String content, Matcher hitMatcher, String hit) {
 
  502         int maxIndex = content.length() - 1;
 
  503         final int end = hitMatcher.end();
 
  504         final int start = hitMatcher.start();
 
  506         return content.substring(Integer.max(0, start - 20), Integer.max(0, start))
 
  507                 + SNIPPET_DELIMITER + hit + SNIPPET_DELIMITER
 
  508                 + content.substring(Integer.min(maxIndex, end), Integer.min(maxIndex, end + 20));
 
  512     public void addFilter(KeywordQueryFilter filter) {
 
  513         this.filters.add(filter);
 
  517     public void setField(String field) {
 
  522     public void setSubstringQuery() {
 
  526     synchronized public void escape() {
 
  527         if (isEscaped() == 
false) {
 
  528             escapedQuery = KeywordSearchUtil.escapeLuceneQuery(keywordString);
 
  534     synchronized public boolean isEscaped() {
 
  539     public boolean isLiteral() {
 
  544     public String getQueryString() {
 
  545         return originalKeyword.getSearchTerm();
 
  549     synchronized public String getEscapedQueryString() {
 
  550         if (
false == isEscaped()) {
 
  573     public BlackboardArtifact createKeywordHitArtifact(Content content, Keyword foundKeyword, KeywordHit hit, String snippet, String listName) {
 
  574         final String MODULE_NAME = KeywordSearchModuleFactory.getModuleName();
 
  576         if (content == null) {
 
  577             LOGGER.log(Level.WARNING, 
"Error adding artifact for keyword hit to blackboard"); 
 
  584         if (originalKeyword.getArtifactAttributeType() == ATTRIBUTE_TYPE.TSK_CARD_NUMBER) {
 
  585             createCCNAccount(content, foundKeyword, hit, snippet, listName);
 
  593         BlackboardArtifact newArtifact;
 
  594         Collection<BlackboardAttribute> attributes = 
new ArrayList<>();
 
  596         attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, foundKeyword.getSearchTerm()));
 
  597         attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_REGEXP, MODULE_NAME, getQueryString()));
 
  600             newArtifact = content.newArtifact(ARTIFACT_TYPE.TSK_KEYWORD_HIT);
 
  601         } 
catch (TskCoreException ex) {
 
  602             LOGGER.log(Level.SEVERE, 
"Error adding artifact for keyword hit to blackboard", ex); 
 
  606         if (StringUtils.isNotBlank(listName)) {
 
  607             attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, listName));
 
  609         if (snippet != null) {
 
  610             attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW, MODULE_NAME, snippet));
 
  613         hit.getArtifactID().ifPresent(artifactID
 
  614                 -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, artifactID))
 
  617         if (originalKeyword.searchTermIsLiteral()) {
 
  618             attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.SUBSTRING.ordinal()));
 
  620             attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.REGEX.ordinal()));
 
  624             newArtifact.addAttributes(attributes);
 
  626         } 
catch (TskCoreException e) {
 
  627             LOGGER.log(Level.SEVERE, 
"Error adding bb attributes for terms search artifact", e); 
 
  632     private void createCCNAccount(Content content, Keyword foundKeyword, KeywordHit hit, String snippet, String listName) {
 
  634         final String MODULE_NAME = KeywordSearchModuleFactory.getModuleName();
 
  636         if (originalKeyword.getArtifactAttributeType() != ATTRIBUTE_TYPE.TSK_CARD_NUMBER) {
 
  637             LOGGER.log(Level.SEVERE, 
"Keyword hit is not a credit card number"); 
 
  645         Collection<BlackboardAttribute> attributes = 
new ArrayList<>();
 
  647         Map<BlackboardAttribute.Type, BlackboardAttribute> parsedTrackAttributeMap = 
new HashMap<>();
 
  648         Matcher matcher = CREDIT_CARD_TRACK1_PATTERN.matcher(hit.getSnippet());
 
  649         if (matcher.find()) {
 
  650             parseTrack1Data(parsedTrackAttributeMap, matcher);
 
  652         matcher = CREDIT_CARD_TRACK2_PATTERN.matcher(hit.getSnippet());
 
  653         if (matcher.find()) {
 
  654             parseTrack2Data(parsedTrackAttributeMap, matcher);
 
  656         final BlackboardAttribute ccnAttribute = parsedTrackAttributeMap.get(
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_CARD_NUMBER));
 
  657         if (ccnAttribute == null || StringUtils.isBlank(ccnAttribute.getValueString())) {
 
  659             if (hit.isArtifactHit()) {
 
  660                 LOGGER.log(Level.SEVERE, String.format(
"Failed to parse credit card account number for artifact keyword hit: term = %s, snippet = '%s', artifact id = %d", foundKeyword.getSearchTerm(), hit.getSnippet(), hit.getArtifactID().get())); 
 
  663                     LOGGER.log(Level.SEVERE, String.format(
"Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s', object id = %d", foundKeyword.getSearchTerm(), hit.getSnippet(), hit.getContentID())); 
 
  664                 } 
catch (TskCoreException ex) {
 
  665                     LOGGER.log(Level.SEVERE, String.format(
"Failed to parse credit card account number for content keyword hit: term = %s, snippet = '%s' ", foundKeyword.getSearchTerm(), hit.getSnippet())); 
 
  666                     LOGGER.log(Level.SEVERE, 
"There was a error getting contentID for keyword hit.", ex); 
 
  671         attributes.addAll(parsedTrackAttributeMap.values());
 
  677         final int bin = Integer.parseInt(ccnAttribute.getValueString().substring(0, 8));
 
  678         CreditCards.BankIdentificationNumber binInfo = CreditCards.getBINInfo(bin);
 
  679         if (binInfo != null) {
 
  680             binInfo.getScheme().ifPresent(scheme
 
  681                     -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CARD_SCHEME, MODULE_NAME, scheme)));
 
  682             binInfo.getCardType().ifPresent(cardType
 
  683                     -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CARD_TYPE, MODULE_NAME, cardType)));
 
  684             binInfo.getBrand().ifPresent(brand
 
  685                     -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_BRAND_NAME, MODULE_NAME, brand)));
 
  686             binInfo.getBankName().ifPresent(bankName
 
  687                     -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_BANK_NAME, MODULE_NAME, bankName)));
 
  688             binInfo.getBankPhoneNumber().ifPresent(phoneNumber
 
  689                     -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER, MODULE_NAME, phoneNumber)));
 
  690             binInfo.getBankURL().ifPresent(url
 
  691                     -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL, MODULE_NAME, url)));
 
  692             binInfo.getCountry().ifPresent(country
 
  693                     -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNTRY, MODULE_NAME, country)));
 
  694             binInfo.getBankCity().ifPresent(city
 
  695                     -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_CITY, MODULE_NAME, city)));
 
  702         if (content instanceof AbstractFile) {
 
  703             AbstractFile file = (AbstractFile) content;
 
  704             if (file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS
 
  705                     || file.getType() == TskData.TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS) {
 
  706                 attributes.add(
new BlackboardAttribute(KEYWORD_SEARCH_DOCUMENT_ID, MODULE_NAME, hit.getSolrDocumentId()));
 
  710         if (StringUtils.isNotBlank(listName)) {
 
  711             attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, listName));
 
  713         if (snippet != null) {
 
  714             attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_PREVIEW, MODULE_NAME, snippet));
 
  717         hit.getArtifactID().ifPresent(artifactID
 
  718                 -> attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT, MODULE_NAME, artifactID))
 
  721         attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_TYPE, MODULE_NAME, KeywordSearch.QueryType.REGEX.ordinal()));
 
  727             AccountFileInstance ccAccountInstance = Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.CREDIT_CARD, ccnAttribute.getValueString(), MODULE_NAME, content);
 
  729             ccAccountInstance.addAttributes(attributes);
 
  731         } 
catch (TskCoreException | NoCurrentCaseException ex) {
 
  732             LOGGER.log(Level.SEVERE, 
"Error creating CCN account instance", ex); 
 
  746     static private void parseTrack2Data(Map<BlackboardAttribute.Type, BlackboardAttribute> attributesMap, Matcher matcher) {
 
  747         addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_NUMBER, 
"accountNumber", matcher);
 
  748         addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_EXPIRATION, 
"expiration", matcher);
 
  749         addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_SERVICE_CODE, 
"serviceCode", matcher);
 
  750         addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_DISCRETIONARY, 
"discretionary", matcher);
 
  751         addAttributeIfNotAlreadyCaptured(attributesMap, ATTRIBUTE_TYPE.TSK_CARD_LRC, 
"LRC", matcher);
 
  763     static private void parseTrack1Data(Map<BlackboardAttribute.Type, BlackboardAttribute> attributeMap, Matcher matcher) {
 
  764         parseTrack2Data(attributeMap, matcher);
 
  765         addAttributeIfNotAlreadyCaptured(attributeMap, ATTRIBUTE_TYPE.TSK_NAME_PERSON, 
"name", matcher);
 
  780     static private void addAttributeIfNotAlreadyCaptured(Map<BlackboardAttribute.Type, BlackboardAttribute> attributeMap, ATTRIBUTE_TYPE attrType, String groupName, Matcher matcher) {
 
  781         BlackboardAttribute.Type type = 
new BlackboardAttribute.Type(attrType);
 
  783         if (!attributeMap.containsKey(type)) {
 
  784             String value = matcher.group(groupName);
 
  785             if (attrType.equals(ATTRIBUTE_TYPE.TSK_CARD_NUMBER)) {
 
  786                 attributeMap.put(
new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_KEYWORD),
 
  787                         new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_KEYWORD, MODULE_NAME, value));
 
  788                 value = CharMatcher.anyOf(
" -").removeFrom(value);
 
  791             if (StringUtils.isNotBlank(value)) {
 
  792                 attributeMap.put(type, 
new BlackboardAttribute(attrType, MODULE_NAME, value));