Autopsy 4.22.1
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 */
19package org.sleuthkit.autopsy.contentviewers;
20
21import java.awt.Component;
22import java.util.List;
23import org.sleuthkit.datamodel.AbstractFile;
24import java.util.Arrays;
25import com.dd.plist.NSDictionary;
26import com.dd.plist.PropertyListParser;
27import com.dd.plist.NSObject;
28import com.dd.plist.NSArray;
29import com.dd.plist.NSDate;
30import com.dd.plist.NSString;
31import com.dd.plist.NSNumber;
32import com.dd.plist.NSData;
33import com.dd.plist.PropertyListFormatException;
34import java.io.File;
35import java.io.IOException;
36import java.text.ParseException;
37import java.util.ArrayList;
38import java.util.concurrent.ExecutionException;
39import java.util.logging.Level;
40import javax.swing.JFileChooser;
41import javax.swing.JOptionPane;
42import javax.swing.JTable;
43import javax.swing.ListSelectionModel;
44import javax.swing.SwingUtilities;
45import javax.swing.SwingWorker;
46import javax.swing.filechooser.FileNameExtensionFilter;
47import javax.swing.table.TableCellRenderer;
48import javax.xml.parsers.ParserConfigurationException;
49import org.netbeans.swing.outline.DefaultOutlineModel;
50import org.netbeans.swing.outline.Outline;
51import org.openide.explorer.ExplorerManager;
52import org.openide.nodes.AbstractNode;
53import org.openide.nodes.Children;
54import org.openide.util.NbBundle;
55import org.openide.windows.WindowManager;
56import org.sleuthkit.autopsy.casemodule.Case;
57import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
58import org.sleuthkit.autopsy.coreutils.Logger;
59import org.sleuthkit.autopsy.guiutils.JFileChooserFactory;
60import org.sleuthkit.datamodel.TskCoreException;
61import org.xml.sax.SAXException;
62
67@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
68class 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
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}
synchronized static Logger getLogger(String name)
Definition Logger.java:124

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.