Autopsy  4.17.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
DiscoveryUiUtils.java
Go to the documentation of this file.
1 /*
2  * Autopsy
3  *
4  * Copyright 2020 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.discovery.ui;
20 
21 import com.google.common.io.Files;
22 import java.awt.Component;
23 import java.awt.Dimension;
24 import java.awt.Image;
25 import java.awt.Point;
26 import java.awt.image.BufferedImage;
27 import java.io.IOException;
28 import java.nio.file.Paths;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.logging.Level;
35 import javax.imageio.ImageIO;
36 import javax.swing.ImageIcon;
37 import javax.swing.JComponent;
38 import javax.swing.JOptionPane;
39 import javax.swing.JScrollPane;
40 import javax.swing.JTextPane;
41 import org.apache.commons.io.FileUtils;
42 import org.apache.commons.io.FilenameUtils;
43 import org.imgscalr.Scalr;
44 import org.netbeans.api.progress.ProgressHandle;
45 import org.opencv.core.Mat;
46 import org.opencv.highgui.VideoCapture;
47 import org.openide.util.ImageUtilities;
48 import org.openide.util.NbBundle;
58 import org.sleuthkit.datamodel.AbstractFile;
59 import org.sleuthkit.datamodel.BlackboardArtifact;
60 import org.sleuthkit.datamodel.BlackboardAttribute;
61 import org.sleuthkit.datamodel.DataSource;
62 import org.sleuthkit.datamodel.IngestJobInfo;
63 import org.sleuthkit.datamodel.SleuthkitCase;
64 import org.sleuthkit.datamodel.TskCoreException;
65 
69 final class DiscoveryUiUtils {
70 
71  private final static Logger logger = Logger.getLogger(DiscoveryUiUtils.class.getName());
72  private static final int BYTE_UNIT_CONVERSION = 1000;
73  private static final int ICON_SIZE = 16;
74  private static final String RED_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/red-circle-exclamation.png";
75  private static final String YELLOW_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/yellow-circle-yield.png";
76  private static final String DELETE_ICON_PATH = "org/sleuthkit/autopsy/images/file-icon-deleted.png";
77  private static final String UNSUPPORTED_DOC_PATH = "org/sleuthkit/autopsy/images/image-extraction-not-supported.png";
78  private static final ImageIcon INTERESTING_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(YELLOW_CIRCLE_ICON_PATH, false));
79  private static final ImageIcon NOTABLE_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(RED_CIRCLE_ICON_PATH, false));
80  private static final ImageIcon DELETED_ICON = new ImageIcon(ImageUtilities.loadImage(DELETE_ICON_PATH, false));
81  private static final ImageIcon UNSUPPORTED_DOCUMENT_THUMBNAIL = new ImageIcon(ImageUtilities.loadImage(UNSUPPORTED_DOC_PATH, false));
82  private static final String THUMBNAIL_FORMAT = "png"; //NON-NLS
83  private static final String VIDEO_THUMBNAIL_DIR = "video-thumbnails"; //NON-NLS
84  private static final BufferedImage VIDEO_DEFAULT_IMAGE = getDefaultVideoThumbnail();
85 
86  @NbBundle.Messages({"# {0} - fileSize",
87  "# {1} - units",
88  "DiscoveryUiUtility.sizeLabel.text=Size: {0} {1}",
89  "DiscoveryUiUtility.bytes.text=bytes",
90  "DiscoveryUiUtility.kiloBytes.text=KB",
91  "DiscoveryUiUtility.megaBytes.text=MB",
92  "DiscoveryUiUtility.gigaBytes.text=GB",
93  "DiscoveryUiUtility.terraBytes.text=TB"})
102  static String getFileSizeString(long bytes) {
103  long size = bytes;
104  int unitsSwitchValue = 0;
105  while (size > BYTE_UNIT_CONVERSION && unitsSwitchValue < 4) {
106  size /= BYTE_UNIT_CONVERSION;
107  unitsSwitchValue++;
108  }
109  String units;
110  switch (unitsSwitchValue) {
111  case 1:
112  units = Bundle.DiscoveryUiUtility_kiloBytes_text();
113  break;
114  case 2:
115  units = Bundle.DiscoveryUiUtility_megaBytes_text();
116  break;
117  case 3:
118  units = Bundle.DiscoveryUiUtility_gigaBytes_text();
119  break;
120  case 4:
121  units = Bundle.DiscoveryUiUtility_terraBytes_text();
122  break;
123  default:
124  units = Bundle.DiscoveryUiUtility_bytes_text();
125  break;
126  }
127  return Bundle.DiscoveryUiUtility_sizeLabel_text(size, units);
128  }
129 
136  static ImageIcon getUnsupportedImageThumbnail() {
137  return UNSUPPORTED_DOCUMENT_THUMBNAIL;
138  }
139 
152  static List<String> getSetNames(BlackboardArtifact.ARTIFACT_TYPE artifactType, BlackboardAttribute.ATTRIBUTE_TYPE setNameAttribute) throws TskCoreException {
153  List<BlackboardArtifact> arts = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifacts(artifactType);
154  List<String> setNames = new ArrayList<>();
155  for (BlackboardArtifact art : arts) {
156  for (BlackboardAttribute attr : art.getAttributes()) {
157  if (attr.getAttributeType().getTypeID() == setNameAttribute.getTypeID()) {
158  String setName = attr.getValueString();
159  if (!setNames.contains(setName)) {
160  setNames.add(setName);
161  }
162  }
163  }
164  }
165  Collections.sort(setNames);
166  return setNames;
167  }
168 
177  @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
178  static boolean isPointOnIcon(Component comp, Point point) {
179  return comp instanceof JComponent && point.x >= comp.getX() && point.x <= comp.getX() + ICON_SIZE && point.y >= comp.getY() && point.y <= comp.getY() + ICON_SIZE;
180  }
181 
190  @NbBundle.Messages({"DiscoveryUiUtils.isDeleted.text=All instances of file are deleted."})
191  @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
192  static void setDeletedIcon(boolean isDeleted, javax.swing.JLabel isDeletedLabel) {
193  if (isDeleted) {
194  isDeletedLabel.setIcon(DELETED_ICON);
195  isDeletedLabel.setToolTipText(Bundle.DiscoveryUiUtils_isDeleted_text());
196  } else {
197  isDeletedLabel.setIcon(null);
198  isDeletedLabel.setToolTipText(null);
199  }
200  }
201 
209  @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
210  static void setScoreIcon(ResultFile resultFile, javax.swing.JLabel scoreLabel) {
211  switch (resultFile.getScore()) {
212  case NOTABLE_SCORE:
213  scoreLabel.setIcon(NOTABLE_SCORE_ICON);
214  break;
215  case INTERESTING_SCORE:
216  scoreLabel.setIcon(INTERESTING_SCORE_ICON);
217  break;
218  case NO_SCORE: // empty case - this is interpreted as an intentional fall-through
219  default:
220  scoreLabel.setIcon(null);
221  break;
222  }
223  scoreLabel.setToolTipText(resultFile.getScoreDescription());
224  }
225 
231  static int getIconSize() {
232  return ICON_SIZE;
233  }
234 
239  @ThreadConfined(type = ThreadConfined.ThreadType.AWT)
240  @NbBundle.Messages({"DiscoveryUiUtils.resultsIncomplete.text=Discovery results may be incomplete"})
241  static void displayErrorMessage(DiscoveryDialog dialog) {
242  //check if modules run and assemble message
243  try {
244  SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
245  Map<Long, DataSourceModulesWrapper> dataSourceIngestModules = new HashMap<>();
246  for (DataSource dataSource : skCase.getDataSources()) {
247  dataSourceIngestModules.put(dataSource.getId(), new DataSourceModulesWrapper(dataSource.getName()));
248  }
249 
250  for (IngestJobInfo jobInfo : skCase.getIngestJobs()) {
251  dataSourceIngestModules.get(jobInfo.getObjectId()).updateModulesRun(jobInfo);
252  }
253  String message = "";
254  for (DataSourceModulesWrapper dsmodulesWrapper : dataSourceIngestModules.values()) {
255  message += dsmodulesWrapper.getMessage();
256  }
257  if (!message.isEmpty()) {
258  JScrollPane messageScrollPane = new JScrollPane();
259  JTextPane messageTextPane = new JTextPane();
260  messageTextPane.setText(message);
261  messageTextPane.setVisible(true);
262  messageTextPane.setEditable(false);
263  messageTextPane.setCaretPosition(0);
264  messageScrollPane.setMaximumSize(new Dimension(600, 100));
265  messageScrollPane.setPreferredSize(new Dimension(600, 100));
266  messageScrollPane.setViewportView(messageTextPane);
267  JOptionPane.showMessageDialog(dialog, messageScrollPane, Bundle.DiscoveryUiUtils_resultsIncomplete_text(), JOptionPane.PLAIN_MESSAGE);
268  }
269  } catch (NoCurrentCaseException | TskCoreException ex) {
270  logger.log(Level.WARNING, "Exception while determining which modules have been run for Discovery", ex);
271  }
272  dialog.validateDialog();
273  }
274 
284  @NbBundle.Messages({"# {0} - file name",
285  "DiscoveryUiUtils.genVideoThumb.progress.text=extracting temporary file {0}"})
286  static void getVideoThumbnails(VideoThumbnailsWrapper thumbnailWrapper) {
287  AbstractFile file = thumbnailWrapper.getResultFile().getFirstInstance();
288  String cacheDirectory;
289  try {
290  cacheDirectory = Case.getCurrentCaseThrows().getCacheDirectory();
291  } catch (NoCurrentCaseException ex) {
292  cacheDirectory = null;
293  logger.log(Level.WARNING, "Unable to get cache directory, video thumbnails will not be saved", ex);
294  }
295  if (cacheDirectory == null || file.getMd5Hash() == null || !Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile().exists()) {
296  java.io.File tempFile;
297  try {
298  tempFile = getVideoFileInTempDir(file);
299  } catch (NoCurrentCaseException ex) {
300  logger.log(Level.WARNING, "Exception while getting open case.", ex); //NON-NLS
301  int[] framePositions = new int[]{
302  0,
303  0,
304  0,
305  0};
306  thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
307  return;
308  }
309  if (tempFile.exists() == false || tempFile.length() < file.getSize()) {
310  ProgressHandle progress = ProgressHandle.createHandle(Bundle.DiscoveryUiUtils_genVideoThumb_progress_text(file.getName()));
311  progress.start(100);
312  try {
313  Files.createParentDirs(tempFile);
314  if (Thread.interrupted()) {
315  int[] framePositions = new int[]{
316  0,
317  0,
318  0,
319  0};
320  thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
321  return;
322  }
323  ContentUtils.writeToFile(file, tempFile, progress, null, true);
324  } catch (IOException ex) {
325  logger.log(Level.WARNING, "Error extracting temporary file for " + file.getParentPath() + "/" + file.getName(), ex); //NON-NLS
326  } finally {
327  progress.finish();
328  }
329  }
330  VideoCapture videoFile = new VideoCapture(); // will contain the video
331  BufferedImage bufferedImage = null;
332 
333  try {
334  if (!videoFile.open(tempFile.toString())) {
335  logger.log(Level.WARNING, "Error opening {0} for preview generation.", file.getParentPath() + "/" + file.getName()); //NON-NLS
336  int[] framePositions = new int[]{
337  0,
338  0,
339  0,
340  0};
341  thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
342  return;
343  }
344  double fps = videoFile.get(5); // gets frame per second
345  double totalFrames = videoFile.get(7); // gets total frames
346  if (fps <= 0 || totalFrames <= 0) {
347  logger.log(Level.WARNING, "Error getting fps or total frames for {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS
348  int[] framePositions = new int[]{
349  0,
350  0,
351  0,
352  0};
353  thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
354  return;
355  }
356  if (Thread.interrupted()) {
357  int[] framePositions = new int[]{
358  0,
359  0,
360  0,
361  0};
362  thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
363  return;
364  }
365 
366  double duration = 1000 * (totalFrames / fps); //total milliseconds
367 
368  int[] framePositions = new int[]{
369  (int) (duration * .01),
370  (int) (duration * .25),
371  (int) (duration * .5),
372  (int) (duration * .75),};
373 
374  Mat imageMatrix = new Mat();
375  List<Image> videoThumbnails = new ArrayList<>();
376  if (cacheDirectory == null || file.getMd5Hash() == null) {
377  cacheDirectory = null;
378  } else {
379  try {
380  FileUtils.forceMkdir(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile());
381  } catch (IOException ex) {
382  cacheDirectory = null;
383  logger.log(Level.WARNING, "Unable to make video thumbnails directory, thumbnails will not be saved", ex);
384  }
385  }
386  for (int i = 0; i < framePositions.length; i++) {
387  if (!videoFile.set(0, framePositions[i])) {
388  logger.log(Level.WARNING, "Error seeking to " + framePositions[i] + "ms in {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS
389  // If we can't set the time, continue to the next frame position and try again.
390 
391  videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
392  if (cacheDirectory != null) {
393  try {
394  ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
395  Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
396  } catch (IOException ex) {
397  logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
398  }
399  }
400  continue;
401  }
402  // Read the frame into the image/matrix.
403  if (!videoFile.read(imageMatrix)) {
404  logger.log(Level.WARNING, "Error reading frame at " + framePositions[i] + "ms from {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS
405  // If the image is bad for some reason, continue to the next frame position and try again.
406  videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
407  if (cacheDirectory != null) {
408  try {
409  ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
410  Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
411  } catch (IOException ex) {
412  logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
413  }
414  }
415 
416  continue;
417  }
418  // If the image is empty, return since no buffered image can be created.
419  if (imageMatrix.empty()) {
420  videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
421  if (cacheDirectory != null) {
422  try {
423  ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
424  Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
425  } catch (IOException ex) {
426  logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
427  }
428  }
429  continue;
430  }
431 
432  int matrixColumns = imageMatrix.cols();
433  int matrixRows = imageMatrix.rows();
434 
435  // Convert the matrix that contains the frame to a buffered image.
436  if (bufferedImage == null) {
437  bufferedImage = new BufferedImage(matrixColumns, matrixRows, BufferedImage.TYPE_3BYTE_BGR);
438  }
439 
440  byte[] data = new byte[matrixRows * matrixColumns * (int) (imageMatrix.elemSize())];
441  imageMatrix.get(0, 0, data); //copy the image to data
442 
443  if (imageMatrix.channels() == 3) {
444  for (int k = 0; k < data.length; k += 3) {
445  byte temp = data[k];
446  data[k] = data[k + 2];
447  data[k + 2] = temp;
448  }
449  }
450 
451  bufferedImage.getRaster().setDataElements(0, 0, matrixColumns, matrixRows, data);
452  if (Thread.interrupted()) {
453  thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
454  try {
455  FileUtils.forceDelete(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile());
456  } catch (IOException ex) {
457  logger.log(Level.WARNING, "Unable to delete directory for cancelled video thumbnail process", ex);
458  }
459  return;
460  }
461  BufferedImage thumbnail = ScalrWrapper.resize(bufferedImage, Scalr.Method.SPEED, Scalr.Mode.FIT_TO_HEIGHT, ImageUtils.ICON_SIZE_LARGE, ImageUtils.ICON_SIZE_MEDIUM, Scalr.OP_ANTIALIAS);
462  //We are height limited here so it can be wider than it can be tall.Scalr maintains the aspect ratio.
463  videoThumbnails.add(thumbnail);
464  if (cacheDirectory != null) {
465  try {
466  ImageIO.write(thumbnail, THUMBNAIL_FORMAT,
467  Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
468  } catch (IOException ex) {
469  logger.log(Level.WARNING, "Unable to save video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
470  }
471  }
472  }
473  thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
474  } finally {
475  videoFile.release(); // close the file}
476  }
477  } else {
478  loadSavedThumbnails(cacheDirectory, thumbnailWrapper, VIDEO_DEFAULT_IMAGE);
479  }
480  }
481 
487  private static BufferedImage getDefaultVideoThumbnail() {
488  try {
489  return ImageIO.read(ImageUtils.class
490  .getResourceAsStream("/org/sleuthkit/autopsy/images/failedToCreateVideoThumb.png"));//NON-NLS
491  } catch (IOException ex) {
492  logger.log(Level.SEVERE, "Failed to load 'failed to create video' placeholder.", ex); //NON-NLS
493  }
494  return null;
495  }
496 
507  private static void loadSavedThumbnails(String cacheDirectory, VideoThumbnailsWrapper thumbnailWrapper, BufferedImage failedVideoThumbImage) {
508  int[] framePositions = new int[4];
509  List<Image> videoThumbnails = new ArrayList<>();
510  int thumbnailNumber = 0;
511  String md5 = thumbnailWrapper.getResultFile().getFirstInstance().getMd5Hash();
512  for (String fileName : Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, md5).toFile().list()) {
513  try {
514  videoThumbnails.add(ImageIO.read(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, md5, fileName).toFile()));
515  } catch (IOException ex) {
516  videoThumbnails.add(failedVideoThumbImage);
517  logger.log(Level.WARNING, "Unable to read saved video thumbnail " + fileName + " for " + md5, ex);
518  }
519  int framePos = Integer.valueOf(FilenameUtils.getBaseName(fileName).substring(2));
520  framePositions[thumbnailNumber] = framePos;
521  thumbnailNumber++;
522  }
523  thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
524  }
525 
532  private static List<Image> createDefaultThumbnailList(BufferedImage failedVideoThumbImage) {
533  List<Image> videoThumbnails = new ArrayList<>();
534  videoThumbnails.add(failedVideoThumbImage);
535  videoThumbnails.add(failedVideoThumbImage);
536  videoThumbnails.add(failedVideoThumbImage);
537  videoThumbnails.add(failedVideoThumbImage);
538  return videoThumbnails;
539  }
540 
544  private DiscoveryUiUtils() {
545  //private constructor in a utility class intentionally left blank
546  }
547 }
static File getVideoFileInTempDir(AbstractFile file)

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