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;
61 import org.xml.sax.SAXException;
67 @SuppressWarnings(
"PMD.SingularField")
68 class PListViewer extends javax.swing.JPanel implements FileTypeViewer, ExplorerManager.Provider {
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());
74 private final org.openide.explorer.view.OutlineView outlineView;
75 private final Outline outline;
76 private ExplorerManager explorerManager;
78 private NSObject rootDict;
80 private final JFileChooserFactory fileChooserHelper =
new JFileChooserFactory();
88 outlineView =
new org.openide.explorer.view.OutlineView();
92 outline = outlineView.getOutline();
94 ((DefaultOutlineModel) outline.getOutlineModel()).setNodesColumnLabel(
"Key");
96 outlineView.setPropertyColumns(
97 "Type", Bundle.PListNode_TypeCol(),
98 "Value", Bundle.PListNode_ValueCol());
103 @NbBundle.Messages({
"PListNode.KeyCol=Key",
104 "PListNode.TypeCol=Type",
105 "PListNode.ValueCol=Value"})
107 private void customize() {
109 outline.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
111 outline.setRootVisible(
false);
112 if (null == explorerManager) {
113 explorerManager =
new ExplorerManager();
117 plistTableScrollPane.setViewportView(outlineView);
119 outline.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
121 this.setVisible(
true);
122 outline.setRowSelectionAllowed(
false);
130 @SuppressWarnings(
"unchecked")
132 private
void initComponents() {
134 jPanel1 =
new javax.swing.JPanel();
135 plistTableScrollPane =
new javax.swing.JScrollPane();
136 hdrPanel =
new javax.swing.JPanel();
137 exportButton =
new javax.swing.JButton();
139 jPanel1.setLayout(
new java.awt.BorderLayout());
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);
146 org.openide.awt.Mnemonics.setLocalizedText(exportButton,
org.openide.util.NbBundle.getMessage(PListViewer.class,
"PListViewer.exportButton.text"));
147 exportButton.addActionListener(
new java.awt.event.ActionListener() {
148 public void actionPerformed(java.awt.event.ActionEvent evt) {
149 exportButtonActionPerformed(evt);
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)
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))
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)
176 .addComponent(jPanel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
178 layout.setVerticalGroup(
179 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
180 .addGroup(layout.createSequentialGroup()
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))
188 @NbBundle.Messages({
"PListViewer.ExportSuccess.message=Plist file exported successfully",
189 "PListViewer.ExportFailed.message=Plist file export failed.",})
194 private void exportButtonActionPerformed(java.awt.event.ActionEvent evt) {
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);
205 logger.log(Level.SEVERE,
"Exception while getting open case.", ex);
209 final JFileChooser fileChooser = fileChooserHelper.getChooser();
210 fileChooser.setCurrentDirectory(
new File(openCase.getExportDirectory()));
211 fileChooser.setFileFilter(
new FileNameExtensionFilter(
"XML file",
"xml"));
213 final int returnVal = fileChooser.showSaveDialog(
this);
214 if (returnVal == JFileChooser.APPROVE_OPTION) {
216 File selectedFile = fileChooser.getSelectedFile();
217 if (!selectedFile.getName().endsWith(
".xml")) {
218 selectedFile =
new File(selectedFile.toString() +
".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);
234 logger.log(Level.SEVERE,
"Error exporting plist to XML file " + selectedFile.getName(), ex);
245 public List<String> getSupportedMIMETypes() {
246 return Arrays.asList(MIMETYPES);
255 public void setFile(
final AbstractFile file) {
265 public Component getComponent() {
274 public void resetComponent() {
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) {
289 new SwingWorker<List<PropKeyValue>, Void>() {
291 protected List<PropKeyValue> doInBackground() throws TskCoreException, IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException {
293 final byte[] plistFileBuf =
new byte[(int) plistFile.getSize()];
294 plistFile.read(plistFileBuf, 0, plistFile.getSize());
295 final List<PropKeyValue> plist = parsePList(plistFileBuf);
301 protected void done() {
303 List<PropKeyValue> plist;
308 SwingUtilities.invokeLater(() -> {
311 }
catch (InterruptedException ex) {
312 logger.log(Level.SEVERE,
"Interruption while parsing/dislaying plist file " + plistFile.getName(), ex);
314 JOptionPane.showMessageDialog(WindowManager.getDefault().getMainWindow(),
316 Bundle.PListViewer_processPlist_interruptedMessage(),
317 JOptionPane.ERROR_MESSAGE);
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);
336 private void setupTable(
final List<PropKeyValue> tableRows) {
337 explorerManager.setRootContext(
new AbstractNode(Children.create(
new PListRowFactory(tableRows),
true)));
344 private void setColumnWidths() {
345 final int margin = 4;
346 final int padding = 8;
349 final int rows = Math.min(20, outline.getRowCount());
350 for (
int col = 0; col < outline.getColumnCount(); col++) {
351 final int columnWidthLimit = 2000;
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);
358 columnWidth = Math.max(comp.getPreferredSize().width, columnWidth);
361 columnWidth += 2 * margin + padding;
362 columnWidth = Math.min(columnWidth, columnWidthLimit);
363 outline.getColumnModel().getColumn(col).setPreferredWidth(columnWidth);
370 @NbBundle.Messages({
"PListViewer.DataType.message=Binary Data value not shown"})
371 private PropKeyValue parseProperty(
final String key,
final NSObject value) {
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());
383 return new PropKeyValue(key, PropertyType.NUMBER, number.floatValue());
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;
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)));
399 pkv.setChildren(children.toArray(
new PropKeyValue[children.size()]));
401 }
else if (value instanceof NSDictionary) {
402 final List<PropKeyValue> children =
new ArrayList<>();
403 final NSDictionary dict = (NSDictionary) value;
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));
411 pkv.setChildren(children.toArray(
new PropKeyValue[children.size()]));
414 logger.log(Level.SEVERE,
"Can''t parse Plist for key = {0} value of type {1}",
new Object[]{key, value.getClass()});
427 private List<PropKeyValue> parsePList(
final byte[] plistbytes)
throws IOException, PropertyListFormatException, ParseException, ParserConfigurationException, SAXException {
429 final List<PropKeyValue> plist =
new ArrayList<>();
430 rootDict = PropertyListParser.parse(plistbytes);
436 if (rootDict instanceof NSArray) {
437 for (
int i = 0; i < ((NSArray) rootDict).count(); i++) {
438 final PropKeyValue pkv = parseProperty(
"", ((NSArray) rootDict).objectAtIndex(i));
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));
457 public ExplorerManager getExplorerManager() {
458 return explorerManager;
478 final static class PropKeyValue {
480 private final String key;
481 private final PropertyType type;
482 private final Object value;
484 private PropKeyValue[] children;
486 PropKeyValue(String key, PropertyType type, Object value) {
491 this.children = null;
497 PropKeyValue(PropKeyValue other) {
498 this.key = other.getKey();
499 this.type = other.getType();
500 this.value = other.getValue();
502 this.setChildren(other.getChildren());
509 PropertyType getType() {
522 PropKeyValue[] getChildren() {
523 if (children == null) {
528 return Arrays.stream(children)
529 .map(child ->
new PropKeyValue(child))
530 .toArray(PropKeyValue[]::
new);
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);
545 public boolean isSupported(AbstractFile file) {
549 private javax.swing.JButton exportButton;
550 private javax.swing.JPanel hdrPanel;
551 private javax.swing.JPanel jPanel1;
552 private javax.swing.JScrollPane plistTableScrollPane;