23 package org.sleuthkit.autopsy.report;
25 import java.awt.image.BufferedImage;
26 import java.io.BufferedWriter;
28 import java.io.FileNotFoundException;
29 import java.io.FileOutputStream;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.OutputStream;
33 import java.io.OutputStreamWriter;
34 import java.io.UnsupportedEncodingException;
35 import java.io.Writer;
36 import java.nio.file.Files;
37 import java.nio.file.Path;
38 import java.nio.file.Paths;
39 import java.text.DateFormat;
40 import java.text.SimpleDateFormat;
41 import java.util.ArrayList;
42 import java.util.Date;
43 import java.util.HashMap;
44 import java.util.List;
47 import java.util.TreeMap;
48 import java.util.concurrent.ExecutionException;
49 import java.util.logging.Level;
50 import javax.imageio.ImageIO;
51 import javax.swing.JPanel;
52 import org.apache.commons.io.FilenameUtils;
53 import org.apache.commons.lang3.StringEscapeUtils;
54 import org.openide.filesystems.FileUtil;
55 import org.openide.util.NbBundle;
56 import org.openide.util.NbBundle.Messages;
73 import org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
82 import org.
sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
84 class ReportHTML
implements TableReportModule {
86 private static final Logger logger = Logger.getLogger(ReportHTML.class.getName());
87 private static final String THUMBS_REL_PATH =
"thumbs" + File.separator;
88 private static ReportHTML instance;
89 private static final int MAX_THUMBS_PER_PAGE = 1000;
90 private static final String HTML_SUBDIR =
"content";
91 private Case currentCase;
92 static Integer THUMBNAIL_COLUMNS = 5;
94 private Map<String, Integer> dataTypes;
96 private String thumbsPath;
97 private String subPath;
98 private String currentDataType;
99 private Integer rowCount;
102 private ReportHTMLConfigurationPanel configPanel;
104 private final ReportBranding reportBranding;
107 public static synchronized ReportHTML getDefault() {
108 if (instance == null) {
109 instance =
new ReportHTML();
115 private ReportHTML() {
116 reportBranding =
new ReportBranding();
120 public JPanel getConfigurationPanel() {
121 if (configPanel == null) {
122 configPanel =
new ReportHTMLConfigurationPanel();
128 private void refresh() throws NoCurrentCaseException {
129 currentCase = Case.getCurrentCaseThrows();
131 dataTypes =
new TreeMap<>();
136 currentDataType =
"";
142 }
catch (IOException ex) {
154 private String dataTypeToFileName(String dataType) {
158 fileName = fileName.replaceAll(
" ",
"_");
167 private String useDataTypeIcon(String dataType) {
171 OutputStream output = null;
173 logger.log(Level.INFO,
"useDataTypeIcon: dataType = {0}", dataType);
176 BlackboardArtifact.ARTIFACT_TYPE artifactType = null;
177 for (ARTIFACT_TYPE v : ARTIFACT_TYPE.values()) {
178 if (v.getDisplayName().equals(dataType)) {
183 if (null != artifactType) {
185 iconFileName = dataTypeToFileName(artifactType.getDisplayName()) +
".png";
186 iconFilePath = subPath + File.separator + iconFileName;
189 switch (artifactType) {
190 case TSK_WEB_BOOKMARK:
191 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/bookmarks.png");
194 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/cookies.png");
196 case TSK_WEB_HISTORY:
197 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/history.png");
199 case TSK_WEB_DOWNLOAD:
200 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/downloads.png");
202 case TSK_RECENT_OBJECT:
203 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/recent.png");
205 case TSK_INSTALLED_PROG:
206 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/installed.png");
208 case TSK_KEYWORD_HIT:
209 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/keywords.png");
211 case TSK_HASHSET_HIT:
212 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/hash.png");
214 case TSK_DEVICE_ATTACHED:
215 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/devices.png");
217 case TSK_WEB_SEARCH_QUERY:
218 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/search.png");
220 case TSK_METADATA_EXIF:
221 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/exif.png");
224 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/userbookmarks.png");
226 case TSK_TAG_ARTIFACT:
227 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/userbookmarks.png");
229 case TSK_SERVICE_ACCOUNT:
230 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/account-icon-16.png");
233 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/contact.png");
236 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/message.png");
239 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/calllog.png");
241 case TSK_CALENDAR_ENTRY:
242 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/calendar.png");
244 case TSK_SPEED_DIAL_ENTRY:
245 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/speeddialentry.png");
247 case TSK_BLUETOOTH_PAIRING:
248 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/bluetooth.png");
250 case TSK_GPS_BOOKMARK:
251 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gpsfav.png");
253 case TSK_GPS_LAST_KNOWN_LOCATION:
254 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps-lastlocation.png");
257 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps-search.png");
260 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/computer.png");
262 case TSK_GPS_TRACKPOINT:
263 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps_trackpoint.png");
266 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps_trackpoint.png");
269 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/mail-icon-16.png");
271 case TSK_ENCRYPTION_SUSPECTED:
272 case TSK_ENCRYPTION_DETECTED:
273 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/encrypted-file.png");
275 case TSK_EXT_MISMATCH_DETECTED:
276 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/mismatch-16.png");
278 case TSK_INTERESTING_ARTIFACT_HIT:
279 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/interesting_item.png");
281 case TSK_INTERESTING_FILE_HIT:
282 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/interesting_item.png");
285 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/installed.png");
287 case TSK_REMOTE_DRIVE:
288 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/drive_network.png");
291 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/accounts.png");
293 case TSK_WIFI_NETWORK:
294 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/network-wifi.png");
296 case TSK_WIFI_NETWORK_ADAPTER:
297 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/network-wifi.png");
299 case TSK_SIM_ATTACHED:
300 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/sim_card.png");
302 case TSK_BLUETOOTH_ADAPTER:
303 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/Bluetooth.png");
305 case TSK_DEVICE_INFO:
306 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/devices.png");
308 case TSK_VERIFICATION_FAILED:
309 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/validationFailed.png");
312 logger.log(Level.WARNING,
"useDataTypeIcon: unhandled artifact type = {0}", dataType);
313 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png");
314 iconFileName =
"star.png";
315 iconFilePath = subPath + File.separator + iconFileName;
318 }
else if (dataType.startsWith(ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName())) {
326 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/accounts.png");
327 iconFileName =
"accounts.png";
328 iconFilePath = subPath + File.separator + iconFileName;
330 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png");
331 iconFileName =
"star.png";
332 iconFilePath = subPath + File.separator + iconFileName;
336 output =
new FileOutputStream(iconFilePath);
337 FileUtil.copy(in, output);
340 }
catch (IOException ex) {
341 logger.log(Level.SEVERE,
"Failed to extract images for HTML report.", ex);
343 if (output != null) {
347 }
catch (IOException ex) {
353 }
catch (IOException ex) {
368 public void startReport(String baseReportDir) {
370 ModuleSettings.setConfigSetting(
"HTMLReport",
"header", configPanel.getHeader());
371 ModuleSettings.setConfigSetting(
"HTMLReport",
"footer", configPanel.getFooter());
376 }
catch (NoCurrentCaseException ex) {
377 logger.log(Level.SEVERE,
"Exception while getting open case.");
381 this.path = baseReportDir;
382 this.subPath = this.path + HTML_SUBDIR + File.separator;
383 this.thumbsPath = this.subPath + THUMBS_REL_PATH;
385 FileUtil.createFolder(
new File(this.subPath));
386 FileUtil.createFolder(
new File(this.thumbsPath));
387 }
catch (IOException ex) {
388 logger.log(Level.SEVERE,
"Unable to make HTML report folder.");
401 public void endReport() {
406 }
catch (IOException ex) {
407 logger.log(Level.WARNING,
"Could not close the output writer when ending report.", ex);
421 public void startDataType(String name, String description) {
422 String title = dataTypeToFileName(name);
424 out =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath + title +
".html"),
"UTF-8"));
425 }
catch (FileNotFoundException ex) {
426 logger.log(Level.SEVERE,
"File not found: {0}", ex);
427 }
catch (UnsupportedEncodingException ex) {
428 logger.log(Level.SEVERE,
"Unrecognized encoding");
432 StringBuilder page =
new StringBuilder();
433 page.append(
"<html>\n<head>\n\t<title>").append(name).append(
"</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n")
434 .append(writePageHeader())
435 .append(
"<div id=\"header\">").append(name).append(
"</div>\n")
436 .append(
"<div id=\"content\">\n");
437 if (!description.isEmpty()) {
438 page.append(
"<p><strong>");
439 page.append(description);
440 page.append(
"</strong></p>\n");
442 out.write(page.toString());
443 currentDataType = name;
445 }
catch (IOException ex) {
446 logger.log(Level.SEVERE,
"Failed to write page head: {0}", ex);
455 public void endDataType() {
456 dataTypes.put(currentDataType, rowCount);
458 StringBuilder builder =
new StringBuilder();
459 builder.append(writePageFooter());
460 builder.append(
"</div>\n</body>\n</html>\n");
461 out.write(builder.toString());
462 }
catch (IOException ex) {
463 logger.log(Level.SEVERE,
"Failed to write end of HTML report.", ex);
469 }
catch (IOException ex) {
470 logger.log(Level.WARNING,
"Could not close the output writer when ending data type.", ex);
483 private String writePageHeader() {
484 StringBuilder output =
new StringBuilder();
485 String pageHeader = configPanel.getHeader();
486 if (pageHeader.isEmpty() ==
false) {
487 output.append(
"<div id=\"pageHeaderFooter\">")
488 .append(StringEscapeUtils.escapeHtml4(pageHeader))
491 return output.toString();
500 private String writePageFooter() {
501 StringBuilder output =
new StringBuilder();
502 String pageFooter = configPanel.getFooter();
503 if (pageFooter.isEmpty() ==
false) {
504 output.append(
"<br/><div id=\"pageHeaderFooter\">")
505 .append(StringEscapeUtils.escapeHtml4(pageFooter))
508 return output.toString();
517 public void startSet(String setName) {
518 StringBuilder set =
new StringBuilder();
519 set.append(
"<h1><a name=\"").append(setName).append(
"\">").append(setName).append(
"</a></h1>\n");
520 set.append(
"<div class=\"keyword_list\">\n");
523 out.write(set.toString());
524 }
catch (IOException ex) {
525 logger.log(Level.SEVERE,
"Failed to write set: {0}", ex);
533 public void endSet() {
535 out.write(
"</div>\n");
536 }
catch (IOException ex) {
537 logger.log(Level.SEVERE,
"Failed to write end of set: {0}", ex);
547 public void addSetIndex(List<String> sets) {
548 StringBuilder index =
new StringBuilder();
549 index.append(
"<ul>\n");
550 for (String set : sets) {
551 index.append(
"\t<li><a href=\"#").append(set).append(
"\">").append(set).append(
"</a></li>\n");
553 index.append(
"</ul>\n");
555 out.write(index.toString());
556 }
catch (IOException ex) {
557 logger.log(Level.SEVERE,
"Failed to add set index: {0}", ex);
567 public void addSetElement(String elementName) {
569 out.write(
"<h4>" + elementName +
"</h4>\n");
570 }
catch (IOException ex) {
571 logger.log(Level.SEVERE,
"Failed to write set element: {0}", ex);
581 public void startTable(List<String> titles) {
582 StringBuilder ele =
new StringBuilder();
583 ele.append(
"<table>\n<thead>\n\t<tr>\n");
584 for (String title : titles) {
585 ele.append(
"\t\t<th>").append(title).append(
"</th>\n");
587 ele.append(
"\t</tr>\n</thead>\n");
590 out.write(ele.toString());
591 }
catch (IOException ex) {
592 logger.log(Level.SEVERE,
"Failed to write table start: {0}", ex);
603 public void startContentTagsTable(List<String> columnHeaders) {
604 StringBuilder htmlOutput =
new StringBuilder();
605 htmlOutput.append(
"<table>\n<thead>\n\t<tr>\n");
608 for (String columnHeader : columnHeaders) {
609 htmlOutput.append(
"\t\t<th>").append(columnHeader).append(
"</th>\n");
613 htmlOutput.append(
"\t\t<th></th>\n");
615 htmlOutput.append(
"\t</tr>\n</thead>\n");
618 out.write(htmlOutput.toString());
619 }
catch (IOException ex) {
620 logger.log(Level.SEVERE,
"Failed to write table start: {0}", ex);
628 public void endTable() {
630 out.write(
"</table>\n");
631 }
catch (IOException ex) {
632 logger.log(Level.SEVERE,
"Failed to write end of table: {0}", ex);
643 public void addRow(List<String> row) {
654 private void addRow(List<String> row,
boolean escapeText) {
655 StringBuilder builder =
new StringBuilder();
656 builder.append(
"\t<tr>\n");
657 for (String cell : row) {
658 String cellText = escapeText ? EscapeUtil.escapeHtml(cell) : cell;
659 builder.append(
"\t\t<td>").append(cellText).append(
"</td>\n");
661 builder.append(
"\t</tr>\n");
665 out.write(builder.toString());
666 }
catch (IOException ex) {
667 logger.log(Level.SEVERE,
"Failed to write row to out.", ex);
668 }
catch (NullPointerException ex) {
669 logger.log(Level.SEVERE,
"Output writer is null. Page was not initialized before writing.", ex);
683 public void addRowWithTaggedContentHyperlink(List<String> row, ContentTag contentTag) {
684 Content content = contentTag.getContent();
685 if (content instanceof AbstractFile ==
false) {
689 AbstractFile file = (AbstractFile) content;
691 StringBuilder localFileLink =
new StringBuilder();
694 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
695 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)) {
696 localFileLink.append(
"<a href=\"");
698 String localFilePath = saveContent(file, contentTag.getName().getDisplayName());
699 localFileLink.append(localFilePath);
700 localFileLink.append(
"\" target=\"_top\">");
703 StringBuilder builder =
new StringBuilder();
704 builder.append(
"\t<tr>\n");
705 int positionCounter = 0;
706 for (String cell : row) {
708 switch (positionCounter) {
711 builder.append(
"\t\t<td class=\"left_align_cell\">").append(localFileLink.toString()).append(cell).append(
"</a></td>\n");
715 builder.append(
"\t\t<td class=\"right_align_cell\">").append(cell).append(
"</td>\n");
719 builder.append(
"\t\t<td>").append(cell).append(
"</td>\n");
724 builder.append(
"\t</tr>\n");
728 out.write(builder.toString());
729 }
catch (IOException ex) {
730 logger.log(Level.SEVERE,
"Failed to write row to out.", ex);
731 }
catch (NullPointerException ex) {
732 logger.log(Level.SEVERE,
"Output writer is null. Page was not initialized before writing.", ex);
742 private List<ImageTagRegion> getTaggedRegions(List<ContentTag> contentTags) {
743 ArrayList<ImageTagRegion> tagRegions =
new ArrayList<>();
744 contentTags.forEach((contentTag) -> {
746 ContentViewerTag<ImageTagRegion> contentViewerTag = ContentViewerTagManager
747 .getTag(contentTag, ImageTagRegion.class);
748 if (contentViewerTag != null) {
749 tagRegions.add(contentViewerTag.getDetails());
751 }
catch (TskCoreException | NoCurrentCaseException ex) {
752 logger.log(Level.WARNING,
"Could not get content viewer tag "
753 +
"from case db for content_tag with id %d", contentTag.getId());
764 public void addThumbnailRows(Set<Content> images) {
765 List<String> currentRow =
new ArrayList<>();
768 for (Content content : images) {
769 if (currentRow.size() == THUMBNAIL_COLUMNS) {
770 addRow(currentRow,
false);
774 if (totalCount == MAX_THUMBS_PER_PAGE) {
778 rowCount = totalCount;
783 startDataType(NbBundle.getMessage(
this.getClass(),
"ReportHTML.addThumbRows.dataType.title", pages),
784 NbBundle.getMessage(
this.getClass(),
"ReportHTML.addThumbRows.dataType.msg"));
785 List<String> emptyHeaders =
new ArrayList<>();
786 for (
int i = 0; i < THUMBNAIL_COLUMNS; i++) {
787 emptyHeaders.add(
"");
789 startTable(emptyHeaders);
792 if (failsContentCheck(content)) {
796 AbstractFile file = (AbstractFile) content;
797 List<ContentTag> contentTags =
new ArrayList<>();
799 String thumbnailPath = null;
800 String imageWithTagsFullPath = null;
803 contentTags = Case.getCurrentCase().getServices()
804 .getTagsManager().getContentTagsByContent(file);
805 List<ImageTagRegion> imageTags = getTaggedRegions(contentTags);
807 if(!imageTags.isEmpty()) {
809 BufferedImage fullImageWithTags = ImageTagsUtil.getImageWithTags(file, imageTags);
811 BufferedImage thumbnailWithTags = ImageTagsUtil.getThumbnailWithTags(file,
812 imageTags, ImageTagsUtil.IconSize.MEDIUM);
817 File thumbnailImageWithTagsFile = Paths.get(thumbsPath, FilenameUtils.removeExtension(fileName) +
".png").toFile();
818 String fullImageWithTagsPath = makeCustomUniqueFilePath(file,
"thumbs_fullsize");
819 fullImageWithTagsPath = FilenameUtils.removeExtension(fullImageWithTagsPath) +
".png";
820 File fullImageWithTagsFile = Paths.get(fullImageWithTagsPath).toFile();
823 ImageIO.write(thumbnailWithTags,
"png", thumbnailImageWithTagsFile);
824 ImageIO.write(fullImageWithTags,
"png", fullImageWithTagsFile);
826 thumbnailPath = THUMBS_REL_PATH + thumbnailImageWithTagsFile.getName();
828 imageWithTagsFullPath = fullImageWithTagsPath.substring(subPath.length());
830 }
catch (TskCoreException ex) {
831 logger.log(Level.WARNING,
"Could not get tags for file.", ex);
832 }
catch (IOException | InterruptedException | ExecutionException ex) {
833 logger.log(Level.WARNING,
"Could make marked up thumbnail.", ex);
837 if(thumbnailPath == null) {
838 thumbnailPath = prepareThumbnail(file);
841 if (thumbnailPath == null) {
844 String contentPath = saveContent(file,
"original");
847 nameInImage = file.getUniquePath();
848 }
catch (TskCoreException ex) {
849 nameInImage = file.getName();
852 StringBuilder linkToThumbnail =
new StringBuilder();
853 linkToThumbnail.append(
"<div id='thumbnail_link'><a href=\"")
854 .append((imageWithTagsFullPath != null) ? imageWithTagsFullPath : contentPath)
855 .append(
"\" target=\"_top\"><img src=\"")
856 .append(thumbnailPath).append(
"\" title=\"").append(nameInImage).append(
"\"/></a><br>")
857 .append(file.getName()).append(
"<br>");
858 if(imageWithTagsFullPath != null) {
859 linkToThumbnail.append(
"<a href=\"").append(contentPath).append(
"\" target=\"_top\">View Original</a><br>");
862 if (!contentTags.isEmpty()) {
863 linkToThumbnail.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.thumbLink.tags"));
865 for (
int i = 0; i < contentTags.size(); i++) {
866 ContentTag tag = contentTags.get(i);
867 String notableString = tag.getName().getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() :
"";
868 linkToThumbnail.append(tag.getName().getDisplayName()).append(notableString);
869 if (i != contentTags.size() - 1) {
870 linkToThumbnail.append(
", ");
874 linkToThumbnail.append(
"</div>");
875 currentRow.add(linkToThumbnail.toString());
880 if (currentRow.isEmpty() ==
false) {
881 int extraCells = THUMBNAIL_COLUMNS - currentRow.size();
882 for (
int i = 0; i < extraCells; i++) {
886 addRow(currentRow,
false);
890 rowCount = totalCount;
893 private boolean failsContentCheck(Content c) {
894 if (c instanceof AbstractFile ==
false) {
897 AbstractFile file = (AbstractFile) c;
899 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
900 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS;
903 private String makeCustomUniqueFilePath(AbstractFile file, String dirName) {
908 StringBuilder localFilePath =
new StringBuilder();
910 localFilePath.append(subPath);
911 localFilePath.append(dirName2);
912 File localFileFolder =
new File(localFilePath.toString());
913 if (!localFileFolder.exists()) {
914 localFileFolder.mkdirs();
925 String objectIdSuffix =
"_" + file.getId();
926 int lastDotIndex = fileName.lastIndexOf(
".");
927 if (lastDotIndex != -1 && lastDotIndex != 0) {
929 fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length());
933 fileName += objectIdSuffix;
935 localFilePath.append(File.separator);
936 localFilePath.append(fileName);
938 return localFilePath.toString();
950 public String saveContent(AbstractFile file, String dirName) {
952 String localFilePath = makeCustomUniqueFilePath(file, dirName);
956 File localFile =
new File(localFilePath);
957 if (!localFile.exists()) {
958 ExtractFscContentVisitor.extract(file, localFile, null, null);
962 return localFilePath.substring(subPath.length());
973 public String dateToString(
long date) {
974 SimpleDateFormat sdf =
new java.text.SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
975 return sdf.format(
new java.util.Date(date * 1000));
979 public String getRelativeFilePath() {
980 return "report.html";
984 public String getName() {
985 return NbBundle.getMessage(this.getClass(),
"ReportHTML.getName.text");
989 public String getDescription() {
990 return NbBundle.getMessage(this.getClass(),
"ReportHTML.getDesc.text");
996 private void writeCss() {
997 Writer cssOut = null;
999 cssOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"index.css"),
"UTF-8"));
1000 String css =
"body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;}\n"
1002 "#content {padding: 30px;}\n"
1004 "#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;}\n"
1006 "#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n"
1008 "h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;}\n"
1010 "h2 {font-size: 20px; font-weight: bolder; color: #07A;}\n"
1012 "h3 {font-size: 16px; color: #07A;}\n"
1014 "h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;}\n"
1016 "ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;}\n"
1018 "ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;}\n"
1020 "ul li a:hover {text-decoration: underline;}\n"
1022 "p {margin: 0 0 20px 0;}\n"
1024 "table {white-space:nowrap; min-width: 700px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;}\n"
1026 ".keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;}\n"
1028 "table th {white-space:nowrap; display: table-cell; text-align: center; padding: 2px 4px; background: #e5e5e5; color: #777; font-size: 11px; text-shadow: #e9f9fd 0 1px 0; border-top: 1px solid #dedede; border-bottom: 2px solid #e5e5e5;}\n"
1030 "table .left_align_cell{display: table-cell; padding: 2px 4px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align: left; }\n"
1032 "table .right_align_cell{display: table-cell; padding: 2px 4px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align: right; }\n"
1034 "table td {white-space:nowrap; display: table-cell; padding: 2px 3px; font: 13px/20px Arial, Helvetica, sans-serif; min-width: 125px; overflow: auto; text-align:left; vertical-align: text-top;}\n"
1036 "table tr:nth-child(even) td {background: #f3f3f3;}\n"
1038 "div#thumbnail_link {max-width: 200px; white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word;}";
1040 }
catch (FileNotFoundException ex) {
1041 logger.log(Level.SEVERE,
"Could not find index.css file to write to.", ex);
1042 }
catch (UnsupportedEncodingException ex) {
1043 logger.log(Level.SEVERE,
"Did not recognize encoding when writing index.css.", ex);
1044 }
catch (IOException ex) {
1045 logger.log(Level.SEVERE,
"Error creating Writer for index.css.", ex);
1048 if (cssOut != null) {
1052 }
catch (IOException ex) {
1060 private void writeIndex() {
1061 Writer indexOut = null;
1062 String indexFilePath = path +
"report.html";
1065 openCase = Case.getCurrentCaseThrows();
1066 }
catch (NoCurrentCaseException ex) {
1067 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
1071 indexOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(indexFilePath),
"UTF-8"));
1072 StringBuilder index =
new StringBuilder();
1073 final String reportTitle = reportBranding.getReportTitle();
1074 String iconPath = reportBranding.getAgencyLogoPath();
1075 if (iconPath == null) {
1077 iconPath = HTML_SUBDIR +
"favicon.ico";
1079 iconPath = Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString();
1081 index.append(
"<head>\n<title>").append(reportTitle).append(
" ").append(
1082 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.title", currentCase.getDisplayName())).append(
1084 index.append(
"<link rel=\"icon\" type=\"image/ico\" href=\"")
1085 .append(iconPath).append(
"\" />\n");
1086 index.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n");
1087 index.append(
"</head>\n");
1088 index.append(
"<frameset cols=\"350px,*\">\n");
1089 index.append(
"<frame src=\"" + HTML_SUBDIR).append(File.separator).append(
"nav.html\" name=\"nav\">\n");
1090 index.append(
"<frame src=\"" + HTML_SUBDIR).append(File.separator).append(
"summary.html\" name=\"content\">\n");
1091 index.append(
"<noframes>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.noFrames.msg")).append(
"<br />\n");
1092 index.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.noFrames.seeNav")).append(
"<br />\n");
1093 index.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.seeSum")).append(
"</noframes>\n");
1094 index.append(
"</frameset>\n");
1095 index.append(
"</html>");
1096 indexOut.write(index.toString());
1097 openCase.addReport(indexFilePath, NbBundle.getMessage(
this.getClass(),
1098 "ReportHTML.writeIndex.srcModuleName.text"),
"");
1099 }
catch (IOException ex) {
1100 logger.log(Level.SEVERE,
"Error creating Writer for report.html: {0}", ex);
1101 }
catch (TskCoreException ex) {
1102 String errorMessage = String.format(
"Error adding %s to case as a report", indexFilePath);
1103 logger.log(Level.SEVERE, errorMessage, ex);
1106 if (indexOut != null) {
1110 }
catch (IOException ex) {
1118 private void writeNav() {
1119 Writer navOut = null;
1121 navOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"nav.html"),
"UTF-8"));
1122 StringBuilder nav =
new StringBuilder();
1123 nav.append(
"<html>\n<head>\n\t<title>").append(
1124 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.title"))
1125 .append(
"</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n");
1126 nav.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n");
1127 nav.append(
"<div id=\"content\">\n<h1>").append(
1128 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.h1")).append(
"</h1>\n");
1129 nav.append(
"<ul class=\"nav\">\n");
1130 nav.append(
"<li style=\"background: url(summary.png) left center no-repeat;\"><a href=\"summary.html\" target=\"content\">")
1131 .append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.summary")).append(
"</a></li>\n");
1133 for (String dataType : dataTypes.keySet()) {
1134 String dataTypeEsc = dataTypeToFileName(dataType);
1135 String iconFileName = useDataTypeIcon(dataType);
1136 nav.append(
"<li style=\"background: url('").append(iconFileName)
1137 .append(
"') left center no-repeat;\"><a href=\"")
1138 .append(dataTypeEsc).append(
".html\" target=\"content\">")
1139 .append(dataType).append(
" (").append(dataTypes.get(dataType))
1140 .append(
")</a></li>\n");
1142 nav.append(
"</ul>\n");
1143 nav.append(
"</div>\n</body>\n</html>");
1144 navOut.write(nav.toString());
1145 }
catch (IOException ex) {
1146 logger.log(Level.SEVERE,
"Failed to write end of report navigation menu: {0}", ex);
1148 if (navOut != null) {
1152 }
catch (IOException ex) {
1153 logger.log(Level.WARNING,
"Could not close navigation out writer.");
1158 InputStream in = null;
1159 OutputStream output = null;
1163 String generatorLogoPath = reportBranding.getGeneratorLogoPath();
1164 if (generatorLogoPath != null && !generatorLogoPath.isEmpty()) {
1165 File from =
new File(generatorLogoPath);
1166 File to =
new File(subPath);
1167 FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to),
"generator_logo");
1170 String agencyLogoPath = reportBranding.getAgencyLogoPath();
1171 if (agencyLogoPath != null && !agencyLogoPath.isEmpty()) {
1172 Path destinationPath = Paths.get(subPath);
1173 Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), destinationPath.resolve(Paths.get(agencyLogoPath).getFileName()));
1176 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/favicon.ico");
1177 output =
new FileOutputStream(
new File(subPath +
"favicon.ico"));
1178 FileUtil.copy(in, output);
1182 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/summary.png");
1183 output =
new FileOutputStream(
new File(subPath +
"summary.png"));
1184 FileUtil.copy(in, output);
1188 }
catch (IOException ex) {
1189 logger.log(Level.SEVERE,
"Failed to extract images for HTML report.", ex);
1191 if (output != null) {
1195 }
catch (IOException ex) {
1201 }
catch (IOException ex) {
1210 private void writeSummary() {
1211 Writer output = null;
1213 output =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"summary.html"),
"UTF-8"));
1214 StringBuilder head =
new StringBuilder();
1215 head.append(
"<html>\n<head>\n<title>").append(
1216 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.title")).append(
"</title>\n");
1217 head.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n");
1218 head.append(
"<style type=\"text/css\">\n");
1219 head.append(
"#pageHeaderFooter {width: 100%; padding: 10px; line-height: 25px; text-align: center; font-size: 20px;}\n");
1220 head.append(
"body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n");
1221 head.append(
"#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n");
1222 head.append(
"h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n");
1223 head.append(
"h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;}\n");
1224 head.append(
"h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; }\n");
1225 head.append(
"h3 { padding: 5 0 3px 0; margin: 0px; color: #07A; font-weight: normal; }\n");
1226 head.append(
"table td { padding: 5px 25px 5px 0px; vertical-align:top;}\n");
1227 head.append(
"p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; }\n");
1228 head.append(
".title { width: 660px; margin-bottom: 50px; }\n");
1229 head.append(
".left { float: left; width: 250px; margin-top: 20px; text-align: center; }\n");
1230 head.append(
".left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; }\n");
1231 head.append(
".right { float: right; width: 385px; margin-top: 25px; font-size: 14px; }\n");
1232 head.append(
".clear { clear: both; }\n");
1233 head.append(
".info { padding: 10px 0;}\n");
1234 head.append(
".info p { padding: 3px 10px; background: #e5e5e5; color: #777; font-size: 12px; font-weight: bold; text-shadow: #e9f9fd 0 1px 0; border-top: 1px solid #dedede; border-bottom: 2px solid #dedede; }\n");
1235 head.append(
".info table { margin: 10px 25px 10px 25px; }\n");
1236 head.append(
"ul {padding: 0;margin: 0;list-style-type: none;}");
1237 head.append(
"li {padding-bottom: 5px;}");
1238 head.append(
"</style>\n");
1239 head.append(
"</head>\n<body>\n");
1240 output.write(head.toString());
1242 DateFormat datetimeFormat =
new SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
1243 Date date =
new Date();
1244 String datetime = datetimeFormat.format(date);
1246 StringBuilder summary =
new StringBuilder();
1247 boolean running =
false;
1248 if (IngestManager.getInstance().isIngestRunning()) {
1251 SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
1252 List<IngestJobInfo> ingestJobs = skCase.getIngestJobs();
1253 final String reportTitle = reportBranding.getReportTitle();
1254 final String reportFooter = reportBranding.getReportFooter();
1255 final boolean generatorLogoSet = reportBranding.getGeneratorLogoPath() != null && !reportBranding.getGeneratorLogoPath().isEmpty();
1257 summary.append(
"<div id=\"wrapper\">\n");
1258 summary.append(writePageHeader());
1259 summary.append(
"<h1>").append(reportTitle)
1260 .append(running ? NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.warningMsg") :
"")
1262 summary.append(
"<p class=\"subheadding\">").append(
1263 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.reportGenOn.text", datetime)).append(
"</p>\n");
1264 summary.append(
"<div class=\"title\">\n");
1265 summary.append(writeSummaryCaseDetails());
1266 summary.append(writeSummaryImageInfo());
1267 summary.append(writeSummarySoftwareInfo(skCase, ingestJobs));
1268 summary.append(writeSummaryIngestHistoryInfo(skCase, ingestJobs));
1269 if (generatorLogoSet) {
1270 summary.append(
"<div class=\"left\">\n");
1271 summary.append(
"<img src=\"generator_logo.png\" />\n");
1272 summary.append(
"</div>\n");
1274 summary.append(
"<div class=\"clear\"></div>\n");
1275 if (reportFooter != null) {
1276 summary.append(
"<p class=\"subheadding\">").append(reportFooter).append(
"</p>\n");
1278 summary.append(
"</div>\n");
1279 summary.append(writePageFooter());
1280 summary.append(
"</body></html>");
1281 output.write(summary.toString());
1282 }
catch (FileNotFoundException ex) {
1283 logger.log(Level.SEVERE,
"Could not find summary.html file to write to.");
1284 }
catch (UnsupportedEncodingException ex) {
1285 logger.log(Level.SEVERE,
"Did not recognize encoding when writing summary.hmtl.");
1286 }
catch (IOException ex) {
1287 logger.log(Level.SEVERE,
"Error creating Writer for summary.html.");
1288 }
catch (NoCurrentCaseException | TskCoreException ex) {
1289 logger.log(Level.WARNING,
"Unable to get current sleuthkit Case for the HTML report.");
1292 if (output != null) {
1296 }
catch (IOException ex) {
1302 "ReportHTML.writeSum.case=Case:",
1303 "ReportHTML.writeSum.caseNumber=Case Number:",
1304 "ReportHTML.writeSum.caseNumImages=Number of Images:",
1305 "ReportHTML.writeSum.caseNotes=Notes:",
1306 "ReportHTML.writeSum.examiner=Examiner:"
1313 private StringBuilder writeSummaryCaseDetails() {
1314 StringBuilder summary =
new StringBuilder();
1316 final boolean agencyLogoSet = reportBranding.getAgencyLogoPath() != null && !reportBranding.getAgencyLogoPath().isEmpty();
1319 String caseName = currentCase.getDisplayName();
1320 String caseNumber = currentCase.getNumber();
1323 imagecount = currentCase.getDataSources().size();
1324 }
catch (TskCoreException ex) {
1327 String caseNotes = currentCase.getCaseNotes();
1330 String examinerName = currentCase.getExaminer();
1333 summary.append(
"<div class=\"title\">\n");
1334 if (agencyLogoSet) {
1335 summary.append(
"<div class=\"left\">\n");
1336 summary.append(
"<img src=\"");
1337 summary.append(Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString());
1338 summary.append(
"\" />\n");
1339 summary.append(
"</div>\n");
1341 final String align = agencyLogoSet ?
"right" :
"left";
1342 summary.append(
"<div class=\"").append(align).append(
"\">\n");
1343 summary.append(
"<table>\n");
1346 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_case()).append(
"</td><td>")
1347 .append(formatHtmlString(caseName)).append(
"</td></tr>\n");
1349 if (!caseNumber.isEmpty()) {
1350 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumber()).append(
"</td><td>")
1351 .append(formatHtmlString(caseNumber)).append(
"</td></tr>\n");
1354 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNumImages()).append(
"</td><td>")
1355 .append(imagecount).append(
"</td></tr>\n");
1357 if (!caseNotes.isEmpty()) {
1358 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_caseNotes()).append(
"</td><td>")
1359 .append(formatHtmlString(caseNotes)).append(
"</td></tr>\n");
1363 if (!examinerName.isEmpty()) {
1364 summary.append(
"<tr><td>").append(Bundle.ReportHTML_writeSum_examiner()).append(
"</td><td>")
1365 .append(formatHtmlString(examinerName)).append(
"</td></tr>\n");
1369 summary.append(
"</table>\n");
1370 summary.append(
"</div>\n");
1371 summary.append(
"<div class=\"clear\"></div>\n");
1372 summary.append(
"</div>\n");
1381 private StringBuilder writeSummaryImageInfo() {
1382 StringBuilder summary =
new StringBuilder();
1383 summary.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.imageInfoHeading"));
1384 summary.append(
"<div class=\"info\">\n");
1386 for (Content c : currentCase.getDataSources()) {
1387 summary.append(
"<p>").append(c.getName()).append(
"</p>\n");
1388 if (c instanceof Image) {
1389 Image img = (Image) c;
1391 summary.append(
"<table>\n");
1392 summary.append(
"<tr><td>").append(
1393 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.timezone"))
1394 .append(
"</td><td>").append(img.getTimeZone()).append(
"</td></tr>\n");
1395 for (String imgPath : img.getPaths()) {
1396 summary.append(
"<tr><td>").append(
1397 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.path"))
1398 .append(
"</td><td>").append(imgPath).append(
"</td></tr>\n");
1400 summary.append(
"</table>\n");
1403 }
catch (TskCoreException ex) {
1404 logger.log(Level.WARNING,
"Unable to get image information for the HTML report.");
1406 summary.append(
"</div>\n");
1415 private StringBuilder writeSummarySoftwareInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
1416 StringBuilder summary =
new StringBuilder();
1417 summary.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.softwareInfoHeading"));
1418 summary.append(
"<div class=\"info\">\n");
1419 summary.append(
"<table>\n");
1420 summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.autopsyVersion"))
1421 .append(
"</td><td>").append(Version.getVersion()).append(
"</td></tr>\n");
1422 Map<Long, IngestModuleInfo> moduleInfoHashMap =
new HashMap<>();
1423 for (IngestJobInfo ingestJob : ingestJobs) {
1424 List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1425 for (IngestModuleInfo ingestModule : ingestModules) {
1426 if (!moduleInfoHashMap.containsKey(ingestModule.getIngestModuleId())) {
1427 moduleInfoHashMap.put(ingestModule.getIngestModuleId(), ingestModule);
1431 TreeMap<String, String> modules =
new TreeMap<>();
1432 for (IngestModuleInfo moduleinfo : moduleInfoHashMap.values()) {
1433 modules.put(moduleinfo.getDisplayName(), moduleinfo.getVersion());
1435 for (Map.Entry<String, String> module : modules.entrySet()) {
1436 summary.append(
"<tr><td>").append(module.getKey()).append(
" Module:")
1437 .append(
"</td><td>").append(module.getValue()).append(
"</td></tr>\n");
1439 summary.append(
"</table>\n");
1440 summary.append(
"</div>\n");
1441 summary.append(
"<div class=\"clear\"></div>\n");
1450 private StringBuilder writeSummaryIngestHistoryInfo(SleuthkitCase skCase, List<IngestJobInfo> ingestJobs) {
1451 StringBuilder summary =
new StringBuilder();
1453 summary.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.ingestHistoryHeading"));
1454 summary.append(
"<div class=\"info\">\n");
1457 for (IngestJobInfo ingestJob : ingestJobs) {
1458 summary.append(
"<h3>Job ").append(jobnumber).append(
":</h3>\n");
1459 summary.append(
"<table>\n");
1460 summary.append(
"<tr><td>").append(
"Data Source:")
1461 .append(
"</td><td>").append(skCase.getContentById(ingestJob.getObjectId()).getName()).append(
"</td></tr>\n");
1462 summary.append(
"<tr><td>").append(
"Status:")
1463 .append(
"</td><td>").append(ingestJob.getStatus()).append(
"</td></tr>\n");
1464 summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.modulesEnabledHeading"))
1465 .append(
"</td><td>");
1466 List<IngestModuleInfo> ingestModules = ingestJob.getIngestModuleInfo();
1467 summary.append(
"<ul>\n");
1468 for (IngestModuleInfo ingestModule : ingestModules) {
1469 summary.append(
"<li>").append(ingestModule.getDisplayName()).append(
"</li>");
1471 summary.append(
"</ul>\n");
1473 summary.append(
"</td></tr>\n");
1474 summary.append(
"</table>\n");
1476 summary.append(
"</div>\n");
1477 }
catch (TskCoreException ex) {
1478 logger.log(Level.WARNING,
"Unable to get ingest jobs for the HTML report.");
1491 private String prepareThumbnail(AbstractFile file) {
1492 BufferedImage bufferedThumb = ImageUtils.getThumbnail(file, ImageUtils.ICON_SIZE_MEDIUM);
1500 File thumbFile = Paths.get(thumbsPath, fileName +
".png").toFile();
1501 if (bufferedThumb == null) {
1505 ImageIO.write(bufferedThumb,
"png", thumbFile);
1506 }
catch (IOException ex) {
1507 logger.log(Level.WARNING,
"Failed to write thumb file to report directory.", ex);
1510 if (thumbFile.exists()
1514 return THUMBS_REL_PATH
1515 + thumbFile.getName();
1526 private String formatHtmlString(String text) {
1527 String formattedString = StringEscapeUtils.escapeHtml4(text);
1528 return formattedString.replaceAll(
"(\r\n|\r|\n|\n\r)",
"<br>");
static String escapeFileName(String fileName)