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.List;
46 import java.util.TreeMap;
47 import java.util.logging.Level;
48 import javax.imageio.ImageIO;
49 import org.openide.filesystems.FileUtil;
50 import org.openide.util.NbBundle;
62 import org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
69 import org.
sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
71 class ReportHTML
implements TableReportModule {
73 private static final Logger logger = Logger.getLogger(ReportHTML.class.getName());
74 private static final String THUMBS_REL_PATH =
"thumbs" + File.separator;
75 private static ReportHTML instance;
76 private static final int MAX_THUMBS_PER_PAGE = 1000;
77 private static final String HTML_SUBDIR =
"content";
78 private Case currentCase;
79 private SleuthkitCase skCase;
80 static Integer THUMBNAIL_COLUMNS = 5;
82 private Map<String, Integer> dataTypes;
84 private String thumbsPath;
85 private String subPath;
86 private String currentDataType;
87 private Integer rowCount;
90 private final ReportBranding reportBranding;
93 public static synchronized ReportHTML getDefault() {
94 if (instance == null) {
95 instance =
new ReportHTML();
101 private ReportHTML() {
102 reportBranding =
new ReportBranding();
106 private void refresh() throws NoCurrentCaseException {
107 currentCase = Case.getOpenCase();
108 skCase = currentCase.getSleuthkitCase();
110 dataTypes =
new TreeMap<>();
115 currentDataType =
"";
121 }
catch (IOException ex) {
133 private String dataTypeToFileName(String dataType) {
137 fileName = fileName.replaceAll(
" ",
"_");
146 private String useDataTypeIcon(String dataType) {
150 OutputStream output = null;
152 logger.log(Level.INFO,
"useDataTypeIcon: dataType = {0}", dataType);
155 BlackboardArtifact.ARTIFACT_TYPE artifactType = null;
156 for (ARTIFACT_TYPE v : ARTIFACT_TYPE.values()) {
157 if (v.getDisplayName().equals(dataType)) {
162 if (null != artifactType) {
164 iconFileName = dataTypeToFileName(artifactType.getDisplayName()) +
".png";
165 iconFilePath = subPath + File.separator + iconFileName;
168 switch (artifactType) {
169 case TSK_WEB_BOOKMARK:
170 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/bookmarks.png");
173 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/cookies.png");
175 case TSK_WEB_HISTORY:
176 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/history.png");
178 case TSK_WEB_DOWNLOAD:
179 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/downloads.png");
181 case TSK_RECENT_OBJECT:
182 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/recent.png");
184 case TSK_INSTALLED_PROG:
185 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/installed.png");
187 case TSK_KEYWORD_HIT:
188 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/keywords.png");
190 case TSK_HASHSET_HIT:
191 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/hash.png");
193 case TSK_DEVICE_ATTACHED:
194 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/devices.png");
196 case TSK_WEB_SEARCH_QUERY:
197 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/search.png");
199 case TSK_METADATA_EXIF:
200 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/exif.png");
203 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/userbookmarks.png");
205 case TSK_TAG_ARTIFACT:
206 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/userbookmarks.png");
208 case TSK_SERVICE_ACCOUNT:
209 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/account-icon-16.png");
212 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/contact.png");
215 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/message.png");
218 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/calllog.png");
220 case TSK_CALENDAR_ENTRY:
221 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/calendar.png");
223 case TSK_SPEED_DIAL_ENTRY:
224 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/speeddialentry.png");
226 case TSK_BLUETOOTH_PAIRING:
227 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/bluetooth.png");
229 case TSK_GPS_BOOKMARK:
230 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gpsfav.png");
232 case TSK_GPS_LAST_KNOWN_LOCATION:
233 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps-lastlocation.png");
236 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps-search.png");
239 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/computer.png");
241 case TSK_GPS_TRACKPOINT:
242 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps_trackpoint.png");
245 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/gps_trackpoint.png");
248 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/mail-icon-16.png");
250 case TSK_ENCRYPTION_SUSPECTED:
251 case TSK_ENCRYPTION_DETECTED:
252 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/encrypted-file.png");
254 case TSK_EXT_MISMATCH_DETECTED:
255 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/mismatch-16.png");
257 case TSK_INTERESTING_ARTIFACT_HIT:
258 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/interesting_item.png");
260 case TSK_INTERESTING_FILE_HIT:
261 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/images/interesting_item.png");
264 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/installed.png");
266 case TSK_REMOTE_DRIVE:
267 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/drive_network.png");
270 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/accounts.png");
273 logger.log(Level.WARNING,
"useDataTypeIcon: unhandled artifact type = " + dataType);
274 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png");
275 iconFileName =
"star.png";
276 iconFilePath = subPath + File.separator + iconFileName;
279 }
else if (dataType.startsWith(ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName())) {
287 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/accounts.png");
288 iconFileName =
"accounts.png";
289 iconFilePath = subPath + File.separator + iconFileName;
291 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/star.png");
292 iconFileName =
"star.png";
293 iconFilePath = subPath + File.separator + iconFileName;
297 output =
new FileOutputStream(iconFilePath);
298 FileUtil.copy(in, output);
301 }
catch (IOException ex) {
302 logger.log(Level.SEVERE,
"Failed to extract images for HTML report.", ex);
304 if (output != null) {
308 }
catch (IOException ex) {
314 }
catch (IOException ex) {
329 public void startReport(String baseReportDir) {
333 }
catch (NoCurrentCaseException ex) {
334 logger.log(Level.SEVERE,
"Exception while getting open case.");
338 this.path = baseReportDir;
339 this.subPath = this.path + HTML_SUBDIR + File.separator;
340 this.thumbsPath = this.subPath + THUMBS_REL_PATH;
342 FileUtil.createFolder(
new File(this.subPath));
343 FileUtil.createFolder(
new File(this.thumbsPath));
344 }
catch (IOException ex) {
345 logger.log(Level.SEVERE,
"Unable to make HTML report folder.");
358 public void endReport() {
363 }
catch (IOException ex) {
364 logger.log(Level.WARNING,
"Could not close the output writer when ending report.", ex);
378 public void startDataType(String name, String description) {
379 String title = dataTypeToFileName(name);
381 out =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath + title +
".html"),
"UTF-8"));
382 }
catch (FileNotFoundException ex) {
383 logger.log(Level.SEVERE,
"File not found: {0}", ex);
384 }
catch (UnsupportedEncodingException ex) {
385 logger.log(Level.SEVERE,
"Unrecognized encoding");
389 StringBuilder page =
new StringBuilder();
390 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");
391 page.append(
"<div id=\"header\">").append(name).append(
"</div>\n<div id=\"content\">\n");
392 if (!description.isEmpty()) {
393 page.append(
"<p><strong>");
394 page.append(description);
395 page.append(
"</string></p>\n");
397 out.write(page.toString());
398 currentDataType = name;
400 }
catch (IOException ex) {
401 logger.log(Level.SEVERE,
"Failed to write page head: {0}", ex);
410 public void endDataType() {
411 dataTypes.put(currentDataType, rowCount);
413 out.write(
"</div>\n</body>\n</html>\n");
414 }
catch (IOException ex) {
415 logger.log(Level.SEVERE,
"Failed to write end of HTML report.", ex);
421 }
catch (IOException ex) {
422 logger.log(Level.WARNING,
"Could not close the output writer when ending data type.", ex);
435 public void startSet(String setName) {
436 StringBuilder set =
new StringBuilder();
437 set.append(
"<h1><a name=\"").append(setName).append(
"\">").append(setName).append(
"</a></h1>\n");
438 set.append(
"<div class=\"keyword_list\">\n");
441 out.write(set.toString());
442 }
catch (IOException ex) {
443 logger.log(Level.SEVERE,
"Failed to write set: {0}", ex);
451 public void endSet() {
453 out.write(
"</div>\n");
454 }
catch (IOException ex) {
455 logger.log(Level.SEVERE,
"Failed to write end of set: {0}", ex);
465 public void addSetIndex(List<String> sets) {
466 StringBuilder index =
new StringBuilder();
467 index.append(
"<ul>\n");
468 for (String set : sets) {
469 index.append(
"\t<li><a href=\"#").append(set).append(
"\">").append(set).append(
"</a></li>\n");
471 index.append(
"</ul>\n");
473 out.write(index.toString());
474 }
catch (IOException ex) {
475 logger.log(Level.SEVERE,
"Failed to add set index: {0}", ex);
485 public void addSetElement(String elementName) {
487 out.write(
"<h4>" + elementName +
"</h4>\n");
488 }
catch (IOException ex) {
489 logger.log(Level.SEVERE,
"Failed to write set element: {0}", ex);
499 public void startTable(List<String> titles) {
500 StringBuilder ele =
new StringBuilder();
501 ele.append(
"<table>\n<thead>\n\t<tr>\n");
502 for (String title : titles) {
503 ele.append(
"\t\t<th>").append(title).append(
"</th>\n");
505 ele.append(
"\t</tr>\n</thead>\n");
508 out.write(ele.toString());
509 }
catch (IOException ex) {
510 logger.log(Level.SEVERE,
"Failed to write table start: {0}", ex);
521 public void startContentTagsTable(List<String> columnHeaders) {
522 StringBuilder htmlOutput =
new StringBuilder();
523 htmlOutput.append(
"<table>\n<thead>\n\t<tr>\n");
526 for (String columnHeader : columnHeaders) {
527 htmlOutput.append(
"\t\t<th>").append(columnHeader).append(
"</th>\n");
531 htmlOutput.append(
"\t\t<th></th>\n");
533 htmlOutput.append(
"\t</tr>\n</thead>\n");
536 out.write(htmlOutput.toString());
537 }
catch (IOException ex) {
538 logger.log(Level.SEVERE,
"Failed to write table start: {0}", ex);
546 public void endTable() {
548 out.write(
"</table>\n");
549 }
catch (IOException ex) {
550 logger.log(Level.SEVERE,
"Failed to write end of table: {0}", ex);
560 public void addRow(List<String> row) {
561 StringBuilder builder =
new StringBuilder();
562 builder.append(
"\t<tr>\n");
563 for (String cell : row) {
564 String escapeHTMLCell = EscapeUtil.escapeHtml(cell);
565 builder.append(
"\t\t<td>").append(escapeHTMLCell).append(
"</td>\n");
567 builder.append(
"\t</tr>\n");
571 out.write(builder.toString());
572 }
catch (IOException ex) {
573 logger.log(Level.SEVERE,
"Failed to write row to out.", ex);
574 }
catch (NullPointerException ex) {
575 logger.log(Level.SEVERE,
"Output writer is null. Page was not initialized before writing.", ex);
589 public void addRowWithTaggedContentHyperlink(List<String> row, ContentTag contentTag) {
590 Content content = contentTag.getContent();
591 if (content instanceof AbstractFile ==
false) {
595 AbstractFile file = (AbstractFile) content;
597 StringBuilder localFileLink =
new StringBuilder();
600 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
601 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)) {
602 localFileLink.append(
"<a href=\"");
604 String localFilePath = saveContent(file, contentTag.getName().getDisplayName());
605 localFileLink.append(localFilePath);
606 localFileLink.append(
"\" target=\"_top\">");
609 StringBuilder builder =
new StringBuilder();
610 builder.append(
"\t<tr>\n");
611 int positionCounter = 0;
612 for (String cell : row) {
614 if (positionCounter == 1) {
615 builder.append(
"\t\t<td class=\"left_align_cell\">").append(localFileLink.toString()).append(cell).append(
"</a></td>\n");
616 }
else if (positionCounter == 7) {
617 builder.append(
"\t\t<td class=\"right_align_cell\">").append(cell).append(
"</td>\n");
619 builder.append(
"\t\t<td>").append(cell).append(
"</td>\n");
623 builder.append(
"\t</tr>\n");
627 out.write(builder.toString());
628 }
catch (IOException ex) {
629 logger.log(Level.SEVERE,
"Failed to write row to out.", ex);
630 }
catch (NullPointerException ex) {
631 logger.log(Level.SEVERE,
"Output writer is null. Page was not initialized before writing.", ex);
640 public void addThumbnailRows(Set<Content> images) {
641 List<String> currentRow =
new ArrayList<>();
644 for (Content content : images) {
645 if (currentRow.size() == THUMBNAIL_COLUMNS) {
650 if (totalCount == MAX_THUMBS_PER_PAGE) {
654 rowCount = totalCount;
659 startDataType(NbBundle.getMessage(
this.getClass(),
"ReportHTML.addThumbRows.dataType.title", pages),
660 NbBundle.getMessage(
this.getClass(),
"ReportHTML.addThumbRows.dataType.msg"));
661 List<String> emptyHeaders =
new ArrayList<>();
662 for (
int i = 0; i < THUMBNAIL_COLUMNS; i++) {
663 emptyHeaders.add(
"");
665 startTable(emptyHeaders);
668 if (failsContentCheck(content)) {
672 AbstractFile file = (AbstractFile) content;
675 String thumbnailPath = prepareThumbnail(file);
676 if (thumbnailPath == null) {
679 String contentPath = saveContent(file,
"thumbs_fullsize");
682 nameInImage = file.getUniquePath();
683 }
catch (TskCoreException ex) {
684 nameInImage = file.getName();
687 StringBuilder linkToThumbnail =
new StringBuilder();
688 linkToThumbnail.append(
"<div id='thumbnail_link'>");
689 linkToThumbnail.append(
"<a href=\"");
690 linkToThumbnail.append(contentPath);
691 linkToThumbnail.append(
"\" target=\"_top\">");
692 linkToThumbnail.append(
"<img src=\"").append(thumbnailPath).append(
"\" title=\"").append(nameInImage).append(
"\"/>");
693 linkToThumbnail.append(
"</a><br>");
694 linkToThumbnail.append(file.getName()).append(
"<br>");
696 Services services = currentCase.getServices();
697 TagsManager tagsManager = services.getTagsManager();
699 List<ContentTag> tags = tagsManager.getContentTagsByContent(content);
700 if (tags.size() > 0) {
701 linkToThumbnail.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.thumbLink.tags"));
703 for (
int i = 0; i < tags.size(); i++) {
704 ContentTag tag = tags.get(i);
705 String notableString = tag.getName().getKnownStatus() == TskData.FileKnown.BAD ? TagsManager.getNotableTagLabel() :
"";
706 linkToThumbnail.append(tag.getName().getDisplayName() + notableString);
707 if (i != tags.size() - 1) {
708 linkToThumbnail.append(
", ");
711 }
catch (TskCoreException ex) {
712 logger.log(Level.WARNING,
"Could not find get tags for file.", ex);
714 linkToThumbnail.append(
"</div>");
715 currentRow.add(linkToThumbnail.toString());
720 if (currentRow.isEmpty() ==
false) {
721 int extraCells = THUMBNAIL_COLUMNS - currentRow.size();
722 for (
int i = 0; i < extraCells; i++) {
730 rowCount = totalCount;
733 private boolean failsContentCheck(Content c) {
734 if (c instanceof AbstractFile ==
false) {
737 AbstractFile file = (AbstractFile) c;
739 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
740 || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) {
755 public String saveContent(AbstractFile file, String dirName) {
760 StringBuilder localFilePath =
new StringBuilder();
762 localFilePath.append(subPath);
763 localFilePath.append(dirName2);
764 File localFileFolder =
new File(localFilePath.toString());
765 if (!localFileFolder.exists()) {
766 localFileFolder.mkdirs();
770 String fileName = file.getName();
771 String objectIdSuffix =
"_" + file.getId();
772 int lastDotIndex = fileName.lastIndexOf(
".");
773 if (lastDotIndex != -1 && lastDotIndex != 0) {
775 fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length());
779 fileName += objectIdSuffix;
781 localFilePath.append(File.separator);
782 localFilePath.append(fileName);
786 File localFile =
new File(localFilePath.toString());
787 if (!localFile.exists()) {
788 ExtractFscContentVisitor.extract(file, localFile, null, null);
792 return localFilePath.toString().substring(subPath.length());
803 public String dateToString(
long date) {
804 SimpleDateFormat sdf =
new java.text.SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
805 return sdf.format(
new java.util.Date(date * 1000));
809 public String getRelativeFilePath() {
810 return "report.html";
814 public String getName() {
815 return NbBundle.getMessage(this.getClass(),
"ReportHTML.getName.text");
819 public String getDescription() {
820 return NbBundle.getMessage(this.getClass(),
"ReportHTML.getDesc.text");
826 private void writeCss() {
827 Writer cssOut = null;
829 cssOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"index.css"),
"UTF-8"));
830 String css =
"body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;}\n"
832 "#content {padding: 30px;}\n"
834 "#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;}\n"
836 "h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;}\n"
838 "h2 {font-size: 20px; font-weight: bolder; color: #07A;}\n"
840 "h3 {font-size: 16px; color: #07A;}\n"
842 "h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;}\n"
844 "ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;}\n"
846 "ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;}\n"
848 "ul li a:hover {text-decoration: underline;}\n"
850 "p {margin: 0 0 20px 0;}\n"
852 "table {white-space:nowrap; min-width: 700px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;}\n"
854 ".keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;}\n"
856 "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"
858 "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"
860 "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"
862 "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"
864 "table tr:nth-child(even) td {background: #f3f3f3;}\n"
866 "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;}";
868 }
catch (FileNotFoundException ex) {
869 logger.log(Level.SEVERE,
"Could not find index.css file to write to.", ex);
870 }
catch (UnsupportedEncodingException ex) {
871 logger.log(Level.SEVERE,
"Did not recognize encoding when writing index.css.", ex);
872 }
catch (IOException ex) {
873 logger.log(Level.SEVERE,
"Error creating Writer for index.css.", ex);
876 if (cssOut != null) {
880 }
catch (IOException ex) {
888 private void writeIndex() {
889 Writer indexOut = null;
890 String indexFilePath = path +
"report.html";
893 openCase = Case.getOpenCase();
894 }
catch (NoCurrentCaseException ex) {
895 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
899 indexOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(indexFilePath),
"UTF-8"));
900 StringBuilder index =
new StringBuilder();
901 final String reportTitle = reportBranding.getReportTitle();
902 String iconPath = reportBranding.getAgencyLogoPath();
903 if (iconPath == null) {
905 iconPath = HTML_SUBDIR +
"favicon.ico";
907 iconPath = Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString();
909 index.append(
"<head>\n<title>").append(reportTitle).append(
" ").append(
910 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.title", currentCase.getDisplayName())).append(
912 index.append(
"<link rel=\"icon\" type=\"image/ico\" href=\"")
913 .append(iconPath).append(
"\" />\n");
914 index.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n");
915 index.append(
"</head>\n");
916 index.append(
"<frameset cols=\"350px,*\">\n");
917 index.append(
"<frame src=\"" + HTML_SUBDIR).append(File.separator).append(
"nav.html\" name=\"nav\">\n");
918 index.append(
"<frame src=\"" + HTML_SUBDIR).append(File.separator).append(
"summary.html\" name=\"content\">\n");
919 index.append(
"<noframes>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.noFrames.msg")).append(
"<br />\n");
920 index.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.noFrames.seeNav")).append(
"<br />\n");
921 index.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeIndex.seeSum")).append(
"</noframes>\n");
922 index.append(
"</frameset>\n");
923 index.append(
"</html>");
924 indexOut.write(index.toString());
925 openCase.addReport(indexFilePath, NbBundle.getMessage(
this.getClass(),
926 "ReportHTML.writeIndex.srcModuleName.text"),
"");
927 }
catch (IOException ex) {
928 logger.log(Level.SEVERE,
"Error creating Writer for report.html: {0}", ex);
929 }
catch (TskCoreException ex) {
930 String errorMessage = String.format(
"Error adding %s to case as a report", indexFilePath);
931 logger.log(Level.SEVERE, errorMessage, ex);
934 if (indexOut != null) {
938 }
catch (IOException ex) {
946 private void writeNav() {
947 Writer navOut = null;
949 navOut =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"nav.html"),
"UTF-8"));
950 StringBuilder nav =
new StringBuilder();
951 nav.append(
"<html>\n<head>\n\t<title>").append(
952 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.title"))
953 .append(
"</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n");
954 nav.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n");
955 nav.append(
"<div id=\"content\">\n<h1>").append(
956 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.h1")).append(
"</h1>\n");
957 nav.append(
"<ul class=\"nav\">\n");
958 nav.append(
"<li style=\"background: url(summary.png) left center no-repeat;\"><a href=\"summary.html\" target=\"content\">")
959 .append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeNav.summary")).append(
"</a></li>\n");
961 for (String dataType : dataTypes.keySet()) {
962 String dataTypeEsc = dataTypeToFileName(dataType);
963 String iconFileName = useDataTypeIcon(dataType);
964 nav.append(
"<li style=\"background: url('").append(iconFileName)
965 .append(
"') left center no-repeat;\"><a href=\"")
966 .append(dataTypeEsc).append(
".html\" target=\"content\">")
967 .append(dataType).append(
" (").append(dataTypes.get(dataType))
968 .append(
")</a></li>\n");
970 nav.append(
"</ul>\n");
971 nav.append(
"</div>\n</body>\n</html>");
972 navOut.write(nav.toString());
973 }
catch (IOException ex) {
974 logger.log(Level.SEVERE,
"Failed to write end of report navigation menu: {0}", ex);
976 if (navOut != null) {
980 }
catch (IOException ex) {
981 logger.log(Level.WARNING,
"Could not close navigation out writer.");
986 InputStream in = null;
987 OutputStream output = null;
991 String generatorLogoPath = reportBranding.getGeneratorLogoPath();
992 if (generatorLogoPath != null && !generatorLogoPath.isEmpty()) {
993 File from =
new File(generatorLogoPath);
994 File to =
new File(subPath);
995 FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to),
"generator_logo");
998 String agencyLogoPath = reportBranding.getAgencyLogoPath();
999 if (agencyLogoPath != null && !agencyLogoPath.isEmpty()) {
1000 Path destinationPath = Paths.get(subPath);
1001 Files.copy(Files.newInputStream(Paths.get(agencyLogoPath)), destinationPath.resolve(Paths.get(agencyLogoPath).getFileName()));
1004 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/favicon.ico");
1005 output =
new FileOutputStream(
new File(subPath +
"favicon.ico"));
1006 FileUtil.copy(in, output);
1010 in = getClass().getResourceAsStream(
"/org/sleuthkit/autopsy/report/images/summary.png");
1011 output =
new FileOutputStream(
new File(subPath +
"summary.png"));
1012 FileUtil.copy(in, output);
1016 }
catch (IOException ex) {
1017 logger.log(Level.SEVERE,
"Failed to extract images for HTML report.", ex);
1019 if (output != null) {
1023 }
catch (IOException ex) {
1029 }
catch (IOException ex) {
1038 private void writeSummary() {
1041 out =
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(subPath +
"summary.html"),
"UTF-8"));
1042 StringBuilder head =
new StringBuilder();
1043 head.append(
"<html>\n<head>\n<title>").append(
1044 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.title")).append(
"</title>\n");
1045 head.append(
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n");
1046 head.append(
"<style type=\"text/css\">\n");
1047 head.append(
"body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n");
1048 head.append(
"#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n");
1049 head.append(
"h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n");
1050 head.append(
"h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;}\n");
1051 head.append(
"h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; }\n");
1052 head.append(
"table td { padding-right: 25px; }\n");
1053 head.append(
"p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; }\n");
1054 head.append(
".title { width: 660px; margin-bottom: 50px; }\n");
1055 head.append(
".left { float: left; width: 250px; margin-top: 20px; text-align: center; }\n");
1056 head.append(
".left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; }\n");
1057 head.append(
".right { float: right; width: 385px; margin-top: 25px; font-size: 14px; }\n");
1058 head.append(
".clear { clear: both; }\n");
1059 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");
1060 head.append(
".info table { margin: 0 25px 20px 25px; }\n");
1061 head.append(
"</style>\n");
1062 head.append(
"</head>\n<body>\n");
1063 out.write(head.toString());
1065 DateFormat datetimeFormat =
new SimpleDateFormat(
"yyyy/MM/dd HH:mm:ss");
1066 Date date =
new Date();
1067 String datetime = datetimeFormat.format(date);
1069 String caseName = currentCase.getDisplayName();
1070 String caseNumber = currentCase.getNumber();
1071 String examiner = currentCase.getExaminer();
1074 imagecount = currentCase.getDataSources().size();
1075 }
catch (TskCoreException ex) {
1079 StringBuilder summary =
new StringBuilder();
1080 boolean running =
false;
1081 if (IngestManager.getInstance().isIngestRunning()) {
1085 final String reportTitle = reportBranding.getReportTitle();
1086 final String reportFooter = reportBranding.getReportFooter();
1087 final boolean agencyLogoSet = reportBranding.getAgencyLogoPath() != null && !reportBranding.getAgencyLogoPath().isEmpty();
1088 final boolean generatorLogoSet = reportBranding.getGeneratorLogoPath() != null && !reportBranding.getGeneratorLogoPath().isEmpty();
1090 summary.append(
"<div id=\"wrapper\">\n");
1091 summary.append(
"<h1>").append(reportTitle)
1092 .append(running ? NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.warningMsg") :
"")
1094 summary.append(
"<p class=\"subheadding\">").append(
1095 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.reportGenOn.text", datetime)).append(
"</p>\n");
1096 summary.append(
"<div class=\"title\">\n");
1097 if (agencyLogoSet) {
1098 summary.append(
"<div class=\"left\">\n");
1099 summary.append(
"<img src=\"");
1100 summary.append(Paths.get(reportBranding.getAgencyLogoPath()).getFileName().toString());
1101 summary.append(
"\" />\n");
1102 summary.append(
"</div>\n");
1104 final String align = agencyLogoSet ?
"right" :
"left";
1105 summary.append(
"<div class=\"").append(align).append(
"\">\n");
1106 summary.append(
"<table>\n");
1107 summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.caseName"))
1108 .append(
"</td><td>").append(caseName).append(
"</td></tr>\n");
1109 summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.caseNum"))
1110 .append(
"</td><td>").append(!caseNumber.isEmpty() ? caseNumber : NbBundle
1111 .getMessage(this.getClass(),
"ReportHTML.writeSum.noCaseNum")).append(
"</td></tr>\n");
1112 summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.examiner")).append(
"</td><td>")
1113 .append(!examiner.isEmpty() ? examiner : NbBundle
1114 .getMessage(this.getClass(),
"ReportHTML.writeSum.noExaminer"))
1115 .append(
"</td></tr>\n");
1116 summary.append(
"<tr><td>").append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.numImages"))
1117 .append(
"</td><td>").append(imagecount).append(
"</td></tr>\n");
1118 summary.append(
"</table>\n");
1119 summary.append(
"</div>\n");
1120 summary.append(
"<div class=\"clear\"></div>\n");
1121 summary.append(
"</div>\n");
1122 summary.append(NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.imageInfoHeading"));
1123 summary.append(
"<div class=\"info\">\n");
1125 for (Content c : currentCase.getDataSources()) {
1126 summary.append(
"<p>").append(c.getName()).append(
"</p>\n");
1127 if (c instanceof Image) {
1128 Image img = (Image) c;
1130 summary.append(
"<table>\n");
1131 summary.append(
"<tr><td>").append(
1132 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.timezone"))
1133 .append(
"</td><td>").append(img.getTimeZone()).append(
"</td></tr>\n");
1134 for (String imgPath : img.getPaths()) {
1135 summary.append(
"<tr><td>").append(
1136 NbBundle.getMessage(
this.getClass(),
"ReportHTML.writeSum.path"))
1137 .append(
"</td><td>").append(imgPath).append(
"</td></tr>\n");
1139 summary.append(
"</table>\n");
1142 }
catch (TskCoreException ex) {
1143 logger.log(Level.WARNING,
"Unable to get image information for the HTML report.");
1145 summary.append(
"</div>\n");
1146 if (generatorLogoSet) {
1147 summary.append(
"<div class=\"left\">\n");
1148 summary.append(
"<img src=\"generator_logo.png\" />\n");
1149 summary.append(
"</div>\n");
1151 summary.append(
"<div class=\"clear\"></div>\n");
1152 if (reportFooter != null) {
1153 summary.append(
"<p class=\"subheadding\">").append(reportFooter).append(
"</p>\n");
1155 summary.append(
"</div>\n");
1156 summary.append(
"</body></html>");
1157 out.write(summary.toString());
1158 }
catch (FileNotFoundException ex) {
1159 logger.log(Level.SEVERE,
"Could not find summary.html file to write to.");
1160 }
catch (UnsupportedEncodingException ex) {
1161 logger.log(Level.SEVERE,
"Did not recognize encoding when writing summary.hmtl.");
1162 }
catch (IOException ex) {
1163 logger.log(Level.SEVERE,
"Error creating Writer for summary.html.");
1170 }
catch (IOException ex) {
1175 private String prepareThumbnail(AbstractFile file) {
1176 BufferedImage bufferedThumb = ImageUtils.getThumbnail(file, ImageUtils.ICON_SIZE_MEDIUM);
1177 File thumbFile = Paths.get(thumbsPath, file.getName() +
".png").toFile();
1178 if (bufferedThumb == null) {
1182 ImageIO.write(bufferedThumb,
"png", thumbFile);
1183 }
catch (IOException ex) {
1184 logger.log(Level.WARNING,
"Failed to write thumb file to report directory.", ex);
1187 if (thumbFile.exists()
1191 return THUMBS_REL_PATH
1192 + thumbFile.getName();
static String escapeFileName(String fileName)