19 package org.sleuthkit.autopsy.contentviewers;
21 import java.awt.Component;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.List;
25 import java.util.function.Function;
26 import java.util.logging.Level;
27 import java.util.stream.Collectors;
28 import javax.swing.JLabel;
29 import javax.swing.text.EditorKit;
30 import javax.swing.text.html.HTMLEditorKit;
31 import org.apache.commons.lang.StringUtils;
32 import org.apache.commons.lang3.tuple.Pair;
34 import static org.openide.util.NbBundle.Messages;
35 import static org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
37 import org.openide.nodes.Node;
38 import org.openide.util.lookup.ServiceProvider;
56 import org.jsoup.Jsoup;
57 import org.jsoup.nodes.Document;
58 import org.jsoup.nodes.Element;
63 @SuppressWarnings(
"PMD.SingularField")
66 "AnnotationsContentViewer.title=Annotations",
67 "AnnotationsContentViewer.toolTip=Displays tags and comments associated with the selected content.",
68 "AnnotationsContentViewer.centralRepositoryEntry.title=Central Repository Comments",
69 "AnnotationsContentViewer.centralRepositoryEntryDataLabel.case=Case:",
70 "AnnotationsContentViewer.centralRepositoryEntryDataLabel.type=Type:",
71 "AnnotationsContentViewer.centralRepositoryEntryDataLabel.comment=Comment:",
72 "AnnotationsContentViewer.centralRepositoryEntryDataLabel.path=Path:",
73 "AnnotationsContentViewer.tagEntry.title=Tags",
74 "AnnotationsContentViewer.tagEntryDataLabel.tag=Tag:",
75 "AnnotationsContentViewer.tagEntryDataLabel.tagUser=Examiner:",
76 "AnnotationsContentViewer.tagEntryDataLabel.comment=Comment:",
77 "AnnotationsContentViewer.fileHitEntry.artifactCommentTitle=Artifact Comment",
78 "AnnotationsContentViewer.fileHitEntry.hashSetHitTitle=Hash Set Hit Comments",
79 "AnnotationsContentViewer.fileHitEntry.interestingFileHitTitle=Interesting File Hit Comments",
80 "AnnotationsContentViewer.fileHitEntry.setName=Set Name:",
81 "AnnotationsContentViewer.fileHitEntry.comment=Comment:",
82 "AnnotationsContentViewer.sourceFile.title=Source File",
83 "AnnotationsContentViewer.onEmpty=No annotations were found for this particular item."
96 private final String itemName;
97 private final Function<T, String> valueRetriever;
99 ItemEntry(String itemName, Function<T, String> valueRetriever) {
100 this.itemName = itemName;
101 this.valueRetriever = valueRetriever;
104 String getItemName() {
108 Function<T, String> getValueRetriever() {
109 return valueRetriever;
112 String retrieveValue(T
object) {
113 return valueRetriever.apply(
object);
129 this.attributes = attributes;
143 List<ItemEntry<T>> getAttributes() {
150 private static final String EMPTY_HTML =
"<html><head></head><body></body></html>";
152 private static final int DEFAULT_FONT_SIZE =
new JLabel().getFont().getSize();
155 private static final int SUBHEADER_FONT_SIZE = DEFAULT_FONT_SIZE * 12 / 11;
158 private static final int HEADER_FONT_SIZE = DEFAULT_FONT_SIZE * 14 / 11;
161 private static final int DEFAULT_SUBSECTION_LEFT_PAD = DEFAULT_FONT_SIZE;
164 private static final int DEFAULT_TABLE_SPACING = DEFAULT_FONT_SIZE;
165 private static final int DEFAULT_SECTION_SPACING = DEFAULT_FONT_SIZE * 2;
166 private static final int DEFAULT_SUBSECTION_SPACING = DEFAULT_FONT_SIZE / 2;
167 private static final int CELL_SPACING = DEFAULT_FONT_SIZE / 2;
170 private static final String MESSAGE_CLASSNAME =
"message";
171 private static final String SUBSECTION_CLASSNAME =
"subsection";
172 private static final String SUBHEADER_CLASSNAME =
"subheader";
173 private static final String SECTION_CLASSNAME =
"section";
174 private static final String HEADER_CLASSNAME =
"header";
175 private static final String VERTICAL_TABLE_CLASSNAME =
"vertical-table";
178 private static final String STYLE_SHEET_RULE
179 = String.format(
" .%s { font-size: %dpx;font-style:italic; margin: 0px; padding: 0px; } ", MESSAGE_CLASSNAME, DEFAULT_FONT_SIZE)
180 + String.format(
" .%s {font-size:%dpx;font-weight:bold; margin: 0px; margin-top: %dpx; padding: 0px; } ",
181 SUBHEADER_CLASSNAME, SUBHEADER_FONT_SIZE, DEFAULT_SUBSECTION_SPACING)
182 + String.format(
" .%s { font-size:%dpx;font-weight:bold; margin: 0px; padding: 0px; } ", HEADER_CLASSNAME, HEADER_FONT_SIZE)
183 + String.format(
" td { vertical-align: top; font-size:%dpx; text-align: left; margin: 0px; padding: 0px %dpx 0px 0px;} ", DEFAULT_FONT_SIZE, CELL_SPACING)
184 + String.format(
" th { vertical-align: top; text-align: left; margin: 0px; padding: 0px %dpx 0px 0px} ", DEFAULT_FONT_SIZE, CELL_SPACING)
185 + String.format(
" .%s { margin: %dpx 0px; padding-left: %dpx; } ", SUBSECTION_CLASSNAME, DEFAULT_SUBSECTION_SPACING, DEFAULT_SUBSECTION_LEFT_PAD)
186 + String.format(
" .%s { margin-bottom: %dpx; } ", SECTION_CLASSNAME, DEFAULT_SECTION_SPACING);
189 private static final List<ItemEntry<Tag>> TAG_ENTRIES = Arrays.asList(
190 new ItemEntry<>(Bundle.AnnotationsContentViewer_tagEntryDataLabel_tag(),
191 (tag) -> (tag.getName() != null) ? tag.getName().getDisplayName() : null),
192 new ItemEntry<>(Bundle.AnnotationsContentViewer_tagEntryDataLabel_tagUser(), (tag) -> tag.getUserName()),
193 new ItemEntry<>(Bundle.AnnotationsContentViewer_tagEntryDataLabel_comment(), (tag) -> tag.getComment())
200 private static final List<ItemEntry<BlackboardArtifact>> FILESET_HIT_ENTRIES = Arrays.asList(
201 new ItemEntry<>(Bundle.AnnotationsContentViewer_fileHitEntry_setName(),
202 (bba) -> tryGetAttribute(bba, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME)),
203 new ItemEntry<>(Bundle.AnnotationsContentViewer_fileHitEntry_comment(),
204 (bba) -> tryGetAttribute(bba, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT))
208 =
new SectionConfig<>(Bundle.AnnotationsContentViewer_fileHitEntry_interestingFileHitTitle(), FILESET_HIT_ENTRIES);
211 =
new SectionConfig<>(Bundle.AnnotationsContentViewer_fileHitEntry_hashSetHitTitle(), FILESET_HIT_ENTRIES);
214 =
new SectionConfig<>(Bundle.AnnotationsContentViewer_fileHitEntry_artifactCommentTitle(), FILESET_HIT_ENTRIES);
217 private static final List<ItemEntry<CorrelationAttributeInstance>> CR_COMMENTS_ENTRIES = Arrays.asList(
218 new ItemEntry<>(Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_case(),
219 cai -> (cai.getCorrelationCase() != null) ? cai.getCorrelationCase().getDisplayName() : null),
220 new ItemEntry<>(Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_comment(), cai -> cai.getComment()),
221 new ItemEntry<>(Bundle.AnnotationsContentViewer_centralRepositoryEntryDataLabel_path(), cai -> cai.getFilePath())
225 =
new SectionConfig<>(Bundle.AnnotationsContentViewer_centralRepositoryEntry_title(), CR_COMMENTS_ENTRIES);
234 EditorKit editorKit = jTextPane1.getEditorKit();
235 if (editorKit instanceof HTMLEditorKit) {
236 HTMLEditorKit htmlKit = (HTMLEditorKit) editorKit;
237 htmlKit.getStyleSheet().addRule(STYLE_SHEET_RULE);
243 if ((node == null) || (!isSupported(node))) {
248 Document html = Jsoup.parse(EMPTY_HTML);
249 Element body = html.getElementsByTag(
"body").first();
251 BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class);
252 Content sourceFile = null;
255 if (artifact != null) {
260 sourceFile = artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID());
267 sourceFile = node.getLookup().lookup(AbstractFile.class);
269 }
catch (TskCoreException ex) {
270 logger.log(Level.SEVERE, String.format(
271 "Exception while trying to retrieve a Content instance from the BlackboardArtifact '%s' (id=%d).",
272 artifact.getDisplayName(), artifact.getArtifactID()), ex);
275 boolean somethingWasRendered =
false;
276 if (artifact != null) {
277 somethingWasRendered = renderArtifact(body, artifact, sourceFile);
279 somethingWasRendered = renderContent(body, sourceFile,
false);
282 if (!somethingWasRendered) {
283 appendMessage(body, Bundle.AnnotationsContentViewer_onEmpty());
286 jTextPane1.setText(html.html());
287 jTextPane1.setCaretPosition(0);
300 private static boolean renderArtifact(Element parent, BlackboardArtifact bba, Content sourceContent) {
301 boolean contentRendered = appendEntries(parent, TAG_CONFIG, getTags(bba),
false);
304 List<CorrelationAttributeInstance> centralRepoComments = getCentralRepositoryData(bba);
305 boolean crRendered = appendEntries(parent, CR_COMMENTS_CONFIG, centralRepoComments,
false);
306 contentRendered = contentRendered || crRendered;
310 if ((ARTIFACT_TYPE.TSK_HASHSET_HIT.getTypeID() == bba.getArtifactTypeID()
311 || ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT.getTypeID() == bba.getArtifactTypeID())
312 && (hasTskComment(bba))) {
314 boolean filesetRendered = appendEntries(parent, ARTIFACT_COMMENT_CONFIG, Arrays.asList(bba),
false);
315 contentRendered = contentRendered || filesetRendered;
318 Element sourceFileSection = appendSection(parent, Bundle.AnnotationsContentViewer_sourceFile_title());
319 boolean sourceFileRendered = renderContent(sourceFileSection, sourceContent,
true);
321 if (!sourceFileRendered) {
322 sourceFileSection.remove();
325 return contentRendered || sourceFileRendered;
338 private static boolean renderContent(Element parent, Content sourceContent,
boolean isSubheader) {
339 boolean contentRendered = appendEntries(parent, TAG_CONFIG, getTags(sourceContent), isSubheader);
341 if (sourceContent instanceof AbstractFile) {
342 AbstractFile sourceFile = (AbstractFile) sourceContent;
345 List<CorrelationAttributeInstance> centralRepoComments = getCentralRepositoryData(sourceFile);
346 boolean crRendered = appendEntries(parent, CR_COMMENTS_CONFIG, centralRepoComments, isSubheader);
347 contentRendered = contentRendered || crRendered;
350 boolean hashsetRendered = appendEntries(parent, HASHSET_CONFIG,
351 getFileSetHits(sourceFile, ARTIFACT_TYPE.TSK_HASHSET_HIT),
354 boolean interestingFileRendered = appendEntries(parent, INTERESTING_FILE_CONFIG,
355 getFileSetHits(sourceFile, ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT),
358 contentRendered = contentRendered || hashsetRendered || interestingFileRendered;
360 return contentRendered;
370 private static List<ContentTag>
getTags(Content sourceContent) {
373 return tskCase.getContentTagsByContent(sourceContent);
375 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
376 }
catch (TskCoreException ex) {
377 logger.log(Level.SEVERE,
"Exception while getting tags from the case database.", ex);
379 return new ArrayList<>();
389 private static List<BlackboardArtifactTag>
getTags(BlackboardArtifact bba) {
392 return tskCase.getBlackboardArtifactTagsByArtifact(bba);
394 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
395 }
catch (TskCoreException ex) {
396 logger.log(Level.SEVERE,
"Exception while getting tags from the case database.", ex);
398 return new ArrayList<>();
410 private static List<BlackboardArtifact>
getFileSetHits(AbstractFile sourceFile, ARTIFACT_TYPE type) {
413 return tskCase.getBlackboardArtifacts(type, sourceFile.getId()).stream()
414 .filter((bba) -> hasTskComment(bba))
415 .collect(Collectors.toList());
417 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
418 }
catch (TskCoreException ex) {
419 logger.log(Level.SEVERE,
"Exception while getting file set hits from the case database.", ex);
421 return new ArrayList<>();
432 return StringUtils.isNotBlank(tryGetAttribute(artifact, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT));
444 private static String
tryGetAttribute(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType) {
445 if (artifact == null) {
449 BlackboardAttribute attr = null;
451 attr = artifact.getAttribute(
new BlackboardAttribute.Type(attributeType));
452 }
catch (TskCoreException ex) {
453 logger.log(Level.WARNING, String.format(
"Unable to fetch attribute of type %s for artifact %s", attributeType, artifact), ex);
460 return attr.getValueString();
473 if (artifact == null) {
474 return new ArrayList<>();
479 .map(cai -> Pair.of(cai.getCorrelationType(), cai.getCorrelationValue()))
480 .collect(Collectors.toList());
482 return getCorrelationAttributeComments(lookupKeys);
495 if (sourceFile == null || StringUtils.isEmpty(sourceFile.getMd5Hash())) {
496 return new ArrayList<>();
503 logger.log(Level.SEVERE,
"Error connecting to the Central Repository database.", ex);
506 if (artifactTypes == null || artifactTypes.isEmpty()) {
507 return new ArrayList<>();
510 String md5 = sourceFile.getMd5Hash();
515 .map((attributeType) -> Pair.of(attributeType, md5))
516 .collect(Collectors.toList());
518 return getCorrelationAttributeComments(lookupKeys);
530 List<CorrelationAttributeInstance> instancesToRet =
new ArrayList<>();
534 for (Pair<CorrelationAttributeInstance.Type, String> typeVal : lookupKeys) {
539 .filter((cai) -> StringUtils.isNotBlank(cai.getComment()))
540 .collect(Collectors.toList()));
544 logger.log(Level.SEVERE,
"Error connecting to the Central Repository database.", ex);
546 logger.log(Level.SEVERE,
"Error normalizing instance from Central Repository database.", ex);
549 return instancesToRet;
568 boolean isSubsection) {
569 if (items == null || items.isEmpty()) {
573 Element sectionDiv = (isSubsection) ? appendSubsection(parent, config.getTitle()) : appendSection(parent, config.getTitle());
574 appendVerticalEntryTables(sectionDiv, items, config.getAttributes());
589 boolean isFirst =
true;
590 for (T item : items) {
595 List<List<String>> tableData = rowHeaders.stream()
596 .map(row -> Arrays.asList(row.getItemName(), row.retrieveValue(item)))
597 .collect(Collectors.toList());
599 Element childTable = appendTable(parent, 2, tableData, null);
600 childTable.attr(
"class", VERTICAL_TABLE_CLASSNAME);
605 childTable.attr(
"style", String.format(
"margin-top: %dpx;", DEFAULT_TABLE_SPACING));
624 private static Element
appendTable(Element parent,
int columnNumber, List<List<String>> content, List<String> columnHeaders) {
625 Element table = parent.appendElement(
"table");
626 if (columnHeaders != null && !columnHeaders.isEmpty()) {
627 Element header = table.appendElement(
"thead");
628 appendRow(header, columnHeaders, columnNumber,
true);
630 Element tableBody = table.appendElement(
"tbody");
632 content.forEach((rowData) -> appendRow(tableBody, rowData, columnNumber,
false));
647 private static Element
appendRow(Element rowParent, List<String> data,
int columnNumber,
boolean isHeader) {
648 String cellType = isHeader ?
"th" :
"td";
649 Element row = rowParent.appendElement(
"tr");
650 for (
int i = 0; i < columnNumber; i++) {
651 Element cell = row.appendElement(cellType);
652 if (data != null && i < data.size()) {
653 cell.text(StringUtils.isEmpty(data.get(i)) ?
"" : data.get(i));
668 Element sectionDiv = parent.appendElement(
"div");
669 sectionDiv.attr(
"class", SECTION_CLASSNAME);
670 Element header = sectionDiv.appendElement(
"h1");
671 header.text(headerText);
672 header.attr(
"class", HEADER_CLASSNAME);
685 Element subsectionDiv = parent.appendElement(
"div");
686 subsectionDiv.attr(
"class", SUBSECTION_CLASSNAME);
687 Element header = subsectionDiv.appendElement(
"h2");
688 header.text(headerText);
689 header.attr(
"class", SUBHEADER_CLASSNAME);
690 return subsectionDiv;
704 Element messageEl = parent.appendElement(
"p");
705 messageEl.text(message);
706 messageEl.attr(
"class", MESSAGE_CLASSNAME);
715 @SuppressWarnings(
"unchecked")
717 private
void initComponents() {
719 jScrollPane5 =
new javax.swing.JScrollPane();
720 jTextPane1 =
new javax.swing.JTextPane();
722 setPreferredSize(
new java.awt.Dimension(100, 58));
724 jTextPane1.setEditable(
false);
725 jTextPane1.setName(
"");
726 jTextPane1.setPreferredSize(
new java.awt.Dimension(600, 52));
727 jScrollPane5.setViewportView(jTextPane1);
729 javax.swing.GroupLayout layout =
new javax.swing.GroupLayout(
this);
730 this.setLayout(layout);
731 layout.setHorizontalGroup(
732 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
733 .addComponent(jScrollPane5, javax.swing.GroupLayout.DEFAULT_SIZE, 907, Short.MAX_VALUE)
735 layout.setVerticalGroup(
736 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
737 .addComponent(jScrollPane5, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 435, Short.MAX_VALUE)
748 return Bundle.AnnotationsContentViewer_title();
753 return Bundle.AnnotationsContentViewer_toolTip();
763 BlackboardArtifact artifact = node.getLookup().lookup(BlackboardArtifact.class);
766 if (artifact != null) {
767 if (artifact.getSleuthkitCase().getAbstractFileById(artifact.getObjectID()) != null) {
771 if (node.getLookup().lookup(AbstractFile.class) != null) {
775 }
catch (TskCoreException ex) {
776 logger.log(Level.SEVERE, String.format(
777 "Exception while trying to retrieve a Content instance from the BlackboardArtifact '%s' (id=%d).",
778 artifact.getDisplayName(), artifact.getArtifactID()), ex);
796 jTextPane1.setText(EMPTY_HTML);
static boolean renderArtifact(Element parent, BlackboardArtifact bba, Content sourceContent)
static< T > Element appendVerticalEntryTables(Element parent, List<?extends T > items, List< ItemEntry< T >> rowHeaders)
static boolean renderContent(Element parent, Content sourceContent, boolean isSubheader)
static List< CorrelationAttributeInstance > getCentralRepositoryData(AbstractFile sourceFile)
static Element appendRow(Element rowParent, List< String > data, int columnNumber, boolean isHeader)
static Element appendMessage(Element parent, String message)
static List< BlackboardArtifactTag > getTags(BlackboardArtifact bba)
List< CorrelationAttributeInstance > getArtifactInstancesByTypeValue(CorrelationAttributeInstance.Type aType, String value)
final List< ItemEntry< T > > attributes
static List< BlackboardArtifact > getFileSetHits(AbstractFile sourceFile, ARTIFACT_TYPE type)
static Element appendSubsection(Element parent, String headerText)
List< CorrelationAttributeInstance.Type > getDefinedCorrelationTypes()
static List< CorrelationAttributeInstance > makeCorrAttrsForCorrelation(BlackboardArtifact artifact)
static Element appendTable(Element parent, int columnNumber, List< List< String >> content, List< String > columnHeaders)
static void configureTextPaneAsHtml(JTextPane pane)
DataContentViewer createInstance()
javax.swing.JTextPane jTextPane1
AnnotationsContentViewer()
SleuthkitCase getSleuthkitCase()
boolean isSupported(Node node)
static String tryGetAttribute(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE attributeType)
static List< CorrelationAttributeInstance > getCorrelationAttributeComments(List< Pair< CorrelationAttributeInstance.Type, String >> lookupKeys)
static< T > boolean appendEntries(Element parent, SectionConfig< T > config, List<?extends T > items, boolean isSubsection)
synchronized static Logger getLogger(String name)
static Case getCurrentCaseThrows()
static boolean hasTskComment(BlackboardArtifact artifact)
static List< CorrelationAttributeInstance > getCentralRepositoryData(BlackboardArtifact artifact)
javax.swing.JScrollPane jScrollPane5
static List< ContentTag > getTags(Content sourceContent)
static CentralRepository getInstance()
int isPreferred(Node node)
static final int FILES_TYPE_ID
static boolean isEnabled()
static Element appendSection(Element parent, String headerText)