/* * Autopsy Forensic Browser * * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sleuthkit.autopsy.corecomponents; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.logging.Level; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.coreutils.Logger; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JTextPane; import javax.swing.text.BadLocationException; import javax.swing.text.Utilities; import org.openide.nodes.Node; import org.openide.util.lookup.ServiceProvider; import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; import org.sleuthkit.autopsy.datamodel.DataConversion; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskException; /** * Hex view of file contents. */ @ServiceProvider(service = DataContentViewer.class, position = 1) public class DataContentViewerHex extends javax.swing.JPanel implements DataContentViewer { private static final long pageLength = 16384; private final byte[] data = new byte[(int) pageLength]; private static int currentPage = 1; private int totalPages; private Content dataSource; private static final Logger logger = Logger.getLogger(DataContentViewerHex.class.getName()); /** * Creates new form DataContentViewerHex */ public DataContentViewerHex() { initComponents(); customizeComponents(); this.resetComponent(); logger.log(Level.INFO, "Created HexView instance: " + this); //NON-NLS } private void customizeComponents() { outputViewPane.setComponentPopupMenu(rightClickMenu); ActionListener actList = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { JMenuItem jmi = (JMenuItem) e.getSource(); if (jmi.equals(copyMenuItem)) { outputViewPane.copy(); } else if (jmi.equals(selectAllMenuItem)) { outputViewPane.selectAll(); } } }; copyMenuItem.addActionListener(actList); selectAllMenuItem.addActionListener(actList); } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents private void initComponents() { rightClickMenu = new javax.swing.JPopupMenu(); copyMenuItem = new javax.swing.JMenuItem(); selectAllMenuItem = new javax.swing.JMenuItem(); hexViewerPanel = new javax.swing.JPanel(); jScrollPane1 = new javax.swing.JScrollPane(); outputViewPane = new JTextPane(){ public boolean getScrollableTracksViewportWidth() { return (getSize().width < 400); }}; this.outputViewPane.setBackground(new java.awt.Color(255, 255, 255)); // to make sure the background color is white this.outputViewPane.requestFocusInWindow(); this.outputViewPane.setCursor(Cursor.getDefaultCursor()); totalPageLabel = new javax.swing.JLabel(); ofLabel = new javax.swing.JLabel(); currentPageLabel = new javax.swing.JLabel(); pageLabel = new javax.swing.JLabel(); prevPageButton = new javax.swing.JButton(); nextPageButton = new javax.swing.JButton(); pageLabel2 = new javax.swing.JLabel(); goToPageTextField = new javax.swing.JTextField(); goToPageLabel = new javax.swing.JLabel(); goToOffsetLabel = new javax.swing.JLabel(); goToOffsetTextField = new javax.swing.JTextField(); copyMenuItem.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.copyMenuItem.text")); // NOI18N rightClickMenu.add(copyMenuItem); selectAllMenuItem.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.selectAllMenuItem.text")); // NOI18N rightClickMenu.add(selectAllMenuItem); jScrollPane1.setBackground(new java.awt.Color(255, 255, 255)); outputViewPane.setEditable(false); outputViewPane.setFont(new java.awt.Font("Courier New", 0, 11)); // NOI18N outputViewPane.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR)); outputViewPane.setMinimumSize(new java.awt.Dimension(700, 20)); outputViewPane.setPreferredSize(new java.awt.Dimension(700, 400)); jScrollPane1.setViewportView(outputViewPane); totalPageLabel.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.totalPageLabel.text_1")); // NOI18N ofLabel.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.ofLabel.text_1")); // NOI18N currentPageLabel.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.currentPageLabel.text_1")); // NOI18N currentPageLabel.setMaximumSize(new java.awt.Dimension(18, 14)); currentPageLabel.setMinimumSize(new java.awt.Dimension(18, 14)); currentPageLabel.setPreferredSize(new java.awt.Dimension(18, 14)); pageLabel.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.pageLabel.text_1")); // NOI18N pageLabel.setMaximumSize(new java.awt.Dimension(33, 14)); pageLabel.setMinimumSize(new java.awt.Dimension(33, 14)); pageLabel.setPreferredSize(new java.awt.Dimension(33, 14)); prevPageButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N prevPageButton.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.prevPageButton.text")); // NOI18N prevPageButton.setBorderPainted(false); prevPageButton.setContentAreaFilled(false); prevPageButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N prevPageButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); prevPageButton.setPreferredSize(new java.awt.Dimension(23, 23)); prevPageButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_hover.png"))); // NOI18N prevPageButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { prevPageButtonActionPerformed(evt); } }); nextPageButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N nextPageButton.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.nextPageButton.text")); // NOI18N nextPageButton.setBorderPainted(false); nextPageButton.setContentAreaFilled(false); nextPageButton.setDisabledIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N nextPageButton.setMargin(new java.awt.Insets(2, 0, 2, 0)); nextPageButton.setPreferredSize(new java.awt.Dimension(23, 23)); nextPageButton.setRolloverIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_hover.png"))); // NOI18N nextPageButton.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { nextPageButtonActionPerformed(evt); } }); pageLabel2.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.pageLabel2.text")); // NOI18N pageLabel2.setMaximumSize(new java.awt.Dimension(29, 14)); pageLabel2.setMinimumSize(new java.awt.Dimension(29, 14)); pageLabel2.setPreferredSize(new java.awt.Dimension(29, 14)); goToPageTextField.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.goToPageTextField.text")); // NOI18N goToPageTextField.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { goToPageTextFieldActionPerformed(evt); } }); goToPageLabel.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.goToPageLabel.text")); // NOI18N goToOffsetLabel.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.goToOffsetLabel.text")); // NOI18N goToOffsetTextField.setText(org.openide.util.NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.goToOffsetTextField.text")); // NOI18N goToOffsetTextField.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { goToOffsetTextFieldActionPerformed(evt); } }); javax.swing.GroupLayout hexViewerPanelLayout = new javax.swing.GroupLayout(hexViewerPanel); hexViewerPanel.setLayout(hexViewerPanelLayout); hexViewerPanelLayout.setHorizontalGroup( hexViewerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(hexViewerPanelLayout.createSequentialGroup() .addContainerGap() .addComponent(pageLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(18, 18, 18) .addComponent(currentPageLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(ofLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(totalPageLabel) .addGap(50, 50, 50) .addComponent(pageLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(prevPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(0, 0, 0) .addComponent(nextPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(18, 18, 18) .addComponent(goToPageLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(goToPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(18, 18, 18) .addComponent(goToOffsetLabel) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(goToOffsetTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addComponent(jScrollPane1) ); hexViewerPanelLayout.setVerticalGroup( hexViewerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(hexViewerPanelLayout.createSequentialGroup() .addGroup(hexViewerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(pageLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGroup(hexViewerPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(currentPageLabel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(ofLabel) .addComponent(totalPageLabel)) .addComponent(pageLabel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(nextPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(prevPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(goToPageLabel) .addComponent(goToPageTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(goToOffsetLabel) .addComponent(goToOffsetTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 382, Short.MAX_VALUE)) ); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 622, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGap(0, 0, 0) .addComponent(hexViewerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addGap(0, 0, 0))) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGap(0, 411, Short.MAX_VALUE) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addGap(0, 0, 0) .addComponent(hexViewerPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addGap(0, 0, 0))) ); }// </editor-fold>//GEN-END:initComponents private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed setDataViewByPageNumber(currentPage - 1); goToPageTextField.setText(Integer.toString(currentPage)); }//GEN-LAST:event_prevPageButtonActionPerformed private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed setDataViewByPageNumber(currentPage + 1); goToPageTextField.setText(Integer.toString(currentPage)); }//GEN-LAST:event_nextPageButtonActionPerformed private void goToPageTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_goToPageTextFieldActionPerformed String pageNumberStr = goToPageTextField.getText(); int pageNumber = 0; try { pageNumber = Integer.parseInt(pageNumberStr); } catch (NumberFormatException ex) { pageNumber = totalPages + 1; } if (pageNumber > totalPages || pageNumber < 1) { JOptionPane.showMessageDialog(this, NbBundle.getMessage(this.getClass(), "DataContentViewerHex.goToPageTextField.msgDlg", totalPages), NbBundle.getMessage(this.getClass(), "DataContentViewerHex.goToPageTextField.err"), JOptionPane.WARNING_MESSAGE); return; } setDataViewByPageNumber(pageNumber); }//GEN-LAST:event_goToPageTextFieldActionPerformed /** * * * Calculates the offset relative to the current caret position. * * @param userInput the user provided signed offset value. * * @return returns the resultant offset value relative to the current caret * position. -1L is returned if the resultant offset cannot be * calculated. */ private long getOffsetRelativeToCaretPosition(Long userInput) { String userSelectedLine; try { // get the selected line. Extract the current hex offset location. userSelectedLine = outputViewPane.getText().subSequence( Utilities.getRowStart(outputViewPane, outputViewPane.getCaretPosition()), Utilities.getRowEnd(outputViewPane, outputViewPane.getCaretPosition())) .toString(); // NOTE: This needs to change if the outputFormat of outputViewPane changes. String hexForUserSelectedLine = userSelectedLine.substring(0, userSelectedLine.indexOf(":")); return Long.decode(hexForUserSelectedLine) + userInput; } catch (BadLocationException | StringIndexOutOfBoundsException | NumberFormatException ex) { // thrown in case the caret location is out of the range of the outputViewPane. return -1L; } } private void goToOffsetTextFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_goToOffsetTextFieldActionPerformed long offset; try { if (goToOffsetTextField.getText().startsWith("+") || goToOffsetTextField.getText().startsWith("-")) { offset = getOffsetRelativeToCaretPosition(Long.decode(goToOffsetTextField.getText())); } else { offset = Long.decode(goToOffsetTextField.getText()); } } catch (NumberFormatException ex) { // notify the user and return JOptionPane.showMessageDialog(this, NbBundle.getMessage(this.getClass(), "DataContentViewerHex.goToOffsetTextField.msgDlg", goToOffsetTextField.getText())); return; } if (offset >= 0) { setDataViewByOffset(offset); } else { outputViewPane.setText(NbBundle.getMessage(DataContentViewerHex.class, "DataContentViewerHex.setDataView.invalidOffset.negativeOffsetValue")); } }//GEN-LAST:event_goToOffsetTextFieldActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JMenuItem copyMenuItem; private javax.swing.JLabel currentPageLabel; private javax.swing.JLabel goToOffsetLabel; private javax.swing.JTextField goToOffsetTextField; private javax.swing.JLabel goToPageLabel; private javax.swing.JTextField goToPageTextField; private javax.swing.JPanel hexViewerPanel; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JButton nextPageButton; private javax.swing.JLabel ofLabel; private javax.swing.JTextPane outputViewPane; private javax.swing.JLabel pageLabel; private javax.swing.JLabel pageLabel2; private javax.swing.JButton prevPageButton; private javax.swing.JPopupMenu rightClickMenu; private javax.swing.JMenuItem selectAllMenuItem; private javax.swing.JLabel totalPageLabel; // End of variables declaration//GEN-END:variables /** * Sets the DataView (The tabbed panel) by page number * * @param page Page to display (1-based counting) */ private void setDataViewByPageNumber(int page) { if (this.dataSource == null) { return; } if (page == 0) { return; } currentPage = page; long offset = (currentPage - 1) * pageLength; setDataView(offset); goToOffsetTextField.setText(Long.toString(offset)); } /** * Sets the DataView (The tabbed panel) by offset * * @param offset Page to display (1-based counting) */ private void setDataViewByOffset(long offset) { if (this.dataSource == null) { return; } currentPage = (int) (offset / pageLength) + 1; setDataView(offset); goToPageTextField.setText(Integer.toString(currentPage)); } private void setDataView(long offset) { // change the cursor to "waiting cursor" for this operation this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); String errorText = null; int bytesRead = 0; if (dataSource.getSize() > 0) { try { bytesRead = dataSource.read(data, offset, pageLength); // read the data } catch (TskException ex) { errorText = NbBundle.getMessage(this.getClass(), "DataContentViewerHex.setDataView.errorText", offset, offset + pageLength); logger.log(Level.WARNING, "Error while trying to show the hex content.", ex); //NON-NLS } } // set the data on the bottom and show it if (bytesRead <= 0) { errorText = NbBundle.getMessage(this.getClass(), "DataContentViewerHex.setDataView.errorText", offset, offset + pageLength); } // disable or enable the next button if ((errorText == null) && (currentPage < totalPages)) { nextPageButton.setEnabled(true); } else { nextPageButton.setEnabled(false); } if ((errorText == null) && (currentPage > 1)) { prevPageButton.setEnabled(true); } else { prevPageButton.setEnabled(false); } currentPageLabel.setText(Integer.toString(currentPage)); setComponentsVisibility(true); // shows the components that not needed // set the output view if (errorText == null) { int showLength = bytesRead < pageLength ? bytesRead : (int) pageLength; outputViewPane.setText(DataConversion.byteArrayToHex(data, showLength, offset)); } else { outputViewPane.setText(errorText); } outputViewPane.setCaretPosition(0); this.setCursor(null); } @Override public void setNode(Node selectedNode) { if ((selectedNode == null) || (!isSupported(selectedNode))) { resetComponent(); return; } Content content = (selectedNode).getLookup().lookup(Content.class); if (content == null) { resetComponent(); return; } dataSource = content; totalPages = 0; if (dataSource.getSize() > 0) { totalPages = Math.round((dataSource.getSize() - 1) / pageLength) + 1; } totalPageLabel.setText(Integer.toString(totalPages)); this.setDataViewByPageNumber(1); } @Override public String getTitle() { return NbBundle.getMessage(this.getClass(), "DataContentViewerHex.title"); } @Override public String getToolTip() { return NbBundle.getMessage(this.getClass(), "DataContentViewerHex.toolTip"); } @Override public DataContentViewer createInstance() { return new DataContentViewerHex(); } @Override public void resetComponent() { // clear / reset the fields currentPage = 1; this.dataSource = null; currentPageLabel.setText(""); totalPageLabel.setText(""); outputViewPane.setText(""); setComponentsVisibility(false); // hides the components that not needed } /** * To set the visibility of specific components in this class. * * @param isVisible whether to show or hide the specific components */ private void setComponentsVisibility(boolean isVisible) { currentPageLabel.setVisible(isVisible); totalPageLabel.setVisible(isVisible); ofLabel.setVisible(isVisible); prevPageButton.setVisible(isVisible); nextPageButton.setVisible(isVisible); pageLabel.setVisible(isVisible); pageLabel2.setVisible(isVisible); goToPageTextField.setVisible(isVisible); goToPageLabel.setVisible(isVisible); goToOffsetTextField.setVisible(isVisible); goToOffsetLabel.setVisible(isVisible); } @Override public boolean isSupported(Node node) { if (node == null) { return false; } Content content = node.getLookup().lookup(Content.class); if (content != null && content.getSize() > 0) { return true; } return false; } @Override public int isPreferred(Node node) { return 1; } @Override public Component getComponent() { return this; } /* * Show the right click menu only if evt is the correct mouse event */ private void maybeShowPopup(java.awt.event.MouseEvent evt) { if (evt.isPopupTrigger()) { rightClickMenu.setLocation(evt.getLocationOnScreen()); rightClickMenu.setVisible(true); copyMenuItem.setEnabled(outputViewPane.getSelectedText() != null); } else { rightClickMenu.setVisible(false); } } }