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;