19 package org.sleuthkit.autopsy.keywordsearch;
21 import com.google.common.collect.Iterators;
22 import java.util.Arrays;
23 import java.util.Collection;
24 import java.util.HashMap;
25 import java.util.HashSet;
26 import java.util.Optional;
28 import java.util.TreeMap;
29 import java.util.logging.Level;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32 import javax.annotation.concurrent.GuardedBy;
33 import org.apache.commons.lang3.StringUtils;
34 import org.apache.solr.client.solrj.SolrQuery;
35 import org.apache.solr.client.solrj.SolrRequest.METHOD;
36 import org.apache.solr.client.solrj.response.QueryResponse;
37 import org.openide.util.NbBundle;
53 class AccountsText
implements IndexedText {
58 private static final String CCN_REGEX =
"(%?)(B?)([0-9][ \\-]*?){12,19}(\\^?)";
60 private static final String HIGHLIGHT_PRE =
"<span style='background:yellow'>";
61 private static final String ANCHOR_NAME_PREFIX = AccountsText.class.getName() +
"_";
63 private static final String INSERT_PREFIX =
"<a name='" + ANCHOR_NAME_PREFIX;
64 private static final String INSERT_POSTFIX =
"'></a>$0";
65 private static final Pattern ANCHOR_DETECTION_PATTERN = Pattern.compile(HIGHLIGHT_PRE);
67 private static final BlackboardAttribute.Type TSK_KEYWORD_SEARCH_DOCUMENT_ID =
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD_SEARCH_DOCUMENT_ID);
68 private static final BlackboardAttribute.Type TSK_CARD_NUMBER =
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_CARD_NUMBER);
69 private static final BlackboardAttribute.Type TSK_KEYWORD =
new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_KEYWORD);
71 private static final String FIELD =
Server.
Schema.CONTENT_STR.toString();
75 private final long solrObjectId;
76 private final Collection<? extends BlackboardArtifact> artifacts;
77 private final Set<String> accountNumbers =
new HashSet<>();
78 private final String title;
81 private boolean isPageInfoLoaded =
false;
82 private int numberPagesForFile = 0;
83 private Integer currentPage = 0;
88 private final TreeMap<Integer, Integer> numberOfHitsPerPage =
new TreeMap<>();
94 private final Set<Integer> pages = numberOfHitsPerPage.keySet();
99 private final HashMap<Integer, Integer> currentHitPerPage =
new HashMap<>();
101 AccountsText(
long objectID, BlackboardArtifact artifact) {
102 this(objectID, Arrays.asList(artifact));
106 "AccountsText.creditCardNumber=Credit Card Number",
107 "AccountsText.creditCardNumbers=Credit Card Numbers"})
108 AccountsText(
long objectID, Collection<? extends BlackboardArtifact> artifacts) {
109 this.solrObjectId = objectID;
110 this.artifacts = artifacts;
111 title = artifacts.size() == 1
112 ? Bundle.AccountsText_creditCardNumber()
113 : Bundle.AccountsText_creditCardNumbers();
117 return this.solrObjectId;
121 public int getNumberPages() {
122 return this.numberPagesForFile;
126 public int getCurrentPage() {
127 return this.currentPage;
131 public boolean hasNextPage() {
132 return getIndexOfCurrentPage() < pages.size() - 1;
137 public boolean hasPreviousPage() {
138 return getIndexOfCurrentPage() > 0;
142 @NbBundle.Messages(
"AccountsText.nextPage.exception.msg=No next page.")
143 public int nextPage() {
145 currentPage = Iterators.get(pages.iterator(), getIndexOfCurrentPage() + 1);
148 throw new IllegalStateException(Bundle.AccountsText_nextPage_exception_msg());
153 @NbBundle.Messages(
"AccountsText.previousPage.exception.msg=No previous page.")
154 public int previousPage() {
155 if (hasPreviousPage()) {
156 currentPage = Iterators.get(pages.iterator(), getIndexOfCurrentPage() - 1);
159 throw new IllegalStateException(Bundle.AccountsText_previousPage_exception_msg());
163 private int getIndexOfCurrentPage() {
164 return Iterators.indexOf(pages.iterator(), this.currentPage::equals);
168 public boolean hasNextItem() {
169 if (this.currentHitPerPage.containsKey(currentPage)) {
170 return this.currentHitPerPage.get(currentPage) < this.numberOfHitsPerPage.get(currentPage);
177 public boolean hasPreviousItem() {
178 if (this.currentHitPerPage.containsKey(currentPage)) {
179 return this.currentHitPerPage.get(currentPage) > 1;
186 @NbBundle.Messages(
"AccountsText.nextItem.exception.msg=No next item.")
187 public int nextItem() {
189 return currentHitPerPage.merge(currentPage, 1, Integer::sum);
191 throw new IllegalStateException(Bundle.AccountsText_nextItem_exception_msg());
196 @NbBundle.Messages(
"AccountsText.previousItem.exception.msg=No previous item.")
197 public int previousItem() {
198 if (hasPreviousItem()) {
199 return currentHitPerPage.merge(currentPage, -1, Integer::sum);
201 throw new IllegalStateException(Bundle.AccountsText_previousItem_exception_msg());
206 public int currentItem() {
207 return currentHitPerPage.getOrDefault(currentPage, 0);
215 if (isPageInfoLoaded) {
221 boolean needsQuery =
false;
223 for (BlackboardArtifact artifact : artifacts) {
224 if (solrObjectId != artifact.getObjectID()) {
225 throw new IllegalStateException(
"not all artifacts are from the same object!");
229 BlackboardAttribute attribute = artifact.getAttribute(TSK_KEYWORD);
230 this.accountNumbers.add(attribute.getValueString());
231 attribute = artifact.getAttribute(TSK_CARD_NUMBER);
232 this.accountNumbers.add(attribute.getValueString());
235 Optional<Integer> chunkID =
236 Optional.ofNullable(artifact.getAttribute(TSK_KEYWORD_SEARCH_DOCUMENT_ID))
237 .map(BlackboardAttribute::getValueString)
240 .map(Integer::valueOf);
241 if (chunkID.isPresent()) {
242 numberOfHitsPerPage.put(chunkID.get(), 0);
243 currentHitPerPage.put(chunkID.get(), 0);
253 Keyword queryKeyword =
new Keyword(CCN_REGEX,
false,
false);
254 KeywordSearchQuery chunksQuery = KeywordSearchUtil.getQueryForKeyword(queryKeyword,
new KeywordList(Arrays.asList(queryKeyword)));
255 chunksQuery.addFilter(
new KeywordQueryFilter(KeywordQueryFilter.FilterType.CHUNK,
this.solrObjectId));
257 loadPageInfoFromHits(chunksQuery.performQuery());
260 this.currentPage = pages.stream().findFirst().orElse(1);
262 isPageInfoLoaded =
true;
270 synchronized private void loadPageInfoFromHits(QueryResults hits) {
272 for (Keyword k : hits.getKeywords()) {
273 for (KeywordHit hit : hits.getResults(k)) {
274 int chunkID = hit.getChunkId();
275 if (chunkID != 0 && this.solrObjectId == hit.getSolrObjectId()) {
276 String hitString = hit.getHit();
277 if (accountNumbers.stream().anyMatch(hitString::contains)) {
278 numberOfHitsPerPage.put(chunkID, 0);
279 currentHitPerPage.put(chunkID, 0);
287 public String getText() {
291 SolrQuery q =
new SolrQuery();
292 q.setShowDebugInfo(DEBUG);
295 final String filterQuery =
Server.
Schema.ID.toString() +
":" + contentIdStr;
297 q.setQuery(filterQuery);
300 QueryResponse queryResponse = solrServer.
query(q, METHOD.POST);
302 String highlightedText =
303 HighlightedText.attemptManualHighlighting(
304 queryResponse.getResults(),
309 highlightedText = insertAnchors(highlightedText);
312 return "<html><pre>" + highlightedText +
"</pre></html>";
313 }
catch (Exception ex) {
314 logger.log(Level.SEVERE,
"Error getting highlighted text for Solr doc id " +
this.solrObjectId +
", chunkID " +
this.currentPage, ex);
315 return Bundle.IndexedText_errorMessage_errorGettingText();
327 private String insertAnchors(String searchableContent) {
332 Matcher m = ANCHOR_DETECTION_PATTERN.matcher(searchableContent);
333 StringBuffer sb =
new StringBuffer(searchableContent.length());
337 m.appendReplacement(sb, INSERT_PREFIX + count + INSERT_POSTFIX);
341 this.numberOfHitsPerPage.put(this.currentPage, count);
342 if (this.currentItem() == 0 && this.hasNextItem()) {
345 return sb.toString();
349 public String toString() {
354 public boolean isSearchable() {
359 public String getAnchorPrefix() {
360 return ANCHOR_NAME_PREFIX;
364 public int getNumberHits() {
365 return numberOfHitsPerPage.getOrDefault(currentPage, 0);
static Version.Type getBuildType()
static synchronized Server getServer()
static final String CHUNK_ID_SEPARATOR
QueryResponse query(SolrQuery sq)
synchronized static Logger getLogger(String name)
int queryNumFileChunks(long fileID)