diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties index 45916b87b3..8267587395 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties @@ -33,3 +33,7 @@ MessageContentViewer.directionText.text=direction MessageContentViewer.ccLabel.text=CC: MessageContentViewer.attachmentsPanel.TabConstraints.tabTitle=Attachments MessageContentViewer.viewInNewWindowButton.text=View in New Window +JPEGViewerDummy.jLabel1.text=You are looking at a JPEG file: +JPEGViewerDummy.jTextField1.text=jTextField1 +SQLiteViewer.jLabel1.text=Table +SQLiteViewer.numEntriesField.text=num Entries diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/FileTypeViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/FileTypeViewer.java new file mode 100644 index 0000000000..f4a677c4f4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/FileTypeViewer.java @@ -0,0 +1,50 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit 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.contentviewers; + +import java.awt.Component; +import java.util.List; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * Defines an interface for application specific content viewer + * + */ +interface FileTypeViewer { + + /** + * Returns list of MIME types supported by this viewer + */ + List getSupportedMIMETypes(); + + /** + * Display the given file's content in the view panel + */ + void setFile(AbstractFile file); + + /** + * Returns panel + */ + Component getComponent(); + + /** + * Clears the data in the panel + */ + void resetComponent(); +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.form new file mode 100644 index 0000000000..d07831cafe --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.form @@ -0,0 +1,41 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java new file mode 100644 index 0000000000..0d17ce2cd6 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java @@ -0,0 +1,235 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit 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.contentviewers; + +import com.google.common.base.Strings; +import java.awt.Component; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import org.openide.nodes.Node; +import org.openide.util.NbBundle; +import org.openide.util.lookup.ServiceProvider; +import org.sleuthkit.autopsy.corecomponentinterfaces.DataContentViewer; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; +import org.sleuthkit.datamodel.AbstractFile; + +/** + * Generic Application content viewer + */ +@ServiceProvider(service = DataContentViewer.class, position = 5) +public class FileViewer extends javax.swing.JPanel implements DataContentViewer { + + private static final int CONFIDENCE_LEVEL = 7; + private static final long serialVersionUID = 1L; + private static final Logger LOGGER = Logger.getLogger(FileViewer.class.getName()); + + private final Map mimeTypeToViewerMap = new HashMap<>(); + + // TBD: This hardcoded list of viewers should be replaced with a dynamic lookup + private static final FileTypeViewer[] KNOWN_VIEWERS = new FileTypeViewer[]{ + // new JPEGViewerDummy(), // this if for testing only + new SQLiteViewer() + }; + + private FileTypeViewer lastViewer; + + /** + * Creates new form ApplicationContentViewer + */ + public FileViewer() { + + // init the mimetype to viewer map + for (FileTypeViewer cv : KNOWN_VIEWERS) { + cv.getSupportedMIMETypes().forEach((mimeType) -> { + if (mimeTypeToViewerMap.containsKey(mimeType) == false) { + mimeTypeToViewerMap.put(mimeType, cv); + } else { + LOGGER.log(Level.WARNING, "Duplicate viewer for mimtype: {0}", mimeType); //NON-NLS + } + }); + } + + initComponents(); + + LOGGER.log(Level.INFO, "Created ApplicationContentViewer instance: {0}", this); //NON-NLS + } + + /** + * Get the FileTypeViewer for a given mimetype + * + * @param mimeType + * + * @return FileTypeViewer, null if no known content viewer supports the mimetype + */ + private FileTypeViewer getSupportingViewer(String mimeType) { + return mimeTypeToViewerMap.get(mimeType); + } + + /** + * 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") + // //GEN-BEGIN:initComponents + private void initComponents() { + + setLayout(new javax.swing.OverlayLayout(this)); + }// //GEN-END:initComponents + + + // Variables declaration - do not modify//GEN-BEGIN:variables + // End of variables declaration//GEN-END:variables + + @Override + public void setNode(Node selectedNode) { + + resetComponent(); + + if (selectedNode == null) { + return; + } + + AbstractFile file = selectedNode.getLookup().lookup(AbstractFile.class); + if (file == null) { + return; + } + + String mimeType = file.getMIMEType(); + if (Strings.isNullOrEmpty(mimeType)) { + LOGGER.log(Level.INFO, "Mimetype not known for file: {0}", file.getName()); //NON-NLS + try { + FileTypeDetector fileTypeDetector = new FileTypeDetector(); + mimeType = fileTypeDetector.detectMIMEType(file); + }catch (FileTypeDetector.FileTypeDetectorInitException ex) { + LOGGER.log(Level.SEVERE, "Failed to initialize FileTypeDetector.", ex); //NON-NLS + return; + } + } + + if (mimeType.equalsIgnoreCase("application/octet-stream")) { + return; + } + else { + FileTypeViewer viewer = getSupportingViewer(mimeType); + if (viewer != null) { + lastViewer = viewer; + + viewer.setFile(file); + this.removeAll(); + this.add(viewer.getComponent()); + this.repaint(); + } + } + + } + + @Override + @NbBundle.Messages("ApplicationContentViewer.title=Application") + public String getTitle() { + return Bundle.ApplicationContentViewer_title(); + } + + @Override + @NbBundle.Messages("ApplicationContentViewer.toolTip=Displays file contents.") + public String getToolTip() { + return Bundle.ApplicationContentViewer_toolTip(); + } + + @Override + public DataContentViewer createInstance() { + return new FileViewer(); + } + + @Override + public Component getComponent() { + return this; + } + + @Override + public void resetComponent() { + + if (lastViewer != null) { + lastViewer.resetComponent(); + } + this.removeAll(); + lastViewer = null; + } + + @Override + public boolean isSupported(Node node) { + + if (node == null) { + return false; + } + + AbstractFile aFile = node.getLookup().lookup(AbstractFile.class); + if (aFile == null) { + return false; + } + + String mimeType = aFile.getMIMEType(); + if (Strings.isNullOrEmpty(mimeType)) { + LOGGER.log(Level.INFO, "Mimetype not known for file: {0}", aFile.getName()); //NON-NLS + try { + FileTypeDetector fileTypeDetector = new FileTypeDetector(); + mimeType = fileTypeDetector.detectMIMEType(aFile); + }catch (FileTypeDetector.FileTypeDetectorInitException ex) { + LOGGER.log(Level.SEVERE, "Failed to initialize FileTypeDetector.", ex); //NON-NLS + return false; + } + } + + if (mimeType.equalsIgnoreCase("application/octet-stream")) { + return false; + } else { + return (getSupportingViewer(mimeType) != null); + } + + } + + @Override + public int isPreferred(Node node) { + AbstractFile file = node.getLookup().lookup(AbstractFile.class); + String mimeType = file.getMIMEType(); + + if (Strings.isNullOrEmpty(mimeType)) { + LOGGER.log(Level.INFO, "Mimetype not known for file: {0}", file.getName()); //NON-NLS + try { + FileTypeDetector fileTypeDetector = new FileTypeDetector(); + mimeType = fileTypeDetector.detectMIMEType(file); + }catch (FileTypeDetector.FileTypeDetectorInitException ex) { + LOGGER.log(Level.SEVERE, "Failed to initialize FileTypeDetector.", ex); //NON-NLS + return 0; + } + } + + if (mimeType.equalsIgnoreCase("application/octet-stream")) { + return 0; + } else { + if (null != getSupportingViewer(mimeType)) { + return CONFIDENCE_LEVEL; + } + } + + return 0; + } +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/JPEGViewerDummy.form b/Core/src/org/sleuthkit/autopsy/contentviewers/JPEGViewerDummy.form new file mode 100644 index 0000000000..587dd3c9a0 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/JPEGViewerDummy.form @@ -0,0 +1,58 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/JPEGViewerDummy.java b/Core/src/org/sleuthkit/autopsy/contentviewers/JPEGViewerDummy.java new file mode 100644 index 0000000000..8aea7540e1 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/JPEGViewerDummy.java @@ -0,0 +1,89 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.contentviewers; + +import java.awt.Component; +import java.util.Arrays; +import java.util.List; +import org.sleuthkit.datamodel.AbstractFile; + +public class JPEGViewerDummy extends javax.swing.JPanel implements FileTypeViewer { + + public static final String[] SUPPORTED_MIMETYPES = new String[]{"image/jpeg"}; + + /** + * Creates new form JPEGViewer + */ + public JPEGViewerDummy() { + initComponents(); + } + + /** + * 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") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jLabel1 = new javax.swing.JLabel(); + jTextField1 = new javax.swing.JTextField(); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(JPEGViewerDummy.class, "JPEGViewerDummy.jLabel1.text")); // NOI18N + + jTextField1.setEditable(false); + jTextField1.setText(org.openide.util.NbBundle.getMessage(JPEGViewerDummy.class, "JPEGViewerDummy.jTextField1.text")); // NOI18N + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGap(43, 43, 43) + .addComponent(jLabel1) + .addGap(35, 35, 35) + .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(120, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(jLabel1) + .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(269, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + @Override + public List getSupportedMIMETypes() { + return Arrays.asList(SUPPORTED_MIMETYPES); + } + + @Override + public Component getComponent() { + return this; + } + + @Override + public void resetComponent() { + this.jTextField1.setText(""); + } + + @Override + public void setFile(AbstractFile file) { + this.jTextField1.setText(file.getName()); + } + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JLabel jLabel1; + private javax.swing.JTextField jTextField1; + // End of variables declaration//GEN-END:variables + +} diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.form b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.form new file mode 100644 index 0000000000..3cdee8658c --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.form @@ -0,0 +1,153 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java new file mode 100644 index 0000000000..fff2f5d781 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/SQLiteViewer.java @@ -0,0 +1,395 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit 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.contentviewers; + +import java.awt.Component; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.logging.Level; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; +import java.sql.SQLException; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.ExecutionException; +import javax.swing.JComboBox; +import javax.swing.SwingWorker; +import org.sleuthkit.autopsy.casemodule.Case; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.datamodel.ContentUtils; +import org.sleuthkit.datamodel.AbstractFile; + +public class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer { + + public static final String[] SUPPORTED_MIMETYPES = new String[]{"application/x-sqlite3"}; + private static final Logger LOGGER = Logger.getLogger(FileViewer.class.getName()); + private Connection connection = null; + + private String tmpDBPathName = null; + private File tmpDBFile = null; + + // TBD: Change the value to be a Array of ColDefs + Map dbTablesMap = new TreeMap<>(); + + /** + * Creates new form SQLiteViewer + */ + public SQLiteViewer() { + initComponents(); + + customizeComponents(); + } + + /** + * 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") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jHdrPanel = new javax.swing.JPanel(); + tablesDropdownList = new javax.swing.JComboBox<>(); + jLabel1 = new javax.swing.JLabel(); + numEntriesField = new javax.swing.JTextField(); + jTableDataPanel = new javax.swing.JPanel(); + jScrollPane1 = new javax.swing.JScrollPane(); + jTable1 = new javax.swing.JTable(); + + tablesDropdownList.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" })); + tablesDropdownList.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + tablesDropdownListActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.jLabel1.text")); // NOI18N + + numEntriesField.setEditable(false); + numEntriesField.setText(org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.numEntriesField.text")); // NOI18N + numEntriesField.setBorder(null); + numEntriesField.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + numEntriesFieldActionPerformed(evt); + } + }); + + javax.swing.GroupLayout jHdrPanelLayout = new javax.swing.GroupLayout(jHdrPanel); + jHdrPanel.setLayout(jHdrPanelLayout); + jHdrPanelLayout.setHorizontalGroup( + jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jHdrPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(tablesDropdownList, javax.swing.GroupLayout.PREFERRED_SIZE, 130, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(23, 23, 23) + .addComponent(numEntriesField, javax.swing.GroupLayout.PREFERRED_SIZE, 71, javax.swing.GroupLayout.PREFERRED_SIZE) + .addContainerGap(130, Short.MAX_VALUE)) + ); + jHdrPanelLayout.setVerticalGroup( + jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jHdrPanelLayout.createSequentialGroup() + .addContainerGap() + .addGroup(jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(tablesDropdownList, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel1) + .addComponent(numEntriesField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addContainerGap(16, Short.MAX_VALUE)) + ); + + jTable1.setModel(new javax.swing.table.DefaultTableModel( + new Object [][] { + {null, null, null, null}, + {null, null, null, null}, + {null, null, null, null}, + {null, null, null, null} + }, + new String [] { + "Title 1", "Title 2", "Title 3", "Title 4" + } + )); + jScrollPane1.setViewportView(jTable1); + + javax.swing.GroupLayout jTableDataPanelLayout = new javax.swing.GroupLayout(jTableDataPanel); + jTableDataPanel.setLayout(jTableDataPanelLayout); + jTableDataPanelLayout.setHorizontalGroup( + jTableDataPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jTableDataPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addGap(15, 15, 15)) + ); + jTableDataPanelLayout.setVerticalGroup( + jTableDataPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(jTableDataPanelLayout.createSequentialGroup() + .addContainerGap() + .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 275, Short.MAX_VALUE) + .addContainerGap()) + ); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(jHdrPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jTableDataPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(jHdrPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jTableDataPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void numEntriesFieldActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_numEntriesFieldActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_numEntriesFieldActionPerformed + + private void tablesDropdownListActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tablesDropdownListActionPerformed + JComboBox cb = (JComboBox) evt.getSource(); + String tableName = (String) cb.getSelectedItem(); + if (null == tableName) { + return; + } + + readTable(tableName); + }//GEN-LAST:event_tablesDropdownListActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JPanel jHdrPanel; + private javax.swing.JLabel jLabel1; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JTable jTable1; + private javax.swing.JPanel jTableDataPanel; + private javax.swing.JTextField numEntriesField; + private javax.swing.JComboBox tablesDropdownList; + // End of variables declaration//GEN-END:variables + + @Override + public List getSupportedMIMETypes() { + return Arrays.asList(SUPPORTED_MIMETYPES); + } + + @Override + public void setFile(AbstractFile file) { + processSQLiteFile(file); + } + + @Override + public Component getComponent() { + return this; + } + + @Override + public void resetComponent() { + + dbTablesMap.clear(); + + tablesDropdownList.setEnabled(true); + tablesDropdownList.removeAllItems(); + numEntriesField.setText(""); + + // close DB connection to file + if (null != connection) { + try { + connection.close(); + connection = null; + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Failed to close DB connection to file.", ex); //NON-NLS + } + } + + // delete last temp file + if (null != tmpDBFile) { + tmpDBFile.delete(); + tmpDBFile = null; + } + } + + private void customizeComponents() { + + // add a actionListener to jTablesComboBox + } + + /** + * Process the given SQLite DB file + * + * @param sqliteFile - + * + * @return none + */ + private void processSQLiteFile(AbstractFile sqliteFile) { + + tablesDropdownList.removeAllItems(); + + new SwingWorker() { + @Override + protected Boolean doInBackground() throws Exception { + + try { + // Copy the file to temp folder + tmpDBPathName = Case.getCurrentCase().getTempDirectory() + File.separator + sqliteFile.getName() + "-" + sqliteFile.getId(); + tmpDBFile = new File(tmpDBPathName); + ContentUtils.writeToFile(sqliteFile, tmpDBFile); + + // Open copy using JDBC + Class.forName("org.sqlite.JDBC"); //NON-NLS //load JDBC driver + connection = DriverManager.getConnection("jdbc:sqlite:" + tmpDBPathName); //NON-NLS + + // Read all table names and schema + return getTables(); + } catch (IOException ex) { + LOGGER.log(Level.SEVERE, "Failed to copy DB file.", ex); //NON-NLS + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Failed to Open DB.", ex); //NON-NLS + } catch (ClassNotFoundException ex) { + LOGGER.log(Level.SEVERE, "Failed to initialize JDBC Sqlite.", ex); //NON-NLS + } + return false; + } + + @Override + protected void done() { + super.done(); + try { + boolean status = get(); + if ((status == true) && (dbTablesMap.size() > 0)) { + dbTablesMap.keySet().forEach((tableName) -> { + tablesDropdownList.addItem(tableName); + }); + } else { + // Populate error message + tablesDropdownList.addItem("No tables found"); + tablesDropdownList.setEnabled(false); + } + } catch (InterruptedException | ExecutionException ex) { + LOGGER.log(Level.SEVERE, "Unexpected exception while opening DB file", ex); //NON-NLS + } + } + }.execute(); + + } + + /** + * Gets the table names and their schema from loaded SQLite db file + * + * @return true if success, false otherwise + */ + private boolean getTables() { + + try { + Statement statement = connection.createStatement(); + + ResultSet resultSet = statement.executeQuery( + "SELECT name, sql FROM sqlite_master " + + " WHERE type= 'table' " + + " ORDER BY name;"); //NON-NLS + + while (resultSet.next()) { + String tableName = resultSet.getString("name"); //NON-NLS + String tableSQL = resultSet.getString("sql"); //NON-NLS + + dbTablesMap.put(tableName, tableSQL); + String query = "PRAGMA table_info(" + tableName + ")"; //NON-NLS + ResultSet rs2; + try { + Statement statement2 = connection.createStatement(); + rs2 = statement2.executeQuery(query); + while (rs2.next()) { + + // System.out.println("RAMAN: Col Name = " + rs2.getString("name")); + // System.out.println("RAMAN: Col Type = " + rs2.getString("type")); + + // RAMAN TBD: parse and save the table schema + } + } catch (Exception ex) { + LOGGER.log(Level.WARNING, "Error while trying to get columns from sqlite db." + connection, ex); //NON-NLS + } + } + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "Error getting table names from the DB", e); //NON-NLS + } + return true; + } + + private void readTable(String tableName) { + // TBD: need to handle cancelling if one is already in progress + + new SwingWorker() { + @Override + protected Integer doInBackground() throws Exception { + + try { + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery( + "SELECT COUNT(*) as count FROM " + tableName); //NON-NLS + + // TBD: read the rows here and popluate the ExplorerManager. + + return resultSet.getInt("count"); + } catch (SQLException ex) { + LOGGER.log(Level.SEVERE, "Failed to get data for table.", ex); //NON-NLS + } + //NON-NLS + return 0; + } + + @Override + protected void done() { + super.done(); + try { + int numRows = get(); + numEntriesField.setText(numRows + " entries"); + } catch (InterruptedException | ExecutionException ex) { + LOGGER.log(Level.SEVERE, "Unexpected exception while reading table.", ex); //NON-NLS + } + } + }.execute(); + + } + + enum SQLStorageClass { + NULL, + INTEGER, + REAL, + TEXT, + BLOB + }; + + private class SQLColDef { + + private final String colName; + private final SQLStorageClass storageClass; + + SQLColDef(String colName, SQLStorageClass sc) { + this.colName = colName; + this.storageClass = sc; + } + + } +} diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/Bundle.properties b/Core/src/org/sleuthkit/autopsy/livetriage/Bundle.properties new file mode 100644 index 0000000000..61f522a6b4 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/livetriage/Bundle.properties @@ -0,0 +1,7 @@ +SelectDriveDialog.bnOk.text=Ok +SelectDriveDialog.bnRefresh.text=Refresh +SelectDriveDialog.lbSelectDrive.text=Select the drive to copy the application and script to: +SelectDriveDialog.jLabel1.text=Select drive to use for live triage (may take time to load): +SelectDriveDialog.errorLabel.text=jLabel2 +SelectDriveDialog.bnCancel.text=Cancel +SelectDriveDialog.jTextArea1.text=This feature copies the application and a batch file to a removable drive,\nallowing systems to be analyzed without installing the software or\nimaging the drives.\n\nTo analyze a system, insert the drive and run "RunFromUSB.bat" as\nadministrator, then select the "Local Disk" option on the Add Data Source\npanel. diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java b/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java new file mode 100644 index 0000000000..846632a092 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/livetriage/CreateLiveTriageDriveAction.java @@ -0,0 +1,284 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2018 Basis Technology Corp. + * Contact: carrier sleuthkit 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.livetriage; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.InvalidPathException; +import java.util.logging.Level; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeEvent; +import javax.swing.JOptionPane; +import java.awt.Frame; +import javax.swing.SwingWorker; +import org.apache.commons.io.FileUtils; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionRegistration; +import org.openide.util.HelpCtx; +import org.openide.util.NbBundle; +import org.openide.util.actions.CallableSystemAction; +import org.openide.windows.WindowManager; +import org.sleuthkit.autopsy.core.UserPreferences; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; +import org.sleuthkit.autopsy.progress.ModalDialogProgressIndicator; + +@ActionID(category = "Tools", id = "org.sleuthkit.autopsy.livetriage.CreateLiveTriageDriveAction") +@ActionReference(path = "Menu/Tools", position = 1401) +@ActionRegistration(displayName = "#CTL_CreateLiveTriageDriveAction", lazy = false) +@NbBundle.Messages({"CTL_CreateLiveTriageDriveAction=Make Live Triage Drive"}) +public final class CreateLiveTriageDriveAction extends CallableSystemAction implements PropertyChangeListener { + + private static final String DISPLAY_NAME = Bundle.CTL_CreateLiveTriageDriveAction(); + private ModalDialogProgressIndicator progressIndicator = null; + private String drivePath = ""; + private CopyFilesWorker worker; + + @Override + public boolean isEnabled() { + return true; + } + + @NbBundle.Messages({"CreateLiveTriageDriveAction.error.title=Error creating live triage disk", + "CreateLiveTriageDriveAction.exenotfound.message=Executable could not be found", + "CreateLiveTriageDriveAction.batchFileError.message=Error creating batch file", + "CreateLiveTriageDriveAction.appPathError.message=Could not location application directory", + "CreateLiveTriageDriveAction.copyError.message=Could not copy application. Only works on installed version.", + "CreateLiveTriageDriveAction.success.title=Success", + "CreateLiveTriageDriveAction.success.message=Live triage drive created. Use RunFromUSB.bat to run the application" + }) + @Override + @SuppressWarnings("fallthrough") + public void performAction() { + + Frame mainWindow = WindowManager.getDefault().getMainWindow(); + + // If this is an installed version, there should be an 64.exe file in the bin folder + String appName = UserPreferences.getAppName(); + String exeName = appName + "64.exe"; + String installPath = PlatformUtil.getInstallPath(); + + Path exePath = Paths.get(installPath, "bin", exeName); + + if (!exePath.toFile().exists()) { + JOptionPane.showMessageDialog(mainWindow, + Bundle.CreateLiveTriageDriveAction_exenotfound_message(), + Bundle.CreateLiveTriageDriveAction_error_title(), + JOptionPane.ERROR_MESSAGE); + return; + } + + Path applicationBasePath; + try { + applicationBasePath = exePath.getParent().getParent(); + } catch (InvalidPathException ex) { + JOptionPane.showMessageDialog(mainWindow, + Bundle.CreateLiveTriageDriveAction_appPathError_message(), + Bundle.CreateLiveTriageDriveAction_error_title(), + JOptionPane.ERROR_MESSAGE); + return; + } + + SelectDriveDialog driveDialog = new SelectDriveDialog(mainWindow, true); + driveDialog.display(); + + if (!driveDialog.getSelectedDrive().isEmpty()) { + drivePath = driveDialog.getSelectedDrive(); + if (drivePath.startsWith("\\\\.\\")) { + drivePath = drivePath.substring(4); + } + + worker = new CopyFilesWorker(applicationBasePath, drivePath, appName); + worker.addPropertyChangeListener(this); + worker.execute(); + } + } + + @NbBundle.Messages({"# {0} - drivePath", + "CreateLiveTriageDriveAction.progressBar.text=Copying live triage files to {0}", + "CreateLiveTriageDriveAction.progressBar.title=Please wait"}) + @Override + public void propertyChange(PropertyChangeEvent evt) { + + if ("state".equals(evt.getPropertyName()) + && (SwingWorker.StateValue.STARTED.equals(evt.getNewValue()))) { + + // Setup progress bar. + String displayStr = NbBundle.getMessage(this.getClass(), "CreateLiveTriageDriveAction.progressBar.text", + drivePath); + + progressIndicator = new ModalDialogProgressIndicator(WindowManager.getDefault().getMainWindow(), + NbBundle.getMessage(this.getClass(), "CreateLiveTriageDriveAction.progressBar.title")); + progressIndicator.start(displayStr); + + } else if ("state".equals(evt.getPropertyName()) + && (SwingWorker.StateValue.DONE.equals(evt.getNewValue()))) { + if (progressIndicator != null) { + progressIndicator.finish(); + } + + if (worker.hadError()) { + MessageNotifyUtil.Message.error(NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.error.text")); + } else { + MessageNotifyUtil.Message.info(NbBundle.getMessage(CopyFilesWorker.class, "CopyFilesWorker.done.text")); + } + } + } + + private class CopyFilesWorker extends SwingWorker { + + private final Path sourceFolder; + private final String drivePath; + private final String appName; + private boolean error = false; + + CopyFilesWorker(Path sourceFolder, String drivePath, String appName) { + this.sourceFolder = sourceFolder; + this.drivePath = drivePath; + this.appName = appName; + } + + boolean hadError() { + return error; + } + + @Override + protected Void doInBackground() throws Exception { + + copyBatchFile(drivePath, appName); + copyApplication(sourceFolder, drivePath, appName); + + return null; + } + + @NbBundle.Messages({"CopyFilesWorker.error.text=Error copying live triage files", + "CopyFilesWorker.done.text=Finished creating live triage disk"}) + @Override + protected void done() { + try { + super.get(); + } catch (Exception ex) { + error = true; + Logger.getLogger(CreateLiveTriageDriveAction.class.getName()).log(Level.SEVERE, "Fatal error during live triage drive creation", ex); //NON-NLS + } + } + } + + private void copyApplication(Path sourceFolder, String destBaseFolder, String appName) throws IOException { + + // Create an appName folder in the destination + Path destAppFolder = Paths.get(destBaseFolder, appName); + if (!destAppFolder.toFile().exists()) { + if (!destAppFolder.toFile().mkdirs()) { + throw new IOException("Failed to create directory " + destAppFolder.toString()); + } + } + + // Now copy the files + FileUtils.copyDirectory(sourceFolder.toFile(), destAppFolder.toFile()); + } + + private void copyBatchFile(String destPath, String appName) throws IOException, InvalidPathException { + Path batchFilePath = Paths.get(destPath, "RunFromUSB.bat"); + FileUtils.writeStringToFile(batchFilePath.toFile(), getBatchFileContents(appName), "UTF-8"); + + } + + private String getBatchFileContents(String appName) { + + String batchFile + = "@echo off\n" + + "\n" + + "REM This restores the working directory when using 'Run as administrator'" + + "@setlocal enableextensions\n" + + "@cd /d \"%~dp0\"" + + "\n" + + "SET appName=\"" + appName + "\"\n" + + "\n" + + "REM Create the configData directory. Exit if it does not exist after attempting to create it\n" + + "if not exist configData mkdir configData\n" + + "if not exist configData (\n" + + " echo Error creating directory configData\n" + + " goto end\n" + + ")\n" + + "\n" + + "REM Create the userdir sub directory. Exit if it does not exist after attempting to create it\n" + + "if not exist configData\\userdir mkdir configData\\userdir\n" + + "if not exist configData\\userdir (\n" + + " echo Error creating directory configData\\userdir\n" + + " goto end\n" + + ")\n" + + "\n" + + "REM Create the cachedir sub directory. Exit if it does not exist after attempting to create it\n" + + "REM If it exists to start with, delete it to clear out old data\n" + + "if exist configData\\cachedir rd /s /q configData\\cachedir\n" + + "mkdir configData\\cachedir\n" + + "if not exist configData\\cachedir (\n" + + " echo Error creating directory configData\\cachedir\n" + + " goto end\n" + + ")\n" + + "\n" + + "REM Create the temp sub directory. Exit if it does not exist after attempting to create it\n" + + "REM If it exists to start with, delete it to clear out old data\n" + + "if exist configData\\temp rd /s /q configData\\temp\n" + + "mkdir configData\\temp\n" + + "if not exist configData\\temp (\n" + + " echo Error creating directory configData\\temp\n" + + " goto end\n" + + ")\n" + + "\n" + + "REM Create the cases directory. It's ok if this fails.\n" + + "if not exist cases mkdir cases\n" + + "\n" + + "if exist %appName% (\n" + + " if not exist %appName%\\bin\\%appName%64.exe (\n" + + " echo %appName%\\bin\\%appName%64.exe does not exist\n" + + " goto end\n" + + " )\n" + + " %appName%\\bin\\%appName%64.exe --userdir ..\\configData\\userdir --cachedir ..\\configData\\cachedir -J-Djava.io.tmpdir=..\\configData\\temp\n" + + ") else (\n" + + " echo Could not find %appName% directory\n" + + " goto end\n" + + ")\n" + + "\n" + + ":end\n" + + "\n" + + "REM Keep the cmd window open in case there was an error\n" + + "@pause\n"; + return batchFile; + } + + @Override + public String getName() { + return DISPLAY_NAME; + } + + @Override + public HelpCtx getHelpCtx() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + public boolean asynchronous() { + return false; // run on edt + } +} diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.form b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.form new file mode 100644 index 0000000000..ee776348dc --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.form @@ -0,0 +1,163 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java new file mode 100644 index 0000000000..c13d82d324 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/livetriage/SelectDriveDialog.java @@ -0,0 +1,388 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.sleuthkit.autopsy.livetriage; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.logging.Level; +import java.awt.Dimension; +import java.awt.Point; +import javax.swing.SwingWorker; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.event.TableModelListener; +import javax.swing.table.TableModel; +import org.openide.util.NbBundle; +import org.sleuthkit.autopsy.coreutils.LocalDisk; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.PlatformUtil; + +/** + * + */ +class SelectDriveDialog extends javax.swing.JDialog { + + private List disks = new ArrayList<>(); + private final LocalDiskModel model = new LocalDiskModel(); + private final java.awt.Frame parent; + private String drivePath = ""; + + /** + * Creates new form SelectDriveDialog + */ + @NbBundle.Messages({"SelectDriveDialog.title=Create Live Triage Drive"}) + SelectDriveDialog(java.awt.Frame parent, boolean modal) { + super(parent, modal); + initComponents(); + this.parent = parent; + + model.loadDisks(); + bnOk.setEnabled(false); + diskTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + if (diskTable.getSelectedRow() >= 0 && diskTable.getSelectedRow() < disks.size()) { + bnOk.setEnabled(true); + } else { //The selection changed to nothing valid being selected, such as with ctrl+click + bnOk.setEnabled(false); + } + } + }); + } + + void display() { + this.setTitle(Bundle.SelectDriveDialog_title()); + + final Dimension parentSize = parent.getSize(); + final Point parentLocationOnScreen = parent.getLocationOnScreen(); + final Dimension childSize = this.getSize(); + int x; + int y; + x = (parentSize.width - childSize.width) / 2; + y = (parentSize.height - childSize.height) / 2; + x += parentLocationOnScreen.x; + y += parentLocationOnScreen.y; + + setLocation(x, y); + setVisible(true); + } + + String getSelectedDrive() { + return this.drivePath; + } + + /** + * 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") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jScrollPane1 = new javax.swing.JScrollPane(); + diskTable = new javax.swing.JTable(); + jLabel1 = new javax.swing.JLabel(); + bnRefresh = new javax.swing.JButton(); + bnOk = new javax.swing.JButton(); + errorLabel = new javax.swing.JLabel(); + jSeparator1 = new javax.swing.JSeparator(); + bnCancel = new javax.swing.JButton(); + jScrollPane2 = new javax.swing.JScrollPane(); + jTextArea1 = new javax.swing.JTextArea(); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + + diskTable.setModel(model); + jScrollPane1.setViewportView(diskTable); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.jLabel1.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(bnRefresh, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.bnRefresh.text")); // NOI18N + bnRefresh.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnRefreshActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(bnOk, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.bnOk.text")); // NOI18N + bnOk.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnOkActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(errorLabel, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.errorLabel.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(bnCancel, org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.bnCancel.text")); // NOI18N + bnCancel.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + bnCancelActionPerformed(evt); + } + }); + + jScrollPane2.setBorder(null); + + jTextArea1.setBackground(new java.awt.Color(240, 240, 240)); + jTextArea1.setColumns(20); + jTextArea1.setFont(new java.awt.Font("Tahoma", 0, 11)); // NOI18N + jTextArea1.setRows(5); + jTextArea1.setText(org.openide.util.NbBundle.getMessage(SelectDriveDialog.class, "SelectDriveDialog.jTextArea1.text")); // NOI18N + jTextArea1.setBorder(null); + jScrollPane2.setViewportView(jTextArea1); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); + getContentPane().setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addGroup(layout.createSequentialGroup() + .addComponent(bnRefresh, javax.swing.GroupLayout.DEFAULT_SIZE, 112, Short.MAX_VALUE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 48, Short.MAX_VALUE) + .addComponent(bnOk, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(bnCancel, javax.swing.GroupLayout.PREFERRED_SIZE, 101, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) + .addComponent(jLabel1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jSeparator1) + .addComponent(errorLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(jScrollPane2)) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addComponent(jScrollPane2, javax.swing.GroupLayout.PREFERRED_SIZE, 107, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jSeparator1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(jLabel1) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) + .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 112, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(bnRefresh) + .addComponent(bnCancel) + .addComponent(bnOk)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(errorLabel) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + + pack(); + }// //GEN-END:initComponents + + private void bnRefreshActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnRefreshActionPerformed + model.loadDisks(); + }//GEN-LAST:event_bnRefreshActionPerformed + + private void bnOkActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnOkActionPerformed + if (diskTable.getSelectedRow() >= 0 && diskTable.getSelectedRow() < disks.size()) { + LocalDisk selectedDisk = disks.get(diskTable.getSelectedRow()); + drivePath = selectedDisk.getPath(); + } else { + drivePath = ""; + } + dispose(); + }//GEN-LAST:event_bnOkActionPerformed + + private void bnCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_bnCancelActionPerformed + dispose(); + }//GEN-LAST:event_bnCancelActionPerformed + + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton bnCancel; + private javax.swing.JButton bnOk; + private javax.swing.JButton bnRefresh; + private javax.swing.JTable diskTable; + private javax.swing.JLabel errorLabel; + private javax.swing.JLabel jLabel1; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JScrollPane jScrollPane2; + private javax.swing.JSeparator jSeparator1; + private javax.swing.JTextArea jTextArea1; + // End of variables declaration//GEN-END:variables + + /** + * Table model for displaying information from LocalDisk Objects in a table. + */ + @NbBundle.Messages({"SelectDriveDialog.localDiskModel.loading.msg=", + "SelectDriveDialog.localDiskModel.nodrives.msg=Executable could not be found", + "SelectDriveDialog.diskTable.column1.title=Disk Name", + "SelectDriveDialog.diskTable.column2.title=Disk Size", + "SelectDriveDialog.errLabel.disksNotDetected.text=Disks were not detected. On some systems it requires admin privileges", + "SelectDriveDialog.errLabel.disksNotDetected.toolTipText=Disks were not detected." + + }) + private class LocalDiskModel implements TableModel { + + private LocalDiskThread worker = null; + private boolean ready = false; + private volatile boolean loadingDisks = false; + + //private String SELECT = "Select a local disk:"; + private final String LOADING = NbBundle.getMessage(this.getClass(), "SelectDriveDialog.localDiskModel.loading.msg"); + private final String NO_DRIVES = NbBundle.getMessage(this.getClass(), "SelectDriveDialog.localDiskModel.nodrives.msg"); + + private void loadDisks() { + + // if there is a worker already building the lists, then cancel it first. + if (loadingDisks && worker != null) { + worker.cancel(false); + } + + // Clear the lists + errorLabel.setText(""); + diskTable.setEnabled(false); + ready = false; + loadingDisks = true; + worker = new LocalDiskThread(); + worker.execute(); + } + + @Override + public int getRowCount() { + if (disks.isEmpty()) { + return 0; + } + return disks.size(); + } + + @Override + public int getColumnCount() { + return 2; + + } + + @Override + public String getColumnName(int columnIndex) { + switch (columnIndex) { + case 0: + return NbBundle.getMessage(this.getClass(), "SelectDriveDialog.diskTable.column1.title"); + case 1: + return NbBundle.getMessage(this.getClass(), "SelectDriveDialog.diskTable.column2.title"); + default: + return "Unnamed"; //NON-NLS + } + } + + @Override + public Class getColumnClass(int columnIndex) { + return String.class; + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + if (ready) { + if (disks.isEmpty()) { + return NO_DRIVES; + } + switch (columnIndex) { + case 0: + return disks.get(rowIndex).getName(); + case 1: + return disks.get(rowIndex).getReadableSize(); + default: + return disks.get(rowIndex).getPath(); + } + } else { + return LOADING; + } + } + + @Override + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + //setter does nothing they should not be able to modify table + } + + @Override + public void addTableModelListener(TableModelListener l) { + + } + + @Override + public void removeTableModelListener(TableModelListener l) { + + } + + /** + * Gets the lists of physical drives and partitions and combines them + * into a list of disks. + */ + class LocalDiskThread extends SwingWorker { + + private final Logger logger = Logger.getLogger(LocalDiskThread.class.getName()); + private List partitions = new ArrayList<>(); + + @Override + protected Object doInBackground() throws Exception { + // Populate the lists + partitions = new ArrayList<>(); + partitions = PlatformUtil.getPartitions(); + return null; + } + + /** + * Display any error messages that might of occurred when getting + * the lists of physical drives or partitions. + */ + private void displayErrors() { + if (partitions.isEmpty()) { + if (PlatformUtil.isWindowsOS()) { + errorLabel.setText( + NbBundle.getMessage(this.getClass(), "SelectDriveDialog.errLabel.disksNotDetected.text")); + errorLabel.setToolTipText(NbBundle.getMessage(this.getClass(), + "SelectDriveDialog.errLabel.disksNotDetected.toolTipText")); + } else { + errorLabel.setText( + NbBundle.getMessage(this.getClass(), "SelectDriveDialog.errLabel.drivesNotDetected.text")); + errorLabel.setToolTipText(NbBundle.getMessage(this.getClass(), + "SelectDriveDialog.errLabel.drivesNotDetected.toolTipText")); + } + errorLabel.setVisible(true); + diskTable.setEnabled(false); + } + } + + @Override + protected void done() { + try { + super.get(); //block and get all exceptions thrown while doInBackground() + } catch (CancellationException ex) { + logger.log(Level.INFO, "Loading local disks was canceled."); //NON-NLS + } catch (InterruptedException ex) { + logger.log(Level.INFO, "Loading local disks was interrupted."); //NON-NLS + } catch (Exception ex) { + logger.log(Level.SEVERE, "Fatal error when loading local disks", ex); //NON-NLS + } finally { + if (!this.isCancelled()) { + displayErrors(); + worker = null; + loadingDisks = false; + disks = new ArrayList<>(); + disks.addAll(partitions); + if (disks.size() > 0) { + diskTable.setEnabled(true); + diskTable.clearSelection(); + } + ready = true; + } + } + diskTable.revalidate(); + } + } + } +} diff --git a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java index c742110f6b..aaa6dbec11 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java +++ b/Core/src/org/sleuthkit/autopsy/modules/filetypeid/FileTypeDetector.java @@ -1,7 +1,7 @@ /* * Autopsy Forensic Browser * - * Copyright 2011-2017 Basis Technology Corp. + * Copyright 2011-2018 Basis Technology Corp. * Contact: carrier sleuthkit org * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -177,6 +177,8 @@ public class FileTypeDetector { * * @return A MIME type name. If file type could not be detected, or results * were uncertain, octet-stream is returned. + * + */ public String detectMIMEType(AbstractFile file) { /* diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java index 0c8aa263c3..ef20238c48 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportGenerator.java @@ -63,7 +63,7 @@ class ReportGenerator { */ private ReportProgressPanel progressPanel; - private String reportPathFormatString; + private static final String REPORT_PATH_FMT_STR = "%s" + File.separator + "%s %s %s" + File.separator; private final ReportGenerationPanel reportGenerationPanel = new ReportGenerationPanel(); static final String REPORTS_DIR = "Reports"; //NON-NLS @@ -89,12 +89,6 @@ class ReportGenerator { * Creates a report generator. */ ReportGenerator() { - // Create the root reports directory path of the form: /Reports/ / - DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss"); - Date date = new Date(); - String dateNoTime = dateFormat.format(date); - this.reportPathFormatString = currentCase.getReportDirectory() + File.separator + currentCase.getDisplayName() + " %s " + dateNoTime + File.separator; - this.errorList = new ArrayList<>(); } @@ -136,19 +130,12 @@ class ReportGenerator { /** * Run the GeneralReportModules using a SwingWorker. */ - void generateGeneralReport(GeneralReportModule generalReportModule) { + void generateGeneralReport(GeneralReportModule generalReportModule) throws IOException { if (generalReportModule != null) { - reportPathFormatString = String.format(reportPathFormatString, generalReportModule.getName()); - // Create the root reports directory. - try { - FileUtil.createFolder(new File(reportPathFormatString)); - } catch (IOException ex) { - errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedMakeRptFolder")); - logger.log(Level.SEVERE, "Failed to make report folder, may be unable to generate reports.", ex); //NON-NLS - } - setupProgressPanel(generalReportModule); + String reportDir = createReportDirectory(generalReportModule); + setupProgressPanel(generalReportModule, reportDir); ReportWorker worker = new ReportWorker(() -> { - generalReportModule.generateReport(reportPathFormatString, progressPanel); + generalReportModule.generateReport(reportDir, progressPanel); }); worker.execute(); displayProgressPanel(); @@ -163,19 +150,12 @@ class ReportGenerator { * @param tagSelections the enabled/disabled state of the tag names * to be included in the report */ - void generateTableReport(TableReportModule tableReport, Map artifactTypeSelections, Map tagNameSelections) { + void generateTableReport(TableReportModule tableReport, Map artifactTypeSelections, Map tagNameSelections) throws IOException { if (tableReport != null && null != artifactTypeSelections) { - reportPathFormatString = String.format(reportPathFormatString, tableReport.getName()); - // Create the root reports directory. - try { - FileUtil.createFolder(new File(reportPathFormatString)); - } catch (IOException ex) { - errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedMakeRptFolder")); - logger.log(Level.SEVERE, "Failed to make report folder, may be unable to generate reports.", ex); //NON-NLS - } - setupProgressPanel(tableReport); + String reportDir = createReportDirectory(tableReport); + setupProgressPanel(tableReport, reportDir); ReportWorker worker = new ReportWorker(() -> { - tableReport.startReport(reportPathFormatString); + tableReport.startReport(reportDir); TableReportGenerator generator = new TableReportGenerator(artifactTypeSelections, tagNameSelections, progressPanel, tableReport); generator.execute(); tableReport.endReport(); @@ -194,23 +174,16 @@ class ReportGenerator { * @param enabledInfo the Information that should be included about each * file in the report. */ - void generateFileListReport(FileReportModule fileReportModule, Map enabledInfo) { + void generateFileListReport(FileReportModule fileReportModule, Map enabledInfo) throws IOException { if (fileReportModule != null && null != enabledInfo) { - reportPathFormatString = String.format(reportPathFormatString, fileReportModule.getName()); - // Create the root reports directory. - try { - FileUtil.createFolder(new File(reportPathFormatString)); - } catch (IOException ex) { - errorList.add(NbBundle.getMessage(this.getClass(), "ReportGenerator.errList.failedMakeRptFolder")); - logger.log(Level.SEVERE, "Failed to make report folder, may be unable to generate reports.", ex); //NON-NLS - } + String reportDir = createReportDirectory(fileReportModule); List enabled = new ArrayList<>(); for (Entry e : enabledInfo.entrySet()) { if (e.getValue()) { enabled.add(e.getKey()); } } - setupProgressPanel(fileReportModule); + setupProgressPanel(fileReportModule, reportDir); ReportWorker worker = new ReportWorker(() -> { if (progressPanel.getStatus() != ReportStatus.CANCELED) { progressPanel.start(); @@ -221,7 +194,7 @@ class ReportGenerator { List files = getFiles(); int numFiles = files.size(); if (progressPanel.getStatus() != ReportStatus.CANCELED) { - fileReportModule.startReport(reportPathFormatString); + fileReportModule.startReport(reportDir); fileReportModule.startTable(enabled); } progressPanel.setIndeterminate(false); @@ -276,15 +249,31 @@ class ReportGenerator { } } - private void setupProgressPanel(ReportModule module) { + private void setupProgressPanel(ReportModule module, String reportDir) { String reportFilePath = module.getRelativeFilePath(); if (!reportFilePath.isEmpty()) { - this.progressPanel = reportGenerationPanel.addReport(module.getName(), String.format(reportPathFormatString, module.getName()) + reportFilePath); + this.progressPanel = reportGenerationPanel.addReport(module.getName(), reportDir + reportFilePath); } else { this.progressPanel = reportGenerationPanel.addReport(module.getName(), null); } } + private static String createReportDirectory(ReportModule module) throws IOException { + Case currentCase = Case.getCurrentCase(); + // Create the root reports directory path of the form: /Reports/ / + DateFormat dateFormat = new SimpleDateFormat("MM-dd-yyyy-HH-mm-ss"); + Date date = new Date(); + String dateNoTime = dateFormat.format(date); + String reportPath = String.format(REPORT_PATH_FMT_STR, currentCase.getReportDirectory(), currentCase.getDisplayName(), module.getName(), dateNoTime); + // Create the root reports directory. + try { + FileUtil.createFolder(new File(reportPath)); + } catch (IOException ex) { + throw new IOException("Failed to make report folder, unable to generate reports.", ex); + } + return reportPath; + } + private class ReportWorker extends SwingWorker { private final Runnable doInBackground; diff --git a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java index 49f37008be..618cba2a38 100644 --- a/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java +++ b/Core/src/org/sleuthkit/autopsy/report/ReportWizardAction.java @@ -2,7 +2,7 @@ * * Autopsy Forensic Browser * - * Copyright 2013-2015 Basis Technology Corp. + * Copyright 2013-2018 Basis Technology Corp. * * Copyright 2012 42six Solutions. * Contact: aebadirad 42six com @@ -26,12 +26,14 @@ import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; +import java.io.IOException; import java.text.MessageFormat; import java.util.EnumSet; import java.util.Map; import javax.swing.ImageIcon; import javax.swing.JButton; import org.openide.DialogDisplayer; +import org.openide.NotifyDescriptor; import org.openide.WizardDescriptor; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; @@ -70,12 +72,17 @@ public final class ReportWizardAction extends CallableSystemAction implements Pr TableReportModule tableReport = (TableReportModule) wiz.getProperty("tableModule"); GeneralReportModule generalReport = (GeneralReportModule) wiz.getProperty("generalModule"); FileReportModule fileReport = (FileReportModule) wiz.getProperty("fileModule"); - if (tableReport != null) { - generator.generateTableReport(tableReport, (Map) wiz.getProperty("artifactStates"), (Map) wiz.getProperty("tagStates")); //NON-NLS - } else if (generalReport != null) { - generator.generateGeneralReport(generalReport); - } else if (fileReport != null) { - generator.generateFileListReport(fileReport, (Map) wiz.getProperty("fileReportOptions")); //NON-NLS + try { + if (tableReport != null) { + generator.generateTableReport(tableReport, (Map) wiz.getProperty("artifactStates"), (Map) wiz.getProperty("tagStates")); //NON-NLS + } else if (generalReport != null) { + generator.generateGeneralReport(generalReport); + } else if (fileReport != null) { + generator.generateFileListReport(fileReport, (Map) wiz.getProperty("fileReportOptions")); //NON-NLS + } + } catch (IOException e) { + NotifyDescriptor descriptor = new NotifyDescriptor.Message(e.getMessage(), NotifyDescriptor.ERROR_MESSAGE); + DialogDisplayer.getDefault().notify(descriptor); } } } diff --git a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties index e0a4c85328..bafc320fad 100644 --- a/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties +++ b/branding/core/core.jar/org/netbeans/core/startup/Bundle.properties @@ -1,5 +1,5 @@ #Updated by build script -#Fri, 05 Jan 2018 10:31:22 -0500 +#Fri, 05 Jan 2018 12:14:19 -0500 LBL_splash_window_title=Starting Autopsy SPLASH_HEIGHT=314 SPLASH_WIDTH=538 diff --git a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties index 8f4ccbd194..6097052361 100644 --- a/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties +++ b/branding/modules/org-netbeans-core-windows.jar/org/netbeans/core/windows/view/ui/Bundle.properties @@ -1,4 +1,4 @@ #Updated by build script -#Fri, 05 Jan 2018 10:31:22 -0500 +#Fri, 05 Jan 2018 12:14:19 -0500 CTL_MainWindow_Title=Autopsy 4.5.0 CTL_MainWindow_Title_No_Project=Autopsy 4.5.0