Autopsy  4.10.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
ExtractAction.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2013-2018 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;
20 
21 import java.awt.Component;
22 import java.awt.event.ActionEvent;
23 import java.io.File;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.HashSet;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Set;
30 import java.util.concurrent.ExecutionException;
31 import java.util.logging.Level;
32 import javax.swing.AbstractAction;
33 import javax.swing.JFileChooser;
34 import javax.swing.JOptionPane;
35 import javax.swing.SwingWorker;
36 import org.netbeans.api.progress.ProgressHandle;
37 import org.openide.util.Cancellable;
38 import org.openide.util.NbBundle;
39 import org.openide.util.Utilities;
47 import org.sleuthkit.datamodel.AbstractFile;
48 
52 public final class ExtractAction extends AbstractAction {
53 
54  private Logger logger = Logger.getLogger(ExtractAction.class.getName());
55 
56  private String userDefinedExportPath;
57 
58  // This class is a singleton to support multi-selection of nodes, since
59  // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every
60  // node in the array returns a reference to the same action object from Node.getActions(boolean).
61  private static ExtractAction instance;
62 
63  public static synchronized ExtractAction getInstance() {
64  if (null == instance) {
65  instance = new ExtractAction();
66  }
67  return instance;
68  }
69 
73  private ExtractAction() {
74  super(NbBundle.getMessage(ExtractAction.class, "ExtractAction.title.extractFiles.text"));
75  }
76 
83  @Override
84  public void actionPerformed(ActionEvent e) {
85  Collection<? extends AbstractFile> selectedFiles = Utilities.actionsGlobalContext().lookupAll(AbstractFile.class);
86  if (selectedFiles.size() > 1) {
87  extractFiles(e, selectedFiles);
88  } else if (selectedFiles.size() == 1) {
89  AbstractFile source = selectedFiles.iterator().next();
90  if (source.isDir()) {
91  extractFiles(e, selectedFiles);
92  } else {
93  extractFile(e, selectedFiles.iterator().next());
94  }
95  }
96  }
97 
104  @NbBundle.Messages({"ExtractAction.noOpenCase.errMsg=No open case available."})
105  private void extractFile(ActionEvent event, AbstractFile selectedFile) {
106  Case openCase;
107  try {
108  openCase = Case.getCurrentCaseThrows();
109  } catch (NoCurrentCaseException ex) {
110  JOptionPane.showMessageDialog((Component) event.getSource(), Bundle.ExtractAction_noOpenCase_errMsg());
111  logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS
112  return;
113  }
114  JFileChooser fileChooser = new JFileChooser();
115  fileChooser.setCurrentDirectory(new File(getExportDirectory(openCase)));
116  // If there is an attribute name, change the ":". Otherwise the extracted file will be hidden
117  fileChooser.setSelectedFile(new File(FileUtil.escapeFileName(selectedFile.getName())));
118  if (fileChooser.showSaveDialog((Component) event.getSource()) == JFileChooser.APPROVE_OPTION) {
119  updateExportDirectory(fileChooser.getSelectedFile().getParent(), openCase);
120 
121  ArrayList<FileExtractionTask> fileExtractionTasks = new ArrayList<>();
122  fileExtractionTasks.add(new FileExtractionTask(selectedFile, fileChooser.getSelectedFile()));
123  runExtractionTasks(event, fileExtractionTasks);
124  }
125  }
126 
133  private void extractFiles(ActionEvent event, Collection<? extends AbstractFile> selectedFiles) {
134  Case openCase;
135  try {
136  openCase = Case.getCurrentCaseThrows();
137  } catch (NoCurrentCaseException ex) {
138  JOptionPane.showMessageDialog((Component) event.getSource(), Bundle.ExtractAction_noOpenCase_errMsg());
139  logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS
140  return;
141  }
142  JFileChooser folderChooser = new JFileChooser();
143  folderChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
144  folderChooser.setCurrentDirectory(new File(getExportDirectory(openCase)));
145  if (folderChooser.showSaveDialog((Component) event.getSource()) == JFileChooser.APPROVE_OPTION) {
146  File destinationFolder = folderChooser.getSelectedFile();
147  if (!destinationFolder.exists()) {
148  try {
149  destinationFolder.mkdirs();
150  } catch (Exception ex) {
151  JOptionPane.showMessageDialog((Component) event.getSource(), NbBundle.getMessage(this.getClass(),
152  "ExtractAction.extractFiles.cantCreateFolderErr.msg"));
153  logger.log(Level.INFO, "Unable to create folder(s) for user " + destinationFolder.getAbsolutePath(), ex); //NON-NLS
154  return;
155  }
156  }
157  updateExportDirectory(destinationFolder.getPath(), openCase);
158 
159  /*
160  * get the unique set of files from the list. A user once reported
161  * extraction taking days because it was extracting the same PST
162  * file 20k times. They selected 20k email messages in the tree and
163  * chose to extract them.
164  */
165  Set<AbstractFile> uniqueFiles = new HashSet<>(selectedFiles);
166 
167  // make a task for each file
168  ArrayList<FileExtractionTask> fileExtractionTasks = new ArrayList<>();
169  for (AbstractFile source : uniqueFiles) {
170  // If there is an attribute name, change the ":". Otherwise the extracted file will be hidden
171  fileExtractionTasks.add(new FileExtractionTask(source, new File(destinationFolder, source.getId() + "-" + FileUtil.escapeFileName(source.getName()))));
172  }
173  runExtractionTasks(event, fileExtractionTasks);
174  }
175  }
176 
184  private String getExportDirectory(Case openCase) {
185  String caseExportPath = openCase.getExportDirectory();
186 
187  if (userDefinedExportPath == null) {
188  return caseExportPath;
189  }
190 
191  File file = new File(userDefinedExportPath);
192  if (file.exists() == false || file.isDirectory() == false) {
193  return caseExportPath;
194  }
195 
196  return userDefinedExportPath;
197  }
198 
208  private void updateExportDirectory(String exportPath, Case openCase) {
209  if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) {
210  userDefinedExportPath = null;
211  } else {
212  userDefinedExportPath = exportPath;
213  }
214  }
215 
223  private void runExtractionTasks(ActionEvent event, List<FileExtractionTask> fileExtractionTasks) {
224 
225  // verify all of the sources and destinations are OK
226  for (Iterator<FileExtractionTask> it = fileExtractionTasks.iterator(); it.hasNext();) {
227  FileExtractionTask task = it.next();
228 
229  if (ContentUtils.isDotDirectory(task.source)) {
230  //JOptionPane.showMessageDialog((Component) e.getSource(), "Cannot extract virtual " + task.source.getName() + " directory.", "File is Virtual Directory", JOptionPane.WARNING_MESSAGE);
231  it.remove();
232  continue;
233  }
234 
235  /*
236  * This code assumes that each destination is unique. We previously
237  * satisfied that by adding the unique ID.
238  */
239  if (task.destination.exists()) {
240  if (JOptionPane.showConfirmDialog((Component) event.getSource(),
241  NbBundle.getMessage(this.getClass(), "ExtractAction.confDlg.destFileExist.msg", task.destination.getAbsolutePath()),
242  NbBundle.getMessage(this.getClass(), "ExtractAction.confDlg.destFileExist.title"),
243  JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
244  if (!FileUtil.deleteFileDir(task.destination)) {
245  JOptionPane.showMessageDialog((Component) event.getSource(),
246  NbBundle.getMessage(this.getClass(), "ExtractAction.msgDlg.cantOverwriteFile.msg", task.destination.getAbsolutePath()));
247  it.remove();
248  }
249  } else {
250  it.remove();
251  }
252  }
253  }
254 
255  // launch a thread to do the work
256  if (!fileExtractionTasks.isEmpty()) {
257  try {
258  FileExtracter extracter = new FileExtracter(fileExtractionTasks);
259  extracter.execute();
260  } catch (Exception ex) {
261  logger.log(Level.WARNING, "Unable to start background file extraction thread", ex); //NON-NLS
262  }
263  } else {
265  NbBundle.getMessage(this.getClass(), "ExtractAction.notifyDlg.noFileToExtr.msg"));
266  }
267  }
268 
272  private class FileExtractionTask {
273 
274  AbstractFile source;
275  File destination;
276 
283  FileExtractionTask(AbstractFile source, File destination) {
284  this.source = source;
285  this.destination = destination;
286  }
287  }
288 
292  private class FileExtracter extends SwingWorker<Object, Void> {
293 
294  private final Logger logger = Logger.getLogger(FileExtracter.class.getName());
295  private ProgressHandle progress;
296  private final List<FileExtractionTask> extractionTasks;
297 
303  FileExtracter(List<FileExtractionTask> extractionTasks) {
304  this.extractionTasks = extractionTasks;
305  }
306 
307  @Override
308  protected Object doInBackground() throws Exception {
309  if (extractionTasks.isEmpty()) {
310  return null;
311  }
312 
313  // Setup progress bar.
314  final String displayName = NbBundle.getMessage(this.getClass(), "ExtractAction.progress.extracting");
315  progress = ProgressHandle.createHandle(displayName, new Cancellable() {
316  @Override
317  public boolean cancel() {
318  if (progress != null) {
319  progress.setDisplayName(
320  NbBundle.getMessage(this.getClass(), "ExtractAction.progress.cancellingExtraction", displayName));
321  }
322  return ExtractAction.FileExtracter.this.cancel(true);
323  }
324  });
325  progress.start();
326  progress.switchToIndeterminate();
327 
328  /*
329  * @@@ Add back in -> Causes exceptions int workUnits = 0; for
330  * (FileExtractionTask task : extractionTasks) { workUnits +=
331  * calculateProgressBarWorkUnits(task.source); }
332  * progress.switchToDeterminate(workUnits);
333  */
334  // Do the extraction tasks.
335  for (FileExtractionTask task : this.extractionTasks) {
336  // @@@ Note, we are no longer passing in progress
337  ExtractFscContentVisitor.extract(task.source, task.destination, null, this);
338  }
339 
340  return null;
341  }
342 
343  @Override
344  protected void done() {
345  boolean msgDisplayed = false;
346  try {
347  super.get();
348  } catch (InterruptedException | ExecutionException ex) {
349  logger.log(Level.SEVERE, "Fatal error during file extraction", ex); //NON-NLS
351  NbBundle.getMessage(this.getClass(), "ExtractAction.done.notifyMsg.extractErr", ex.getMessage()));
352  msgDisplayed = true;
353  } finally {
354  progress.finish();
355  if (!this.isCancelled() && !msgDisplayed) {
357  NbBundle.getMessage(this.getClass(), "ExtractAction.done.notifyMsg.fileExtr.text"));
358  }
359  }
360  }
361 
370  /*
371  * private int calculateProgressBarWorkUnits(AbstractFile file) { int
372  * workUnits = 0; if (file.isFile()) { workUnits += file.getSize(); }
373  * else { try { for (Content child : file.getChildren()) { if (child
374  * instanceof AbstractFile) { workUnits +=
375  * calculateProgressBarWorkUnits((AbstractFile) child); } } } catch
376  * (TskCoreException ex) { logger.log(Level.SEVERE, "Could not get
377  * children of content", ex); //NON-NLS } } return workUnits;
378  }
379  */
380  }
381 }
void runExtractionTasks(ActionEvent event, List< FileExtractionTask > fileExtractionTasks)
static boolean deleteFileDir(File path)
Definition: FileUtil.java:87
static synchronized ExtractAction getInstance()
static< T, V > void extract(Content cntnt, java.io.File dest, ProgressHandle progress, SwingWorker< T, V > worker)
void extractFiles(ActionEvent event, Collection<?extends AbstractFile > selectedFiles)
void updateExportDirectory(String exportPath, Case openCase)
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)

Copyright © 2012-2018 Basis Technology. Generated on: Fri Mar 22 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.