Autopsy  4.16.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;
57 import org.sleuthkit.datamodel.AbstractFile;
58 import org.sleuthkit.datamodel.BlackboardArtifact;
59 import org.sleuthkit.datamodel.BlackboardAttribute;
60 import org.sleuthkit.datamodel.DataSource;
61 import org.sleuthkit.datamodel.IngestJobInfo;
62 import org.sleuthkit.datamodel.SleuthkitCase;
63 import org.sleuthkit.datamodel.TskCoreException;
64 
68 final class DiscoveryUiUtils {
69 
70  private final static Logger logger = Logger.getLogger(DiscoveryUiUtils.class.getName());
71  private static final int BYTE_UNIT_CONVERSION = 1000;
72  private static final int ICON_SIZE = 16;
73  private static final String RED_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/red-circle-exclamation.png";
74  private static final String YELLOW_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/yellow-circle-yield.png";
75  private static final String DELETE_ICON_PATH = "org/sleuthkit/autopsy/images/file-icon-deleted.png";
76  private static final String UNSUPPORTED_DOC_PATH = "org/sleuthkit/autopsy/images/image-extraction-not-supported.png";
77  private static final ImageIcon INTERESTING_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(YELLOW_CIRCLE_ICON_PATH, false));
78  private static final ImageIcon NOTABLE_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(RED_CIRCLE_ICON_PATH, false));
79  private static final ImageIcon DELETED_ICON = new ImageIcon(ImageUtilities.loadImage(DELETE_ICON_PATH, false));
80  private static final ImageIcon UNSUPPORTED_DOCUMENT_THUMBNAIL = new ImageIcon(ImageUtilities.loadImage(UNSUPPORTED_DOC_PATH, false));
81  private static final String THUMBNAIL_FORMAT = "png"; //NON-NLS
82  private static final String VIDEO_THUMBNAIL_DIR = "video-thumbnails"; //NON-NLS
83  private static final BufferedImage VIDEO_DEFAULT_IMAGE = getDefaultVideoThumbnail();
84 
85  @NbBundle.Messages({"# {0} - fileSize",
86  "# {1} - units",
87  "DiscoveryUiUtility.sizeLabel.text=Size: {0} {1}",
88  "DiscoveryUiUtility.bytes.text=bytes",
89  "DiscoveryUiUtility.kiloBytes.text=KB",
90  "DiscoveryUiUtility.megaBytes.text=MB",
91  "DiscoveryUiUtility.gigaBytes.text=GB",
92  "DiscoveryUiUtility.terraBytes.text=TB"})
101  static String getFileSizeString(long bytes) {
102  long size = bytes;
103  int unitsSwitchValue = 0;
104  while (size > BYTE_UNIT_CONVERSION && unitsSwitchValue < 4) {
105  size /= BYTE_UNIT_CONVERSION;
106  unitsSwitchValue++;
107  }
108  String units;
109  switch (unitsSwitchValue) {
110  case 1:
111  units = Bundle.DiscoveryUiUtility_kiloBytes_text();
112  break;
113  case 2:
114  units = Bundle.DiscoveryUiUtility_megaBytes_text();
115  break;
116  case 3:
117  units = Bundle.DiscoveryUiUtility_gigaBytes_text();
118  break;
119  case 4:
120  units = Bundle.DiscoveryUiUtility_terraBytes_text();
121  break;
122  default:
123  units = Bundle.DiscoveryUiUtility_bytes_text();
124  break;
125  }
126  return Bundle.DiscoveryUiUtility_sizeLabel_text(size, units);
127  }
128 
135  static ImageIcon getUnsupportedImageThumbnail() {
136  return UNSUPPORTED_DOCUMENT_THUMBNAIL;
137  }
138 
151  static List<String> getSetNames(BlackboardArtifact.ARTIFACT_TYPE artifactType, BlackboardAttribute.ATTRIBUTE_TYPE setNameAttribute) throws TskCoreException {
152  List<BlackboardArtifact> arts = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifacts(artifactType);
153  List<String> setNames = new ArrayList<>();
154  for (BlackboardArtifact art : arts) {
155  for (BlackboardAttribute attr : art.getAttributes()) {
156  if (attr.getAttributeType().getTypeID() == setNameAttribute.getTypeID()) {
157  String setName = attr.getValueString();
158  if (!setNames.contains(setName)) {
159  setNames.add(setName);
160  }
161  }
162  }
163  }
164  Collections.sort(setNames);
165  return setNames;
166  }
167 
176  static boolean isPointOnIcon(Component comp, Point point) {
177  return comp instanceof JComponent && point.x >= comp.getX() && point.x <= comp.getX() + ICON_SIZE && point.y >= comp.getY() && point.y <= comp.getY() + ICON_SIZE;
178  }
179 
188  @NbBundle.Messages({"DiscoveryUiUtils.isDeleted.text=All instances of file are deleted."})
189  static void setDeletedIcon(boolean isDeleted, javax.swing.JLabel isDeletedLabel) {
190  if (isDeleted) {
191  isDeletedLabel.setIcon(DELETED_ICON);
192  isDeletedLabel.setToolTipText(Bundle.DiscoveryUiUtils_isDeleted_text());
193  } else {
194  isDeletedLabel.setIcon(null);
195  isDeletedLabel.setToolTipText(null);
196  }
197  }
198 
206  static void setScoreIcon(ResultFile resultFile, javax.swing.JLabel scoreLabel) {
207  switch (resultFile.getScore()) {
208  case NOTABLE_SCORE:
209  scoreLabel.setIcon(NOTABLE_SCORE_ICON);
210  break;
211  case INTERESTING_SCORE:
212  scoreLabel.setIcon(INTERESTING_SCORE_ICON);
213  break;
214  case NO_SCORE: // empty case - this is interpreted as an intentional fall-through
215  default:
216  scoreLabel.setIcon(null);
217  break;
218  }
219  scoreLabel.setToolTipText(resultFile.getScoreDescription());
220  }
221 
227  static int getIconSize() {
228  return ICON_SIZE;
229  }
230 
235  @NbBundle.Messages({"DiscoveryUiUtils.resultsIncomplete.text=Discovery results may be incomplete"})
236  static void displayErrorMessage(DiscoveryDialog dialog) {
237  //check if modules run and assemble message
238  try {
239  SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
240  Map<Long, DataSourceModulesWrapper> dataSourceIngestModules = new HashMap<>();
241  for (DataSource dataSource : skCase.getDataSources()) {
242  dataSourceIngestModules.put(dataSource.getId(), new DataSourceModulesWrapper(dataSource.getName()));
243  }
244 
245  for (IngestJobInfo jobInfo : skCase.getIngestJobs()) {
246  dataSourceIngestModules.get(jobInfo.getObjectId()).updateModulesRun(jobInfo);
247  }
248  String message = "";
249  for (DataSourceModulesWrapper dsmodulesWrapper : dataSourceIngestModules.values()) {
250  message += dsmodulesWrapper.getMessage();
251  }
252  if (!message.isEmpty()) {
253  JScrollPane messageScrollPane = new JScrollPane();
254  JTextPane messageTextPane = new JTextPane();
255  messageTextPane.setText(message);
256  messageTextPane.setVisible(true);
257  messageTextPane.setEditable(false);
258  messageTextPane.setCaretPosition(0);
259  messageScrollPane.setMaximumSize(new Dimension(600, 100));
260  messageScrollPane.setPreferredSize(new Dimension(600, 100));
261  messageScrollPane.setViewportView(messageTextPane);
262  JOptionPane.showMessageDialog(dialog, messageScrollPane, Bundle.DiscoveryUiUtils_resultsIncomplete_text(), JOptionPane.PLAIN_MESSAGE);
263  }
264  } catch (NoCurrentCaseException | TskCoreException ex) {
265  logger.log(Level.WARNING, "Exception while determining which modules have been run for Discovery", ex);
266  }
267  dialog.validateDialog();
268  }
269 
279  @NbBundle.Messages({"# {0} - file name",
280  "DiscoveryUiUtils.genVideoThumb.progress.text=extracting temporary file {0}"})
281  static void getVideoThumbnails(VideoThumbnailsWrapper thumbnailWrapper) {
282  AbstractFile file = thumbnailWrapper.getResultFile().getFirstInstance();
283  String cacheDirectory;
284  try {
285  cacheDirectory = Case.getCurrentCaseThrows().getCacheDirectory();
286  } catch (NoCurrentCaseException ex) {
287  cacheDirectory = null;
288  logger.log(Level.WARNING, "Unable to get cache directory, video thumbnails will not be saved", ex);
289  }
290  if (cacheDirectory == null || file.getMd5Hash() == null || !Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile().exists()) {
291  java.io.File tempFile;
292  try {
293  tempFile = getVideoFileInTempDir(file);
294  } catch (NoCurrentCaseException ex) {
295  logger.log(Level.WARNING, "Exception while getting open case.", ex); //NON-NLS
296  int[] framePositions = new int[]{
297  0,
298  0,
299  0,
300  0};
301  thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
302  return;
303  }
304  if (tempFile.exists() == false || tempFile.length() < file.getSize()) {
305  ProgressHandle progress = ProgressHandle.createHandle(Bundle.DiscoveryUiUtils_genVideoThumb_progress_text(file.getName()));
306  progress.start(100);
307  try {
308  Files.createParentDirs(tempFile);
309  if (Thread.interrupted()) {
310  int[] framePositions = new int[]{
311  0,
312  0,
313  0,
314  0};
315  thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
316  return;
317  }
318  ContentUtils.writeToFile(file, tempFile, progress, null, true);
319  } catch (IOException ex) {
320  logger.log(Level.WARNING, "Error extracting temporary file for " + file.getParentPath() + "/" + file.getName(), ex); //NON-NLS
321  } finally {
322  progress.finish();
323  }
324  }
325  VideoCapture videoFile = new VideoCapture(); // will contain the video
326  BufferedImage bufferedImage = null;
327 
328  try {
329  if (!videoFile.open(tempFile.toString())) {
330  logger.log(Level.WARNING, "Error opening {0} for preview generation.", file.getParentPath() + "/" + file.getName()); //NON-NLS
331  int[] framePositions = new int[]{
332  0,
333  0,
334  0,
335  0};
336  thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
337  return;
338  }
339  double fps = videoFile.get(5); // gets frame per second
340  double totalFrames = videoFile.get(7); // gets total frames
341  if (fps <= 0 || totalFrames <= 0) {
342  logger.log(Level.WARNING, "Error getting fps or total frames for {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS
343  int[] framePositions = new int[]{
344  0,
345  0,
346  0,
347  0};
348  thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
349  return;
350  }
351  if (Thread.interrupted()) {
352  int[] framePositions = new int[]{
353  0,
354  0,
355  0,
356  0};
357  thumbnailWrapper.setThumbnails(createDefaultThumbnailList(VIDEO_DEFAULT_IMAGE), framePositions);
358  return;
359  }
360 
361  double duration = 1000 * (totalFrames / fps); //total milliseconds
362 
363  int[] framePositions = new int[]{
364  (int) (duration * .01),
365  (int) (duration * .25),
366  (int) (duration * .5),
367  (int) (duration * .75),};
368 
369  Mat imageMatrix = new Mat();
370  List<Image> videoThumbnails = new ArrayList<>();
371  if (cacheDirectory == null || file.getMd5Hash() == null) {
372  cacheDirectory = null;
373  } else {
374  try {
375  FileUtils.forceMkdir(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile());
376  } catch (IOException ex) {
377  cacheDirectory = null;
378  logger.log(Level.WARNING, "Unable to make video thumbnails directory, thumbnails will not be saved", ex);
379  }
380  }
381  for (int i = 0; i < framePositions.length; i++) {
382  if (!videoFile.set(0, framePositions[i])) {
383  logger.log(Level.WARNING, "Error seeking to " + framePositions[i] + "ms in {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS
384  // If we can't set the time, continue to the next frame position and try again.
385 
386  videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
387  if (cacheDirectory != null) {
388  try {
389  ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
390  Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
391  } catch (IOException ex) {
392  logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
393  }
394  }
395  continue;
396  }
397  // Read the frame into the image/matrix.
398  if (!videoFile.read(imageMatrix)) {
399  logger.log(Level.WARNING, "Error reading frame at " + framePositions[i] + "ms from {0}", file.getParentPath() + "/" + file.getName()); //NON-NLS
400  // If the image is bad for some reason, continue to the next frame position and try again.
401  videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
402  if (cacheDirectory != null) {
403  try {
404  ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
405  Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
406  } catch (IOException ex) {
407  logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
408  }
409  }
410 
411  continue;
412  }
413  // If the image is empty, return since no buffered image can be created.
414  if (imageMatrix.empty()) {
415  videoThumbnails.add(VIDEO_DEFAULT_IMAGE);
416  if (cacheDirectory != null) {
417  try {
418  ImageIO.write(VIDEO_DEFAULT_IMAGE, THUMBNAIL_FORMAT,
419  Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
420  } catch (IOException ex) {
421  logger.log(Level.WARNING, "Unable to save default video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
422  }
423  }
424  continue;
425  }
426 
427  int matrixColumns = imageMatrix.cols();
428  int matrixRows = imageMatrix.rows();
429 
430  // Convert the matrix that contains the frame to a buffered image.
431  if (bufferedImage == null) {
432  bufferedImage = new BufferedImage(matrixColumns, matrixRows, BufferedImage.TYPE_3BYTE_BGR);
433  }
434 
435  byte[] data = new byte[matrixRows * matrixColumns * (int) (imageMatrix.elemSize())];
436  imageMatrix.get(0, 0, data); //copy the image to data
437 
438  if (imageMatrix.channels() == 3) {
439  for (int k = 0; k < data.length; k += 3) {
440  byte temp = data[k];
441  data[k] = data[k + 2];
442  data[k + 2] = temp;
443  }
444  }
445 
446  bufferedImage.getRaster().setDataElements(0, 0, matrixColumns, matrixRows, data);
447  if (Thread.interrupted()) {
448  thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
449  try {
450  FileUtils.forceDelete(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash()).toFile());
451  } catch (IOException ex) {
452  logger.log(Level.WARNING, "Unable to delete directory for cancelled video thumbnail process", ex);
453  }
454  return;
455  }
456  BufferedImage thumbnail = ScalrWrapper.resize(bufferedImage, Scalr.Method.SPEED, Scalr.Mode.FIT_TO_HEIGHT, ImageUtils.ICON_SIZE_LARGE, ImageUtils.ICON_SIZE_MEDIUM, Scalr.OP_ANTIALIAS);
457  //We are height limited here so it can be wider than it can be tall.Scalr maintains the aspect ratio.
458  videoThumbnails.add(thumbnail);
459  if (cacheDirectory != null) {
460  try {
461  ImageIO.write(thumbnail, THUMBNAIL_FORMAT,
462  Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, file.getMd5Hash(), i + "-" + framePositions[i] + "." + THUMBNAIL_FORMAT).toFile()); //NON-NLS)
463  } catch (IOException ex) {
464  logger.log(Level.WARNING, "Unable to save video thumbnail for " + file.getMd5Hash() + " at frame position " + framePositions[i], ex);
465  }
466  }
467  }
468  thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
469  } finally {
470  videoFile.release(); // close the file}
471  }
472  } else {
473  loadSavedThumbnails(cacheDirectory, thumbnailWrapper, VIDEO_DEFAULT_IMAGE);
474  }
475  }
476 
482  private static BufferedImage getDefaultVideoThumbnail() {
483  try {
484  return ImageIO.read(ImageUtils.class
485  .getResourceAsStream("/org/sleuthkit/autopsy/images/failedToCreateVideoThumb.png"));//NON-NLS
486  } catch (IOException ex) {
487  logger.log(Level.SEVERE, "Failed to load 'failed to create video' placeholder.", ex); //NON-NLS
488  }
489  return null;
490  }
491 
502  private static void loadSavedThumbnails(String cacheDirectory, VideoThumbnailsWrapper thumbnailWrapper, BufferedImage failedVideoThumbImage) {
503  int[] framePositions = new int[4];
504  List<Image> videoThumbnails = new ArrayList<>();
505  int thumbnailNumber = 0;
506  String md5 = thumbnailWrapper.getResultFile().getFirstInstance().getMd5Hash();
507  for (String fileName : Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, md5).toFile().list()) {
508  try {
509  videoThumbnails.add(ImageIO.read(Paths.get(cacheDirectory, VIDEO_THUMBNAIL_DIR, md5, fileName).toFile()));
510  } catch (IOException ex) {
511  videoThumbnails.add(failedVideoThumbImage);
512  logger.log(Level.WARNING, "Unable to read saved video thumbnail " + fileName + " for " + md5, ex);
513  }
514  int framePos = Integer.valueOf(FilenameUtils.getBaseName(fileName).substring(2));
515  framePositions[thumbnailNumber] = framePos;
516  thumbnailNumber++;
517  }
518  thumbnailWrapper.setThumbnails(videoThumbnails, framePositions);
519  }
520 
527  private static List<Image> createDefaultThumbnailList(BufferedImage failedVideoThumbImage) {
528  List<Image> videoThumbnails = new ArrayList<>();
529  videoThumbnails.add(failedVideoThumbImage);
530  videoThumbnails.add(failedVideoThumbImage);
531  videoThumbnails.add(failedVideoThumbImage);
532  videoThumbnails.add(failedVideoThumbImage);
533  return videoThumbnails;
534  }
535 
539  private DiscoveryUiUtils() {
540  //private constructor in a utility class intentionally left blank
541  }
542 }
static File getVideoFileInTempDir(AbstractFile file)

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