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

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.