Autopsy  4.19.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
ExportCSVAction.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2019-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;
20 
21 import java.awt.Component;
22 import java.awt.event.ActionEvent;
23 import java.io.File;
24 import java.io.BufferedWriter;
25 import java.io.FileOutputStream;
26 import java.io.OutputStreamWriter;
27 import java.lang.reflect.InvocationTargetException;
28 import java.nio.charset.StandardCharsets;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Calendar;
32 import java.util.Collection;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.concurrent.ExecutionException;
36 import java.util.logging.Level;
37 import javax.swing.AbstractAction;
38 import javax.swing.JFileChooser;
39 import javax.swing.JOptionPane;
40 import javax.swing.SwingWorker;
41 import javax.swing.filechooser.FileNameExtensionFilter;
42 import org.netbeans.api.progress.ProgressHandle;
43 import org.openide.util.Cancellable;
44 import org.openide.util.NbBundle;
45 import org.openide.util.Utilities;
51 import org.openide.nodes.Node;
52 import org.openide.nodes.Node.PropertySet;
53 import org.openide.nodes.Node.Property;
55 
59 public final class ExportCSVAction extends AbstractAction {
60 
61  private static final Logger logger = Logger.getLogger(ExportCSVAction.class.getName());
62  private final static String DEFAULT_FILENAME = "Results";
63  private final static List<String> columnsToSkip = Arrays.asList(AbstractFilePropertyType.SCORE.toString(),
65 
66  private static String userDefinedExportPath;
67 
68  // This class is a singleton to support multi-selection of nodes, since
69  // org.openide.nodes.NodeOp.findActions(Node[] nodes) will only pick up an Action if every
70  // node in the array returns a reference to the same action object from Node.getActions(boolean).
71  private static ExportCSVAction instance;
72 
74 
81  public static synchronized ExportCSVAction getInstance() {
82  if (null == instance) {
83  instance = new ExportCSVAction();
84  }
85  return instance;
86  }
87 
91  @NbBundle.Messages({"ExportCSV.title.text=Export Selected Rows to CSV"})
92  private ExportCSVAction() {
93  super(Bundle.ExportCSV_title_text());
94  }
95 
103  @Override
104  public void actionPerformed(ActionEvent e) {
105  Collection<? extends Node> selectedNodes = Utilities.actionsGlobalContext().lookupAll(Node.class);
106  saveNodesToCSV(selectedNodes, (Component)e.getSource());
107  }
108 
115  @NbBundle.Messages({
116  "# {0} - Output file",
117  "ExportCSV.saveNodesToCSV.fileExists=File {0} already exists",
118  "ExportCSV.saveNodesToCSV.noCurrentCase=No open case available",
119  "ExportCSV.saveNodesToCSV.empty=No data to export"})
120  public static void saveNodesToCSV(Collection<? extends Node> nodesToExport, Component component) {
121 
122  if (nodesToExport.isEmpty()) {
123  MessageNotifyUtil.Message.info(Bundle.ExportCSV_saveNodesToCSV_empty());
124  return;
125  }
126 
127  try {
128  // Set up the file chooser with a default name and either the Export
129  // folder or the last used folder.
130  String fileName = getDefaultOutputFileName(nodesToExport.iterator().next().getParentNode());
131  JFileChooser fileChooser = chooserHelper.getChooser();
132  fileChooser.setCurrentDirectory(new File(getExportDirectory(Case.getCurrentCaseThrows())));
133  fileChooser.setSelectedFile(new File(fileName));
134  fileChooser.setFileFilter(new FileNameExtensionFilter("csv file", "csv"));
135 
136  int returnVal = fileChooser.showSaveDialog(component);
137  if (returnVal == JFileChooser.APPROVE_OPTION) {
138 
139  // Get the file name, appending .csv if necessary
140  File selectedFile = fileChooser.getSelectedFile();
141  if (!selectedFile.getName().endsWith(".csv")) { // NON-NLS
142  selectedFile = new File(selectedFile.toString() + ".csv"); // NON-NLS
143  }
144 
145  // Save the directory used for next time
146  updateExportDirectory(selectedFile.getParent(), Case.getCurrentCaseThrows());
147 
148  if (selectedFile.exists()) {
149  logger.log(Level.SEVERE, "File {0} already exists", selectedFile.getAbsolutePath()); //NON-NLS
150  MessageNotifyUtil.Message.info(Bundle.ExportCSV_saveNodesToCSV_fileExists(selectedFile));
151  return;
152  }
153 
154  CSVWriter writer = new CSVWriter(nodesToExport, selectedFile);
155  writer.execute();
156  }
157  } catch (NoCurrentCaseException ex) {
158  JOptionPane.showMessageDialog(component, Bundle.ExportCSV_saveNodesToCSV_noCurrentCase());
159  logger.log(Level.INFO, "Exception while getting open case.", ex); //NON-NLS
160  }
161  }
162 
170  private static String getDefaultOutputFileName(Node parent) {
171  String dateStr = String.format("%1$tY%1$tm%1$te%1$tI%1$tM%1$tS", Calendar.getInstance());
172 
173  if (parent != null) {
174  // The first value in the property set is generally a reasonable name
175  for (PropertySet set : parent.getPropertySets()) {
176  for (Property<?> prop : set.getProperties()) {
177  try {
178  String parentName = prop.getValue().toString();
179 
180  // Strip off the count (if present)
181  parentName = parentName.replaceAll("\\([0-9]+\\)$", "");
182 
183  // Strip out any invalid characters
184  parentName = parentName.replaceAll("[\\\\/:*?\"<>|]", "_");
185 
186  return parentName + " " + dateStr;
187  } catch (IllegalAccessException | InvocationTargetException ex) {
188  logger.log(Level.WARNING, "Failed to get property set value as string", ex);
189  }
190  }
191  }
192  }
193  return DEFAULT_FILENAME + " " + dateStr;
194  }
195 
203  private static String getExportDirectory(Case openCase) {
204  String caseExportPath = openCase.getExportDirectory();
205 
206  if (userDefinedExportPath == null) {
207  return caseExportPath;
208  }
209 
210  File file = new File(userDefinedExportPath);
211  if (file.exists() == false || file.isDirectory() == false) {
212  return caseExportPath;
213  }
214 
215  return userDefinedExportPath;
216  }
217 
227  private static void updateExportDirectory(String exportPath, Case openCase) {
228  if (exportPath.equalsIgnoreCase(openCase.getExportDirectory())) {
229  userDefinedExportPath = null;
230  } else {
231  userDefinedExportPath = exportPath;
232  }
233  }
234 
235 
239  private static class CSVWriter extends SwingWorker<Object, Void> {
240 
241  private static final Logger logger = Logger.getLogger(CSVWriter.class.getName());
242  private ProgressHandle progress;
243 
244  private final Collection<? extends Node> nodesToExport;
245  private final File outputFile;
246 
252  CSVWriter(Collection<? extends Node> nodesToExport, File outputFile) {
253  this.nodesToExport = nodesToExport;
254  this.outputFile = outputFile;
255  }
256 
257  @NbBundle.Messages({"CSVWriter.progress.extracting=Exporting to CSV file",
258  "CSVWriter.progress.cancelling=Cancelling"})
259  @Override
260  protected Object doInBackground() throws Exception {
261  if (nodesToExport.isEmpty()) {
262  return null;
263  }
264 
265  // Set up progress bar.
266  final String displayName = Bundle.CSVWriter_progress_extracting();
267  progress = ProgressHandle.createHandle(displayName, new Cancellable() {
268  @Override
269  public boolean cancel() {
270  if (progress != null) {
271  progress.setDisplayName(Bundle.CSVWriter_progress_cancelling());
272  }
273  return ExportCSVAction.CSVWriter.this.cancel(true);
274  }
275  });
276  progress.start();
277  progress.switchToIndeterminate();
278 
279  try (BufferedWriter br = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile), StandardCharsets.UTF_8))) {
280  // Write BOM
281  br.write('\ufeff');
282 
283  // Write the header
284  List<String> headers = new ArrayList<>();
285  PropertySet[] sets = nodesToExport.iterator().next().getPropertySets();
286  for(PropertySet set : sets) {
287  for (Property<?> prop : set.getProperties()) {
288  if ( ! columnsToSkip.contains(prop.getDisplayName())) {
289  headers.add(prop.getDisplayName());
290  }
291  }
292  }
293  br.write(listToCSV(headers));
294 
295  // Write each line
296  Iterator<?> nodeIterator = nodesToExport.iterator();
297  while (nodeIterator.hasNext()) {
298  if (this.isCancelled()) {
299  break;
300  }
301 
302  Node node = (Node)nodeIterator.next();
303  List<String> values = new ArrayList<>();
304  sets = node.getPropertySets();
305  for(PropertySet set : sets) {
306  for (Property<?> prop : set.getProperties()) {
307  if ( ! columnsToSkip.contains(prop.getDisplayName())) {
308  values.add(escapeQuotes(prop.getValue().toString()));
309  }
310  }
311  }
312  br.write(listToCSV(values));
313  }
314  }
315 
316  return null;
317  }
318 
326  private String escapeQuotes(String original) {
327  return original.replaceAll("\"", "\\\\\"");
328  }
329 
337  private String listToCSV(List<String> values) {
338  return "\"" + String.join("\",\"", values) + "\"\n";
339  }
340 
341  @NbBundle.Messages({"CSVWriter.done.notifyMsg.error=Error exporting to CSV file",
342  "# {0} - Output file",
343  "CSVWriter.done.notifyMsg.success=Wrote to {0}"})
344  @Override
345  protected void done() {
346  boolean msgDisplayed = false;
347  try {
348  super.get();
349  } catch (InterruptedException | ExecutionException ex) {
350  logger.log(Level.SEVERE, "Fatal error during file extraction", ex); //NON-NLS
351  MessageNotifyUtil.Message.info(Bundle.CSVWriter_done_notifyMsg_error());
352  msgDisplayed = true;
353  } catch (java.util.concurrent.CancellationException ex) {
354  // catch and ignore if we were cancelled
355  } finally {
356  progress.finish();
357  if (!this.isCancelled() && !msgDisplayed) {
358  MessageNotifyUtil.Message.info(Bundle.CSVWriter_done_notifyMsg_success(outputFile));
359  }
360  }
361  }
362  }
363 }
static synchronized ExportCSVAction getInstance()
static void updateExportDirectory(String exportPath, Case openCase)
synchronized static Logger getLogger(String name)
Definition: Logger.java:124
static void saveNodesToCSV(Collection<?extends Node > nodesToExport, Component component)

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