Autopsy 4.22.1
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 */
19package org.sleuthkit.autopsy.directorytree.actionhelpers;
20
21import java.awt.Component;
22import java.awt.event.ActionEvent;
23import java.io.File;
24import java.io.IOException;
25import java.text.MessageFormat;
26import java.util.ArrayList;
27import java.util.Collection;
28import java.util.HashSet;
29import java.util.Iterator;
30import java.util.List;
31import java.util.Set;
32import java.util.concurrent.ExecutionException;
33import java.util.logging.Level;
34import javax.swing.JFileChooser;
35import javax.swing.JOptionPane;
36import javax.swing.SwingWorker;
37import org.netbeans.api.progress.ProgressHandle;
38import org.openide.util.Cancellable;
39import org.openide.util.NbBundle;
40import org.openide.util.NbBundle.Messages;
41import org.sleuthkit.autopsy.casemodule.Case;
42import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
43import org.sleuthkit.autopsy.coreutils.FileUtil;
44import org.sleuthkit.autopsy.coreutils.Logger;
45import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
46import org.sleuthkit.autopsy.datamodel.ContentUtils;
47import org.sleuthkit.autopsy.datamodel.ContentUtils.ExtractFscContentVisitor;
48import org.sleuthkit.autopsy.guiutils.JFileChooserFactory;
49import org.sleuthkit.datamodel.AbstractFile;
50import org.sleuthkit.datamodel.Content;
51
55public 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
182 }
183
193 private void updateExportDirectory(String exportPath, Case openCase) {
194 if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) {
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}
static String escapeFileName(String fileName)
static boolean deleteFileDir(File path)
Definition FileUtil.java:87
synchronized static Logger getLogger(String name)
Definition Logger.java:124
ExtractFscContentVisitor(java.io.File dest, ProgressHandle progress, SwingWorker< T, V > worker, boolean source)
static boolean isDotDirectory(AbstractFile dir)
ExtractFscContentVisitor< T, V > getChildVisitor(File childFile, ProgressHandle progress, SwingWorker< T, V > worker)
void writeFile(Content file, File dest, ProgressHandle progress, SwingWorker< T, V > worker, boolean source)
void extract(ActionEvent event, Collection<? extends AbstractFile > selectedFiles)
void extractFile(ActionEvent event, AbstractFile selectedFile)
void runExtractionTasks(ActionEvent event, List< FileExtractionTask > fileExtractionTasks, String destName)
void extractFiles(ActionEvent event, Collection<? extends AbstractFile > selectedFiles)

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.