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;
 
   82     public PListViewer() {
 
   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     private javax.swing.JButton exportButton;
 
  543     private javax.swing.JPanel hdrPanel;
 
  544     private javax.swing.JPanel jPanel1;
 
  545     private javax.swing.JScrollPane plistTableScrollPane;