19 package org.sleuthkit.autopsy.contentviewers;
21 import java.awt.Component;
22 import java.util.List;
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;
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.xml.sax.SAXException;
66 @SuppressWarnings(
"PMD.SingularField")
67 class PListViewer extends javax.swing.JPanel implements FileTypeViewer, ExplorerManager.Provider {
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());
73 private final org.openide.explorer.view.OutlineView outlineView;
74 private final Outline outline;
75 private ExplorerManager explorerManager;
77 private NSObject rootDict;
85 outlineView =
new org.openide.explorer.view.OutlineView();
89 outline = outlineView.getOutline();
91 ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(
"Key");
93 outlineView.setPropertyColumns(
94 "Type", Bundle.PListNode_TypeCol(),
95 "Value", Bundle.PListNode_ValueCol());
100 @NbBundle.Messages({
"PListNode.KeyCol=Key",
101 "PListNode.TypeCol=Type",
102 "PListNode.ValueCol=Value"})
104 private void customize() {
106 outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
108 outline.setRootVisible(
false);
109 if (null == explorerManager) {
110 explorerManager =
new ExplorerManager();
114 plistTableScrollPane.setViewportView(outlineView);
116 outline.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
118 this.setVisible(
true);
119 outline.setRowSelectionAllowed(
false);
127 @SuppressWarnings(
"unchecked")
129 private
void initComponents() {
131 jPanel1 =
new javax.swing.JPanel();
132 plistTableScrollPane =
new javax.swing.JScrollPane();
133 hdrPanel =
new javax.swing.JPanel();
134 exportButton =
new javax.swing.JButton();
136 jPanel1.setLayout(
new java.awt.BorderLayout());
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);
143 org.openide.awt.Mnemonics.setLocalizedText(exportButton,
org.openide.util.NbBundle.getMessage(PListViewer.class,
"PListViewer.exportButton.text"));
144 exportButton.addActionListener(
new java.awt.event.ActionListener() {
145 public void actionPerformed(java.awt.event.ActionEvent evt) {
146 exportButtonActionPerformed(evt);
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)
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))
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)
173 .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
175 layout.setVerticalGroup(
176 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
177 .addGroup(layout.createSequentialGroup()
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))
185 @NbBundle.Messages({
"PListViewer.ExportSuccess.message=Plist file exported successfully",
186 "PListViewer.ExportFailed.message=Plist file export failed.",})
191 private void exportButtonActionPerformed(java.awt.event.ActionEvent evt) {
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);
202 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
206 final JFileChooser fileChooser =
new JFileChooser();
207 fileChooser.setCurrentDirectory(
new File(openCase.getExportDirectory()));
208 fileChooser.setFileFilter(
new FileNameExtensionFilter(
"XML file",
"xml"));
210 final int returnVal = fileChooser.showSaveDialog(
this);
211 if (returnVal == JFileChooser.APPROVE_OPTION) {
213 File selectedFile = fileChooser.getSelectedFile();
214 if (!selectedFile.getName().endsWith(
".xml")) {
215 selectedFile =
new File(selectedFile.toString() +
".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);
231 logger.log(Level.SEVERE,
"Error exporting plist to XML file " + selectedFile.getName(), ex);
242 public List<String> getSupportedMIMETypes() {
243 return Arrays.asList(MIMETYPES);
252 public void setFile(
final AbstractFile file) {
262 public Component getComponent() {
271 public void resetComponent() {
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) {
286 new SwingWorker<List<PropKeyValue>, Void>() {
288 protected List<PropKeyValue> doInBackground() throws TskCoreException, IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException {
290 final byte[] plistFileBuf =
new byte[(int) plistFile.getSize()];
291 plistFile.read(plistFileBuf, 0, plistFile.getSize());
292 final List<PropKeyValue> plist = parsePList(plistFileBuf);
298 protected void done() {
300 List<PropKeyValue> plist;
305 SwingUtilities.invokeLater(() -> {
308 }
catch (InterruptedException ex) {
309 logger.log(Level.SEVERE,
"Interruption while parsing/dislaying plist file " + plistFile.getName(), ex);
311 JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
313 Bundle.PListViewer_processPlist_interruptedMessage(),
314 JOptionPane.ERROR_MESSAGE);
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);
333 private void setupTable(
final List<PropKeyValue> tableRows) {
334 explorerManager.setRootContext(
new AbstractNode(Children.create(
new PListRowFactory(tableRows),
true)));
341 private void setColumnWidths() {
342 final int margin = 4;
343 final int padding = 8;
346 final int rows = Math.min(20, outline.getRowCount());
347 for (
int col = 0; col < outline.getColumnCount(); col++) {
348 final int columnWidthLimit = 2000;
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);
355 columnWidth = Math.max(comp.getPreferredSize().width, columnWidth);
358 columnWidth += 2 * margin + padding;
359 columnWidth = Math.min(columnWidth, columnWidthLimit);
360 outline.getColumnModel().getColumn(col).setPreferredWidth(columnWidth);
367 @NbBundle.Messages({
"PListViewer.DataType.message=Binary Data value not shown"})
368 private PropKeyValue parseProperty(
final String key,
final NSObject value) {
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());
380 return new PropKeyValue(key, PropertyType.NUMBER, number.floatValue());
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;
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)));
396 pkv.setChildren(children.toArray(
new PropKeyValue[children.size()]));
398 }
else if (value instanceof NSDictionary) {
399 final List<PropKeyValue> children =
new ArrayList<>();
400 final NSDictionary dict = (NSDictionary) value;
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));
408 pkv.setChildren(children.toArray(
new PropKeyValue[children.size()]));
411 logger.log(Level.SEVERE,
"Can''t parse Plist for key = {0} value of type {1}",
new Object[]{key, value.getClass()});
424 private List<PropKeyValue> parsePList(
final byte[] plistbytes)
throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException {
426 final List<PropKeyValue> plist =
new ArrayList<>();
427 rootDict = PropertyListParser.parse(plistbytes);
433 if (rootDict instanceof NSArray) {
434 for (
int i = 0; i < ((NSArray) rootDict).count(); i++) {
435 final PropKeyValue pkv = parseProperty(
"", ((NSArray) rootDict).objectAtIndex(i));
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));
454 public ExplorerManager getExplorerManager() {
455 return explorerManager;
475 final static class PropKeyValue {
477 private final String key;
478 private final PropertyType type;
479 private final Object value;
481 private PropKeyValue[] children;
483 PropKeyValue(String key, PropertyType type, Object value) {
488 this.children = null;
494 PropKeyValue(PropKeyValue other) {
495 this.key = other.getKey();
496 this.type = other.getType();
497 this.value = other.getValue();
499 this.setChildren(other.getChildren());
506 PropertyType getType() {
519 PropKeyValue[] getChildren() {
520 if (children == null) {
525 return Arrays.stream(children)
526 .map(child ->
new PropKeyValue(child))
527 .toArray(PropKeyValue[]::
new);
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);
542 public boolean isSupported(AbstractFile file) {
546 private javax.swing.JButton exportButton;
547 private javax.swing.JPanel hdrPanel;
548 private javax.swing.JPanel jPanel1;
549 private javax.swing.JScrollPane plistTableScrollPane;