Autopsy  4.20.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
ExtractActionHelper.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2013-2021 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 content 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.directorytree.actionhelpers;
20 
21 import java.awt.Component;
22 import java.awt.event.ActionEvent;
23 import java.io.File;
24 import java.io.IOException;
25 import java.text.MessageFormat;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.HashSet;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Set;
32 import java.util.concurrent.ExecutionException;
33 import java.util.logging.Level;
34 import javax.swing.JFileChooser;
35 import javax.swing.JOptionPane;
36 import javax.swing.SwingWorker;
37 import org.netbeans.api.progress.ProgressHandle;
38 import org.openide.util.Cancellable;
39 import org.openide.util.NbBundle;
40 import org.openide.util.NbBundle.Messages;
49 import org.sleuthkit.datamodel.AbstractFile;
50 import org.sleuthkit.datamodel.Content;
51 
55 public class ExtractActionHelper {
56 
57  private final Logger logger = Logger.getLogger(ExtractActionHelper.class.getName());
58  private String userDefinedExportPath;
59 
62 
71  public void extract(ActionEvent event, Collection<? extends AbstractFile> selectedFiles) {
72  if (selectedFiles.size() > 1) {
73  extractFiles(event, selectedFiles);
74  } else if (selectedFiles.size() == 1) {
75  extractFile(event, selectedFiles.iterator().next());
76  }
77  }
78 
85  @NbBundle.Messages({"ExtractActionHelper.noOpenCase.errMsg=No open case available.",
86  "ExtractActionHelper.extractOverwrite.title=Export to csv file",
87  "# {0} - fileName",
88  "ExtractActionHelper.extractOverwrite.msg=A file already exists at {0}. Do you want to overwrite the existing file?"
89  })
90  private void extractFile(ActionEvent event, AbstractFile selectedFile) {
91  Case openCase;
92  try {
93  openCase = Case.getCurrentCaseThrows();
94  } catch (NoCurrentCaseException ex) {
95  JOptionPane.showMessageDialog((Component) event.getSource(), Bundle.ExtractActionHelper_noOpenCase_errMsg());
96  logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS
97  return;
98  }
99  JFileChooser fileChooser = extractFileHelper.getChooser();
100  fileChooser.setCurrentDirectory(new File(getExportDirectory(openCase)));
101  // If there is an attribute name, change the ":". Otherwise the extracted file will be hidden
102  fileChooser.setSelectedFile(new File(FileUtil.escapeFileName(selectedFile.getName())));
103  if (fileChooser.showSaveDialog((Component) event.getSource()) == JFileChooser.APPROVE_OPTION) {
104  updateExportDirectory(fileChooser.getSelectedFile().getParent(), openCase);
105 
106  ArrayList<FileExtractionTask> fileExtractionTasks = new ArrayList<>();
107  fileExtractionTasks.add(new FileExtractionTask(selectedFile, fileChooser.getSelectedFile()));
108  runExtractionTasks(event, fileExtractionTasks, fileChooser.getSelectedFile().getName());
109  }
110  }
111 
118  private void extractFiles(ActionEvent event, Collection<? extends AbstractFile> selectedFiles) {
119  Case openCase;
120  try {
121  openCase = Case.getCurrentCaseThrows();
122  } catch (NoCurrentCaseException ex) {
123  JOptionPane.showMessageDialog((Component) event.getSource(), Bundle.ExtractActionHelper_noOpenCase_errMsg());
124  logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS
125  return;
126  }
127  JFileChooser folderChooser = extractFilesHelper.getChooser();
128  folderChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
129  folderChooser.setCurrentDirectory(new File(getExportDirectory(openCase)));
130  if (folderChooser.showSaveDialog((Component) event.getSource()) == JFileChooser.APPROVE_OPTION) {
131  File destinationFolder = folderChooser.getSelectedFile();
132  if (!destinationFolder.exists()) {
133  try {
134  destinationFolder.mkdirs();
135  } catch (Exception ex) {
136  JOptionPane.showMessageDialog((Component) event.getSource(), NbBundle.getMessage(this.getClass(),
137  "ExtractAction.extractFiles.cantCreateFolderErr.msg"));
138  logger.log(Level.INFO, "Unable to create folder(s) for user " + destinationFolder.getAbsolutePath(), ex); //NON-NLS
139  return;
140  }
141  }
142  updateExportDirectory(destinationFolder.getPath(), openCase);
143 
144  /*
145  * get the unique set of files from the list. A user once reported
146  * extraction taking days because it was extracting the same PST
147  * file 20k times. They selected 20k email messages in the tree and
148  * chose to extract them.
149  */
150  Set<AbstractFile> uniqueFiles = new HashSet<>(selectedFiles);
151 
152  // make a task for each file
153  ArrayList<FileExtractionTask> fileExtractionTasks = new ArrayList<>();
154  for (AbstractFile source : uniqueFiles) {
155  // If there is an attribute name, change the ":". Otherwise the extracted file will be hidden
156  fileExtractionTasks.add(new FileExtractionTask(source, new File(destinationFolder, source.getId() + "-" + FileUtil.escapeFileName(source.getName()))));
157  }
158  runExtractionTasks(event, fileExtractionTasks, destinationFolder.getName());
159  }
160  }
161 
169  private String getExportDirectory(Case openCase) {
170  String caseExportPath = openCase.getExportDirectory();
171 
172  if (userDefinedExportPath == null) {
173  return caseExportPath;
174  }
175 
176  File file = new File(userDefinedExportPath);
177  if (file.exists() == false || file.isDirectory() == false) {
178  return caseExportPath;
179  }
180 
181  return userDefinedExportPath;
182  }
183 
193  private void updateExportDirectory(String exportPath, Case openCase) {
194  if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) {
195  userDefinedExportPath = null;
196  } else {
197  userDefinedExportPath = exportPath;
198  }
199  }
200 
210  private void runExtractionTasks(ActionEvent event, List<FileExtractionTask> fileExtractionTasks, String destName) {
211 
212  // verify all of the sources and destinations are OK
213  for (Iterator<FileExtractionTask> it = fileExtractionTasks.iterator(); it.hasNext();) {
214  FileExtractionTask task = it.next();
215 
216  if (ContentUtils.isDotDirectory(task.source)) {
217  it.remove();
218  continue;
219  }
220 
221  /*
222  * This code assumes that each destination is unique. We previously
223  * satisfied that by adding the unique ID.
224  */
225  if (task.destination.exists()) {
226  if (JOptionPane.showConfirmDialog((Component) event.getSource(),
227  NbBundle.getMessage(this.getClass(), "ExtractActionHelper.confDlg.destFileExist.msg", task.destination.getAbsolutePath()),
228  NbBundle.getMessage(this.getClass(), "ExtractActionHelper.confDlg.destFileExist.title"),
229  JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
230  if (!FileUtil.deleteFileDir(task.destination)) {
231  JOptionPane.showMessageDialog((Component) event.getSource(),
232  NbBundle.getMessage(this.getClass(), "ExtractActionHelper.msgDlg.cantOverwriteFile.msg", task.destination.getAbsolutePath()));
233  it.remove();
234  }
235  } else {
236  it.remove();
237  }
238  }
239  }
240 
241  // launch a thread to do the work
242  if (!fileExtractionTasks.isEmpty()) {
243  try {
244  FileExtracter extracter = new FileExtracter(fileExtractionTasks, destName);
245  extracter.execute();
246  } catch (Exception ex) {
247  logger.log(Level.WARNING, "Unable to start background file extraction thread", ex); //NON-NLS
248  }
249  } else {
251  NbBundle.getMessage(this.getClass(), "ExtractActionHelper.notifyDlg.noFileToExtr.msg"));
252  }
253  }
254 
258  private class FileExtractionTask {
259 
260  AbstractFile source;
261  File destination;
262 
269  FileExtractionTask(AbstractFile source, File destination) {
270  this.source = source;
271  this.destination = destination;
272  }
273  }
274 
279  private static class UIExtractionVisitor<T, V> extends ExtractFscContentVisitor<T, V> {
280 
291  UIExtractionVisitor(File dest, ProgressHandle progress, SwingWorker<T, V> worker, boolean source) {
292  super(dest, progress, worker, source);
293  }
294 
308  static <T,V> void writeContent(Content content, File dest, ProgressHandle progress, SwingWorker<T, V> worker) {
309  content.accept(new UIExtractionVisitor<>(dest, progress, worker, true));
310  }
311 
312 
313  @Override
314  protected void writeFile(Content file, File dest, ProgressHandle progress, SwingWorker<T, V> worker, boolean source) throws IOException {
315  File destFile;
316  if (dest.exists()) {
317  String parent = dest.getParent();
318  String fileName = dest.getName();
319  String objIdFileName = MessageFormat.format("{0}-{1}", file.getId(), fileName);
320  destFile = new File(parent, objIdFileName);
321  } else {
322  destFile = dest;
323  }
324 
325  super.writeFile(file, destFile, progress, worker, source);
326  }
327 
328  @Override
329  protected ExtractFscContentVisitor<T, V> getChildVisitor(File childFile, ProgressHandle progress, SwingWorker<T, V> worker) {
330  return new UIExtractionVisitor<>(childFile, progress, worker, false);
331  }
332 
333 
334 
335  }
336 
340  private class FileExtracter extends SwingWorker<Object, Void> {
341 
342  private final Logger logger = Logger.getLogger(FileExtracter.class.getName());
343  private final String destName;
344  private ProgressHandle progress;
345  private final List<FileExtractionTask> extractionTasks;
346 
354  FileExtracter(List<FileExtractionTask> extractionTasks, String destName) {
355  this.extractionTasks = extractionTasks;
356  this.destName = destName;
357  }
358 
359  @Override
360  @Messages({
361  "# {0} - outputFolderName",
362  "ExtractActionHelper.progress.extracting=Extracting to {0}",
363  "# {0} - fileName",
364  "ExtractActionHelper.progress.fileExtracting=Extracting file: {0}"
365  })
366  protected Object doInBackground() throws Exception {
367  if (extractionTasks.isEmpty()) {
368  return null;
369  }
370 
371  // Setup progress bar.
372  final String displayName = Bundle.ExtractActionHelper_progress_extracting(destName);
373  progress = ProgressHandle.createHandle(displayName, new Cancellable() {
374  @Override
375  public boolean cancel() {
376  if (progress != null) {
377  progress.setDisplayName(
378  NbBundle.getMessage(this.getClass(), "ExtractActionHelper.progress.cancellingExtraction", displayName));
379  }
380  return ExtractActionHelper.FileExtracter.this.cancel(true);
381  }
382  });
383  progress.start();
384  progress.switchToIndeterminate();
385 
386  // Do the extraction tasks.
387  for (FileExtractionTask task : this.extractionTasks) {
388  progress.progress(Bundle.ExtractActionHelper_progress_fileExtracting(task.destination.getName()));
389  UIExtractionVisitor.writeContent(task.source, task.destination, null, this);
390  }
391 
392  return null;
393  }
394 
395  @Override
396  protected void done() {
397  boolean msgDisplayed = false;
398  try {
399  super.get();
400  } catch (InterruptedException | ExecutionException ex) {
401  logger.log(Level.SEVERE, "Fatal error during file extraction", ex); //NON-NLS
403  NbBundle.getMessage(this.getClass(), "ExtractActionHelper.done.notifyMsg.extractErr", ex.getMessage()));
404  msgDisplayed = true;
405  } finally {
406  progress.finish();
407  if (!this.isCancelled() && !msgDisplayed) {
409  NbBundle.getMessage(this.getClass(), "ExtractActionHelper.done.notifyMsg.fileExtr.text"));
410  }
411  }
412  }
413 
422  /*
423  * private int calculateProgressBarWorkUnits(AbstractFile file) { int
424  * workUnits = 0; if (file.isFile()) { workUnits += file.getSize(); }
425  * else { try { for (Content child : file.getChildren()) { if (child
426  * instanceof AbstractFile) { workUnits +=
427  * calculateProgressBarWorkUnits((AbstractFile) child); } } } catch
428  * (TskCoreException ex) { logger.log(Level.SEVERE, "Could not get
429  * children of content", ex); //NON-NLS } } return workUnits; }
430  */
431  }
432 }
void extractFiles(ActionEvent event, Collection<?extends AbstractFile > selectedFiles)
void runExtractionTasks(ActionEvent event, List< FileExtractionTask > fileExtractionTasks, String destName)
static boolean deleteFileDir(File path)
Definition: FileUtil.java:87
void extract(ActionEvent event, Collection<?extends AbstractFile > selectedFiles)
ExtractFscContentVisitor< T, V > getChildVisitor(File childFile, ProgressHandle progress, SwingWorker< T, V > worker)
static String escapeFileName(String fileName)
Definition: FileUtil.java:169
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
void extractFile(ActionEvent event, AbstractFile selectedFile)
static boolean isDotDirectory(AbstractFile dir)
void writeFile(Content file, File dest, ProgressHandle progress, SwingWorker< T, V > worker, boolean source)

Copyright © 2012-2022 Basis Technology. Generated on: Tue Aug 1 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.