Autopsy  4.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
ReportHTML.java
Go to the documentation of this file.
1 /*
2  *
3  * Autopsy Forensic Browser
4  *
5  * Copyright 2012-2014 Basis Technology Corp.
6  *
7  * Copyright 2012 42six Solutions.
8  * Contact: aebadirad <at> 42six <dot> com
9  * Project Contact/Architect: carrier <at> sleuthkit <dot> org
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 package org.sleuthkit.autopsy.report;
24 
25 import java.io.BufferedWriter;
26 import java.io.File;
27 import java.io.FileNotFoundException;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.io.OutputStreamWriter;
33 import java.io.UnsupportedEncodingException;
34 import java.io.Writer;
35 import java.text.DateFormat;
36 import java.text.SimpleDateFormat;
37 import java.util.ArrayList;
38 import java.util.Date;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.TreeMap;
42 import java.util.logging.Level;
43 import org.openide.filesystems.FileObject;
44 import org.openide.filesystems.FileUtil;
45 import org.openide.util.NbBundle;
53 import org.sleuthkit.datamodel.AbstractFile;
54 import org.sleuthkit.datamodel.BlackboardArtifact;
55 import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
56 import org.sleuthkit.datamodel.Content;
57 import org.sleuthkit.datamodel.ContentTag;
58 import org.sleuthkit.datamodel.Image;
59 import org.sleuthkit.datamodel.SleuthkitCase;
60 import org.sleuthkit.datamodel.TskCoreException;
61 import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
62 
63 class ReportHTML implements TableReportModule {
64 
65  private static final Logger logger = Logger.getLogger(ReportHTML.class.getName());
66  private static final String THUMBS_REL_PATH = "thumbs" + File.separator; //NON-NLS
67  private static ReportHTML instance;
68  private static final int MAX_THUMBS_PER_PAGE = 1000;
69  private Case currentCase;
70  private SleuthkitCase skCase;
71  static Integer THUMBNAIL_COLUMNS = 5;
72 
73  private Map<String, Integer> dataTypes;
74  private String path;
75  private String thumbsPath;
76  private String currentDataType; // name of current data type
77  private Integer rowCount; // number of rows (aka artifacts or tags) for the current data type
78  private Writer out;
79 
80  private final ReportBranding reportBranding;
81 
82  // Get the default instance of this report
83  public static synchronized ReportHTML getDefault() {
84  if (instance == null) {
85  instance = new ReportHTML();
86  }
87  return instance;
88  }
89 
90  // Hidden constructor
91  private ReportHTML() {
92  reportBranding = new ReportBranding();
93  }
94 
95  // Refesh the member variables
96  private void refresh() {
97  currentCase = Case.getCurrentCase();
98  skCase = currentCase.getSleuthkitCase();
99 
100  dataTypes = new TreeMap<>();
101 
102  path = "";
103  thumbsPath = "";
104  currentDataType = "";
105  rowCount = 0;
106 
107  if (out != null) {
108  try {
109  out.close();
110  } catch (IOException ex) {
111  }
112  }
113  out = null;
114  }
115 
122  private String dataTypeToFileName(String dataType) {
123 
124  String fileName = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(dataType);
125  // replace all ' ' with '_'
126  fileName = fileName.replaceAll(" ", "_");
127 
128  return fileName;
129  }
130 
135  private String useDataTypeIcon(String dataType) {
136  String iconFilePath;
137  String iconFileName;
138  InputStream in;
139  OutputStream output = null;
140 
141  logger.log(Level.INFO, "useDataTypeIcon: dataType = {0}", dataType); //NON-NLS
142 
143  // find the artifact with matching display name
144  BlackboardArtifact.ARTIFACT_TYPE artifactType = null;
145  for (ARTIFACT_TYPE v : ARTIFACT_TYPE.values()) {
146  if (v.getDisplayName().equals(dataType)) {
147  artifactType = v;
148  }
149  }
150 
151  if (null != artifactType) {
152  // set the icon file name
153  iconFileName = dataTypeToFileName(artifactType.getDisplayName()) + ".png"; //NON-NLS
154  iconFilePath = path + File.separator + iconFileName;
155 
156  // determine the source image to use
157  switch (artifactType) {
158  case TSK_WEB_BOOKMARK:
159  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/bookmarks.png"); //NON-NLS
160  break;
161  case TSK_WEB_COOKIE:
162  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/cookies.png"); //NON-NLS
163  break;
164  case TSK_WEB_HISTORY:
165  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/history.png"); //NON-NLS
166  break;
167  case TSK_WEB_DOWNLOAD:
168  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/downloads.png"); //NON-NLS
169  break;
170  case TSK_RECENT_OBJECT:
171  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/recent.png"); //NON-NLS
172  break;
173  case TSK_INSTALLED_PROG:
174  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/installed.png"); //NON-NLS
175  break;
176  case TSK_KEYWORD_HIT:
177  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/keywords.png"); //NON-NLS
178  break;
179  case TSK_HASHSET_HIT:
180  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/hash.png"); //NON-NLS
181  break;
182  case TSK_DEVICE_ATTACHED:
183  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/devices.png"); //NON-NLS
184  break;
185  case TSK_WEB_SEARCH_QUERY:
186  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/search.png"); //NON-NLS
187  break;
188  case TSK_METADATA_EXIF:
189  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/exif.png"); //NON-NLS
190  break;
191  case TSK_TAG_FILE:
192  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/userbookmarks.png"); //NON-NLS
193  break;
194  case TSK_TAG_ARTIFACT:
195  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/userbookmarks.png"); //NON-NLS
196  break;
197  case TSK_SERVICE_ACCOUNT:
198  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/account-icon-16.png"); //NON-NLS
199  break;
200  case TSK_CONTACT:
201  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/contact.png"); //NON-NLS
202  break;
203  case TSK_MESSAGE:
204  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/message.png"); //NON-NLS
205  break;
206  case TSK_CALLLOG:
207  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/calllog.png"); //NON-NLS
208  break;
209  case TSK_CALENDAR_ENTRY:
210  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/calendar.png"); //NON-NLS
211  break;
212  case TSK_SPEED_DIAL_ENTRY:
213  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/speeddialentry.png"); //NON-NLS
214  break;
215  case TSK_BLUETOOTH_PAIRING:
216  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/bluetooth.png"); //NON-NLS
217  break;
218  case TSK_GPS_BOOKMARK:
219  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gpsfav.png"); //NON-NLS
220  break;
221  case TSK_GPS_LAST_KNOWN_LOCATION:
222  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps-lastlocation.png"); //NON-NLS
223  break;
224  case TSK_GPS_SEARCH:
225  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps-search.png"); //NON-NLS
226  break;
227  case TSK_OS_INFO:
228  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/computer.png"); //NON-NLS
229  break;
230  case TSK_GPS_TRACKPOINT:
231  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); //NON-NLS
232  break;
233  case TSK_GPS_ROUTE:
234  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/gps_trackpoint.png"); //NON-NLS
235  break;
236  case TSK_EMAIL_MSG:
237  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/mail-icon-16.png"); //NON-NLS
238  break;
239  case TSK_ENCRYPTION_DETECTED:
240  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/encrypted-file.png"); //NON-NLS
241  break;
242  case TSK_EXT_MISMATCH_DETECTED:
243  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/mismatch-16.png"); //NON-NLS
244  break;
245  case TSK_INTERESTING_ARTIFACT_HIT:
246  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS
247  break;
248  case TSK_INTERESTING_FILE_HIT:
249  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/images/interesting_item.png"); //NON-NLS
250  break;
251  case TSK_PROG_RUN:
252  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/installed.png"); //NON-NLS
253  break;
254  case TSK_REMOTE_DRIVE:
255  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/drive_network.png"); //NON-NLS
256  break;
257  case TSK_ACCOUNT:
258  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS
259  break;
260  default:
261  logger.log(Level.WARNING, "useDataTypeIcon: unhandled artifact type = " + dataType); //NON-NLS
262  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS
263  iconFileName = "star.png"; //NON-NLS
264  iconFilePath = path + File.separator + iconFileName;
265  break;
266  }
267  } else if (dataType.startsWith(ARTIFACT_TYPE.TSK_ACCOUNT.getDisplayName())) {
268  /* TSK_ACCOUNT artifacts get separated by their TSK_ACCOUNT_TYPE
269  * attribute, with a synthetic compound dataType name, so they are
270  * not caught by the switch statement above. For now we just give
271  * them all the general account icon, but we could do something else
272  * in the future.
273  */
274  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/accounts.png"); //NON-NLS
275  iconFileName = "accounts.png"; //NON-NLS
276  iconFilePath = path + File.separator + iconFileName;
277  } else { // no defined artifact found for this dataType
278  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/star.png"); //NON-NLS
279  iconFileName = "star.png"; //NON-NLS
280  iconFilePath = path + File.separator + iconFileName;
281  }
282 
283  try {
284  output = new FileOutputStream(iconFilePath);
285  FileUtil.copy(in, output);
286  in.close();
287  output.close();
288  } catch (IOException ex) {
289  logger.log(Level.SEVERE, "Failed to extract images for HTML report.", ex); //NON-NLS
290  } finally {
291  if (output != null) {
292  try {
293  output.flush();
294  output.close();
295  } catch (IOException ex) {
296  }
297  }
298  if (in != null) {
299  try {
300  in.close();
301  } catch (IOException ex) {
302  }
303  }
304  }
305 
306  return iconFileName;
307  }
308 
315  @Override
316  public void startReport(String baseReportDir) {
317  // Refresh the HTML report
318  refresh();
319  // Setup the path for the HTML report
320  this.path = baseReportDir + "HTML Report" + File.separator; //NON-NLS
321  this.thumbsPath = this.path + "thumbs" + File.separator; //NON-NLS
322  try {
323  FileUtil.createFolder(new File(this.path));
324  FileUtil.createFolder(new File(this.thumbsPath));
325  } catch (IOException ex) {
326  logger.log(Level.SEVERE, "Unable to make HTML report folder."); //NON-NLS
327  }
328  // Write the basic files
329  writeCss();
330  writeIndex();
331  writeSummary();
332  }
333 
338  @Override
339  public void endReport() {
340  writeNav();
341  if (out != null) {
342  try {
343  out.close();
344  } catch (IOException ex) {
345  logger.log(Level.WARNING, "Could not close the output writer when ending report.", ex); //NON-NLS
346  }
347  }
348  }
349 
358  @Override
359  public void startDataType(String name, String description) {
360  String title = dataTypeToFileName(name);
361  try {
362  out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path + title + ".html"), "UTF-8")); //NON-NLS
363  } catch (FileNotFoundException ex) {
364  logger.log(Level.SEVERE, "File not found: {0}", ex); //NON-NLS
365  } catch (UnsupportedEncodingException ex) {
366  logger.log(Level.SEVERE, "Unrecognized encoding"); //NON-NLS
367  }
368 
369  try {
370  StringBuilder page = new StringBuilder();
371  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"); //NON-NLS
372  page.append("<div id=\"header\">").append(name).append("</div>\n<div id=\"content\">\n"); //NON-NLS
373  if (!description.isEmpty()) {
374  page.append("<p><strong>"); //NON-NLS
375  page.append(description);
376  page.append("</string></p>\n"); //NON-NLS
377  }
378  out.write(page.toString());
379  currentDataType = name;
380  rowCount = 0;
381  } catch (IOException ex) {
382  logger.log(Level.SEVERE, "Failed to write page head: {0}", ex); //NON-NLS
383  }
384  }
385 
390  @Override
391  public void endDataType() {
392  dataTypes.put(currentDataType, rowCount);
393  try {
394  out.write("</div>\n</body>\n</html>\n"); //NON-NLS
395  } catch (IOException ex) {
396  logger.log(Level.SEVERE, "Failed to write end of HTML report.", ex); //NON-NLS
397  } finally {
398  if (out != null) {
399  try {
400  out.flush();
401  out.close();
402  } catch (IOException ex) {
403  logger.log(Level.WARNING, "Could not close the output writer when ending data type.", ex); //NON-NLS
404  }
405  out = null;
406  }
407  }
408  }
409 
415  @Override
416  public void startSet(String setName) {
417  StringBuilder set = new StringBuilder();
418  set.append("<h1><a name=\"").append(setName).append("\">").append(setName).append("</a></h1>\n"); //NON-NLS
419  set.append("<div class=\"keyword_list\">\n"); //NON-NLS
420 
421  try {
422  out.write(set.toString());
423  } catch (IOException ex) {
424  logger.log(Level.SEVERE, "Failed to write set: {0}", ex); //NON-NLS
425  }
426  }
427 
431  @Override
432  public void endSet() {
433  try {
434  out.write("</div>\n"); //NON-NLS
435  } catch (IOException ex) {
436  logger.log(Level.SEVERE, "Failed to write end of set: {0}", ex); //NON-NLS
437  }
438  }
439 
445  @Override
446  public void addSetIndex(List<String> sets) {
447  StringBuilder index = new StringBuilder();
448  index.append("<ul>\n"); //NON-NLS
449  for (String set : sets) {
450  index.append("\t<li><a href=\"#").append(set).append("\">").append(set).append("</a></li>\n"); //NON-NLS
451  }
452  index.append("</ul>\n"); //NON-NLS
453  try {
454  out.write(index.toString());
455  } catch (IOException ex) {
456  logger.log(Level.SEVERE, "Failed to add set index: {0}", ex); //NON-NLS
457  }
458  }
459 
465  @Override
466  public void addSetElement(String elementName) {
467  try {
468  out.write("<h4>" + elementName + "</h4>\n"); //NON-NLS
469  } catch (IOException ex) {
470  logger.log(Level.SEVERE, "Failed to write set element: {0}", ex); //NON-NLS
471  }
472  }
473 
479  @Override
480  public void startTable(List<String> titles) {
481  StringBuilder ele = new StringBuilder();
482  ele.append("<table>\n<thead>\n\t<tr>\n"); //NON-NLS
483  for (String title : titles) {
484  ele.append("\t\t<th>").append(title).append("</th>\n"); //NON-NLS
485  }
486  ele.append("\t</tr>\n</thead>\n"); //NON-NLS
487 
488  try {
489  out.write(ele.toString());
490  } catch (IOException ex) {
491  logger.log(Level.SEVERE, "Failed to write table start: {0}", ex); //NON-NLS
492  }
493  }
494 
502  public void startContentTagsTable(List<String> columnHeaders) {
503  StringBuilder htmlOutput = new StringBuilder();
504  htmlOutput.append("<table>\n<thead>\n\t<tr>\n"); //NON-NLS
505 
506  // Add the specified columns.
507  for (String columnHeader : columnHeaders) {
508  htmlOutput.append("\t\t<th>").append(columnHeader).append("</th>\n"); //NON-NLS
509  }
510 
511  // Add a column for a hyperlink to a local copy of the tagged content.
512  htmlOutput.append("\t\t<th></th>\n"); //NON-NLS
513 
514  htmlOutput.append("\t</tr>\n</thead>\n"); //NON-NLS
515 
516  try {
517  out.write(htmlOutput.toString());
518  } catch (IOException ex) {
519  logger.log(Level.SEVERE, "Failed to write table start: {0}", ex); //NON-NLS
520  }
521  }
522 
526  @Override
527  public void endTable() {
528  try {
529  out.write("</table>\n"); //NON-NLS
530  } catch (IOException ex) {
531  logger.log(Level.SEVERE, "Failed to write end of table: {0}", ex); //NON-NLS
532  }
533  }
534 
540  @Override
541  public void addRow(List<String> row) {
542  StringBuilder builder = new StringBuilder();
543  builder.append("\t<tr>\n"); //NON-NLS
544  for (String cell : row) {
545  builder.append("\t\t<td>").append(cell).append("</td>\n"); //NON-NLS
546  }
547  builder.append("\t</tr>\n"); //NON-NLS
548  rowCount++;
549 
550  try {
551  out.write(builder.toString());
552  } catch (IOException ex) {
553  logger.log(Level.SEVERE, "Failed to write row to out.", ex); //NON-NLS
554  } catch (NullPointerException ex) {
555  logger.log(Level.SEVERE, "Output writer is null. Page was not initialized before writing.", ex); //NON-NLS
556  }
557  }
558 
569  public void addRowWithTaggedContentHyperlink(List<String> row, ContentTag contentTag) {
570  Content content = contentTag.getContent();
571  if (content instanceof AbstractFile == false) {
572  addRow(row);
573  return;
574  }
575  AbstractFile file = (AbstractFile) content;
576  // Add the hyperlink to the row. A column header for it was created in startTable().
577  StringBuilder localFileLink = new StringBuilder();
578  // Don't make a local copy of the file if it is a directory or unallocated space.
579  if (!(file.isDir()
580  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
581  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS)) {
582  localFileLink.append("<a href=\""); //NON-NLS
583  // save it in a folder based on the tag name
584  String localFilePath = saveContent(file, contentTag.getName().getDisplayName());
585  localFileLink.append(localFilePath);
586  localFileLink.append("\">");
587  }
588 
589  StringBuilder builder = new StringBuilder();
590  builder.append("\t<tr>\n"); //NON-NLS
591  int positionCounter = 0;
592  for (String cell : row) {
593  // position-dependent code used to format this report. Not great, but understandable for formatting.
594  if (positionCounter == 1) { // Convert the file name to a hyperlink and left-align it
595  builder.append("\t\t<td class=\"left_align_cell\">").append(localFileLink.toString()).append(cell).append("</a></td>\n"); //NON-NLS
596  } else if (positionCounter == 7) { // Right-align the bytes column.
597  builder.append("\t\t<td class=\"right_align_cell\">").append(cell).append("</td>\n"); //NON-NLS
598  } else { // Regular case, not a file name nor a byte count
599  builder.append("\t\t<td>").append(cell).append("</td>\n"); //NON-NLS
600  }
601  ++positionCounter;
602  }
603  builder.append("\t</tr>\n"); //NON-NLS
604  rowCount++;
605 
606  try {
607  out.write(builder.toString());
608  } catch (IOException ex) {
609  logger.log(Level.SEVERE, "Failed to write row to out.", ex); //NON-NLS
610  } catch (NullPointerException ex) {
611  logger.log(Level.SEVERE, "Output writer is null. Page was not initialized before writing.", ex); //NON-NLS
612  }
613  }
614 
620  public void addThumbnailRows(List<Content> images) {
621  List<String> currentRow = new ArrayList<>();
622  int totalCount = 0;
623  int pages = 0;
624  for (Content content : images) {
625  if (currentRow.size() == THUMBNAIL_COLUMNS) {
626  addRow(currentRow);
627  currentRow.clear();
628  }
629 
630  if (totalCount == MAX_THUMBS_PER_PAGE) {
631  // manually set the row count so the count of items shown in the
632  // navigation page reflects the number of thumbnails instead of
633  // the number of rows.
634  rowCount = totalCount;
635  totalCount = 0;
636  pages++;
637  endTable();
638  endDataType();
639  startDataType(NbBundle.getMessage(this.getClass(), "ReportHTML.addThumbRows.dataType.title", pages),
640  NbBundle.getMessage(this.getClass(), "ReportHTML.addThumbRows.dataType.msg"));
641  List<String> emptyHeaders = new ArrayList<>();
642  for (int i = 0; i < THUMBNAIL_COLUMNS; i++) {
643  emptyHeaders.add("");
644  }
645  startTable(emptyHeaders);
646  }
647 
648  if (failsContentCheck(content)) {
649  continue;
650  }
651 
652  AbstractFile file = (AbstractFile) content;
653 
654  // save copies of the orginal image and thumbnail image
655  String thumbnailPath = prepareThumbnail(file);
656  if (thumbnailPath == null) {
657  continue;
658  }
659  String contentPath = saveContent(file, "thumbs_fullsize"); //NON-NLS
660  String nameInImage;
661  try {
662  nameInImage = file.getUniquePath();
663  } catch (TskCoreException ex) {
664  nameInImage = file.getName();
665  }
666 
667  StringBuilder linkToThumbnail = new StringBuilder();
668  linkToThumbnail.append("<a href=\""); //NON-NLS
669  linkToThumbnail.append(contentPath);
670  linkToThumbnail.append("\">");
671  linkToThumbnail.append("<img src=\"").append(thumbnailPath).append("\" title=\"").append(nameInImage).append("\"/>"); //NON-NLS
672  linkToThumbnail.append("</a><br>"); //NON-NLS
673  linkToThumbnail.append(file.getName()).append("<br>"); //NON-NLS
674 
675  Services services = currentCase.getServices();
676  TagsManager tagsManager = services.getTagsManager();
677  try {
678  List<ContentTag> tags = tagsManager.getContentTagsByContent(content);
679  if (tags.size() > 0) {
680  linkToThumbnail.append(NbBundle.getMessage(this.getClass(), "ReportHTML.thumbLink.tags"));
681  }
682  for (int i = 0; i < tags.size(); i++) {
683  ContentTag tag = tags.get(i);
684  linkToThumbnail.append(tag.getName().getDisplayName());
685  if (i != tags.size() - 1) {
686  linkToThumbnail.append(", ");
687  }
688  }
689  } catch (TskCoreException ex) {
690  logger.log(Level.WARNING, "Could not find get tags for file.", ex); //NON-NLS
691  }
692 
693  currentRow.add(linkToThumbnail.toString());
694 
695  totalCount++;
696  }
697 
698  if (currentRow.isEmpty() == false) {
699  int extraCells = THUMBNAIL_COLUMNS - currentRow.size();
700  for (int i = 0; i < extraCells; i++) {
701  // Finish out the row.
702  currentRow.add("");
703  }
704  addRow(currentRow);
705  }
706 
707  // manually set rowCount to be the total number of images.
708  rowCount = totalCount;
709  }
710 
711  private boolean failsContentCheck(Content c) {
712  if (c instanceof AbstractFile == false) {
713  return true;
714  }
715  AbstractFile file = (AbstractFile) c;
716  if (file.isDir()
717  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS
718  || file.getType() == TSK_DB_FILES_TYPE_ENUM.UNUSED_BLOCKS) {
719  return true;
720  }
721  return false;
722  }
723 
733  public String saveContent(AbstractFile file, String dirName) {
734  // clean up the dir name passed in
735  String dirName2 = org.sleuthkit.autopsy.coreutils.FileUtil.escapeFileName(dirName);
736 
737  // Make a folder for the local file with the same tagName as the tag.
738  StringBuilder localFilePath = new StringBuilder(); // full path
739 
740  localFilePath.append(path);
741  localFilePath.append(dirName2);
742  File localFileFolder = new File(localFilePath.toString());
743  if (!localFileFolder.exists()) {
744  localFileFolder.mkdirs();
745  }
746 
747  // Construct a file tagName for the local file that incorporates the file id to ensure uniqueness.
748  String fileName = file.getName();
749  String objectIdSuffix = "_" + file.getId();
750  int lastDotIndex = fileName.lastIndexOf(".");
751  if (lastDotIndex != -1 && lastDotIndex != 0) {
752  // The file tagName has a conventional extension. Insert the object id before the '.' of the extension.
753  fileName = fileName.substring(0, lastDotIndex) + objectIdSuffix + fileName.substring(lastDotIndex, fileName.length());
754  } else {
755  // The file has no extension or the only '.' in the file is an initial '.', as in a hidden file.
756  // Add the object id to the end of the file tagName.
757  fileName += objectIdSuffix;
758  }
759  localFilePath.append(File.separator);
760  localFilePath.append(fileName);
761 
762  // If the local file doesn't already exist, create it now.
763  // The existence check is necessary because it is possible to apply multiple tags with the same tagName to a file.
764  File localFile = new File(localFilePath.toString());
765  if (!localFile.exists()) {
766  ExtractFscContentVisitor.extract(file, localFile, null, null);
767  }
768 
769  // get the relative path
770  return localFilePath.toString().substring(path.length());
771  }
772 
780  @Override
781  public String dateToString(long date) {
782  SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
783  return sdf.format(new java.util.Date(date * 1000));
784  }
785 
786  @Override
787  public String getRelativeFilePath() {
788  return "HTML Report" + File.separator + "index.html"; //NON-NLS
789  }
790 
791  @Override
792  public String getName() {
793  return NbBundle.getMessage(this.getClass(), "ReportHTML.getName.text");
794  }
795 
796  @Override
797  public String getDescription() {
798  return NbBundle.getMessage(this.getClass(), "ReportHTML.getDesc.text");
799  }
800 
804  private void writeCss() {
805  Writer cssOut = null;
806  try {
807  cssOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path + "index.css"), "UTF-8")); //NON-NLS NON-NLS
808  String css = "body {margin: 0px; padding: 0px; background: #FFFFFF; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353;}\n" + //NON-NLS
809  "#content {padding: 30px;}\n" + //NON-NLS
810  "#header {width:100%; padding: 10px; line-height: 25px; background: #07A; color: #FFF; font-size: 20px;}\n" + //NON-NLS
811  "h1 {font-size: 20px; font-weight: normal; color: #07A; padding: 0 0 7px 0; margin-top: 25px; border-bottom: 1px solid #D6D6D6;}\n" + //NON-NLS
812  "h2 {font-size: 20px; font-weight: bolder; color: #07A;}\n" + //NON-NLS
813  "h3 {font-size: 16px; color: #07A;}\n" + //NON-NLS
814  "h4 {background: #07A; color: #FFF; font-size: 16px; margin: 0 0 0 25px; padding: 0; padding-left: 15px;}\n" + //NON-NLS
815  "ul.nav {list-style-type: none; line-height: 35px; padding: 0px; margin-left: 15px;}\n" + //NON-NLS
816  "ul li a {font-size: 14px; color: #444; text-decoration: none; padding-left: 25px;}\n" + //NON-NLS
817  "ul li a:hover {text-decoration: underline;}\n" + //NON-NLS
818  "p {margin: 0 0 20px 0;}\n" + //NON-NLS
819  "table {white-space:nowrap; min-width: 700px; padding: 2; margin: 0; border-collapse: collapse; border-bottom: 2px solid #e5e5e5;}\n" + //NON-NLS
820  ".keyword_list table {margin: 0 0 25px 25px; border-bottom: 2px solid #dedede;}\n" + //NON-NLS
821  "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" + //NON-NLS
822  "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" + //NON-NLS
823  "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" + //NON-NLS
824  "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; }\n" + //NON-NLS
825  "table tr:nth-child(even) td {background: #f3f3f3;}"; //NON-NLS
826  cssOut.write(css);
827  } catch (FileNotFoundException ex) {
828  logger.log(Level.SEVERE, "Could not find index.css file to write to.", ex); //NON-NLS
829  } catch (UnsupportedEncodingException ex) {
830  logger.log(Level.SEVERE, "Did not recognize encoding when writing index.css.", ex); //NON-NLS
831  } catch (IOException ex) {
832  logger.log(Level.SEVERE, "Error creating Writer for index.css.", ex); //NON-NLS
833  } finally {
834  try {
835  if (cssOut != null) {
836  cssOut.flush();
837  cssOut.close();
838  }
839  } catch (IOException ex) {
840  }
841  }
842  }
843 
847  private void writeIndex() {
848  Writer indexOut = null;
849  String indexFilePath = path + "index.html"; //NON-NLS
850  try {
851  indexOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(indexFilePath), "UTF-8")); //NON-NLS
852  StringBuilder index = new StringBuilder();
853  final String reportTitle = reportBranding.getReportTitle();
854  String iconPath = reportBranding.getAgencyLogoPath();
855  if (iconPath == null) {
856  // use default Autopsy icon if custom icon is not set
857  iconPath = "favicon.ico";
858  }
859  index.append("<head>\n<title>").append(reportTitle).append(" ").append(
860  NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.title", currentCase.getName())).append(
861  "</title>\n"); //NON-NLS
862  index.append("<link rel=\"icon\" type=\"image/ico\" href=\"")
863  .append(iconPath).append("\" />\n"); //NON-NLS
864  index.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); //NON-NLS
865  index.append("</head>\n"); //NON-NLS
866  index.append("<frameset cols=\"350px,*\">\n"); //NON-NLS
867  index.append("<frame src=\"nav.html\" name=\"nav\">\n"); //NON-NLS
868  index.append("<frame src=\"summary.html\" name=\"content\">\n"); //NON-NLS
869  index.append("<noframes>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.noFrames.msg")).append("<br />\n"); //NON-NLS
870  index.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.noFrames.seeNav")).append("<br />\n"); //NON-NLS
871  index.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeIndex.seeSum")).append("</noframes>\n"); //NON-NLS
872  index.append("</frameset>\n"); //NON-NLS
873  index.append("</html>"); //NON-NLS
874  indexOut.write(index.toString());
875  Case.getCurrentCase().addReport(indexFilePath, NbBundle.getMessage(this.getClass(),
876  "ReportHTML.writeIndex.srcModuleName.text"), "");
877  } catch (IOException ex) {
878  logger.log(Level.SEVERE, "Error creating Writer for index.html: {0}", ex); //NON-NLS
879  } catch (TskCoreException ex) {
880  String errorMessage = String.format("Error adding %s to case as a report", indexFilePath); //NON-NLS
881  logger.log(Level.SEVERE, errorMessage, ex);
882  } finally {
883  try {
884  if (indexOut != null) {
885  indexOut.flush();
886  indexOut.close();
887  }
888  } catch (IOException ex) {
889  }
890  }
891  }
892 
896  private void writeNav() {
897  Writer navOut = null;
898  try {
899  navOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path + "nav.html"), "UTF-8")); //NON-NLS
900  StringBuilder nav = new StringBuilder();
901  nav.append("<html>\n<head>\n\t<title>").append( //NON-NLS
902  NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.title"))
903  .append("</title>\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"index.css\" />\n"); //NON-NLS
904  nav.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n</head>\n<body>\n"); //NON-NLS
905  nav.append("<div id=\"content\">\n<h1>").append( //NON-NLS
906  NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.h1")).append("</h1>\n"); //NON-NLS
907  nav.append("<ul class=\"nav\">\n"); //NON-NLS
908  nav.append("<li style=\"background: url(summary.png) left center no-repeat;\"><a href=\"summary.html\" target=\"content\">") //NON-NLS
909  .append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeNav.summary")).append("</a></li>\n"); //NON-NLS
910 
911  for (String dataType : dataTypes.keySet()) {
912  String dataTypeEsc = dataTypeToFileName(dataType);
913  String iconFileName = useDataTypeIcon(dataType);
914  nav.append("<li style=\"background: url('").append(iconFileName) //NON-NLS
915  .append("') left center no-repeat;\"><a href=\"") //NON-NLS
916  .append(dataTypeEsc).append(".html\" target=\"content\">") //NON-NLS
917  .append(dataType).append(" (").append(dataTypes.get(dataType))
918  .append(")</a></li>\n"); //NON-NLS
919  }
920  nav.append("</ul>\n"); //NON-NLS
921  nav.append("</div>\n</body>\n</html>"); //NON-NLS
922  navOut.write(nav.toString());
923  } catch (IOException ex) {
924  logger.log(Level.SEVERE, "Failed to write end of report navigation menu: {0}", ex); //NON-NLS
925  } finally {
926  if (navOut != null) {
927  try {
928  navOut.flush();
929  navOut.close();
930  } catch (IOException ex) {
931  logger.log(Level.WARNING, "Could not close navigation out writer."); //NON-NLS
932  }
933  }
934  }
935 
936  InputStream in = null;
937  OutputStream output = null;
938  try {
939 
940  //pull generator and agency logo from branding, and the remaining resources from the core jar
941  String generatorLogoPath = reportBranding.getGeneratorLogoPath();
942  if (generatorLogoPath != null && !generatorLogoPath.isEmpty()) {
943  File from = new File(generatorLogoPath);
944  File to = new File(path);
945  FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to), "generator_logo"); //NON-NLS
946  }
947 
948  String agencyLogoPath = reportBranding.getAgencyLogoPath();
949  if (agencyLogoPath != null && !agencyLogoPath.isEmpty()) {
950  File from = new File(agencyLogoPath);
951  File to = new File(path);
952  FileUtil.copyFile(FileUtil.toFileObject(from), FileUtil.toFileObject(to), "agency_logo"); //NON-NLS
953  }
954 
955  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/favicon.ico"); //NON-NLS
956  output = new FileOutputStream(new File(path + File.separator + "favicon.ico"));
957  FileUtil.copy(in, output);
958  in.close();
959  output.close();
960 
961  in = getClass().getResourceAsStream("/org/sleuthkit/autopsy/report/images/summary.png"); //NON-NLS
962  output = new FileOutputStream(new File(path + File.separator + "summary.png"));
963  FileUtil.copy(in, output);
964  in.close();
965  output.close();
966 
967  } catch (IOException ex) {
968  logger.log(Level.SEVERE, "Failed to extract images for HTML report.", ex); //NON-NLS
969  } finally {
970  if (output != null) {
971  try {
972  output.flush();
973  output.close();
974  } catch (IOException ex) {
975  }
976  }
977  if (in != null) {
978  try {
979  in.close();
980  } catch (IOException ex) {
981  }
982  }
983  }
984  }
985 
989  private void writeSummary() {
990  Writer out = null;
991  try {
992  out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path + "summary.html"), "UTF-8")); //NON-NLS
993  StringBuilder head = new StringBuilder();
994  head.append("<html>\n<head>\n<title>").append( //NON-NLS
995  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.title")).append("</title>\n"); //NON-NLS
996  head.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n"); //NON-NLS
997  head.append("<style type=\"text/css\">\n"); //NON-NLS
998  head.append("body { padding: 0px; margin: 0px; font: 13px/20px Arial, Helvetica, sans-serif; color: #535353; }\n"); //NON-NLS
999  head.append("#wrapper { width: 90%; margin: 0px auto; margin-top: 35px; }\n"); //NON-NLS
1000  head.append("h1 { color: #07A; font-size: 36px; line-height: 42px; font-weight: normal; margin: 0px; border-bottom: 1px solid #81B9DB; }\n"); //NON-NLS
1001  head.append("h1 span { color: #F00; display: block; font-size: 16px; font-weight: bold; line-height: 22px;}\n"); //NON-NLS
1002  head.append("h2 { padding: 0 0 3px 0; margin: 0px; color: #07A; font-weight: normal; border-bottom: 1px dotted #81B9DB; }\n"); //NON-NLS
1003  head.append("table td { padding-right: 25px; }\n"); //NON-NLS
1004  head.append("p.subheadding { padding: 0px; margin: 0px; font-size: 11px; color: #B5B5B5; }\n"); //NON-NLS
1005  head.append(".title { width: 660px; margin-bottom: 50px; }\n"); //NON-NLS
1006  head.append(".left { float: left; width: 250px; margin-top: 20px; text-align: center; }\n"); //NON-NLS
1007  head.append(".left img { max-width: 250px; max-height: 250px; min-width: 200px; min-height: 200px; }\n"); //NON-NLS
1008  head.append(".right { float: right; width: 385px; margin-top: 25px; font-size: 14px; }\n"); //NON-NLS
1009  head.append(".clear { clear: both; }\n"); //NON-NLS
1010  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"); //NON-NLS
1011  head.append(".info table { margin: 0 25px 20px 25px; }\n"); //NON-NLS
1012  head.append("</style>\n"); //NON-NLS
1013  head.append("</head>\n<body>\n"); //NON-NLS
1014  out.write(head.toString());
1015 
1016  DateFormat datetimeFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
1017  Date date = new Date();
1018  String datetime = datetimeFormat.format(date);
1019 
1020  String caseName = currentCase.getName();
1021  String caseNumber = currentCase.getNumber();
1022  String examiner = currentCase.getExaminer();
1023  int imagecount;
1024  try {
1025  imagecount = currentCase.getDataSources().size();
1026  } catch (TskCoreException ex) {
1027  imagecount = 0;
1028  }
1029 
1030  StringBuilder summary = new StringBuilder();
1031  boolean running = false;
1032  if (IngestManager.getInstance().isIngestRunning()) {
1033  running = true;
1034  }
1035 
1036  final String reportTitle = reportBranding.getReportTitle();
1037  final String reportFooter = reportBranding.getReportFooter();
1038  final boolean agencyLogoSet = reportBranding.getAgencyLogoPath() != null && !reportBranding.getAgencyLogoPath().isEmpty();
1039  final boolean generatorLogoSet = reportBranding.getGeneratorLogoPath() != null && !reportBranding.getGeneratorLogoPath().isEmpty();
1040 
1041  summary.append("<div id=\"wrapper\">\n"); //NON-NLS
1042  summary.append("<h1>").append(reportTitle) //NON-NLS
1043  .append(running ? NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.warningMsg") : "")
1044  .append("</h1>\n"); //NON-NLS
1045  summary.append("<p class=\"subheadding\">").append( //NON-NLS
1046  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.reportGenOn.text", datetime)).append("</p>\n"); //NON-NLS
1047  summary.append("<div class=\"title\">\n"); //NON-NLS
1048  if (agencyLogoSet) {
1049  summary.append("<div class=\"left\">\n"); //NON-NLS
1050  summary.append("<img src=\"agency_logo.png\" />\n"); //NON-NLS
1051  summary.append("</div>\n"); //NON-NLS
1052  }
1053  final String align = agencyLogoSet ? "right" : "left"; //NON-NLS NON-NLS
1054  summary.append("<div class=\"").append(align).append("\">\n"); //NON-NLS
1055  summary.append("<table>\n"); //NON-NLS
1056  summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.caseName")) //NON-NLS
1057  .append("</td><td>").append(caseName).append("</td></tr>\n"); //NON-NLS NON-NLS
1058  summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.caseNum")) //NON-NLS
1059  .append("</td><td>").append(!caseNumber.isEmpty() ? caseNumber : NbBundle //NON-NLS
1060  .getMessage(this.getClass(), "ReportHTML.writeSum.noCaseNum")).append("</td></tr>\n"); //NON-NLS
1061  summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.examiner")).append("</td><td>") //NON-NLS
1062  .append(!examiner.isEmpty() ? examiner : NbBundle
1063  .getMessage(this.getClass(), "ReportHTML.writeSum.noExaminer"))
1064  .append("</td></tr>\n"); //NON-NLS
1065  summary.append("<tr><td>").append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.numImages")) //NON-NLS
1066  .append("</td><td>").append(imagecount).append("</td></tr>\n"); //NON-NLS
1067  summary.append("</table>\n"); //NON-NLS
1068  summary.append("</div>\n"); //NON-NLS
1069  summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1070  summary.append("</div>\n"); //NON-NLS
1071  summary.append(NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.imageInfoHeading"));
1072  summary.append("<div class=\"info\">\n"); //NON-NLS
1073  try {
1074  for (Content c : currentCase.getDataSources()) {
1075  summary.append("<p>").append(c.getName()).append("</p>\n"); //NON-NLS
1076  if (c instanceof Image) {
1077  Image img = (Image) c;
1078 
1079  summary.append("<table>\n"); //NON-NLS
1080  summary.append("<tr><td>").append( //NON-NLS
1081  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.timezone"))
1082  .append("</td><td>").append(img.getTimeZone()).append("</td></tr>\n"); //NON-NLS
1083  for (String imgPath : img.getPaths()) {
1084  summary.append("<tr><td>").append( //NON-NLS
1085  NbBundle.getMessage(this.getClass(), "ReportHTML.writeSum.path"))
1086  .append("</td><td>").append(imgPath).append("</td></tr>\n"); //NON-NLS
1087  }
1088  summary.append("</table>\n"); //NON-NLS
1089  }
1090  }
1091  } catch (TskCoreException ex) {
1092  logger.log(Level.WARNING, "Unable to get image information for the HTML report."); //NON-NLS
1093  }
1094  summary.append("</div>\n"); //NON-NLS
1095  if (generatorLogoSet) {
1096  summary.append("<div class=\"left\">\n"); //NON-NLS
1097  summary.append("<img src=\"generator_logo.png\" />\n"); //NON-NLS
1098  summary.append("</div>\n"); //NON-NLS
1099  }
1100  summary.append("<div class=\"clear\"></div>\n"); //NON-NLS
1101  if (reportFooter != null) {
1102  summary.append("<p class=\"subheadding\">").append(reportFooter).append("</p>\n"); //NON-NLS
1103  }
1104  summary.append("</div>\n"); //NON-NLS
1105  summary.append("</body></html>"); //NON-NLS
1106  out.write(summary.toString());
1107  } catch (FileNotFoundException ex) {
1108  logger.log(Level.SEVERE, "Could not find summary.html file to write to."); //NON-NLS
1109  } catch (UnsupportedEncodingException ex) {
1110  logger.log(Level.SEVERE, "Did not recognize encoding when writing summary.hmtl."); //NON-NLS
1111  } catch (IOException ex) {
1112  logger.log(Level.SEVERE, "Error creating Writer for summary.html."); //NON-NLS
1113  } finally {
1114  try {
1115  if (out != null) {
1116  out.flush();
1117  out.close();
1118  }
1119  } catch (IOException ex) {
1120  }
1121  }
1122  }
1123 
1124  private String prepareThumbnail(AbstractFile file) {
1125  File thumbFile = ImageUtils.getCachedThumbnailFile(file, ImageUtils.ICON_SIZE_MEDIUM);
1126  if (thumbFile.exists() == false) {
1127  return null;
1128  }
1129  File to = new File(thumbsPath);
1130  FileObject from = FileUtil.toFileObject(thumbFile);
1131  FileObject dest = FileUtil.toFileObject(to);
1132  try {
1133  FileUtil.copyFile(from, dest, thumbFile.getName(), "");
1134  } catch (IOException ex) {
1135  logger.log(Level.SEVERE, "Failed to write thumb file to report directory.", ex); //NON-NLS
1136  } catch (NullPointerException ex) {
1137  logger.log(Level.SEVERE, "NPE generated from FileUtil.copyFile, probably because FileUtil.toFileObject returned null. \n" +
1138  "The File argument for toFileObject was " + thumbFile + " with toString: " + thumbFile.toString() + "\n" +
1139  "The FileObject returned by toFileObject, passed into FileUtil.copyFile, was " + from, ex);
1140  }
1141 
1142  return THUMBS_REL_PATH + thumbFile.getName();
1143  }
1144 
1145 }
static String escapeFileName(String fileName)
Definition: FileUtil.java:169

Copyright © 2012-2016 Basis Technology. Generated on: Tue Oct 25 2016
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.