Autopsy  4.17.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
PListViewer.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2018-2019 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.contentviewers;
20 
21 import java.awt.Component;
22 import java.util.List;
23 import org.sleuthkit.datamodel.AbstractFile;
24 import java.util.Arrays;
25 import com.dd.plist.NSDictionary;
26 import com.dd.plist.PropertyListParser;
27 import com.dd.plist.NSObject;
28 import com.dd.plist.NSArray;
29 import com.dd.plist.NSDate;
30 import com.dd.plist.NSString;
31 import com.dd.plist.NSNumber;
32 import com.dd.plist.NSData;
33 import com.dd.plist.PropertyListFormatException;
34 import java.io.File;
35 import java.io.IOException;
36 import java.text.ParseException;
37 import java.util.ArrayList;
38 import java.util.concurrent.ExecutionException;
39 import java.util.logging.Level;
40 import javax.swing.JFileChooser;
41 import javax.swing.JOptionPane;
42 import javax.swing.JTable;
43 import javax.swing.ListSelectionModel;
44 import javax.swing.SwingUtilities;
45 import javax.swing.SwingWorker;
46 import javax.swing.filechooser.FileNameExtensionFilter;
47 import javax.swing.table.TableCellRenderer;
48 import javax.xml.parsers.ParserConfigurationException;
49 import org.netbeans.swing.outline.DefaultOutlineModel;
50 import org.netbeans.swing.outline.Outline;
51 import org.openide.explorer.ExplorerManager;
52 import org.openide.nodes.AbstractNode;
53 import org.openide.nodes.Children;
54 import org.openide.util.NbBundle;
55 import org.openide.windows.WindowManager;
59 import org.sleuthkit.datamodel.TskCoreException;
60 import org.xml.sax.SAXException;
61 
66 @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
67 class PListViewer extends javax.swing.JPanel implements FileTypeViewer, ExplorerManager.Provider {
68 
69  private static final long serialVersionUID = 1L;
70  private static final String[] MIMETYPES = new String[]{"application/x-bplist"};
71  private static final Logger logger = Logger.getLogger(PListViewer.class.getName());
72 
73  private final org.openide.explorer.view.OutlineView outlineView;
74  private final Outline outline;
75  private ExplorerManager explorerManager;
76 
77  private NSObject rootDict;
78 
82  PListViewer() {
83 
84  // Create an Outlineview and add to the panel
85  outlineView = new org.openide.explorer.view.OutlineView();
86 
87  initComponents();
88 
89  outline = outlineView.getOutline();
90 
91  ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel("Key");
92 
93  outlineView.setPropertyColumns(
94  "Type", Bundle.PListNode_TypeCol(),
95  "Value", Bundle.PListNode_ValueCol());
96 
97  customize();
98  }
99 
100  @NbBundle.Messages({"PListNode.KeyCol=Key",
101  "PListNode.TypeCol=Type",
102  "PListNode.ValueCol=Value"})
103 
104  private void customize() {
105 
106  outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
107 
108  outline.setRootVisible(false);
109  if (null == explorerManager) {
110  explorerManager = new ExplorerManager();
111  }
112 
113  //outline.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
114  plistTableScrollPane.setViewportView(outlineView);
115 
116  outline.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
117 
118  this.setVisible(true);
119  outline.setRowSelectionAllowed(false);
120  }
121 
127  @SuppressWarnings("unchecked")
128  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
129  private void initComponents() {
130 
131  jPanel1 = new javax.swing.JPanel();
132  plistTableScrollPane = new javax.swing.JScrollPane();
133  hdrPanel = new javax.swing.JPanel();
134  exportButton = new javax.swing.JButton();
135 
136  jPanel1.setLayout(new java.awt.BorderLayout());
137 
138  plistTableScrollPane.setBorder(null);
139  plistTableScrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
140  plistTableScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
141  jPanel1.add(plistTableScrollPane, java.awt.BorderLayout.CENTER);
142 
143  org.openide.awt.Mnemonics.setLocalizedText(exportButton, org.openide.util.NbBundle.getMessage(PListViewer.class, "PListViewer.exportButton.text")); // NOI18N
144  exportButton.addActionListener(new java.awt.event.ActionListener() {
145  public void actionPerformed(java.awt.event.ActionEvent evt) {
146  exportButtonActionPerformed(evt);
147  }
148  });
149 
150  javax.swing.GroupLayout hdrPanelLayout = new javax.swing.GroupLayout(hdrPanel);
151  hdrPanel.setLayout(hdrPanelLayout);
152  hdrPanelLayout.setHorizontalGroup(
153  hdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
154  .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, hdrPanelLayout.createSequentialGroup()
155  .addContainerGap(320, Short.MAX_VALUE)
156  .addComponent(exportButton)
157  .addContainerGap())
158  );
159  hdrPanelLayout.setVerticalGroup(
160  hdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
161  .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, hdrPanelLayout.createSequentialGroup()
162  .addGap(0, 6, Short.MAX_VALUE)
163  .addComponent(exportButton))
164  );
165 
166  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
167  this.setLayout(layout);
168  layout.setHorizontalGroup(
169  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
170  .addGroup(layout.createSequentialGroup()
171  .addComponent(hdrPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
172  .addGap(5, 5, 5))
173  .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
174  );
175  layout.setVerticalGroup(
176  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
177  .addGroup(layout.createSequentialGroup()
178  .addGap(3, 3, 3)
179  .addComponent(hdrPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
180  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
181  .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, 249, Short.MAX_VALUE))
182  );
183  }// </editor-fold>//GEN-END:initComponents
184 
185  @NbBundle.Messages({"PListViewer.ExportSuccess.message=Plist file exported successfully",
186  "PListViewer.ExportFailed.message=Plist file export failed.",})
187 
191  private void exportButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportButtonActionPerformed
192 
193  Case openCase;
194  try {
195  openCase = Case.getCurrentCaseThrows();
196  } catch (NoCurrentCaseException ex) {
197  JOptionPane.showMessageDialog(this,
198  "Failed to export plist file.",
199  Bundle.PListViewer_ExportFailed_message(),
200  JOptionPane.ERROR_MESSAGE);
201 
202  logger.log(Level.SEVERE, "Exception while getting open case.", ex);
203  return;
204  }
205 
206  final JFileChooser fileChooser = new JFileChooser();
207  fileChooser.setCurrentDirectory(new File(openCase.getExportDirectory()));
208  fileChooser.setFileFilter(new FileNameExtensionFilter("XML file", "xml"));
209 
210  final int returnVal = fileChooser.showSaveDialog(this);
211  if (returnVal == JFileChooser.APPROVE_OPTION) {
212 
213  File selectedFile = fileChooser.getSelectedFile();
214  if (!selectedFile.getName().endsWith(".xml")) { // NON-NLS
215  selectedFile = new File(selectedFile.toString() + ".xml"); // NON-NLS
216  }
217 
218  try {
219  //Save the propery list as XML
220  PropertyListParser.saveAsXML(this.rootDict, selectedFile);
221  JOptionPane.showMessageDialog(this,
222  String.format("Plist file exported successfully to %s ", selectedFile.getName()),
223  Bundle.PListViewer_ExportSuccess_message(),
224  JOptionPane.INFORMATION_MESSAGE);
225  } catch (IOException ex) {
226  JOptionPane.showMessageDialog(this,
227  String.format("Failed to export plist file to %s ", selectedFile.getName()),
228  Bundle.PListViewer_ExportFailed_message(),
229  JOptionPane.ERROR_MESSAGE);
230 
231  logger.log(Level.SEVERE, "Error exporting plist to XML file " + selectedFile.getName(), ex);
232  }
233  }
234  }//GEN-LAST:event_exportButtonActionPerformed
235 
241  @Override
242  public List<String> getSupportedMIMETypes() {
243  return Arrays.asList(MIMETYPES);
244  }
245 
251  @Override
252  public void setFile(final AbstractFile file) {
253  processPlist(file);
254  }
255 
261  @Override
262  public Component getComponent() {
263  return this;
264  }
265 
270  @Override
271  public void resetComponent() {
272  rootDict = null;
273  }
274 
282  @NbBundle.Messages({"PListViewer.processPlist.interruptedMessage=Interrupted while parsing/displaying plist file.",
283  "PListViewer.processPlist.errorMessage=Error while parsing/displaying plist file."})
284  private void processPlist(final AbstractFile plistFile) {
285 
286  new SwingWorker<List<PropKeyValue>, Void>() {
287  @Override
288  protected List<PropKeyValue> doInBackground() throws TskCoreException, IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException {
289  // Read in and parse the file
290  final byte[] plistFileBuf = new byte[(int) plistFile.getSize()];
291  plistFile.read(plistFileBuf, 0, plistFile.getSize());
292  final List<PropKeyValue> plist = parsePList(plistFileBuf);
293 
294  return plist;
295  }
296 
297  @Override
298  protected void done() {
299  super.done();
300  List<PropKeyValue> plist;
301  try {
302  plist = get();
303  setupTable(plist);
304 
305  SwingUtilities.invokeLater(() -> {
306  setColumnWidths();
307  });
308  } catch (InterruptedException ex) {
309  logger.log(Level.SEVERE, "Interruption while parsing/dislaying plist file " + plistFile.getName(), ex);
310 
311  JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
312  ex.getMessage(),
313  Bundle.PListViewer_processPlist_interruptedMessage(),
314  JOptionPane.ERROR_MESSAGE);
315 
316  } catch (ExecutionException ex) {
317  logger.log(Level.SEVERE, "Exception while parsing/dislaying plist file " + plistFile.getName(), ex);
318  JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
319  ex.getCause().getMessage(),
320  Bundle.PListViewer_processPlist_errorMessage(),
321  JOptionPane.ERROR_MESSAGE);
322  }
323 
324  }
325  }.execute();
326  }
327 
333  private void setupTable(final List<PropKeyValue> tableRows) {
334  explorerManager.setRootContext(new AbstractNode(Children.create(new PListRowFactory(tableRows), true)));
335  }
336 
341  private void setColumnWidths() {
342  final int margin = 4;
343  final int padding = 8;
344 
345  // find the maximum width needed to fit the values for the first N rows, at most
346  final int rows = Math.min(20, outline.getRowCount());
347  for (int col = 0; col < outline.getColumnCount(); col++) {
348  final int columnWidthLimit = 2000;
349  int columnWidth = 0;
350 
351  for (int row = 0; row < rows; row++) {
352  final TableCellRenderer renderer = outline.getCellRenderer(row, col);
353  final Component comp = outline.prepareRenderer(renderer, row, col);
354 
355  columnWidth = Math.max(comp.getPreferredSize().width, columnWidth);
356  }
357 
358  columnWidth += 2 * margin + padding; // add margin and regular padding
359  columnWidth = Math.min(columnWidth, columnWidthLimit);
360  outline.getColumnModel().getColumn(col).setPreferredWidth(columnWidth);
361  }
362  }
363 
367  @NbBundle.Messages({"PListViewer.DataType.message=Binary Data value not shown"})
368  private PropKeyValue parseProperty(final String key, final NSObject value) {
369  if (value == null) {
370  return null;
371  } else if (value instanceof NSString) {
372  return new PropKeyValue(key, PropertyType.STRING, value.toString());
373  } else if (value instanceof NSNumber) {
374  final NSNumber number = (NSNumber) value;
375  if (number.isInteger()) {
376  return new PropKeyValue(key, PropertyType.NUMBER, number.longValue());
377  } else if (number.isBoolean()) {
378  return new PropKeyValue(key, PropertyType.BOOLEAN, number.boolValue());
379  } else {
380  return new PropKeyValue(key, PropertyType.NUMBER, number.floatValue());
381  }
382  } else if (value instanceof NSDate) {
383  final NSDate date = (NSDate) value;
384  return new PropKeyValue(key, PropertyType.DATE, date.toString());
385  } else if (value instanceof NSData) {
386  return new PropKeyValue(key, PropertyType.DATA, Bundle.PListViewer_DataType_message());
387  } else if (value instanceof NSArray) {
388  final List<PropKeyValue> children = new ArrayList<>();
389  final NSArray array = (NSArray) value;
390 
391  final PropKeyValue pkv = new PropKeyValue(key, PropertyType.ARRAY, array);
392  for (int i = 0; i < array.count(); i++) {
393  children.add(parseProperty("", array.objectAtIndex(i)));
394  }
395 
396  pkv.setChildren(children.toArray(new PropKeyValue[children.size()]));
397  return pkv;
398  } else if (value instanceof NSDictionary) {
399  final List<PropKeyValue> children = new ArrayList<>();
400  final NSDictionary dict = (NSDictionary) value;
401 
402  final PropKeyValue pkv = new PropKeyValue(key, PropertyType.DICTIONARY, dict);
403  for (final String key2 : ((NSDictionary) value).allKeys()) {
404  final NSObject obj = ((NSDictionary) value).objectForKey(key2);
405  children.add(parseProperty(key2, obj));
406  }
407 
408  pkv.setChildren(children.toArray(new PropKeyValue[children.size()]));
409  return pkv;
410  } else {
411  logger.log(Level.SEVERE, "Can''t parse Plist for key = {0} value of type {1}", new Object[]{key, value.getClass()});
412  }
413 
414  return null;
415  }
416 
424  private List<PropKeyValue> parsePList(final byte[] plistbytes) throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException {
425 
426  final List<PropKeyValue> plist = new ArrayList<>();
427  rootDict = PropertyListParser.parse(plistbytes);
428 
429  /*
430  * Parse the data if the root is an NSArray or NSDictionary. Anything
431  * else is unexpected and will be ignored.
432  */
433  if (rootDict instanceof NSArray) {
434  for (int i = 0; i < ((NSArray) rootDict).count(); i++) {
435  final PropKeyValue pkv = parseProperty("", ((NSArray) rootDict).objectAtIndex(i));
436  if (null != pkv) {
437  plist.add(pkv);
438  }
439  }
440  } else if (rootDict instanceof NSDictionary) {
441  final String[] keys = ((NSDictionary) rootDict).allKeys();
442  for (final String key : keys) {
443  final PropKeyValue pkv = parseProperty(key, ((NSDictionary) rootDict).objectForKey(key));
444  if (null != pkv) {
445  plist.add(pkv);
446  }
447  }
448  }
449 
450  return plist;
451  }
452 
453  @Override
454  public ExplorerManager getExplorerManager() {
455  return explorerManager;
456  }
457 
461  enum PropertyType {
462  STRING,
463  NUMBER,
464  BOOLEAN,
465  DATE,
466  DATA,
467  ARRAY,
468  DICTIONARY
469  };
470 
475  final static class PropKeyValue {
476 
477  private final String key;
478  private final PropertyType type;
479  private final Object value;
480 
481  private PropKeyValue[] children;
482 
483  PropKeyValue(String key, PropertyType type, Object value) {
484  this.key = key;
485  this.type = type;
486  this.value = value;
487 
488  this.children = null;
489  }
490 
494  PropKeyValue(PropKeyValue other) {
495  this.key = other.getKey();
496  this.type = other.getType();
497  this.value = other.getValue();
498 
499  this.setChildren(other.getChildren());
500  }
501 
502  String getKey() {
503  return this.key;
504  }
505 
506  PropertyType getType() {
507  return this.type;
508  }
509 
510  Object getValue() {
511  return this.value;
512  }
513 
519  PropKeyValue[] getChildren() {
520  if (children == null) {
521  return null;
522  }
523 
524  // return a copy
525  return Arrays.stream(children)
526  .map(child -> new PropKeyValue(child))
527  .toArray(PropKeyValue[]::new);
528  }
529 
530  void setChildren(final PropKeyValue... children) {
531  if (children != null) {
532  this.children = Arrays.stream(children)
533  .map(child -> new PropKeyValue(child))
534  .toArray(PropKeyValue[]::new);
535  }
536 
537  }
538 
539  }
540 
541  @Override
542  public boolean isSupported(AbstractFile file) {
543  return true;
544  }
545  // Variables declaration - do not modify//GEN-BEGIN:variables
546  private javax.swing.JButton exportButton;
547  private javax.swing.JPanel hdrPanel;
548  private javax.swing.JPanel jPanel1;
549  private javax.swing.JScrollPane plistTableScrollPane;
550  // End of variables declaration//GEN-END:variables
551 }

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