19 package org.sleuthkit.autopsy.contentviewers;
21 import java.awt.BorderLayout;
22 import java.awt.Component;
23 import java.awt.Cursor;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.LinkedHashMap;
32 import java.util.List;
34 import java.util.Objects;
35 import java.util.function.Consumer;
36 import java.util.logging.Level;
37 import javax.swing.JComboBox;
38 import javax.swing.JFileChooser;
39 import javax.swing.JOptionPane;
40 import javax.swing.filechooser.FileNameExtensionFilter;
41 import org.apache.commons.io.FilenameUtils;
42 import org.openide.util.NbBundle;
43 import org.openide.windows.WindowManager;
54 @SuppressWarnings(
"PMD.SingularField")
55 class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
57 private static final long serialVersionUID = 1L;
58 public static final String[] SUPPORTED_MIMETYPES =
new String[]{
"application/x-sqlite3"};
59 private static final int ROWS_PER_PAGE = 100;
60 private static final Logger logger = Logger.getLogger(FileViewer.class.getName());
61 private final SQLiteTableView selectedTableView =
new SQLiteTableView();
62 private AbstractFile sqliteDbFile;
64 private SQLiteTableReader viewReader;
66 private Map<String, Object> row =
new LinkedHashMap<>();
67 private List<Map<String, Object>> pageOfTableRows =
new ArrayList<>();
68 private List<String> currentTableHeader =
new ArrayList<>();
69 private String prevTableName;
72 private int currPage = 0;
79 jTableDataPanel.add(selectedTableView, BorderLayout.CENTER);
87 @SuppressWarnings(
"unchecked")
89 private
void initComponents() {
91 jHdrPanel =
new javax.swing.JPanel();
92 tablesDropdownList =
new javax.swing.JComboBox<>();
93 jLabel1 =
new javax.swing.JLabel();
94 numEntriesField =
new javax.swing.JTextField();
95 jLabel2 =
new javax.swing.JLabel();
96 currPageLabel =
new javax.swing.JLabel();
97 jLabel3 =
new javax.swing.JLabel();
98 numPagesLabel =
new javax.swing.JLabel();
99 prevPageButton =
new javax.swing.JButton();
100 nextPageButton =
new javax.swing.JButton();
101 exportCsvButton =
new javax.swing.JButton();
102 jTableDataPanel =
new javax.swing.JPanel();
104 jHdrPanel.setPreferredSize(
new java.awt.Dimension(536, 40));
106 tablesDropdownList.setModel(
new javax.swing.DefaultComboBoxModel<>(
new String[] {
"Item 1",
"Item 2",
"Item 3",
"Item 4" }));
107 tablesDropdownList.addActionListener(
new java.awt.event.ActionListener() {
108 public void actionPerformed(java.awt.event.ActionEvent evt) {
109 tablesDropdownListActionPerformed(evt);
113 org.openide.awt.Mnemonics.setLocalizedText(jLabel1,
org.openide.util.NbBundle.getMessage(SQLiteViewer.class,
"SQLiteViewer.jLabel1.text"));
115 numEntriesField.setEditable(
false);
116 numEntriesField.setText(
org.openide.util.NbBundle.getMessage(SQLiteViewer.class,
"SQLiteViewer.numEntriesField.text"));
117 numEntriesField.setBorder(null);
119 org.openide.awt.Mnemonics.setLocalizedText(jLabel2,
org.openide.util.NbBundle.getMessage(SQLiteViewer.class,
"SQLiteViewer.jLabel2.text"));
121 org.openide.awt.Mnemonics.setLocalizedText(currPageLabel,
org.openide.util.NbBundle.getMessage(SQLiteViewer.class,
"SQLiteViewer.currPageLabel.text"));
123 org.openide.awt.Mnemonics.setLocalizedText(jLabel3,
org.openide.util.NbBundle.getMessage(SQLiteViewer.class,
"SQLiteViewer.jLabel3.text"));
125 org.openide.awt.Mnemonics.setLocalizedText(numPagesLabel,
org.openide.util.NbBundle.getMessage(SQLiteViewer.class,
"SQLiteViewer.numPagesLabel.text"));
127 prevPageButton.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/corecomponents/btn_step_back.png")));
128 org.openide.awt.Mnemonics.setLocalizedText(prevPageButton,
org.openide.util.NbBundle.getMessage(SQLiteViewer.class,
"SQLiteViewer.prevPageButton.text"));
129 prevPageButton.setBorderPainted(
false);
130 prevPageButton.setContentAreaFilled(
false);
131 prevPageButton.setDisabledSelectedIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png")));
132 prevPageButton.setMargin(
new java.awt.Insets(2, 0, 2, 0));
133 prevPageButton.setPreferredSize(
new java.awt.Dimension(23, 23));
134 prevPageButton.addActionListener(
new java.awt.event.ActionListener() {
135 public void actionPerformed(java.awt.event.ActionEvent evt) {
136 prevPageButtonActionPerformed(evt);
140 nextPageButton.setIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png")));
141 org.openide.awt.Mnemonics.setLocalizedText(nextPageButton,
org.openide.util.NbBundle.getMessage(SQLiteViewer.class,
"SQLiteViewer.nextPageButton.text"));
142 nextPageButton.setBorderPainted(
false);
143 nextPageButton.setContentAreaFilled(
false);
144 nextPageButton.setDisabledSelectedIcon(
new javax.swing.ImageIcon(getClass().getResource(
"/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png")));
145 nextPageButton.setMargin(
new java.awt.Insets(2, 0, 2, 0));
146 nextPageButton.setPreferredSize(
new java.awt.Dimension(23, 23));
147 nextPageButton.addActionListener(
new java.awt.event.ActionListener() {
148 public void actionPerformed(java.awt.event.ActionEvent evt) {
149 nextPageButtonActionPerformed(evt);
153 org.openide.awt.Mnemonics.setLocalizedText(exportCsvButton,
org.openide.util.NbBundle.getMessage(SQLiteViewer.class,
"SQLiteViewer.exportCsvButton.text"));
154 exportCsvButton.addActionListener(
new java.awt.event.ActionListener() {
155 public void actionPerformed(java.awt.event.ActionEvent evt) {
156 exportCsvButtonActionPerformed(evt);
160 javax.swing.GroupLayout jHdrPanelLayout =
new javax.swing.GroupLayout(jHdrPanel);
161 jHdrPanel.setLayout(jHdrPanelLayout);
162 jHdrPanelLayout.setHorizontalGroup(
163 jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
164 .addGroup(jHdrPanelLayout.createSequentialGroup()
166 .addComponent(jLabel1)
167 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
168 .addComponent(tablesDropdownList, javax.swing.GroupLayout.PREFERRED_SIZE, 130, javax.swing.GroupLayout.PREFERRED_SIZE)
170 .addComponent(numEntriesField, javax.swing.GroupLayout.PREFERRED_SIZE, 71, javax.swing.GroupLayout.PREFERRED_SIZE)
172 .addComponent(jLabel2)
173 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
174 .addComponent(currPageLabel)
175 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
176 .addComponent(jLabel3)
177 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
178 .addComponent(numPagesLabel)
180 .addComponent(prevPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
182 .addComponent(nextPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
184 .addComponent(exportCsvButton)
185 .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
187 jHdrPanelLayout.setVerticalGroup(
188 jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
189 .addGroup(jHdrPanelLayout.createSequentialGroup()
191 .addGroup(jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
192 .addComponent(exportCsvButton)
193 .addComponent(nextPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
194 .addComponent(prevPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
195 .addGroup(jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
196 .addComponent(tablesDropdownList, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
197 .addComponent(jLabel1)
198 .addComponent(numEntriesField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
199 .addComponent(jLabel2)
200 .addComponent(currPageLabel)
201 .addComponent(jLabel3)
202 .addComponent(numPagesLabel)))
206 jTableDataPanel.setLayout(
new java.awt.BorderLayout());
208 javax.swing.GroupLayout layout =
new javax.swing.GroupLayout(
this);
209 this.setLayout(layout);
210 layout.setHorizontalGroup(
211 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
212 .addComponent(jHdrPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 569, Short.MAX_VALUE)
213 .addComponent(jTableDataPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
215 layout.setVerticalGroup(
216 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
217 .addGroup(layout.createSequentialGroup()
218 .addComponent(jHdrPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE)
220 .addComponent(jTableDataPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 317, Short.MAX_VALUE))
224 private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {
225 WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
227 if (currPage * ROWS_PER_PAGE > numRows) {
228 nextPageButton.setEnabled(
false);
230 currPageLabel.setText(Integer.toString(currPage));
231 prevPageButton.setEnabled(
true);
234 String tableName = (String) this.tablesDropdownList.getSelectedItem();
235 readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE);
236 WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
239 private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {
241 WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
244 prevPageButton.setEnabled(
false);
246 currPageLabel.setText(Integer.toString(currPage));
247 nextPageButton.setEnabled(
true);
250 String tableName = (String) this.tablesDropdownList.getSelectedItem();
251 readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE);
252 WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
255 private void tablesDropdownListActionPerformed(java.awt.event.ActionEvent evt) {
256 JComboBox<?> cb = (JComboBox<?>) evt.getSource();
257 String tableName = (String) cb.getSelectedItem();
258 if (null == tableName) {
261 WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
262 selectTable(tableName);
263 WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
273 @NbBundle.Messages({
"SQLiteViewer.csvExport.fileName.empty=Please input a file name for exporting.",
274 "SQLiteViewer.csvExport.title=Export to csv file",
275 "SQLiteViewer.csvExport.confirm.msg=Do you want to overwrite the existing file?"})
276 private void exportCsvButtonActionPerformed(java.awt.event.ActionEvent evt) {
277 Case openCase = Case.getCurrentCase();
278 File caseDirectory =
new File(openCase.getExportDirectory());
279 JFileChooser fileChooser =
new JFileChooser();
280 fileChooser.setDragEnabled(
false);
281 fileChooser.setCurrentDirectory(caseDirectory);
283 FileNameExtensionFilter csvFilter =
new FileNameExtensionFilter(
"*.csv",
"csv");
284 fileChooser.addChoosableFileFilter(csvFilter);
285 fileChooser.setAcceptAllFileFilterUsed(
true);
286 fileChooser.setFileFilter(csvFilter);
287 fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
288 String defaultFileName = (String) this.tablesDropdownList.getSelectedItem();
289 fileChooser.setSelectedFile(
new File(defaultFileName));
290 int choice = fileChooser.showSaveDialog((Component) evt.getSource());
291 if (JFileChooser.APPROVE_OPTION == choice) {
292 File file = fileChooser.getSelectedFile();
293 if (file.exists() && FilenameUtils.getExtension(file.getName()).equalsIgnoreCase(
"csv")) {
294 if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(
this,
295 Bundle.SQLiteViewer_csvExport_confirm_msg(),
296 Bundle.SQLiteViewer_csvExport_title(),
297 JOptionPane.YES_NO_OPTION)) {
303 exportTableToCsv(file);
308 private javax.swing.JLabel currPageLabel;
309 private javax.swing.JButton exportCsvButton;
310 private javax.swing.JPanel jHdrPanel;
311 private javax.swing.JLabel jLabel1;
312 private javax.swing.JLabel jLabel2;
313 private javax.swing.JLabel jLabel3;
314 private javax.swing.JPanel jTableDataPanel;
315 private javax.swing.JButton nextPageButton;
316 private javax.swing.JTextField numEntriesField;
317 private javax.swing.JLabel numPagesLabel;
318 private javax.swing.JButton prevPageButton;
319 private javax.swing.JComboBox<String> tablesDropdownList;
323 public List<String> getSupportedMIMETypes() {
324 return Arrays.asList(SUPPORTED_MIMETYPES);
328 public void setFile(AbstractFile file) {
329 WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
333 WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
337 public Component getComponent() {
342 public void resetComponent() {
343 tablesDropdownList.setEnabled(
true);
344 tablesDropdownList.removeAllItems();
345 numEntriesField.setText(
"");
349 }
catch (SQLiteTableReaderException ex) {
352 row =
new LinkedHashMap<>();
353 pageOfTableRows =
new ArrayList<>();
354 currentTableHeader =
new ArrayList<>();
363 "SQLiteViewer.comboBox.noTableEntry=No tables found",
364 "SQLiteViewer.errorMessage.interrupted=The processing of the file was interrupted.",
365 "SQLiteViewer.errorMessage.noCurrentCase=The case has been closed.",
366 "SQLiteViewer.errorMessage.failedToExtractFile=The file could not be extracted from the data source.",
367 "SQLiteViewer.errorMessage.failedToQueryDatabase=The database tables in the file could not be read.",
368 "SQLiteViewer.errorMessage.failedToinitJDBCDriver=The JDBC driver for SQLite could not be loaded.",
369 "# {0} - exception message",
"SQLiteViewer.errorMessage.unexpectedError=An unexpected error occurred:\n{0).",})
370 private void processSQLiteFile() {
372 tablesDropdownList.removeAllItems();
374 Collection<String> dbTablesMap = viewReader.getTableNames();
375 if (dbTablesMap.isEmpty()) {
376 tablesDropdownList.addItem(Bundle.SQLiteViewer_comboBox_noTableEntry());
377 tablesDropdownList.setEnabled(
false);
379 dbTablesMap.forEach((tableName) -> {
380 tablesDropdownList.addItem(tableName);
383 }
catch (SQLiteTableReaderException ex) {
384 logger.log(Level.WARNING, String.format(
"Unable to get table names "
385 +
"from sqlite file [%s] with id=[%d].", sqliteDbFile.getName(),
386 sqliteDbFile.getId(), ex.getMessage()));
387 MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_errorMessage_failedToQueryDatabase());
391 @NbBundle.Messages({
"# {0} - tableName",
392 "SQLiteViewer.selectTable.errorText=Error getting row count for table: {0}"
394 private void selectTable(String tableName) {
396 numRows = viewReader.getRowCount(tableName);
397 numEntriesField.setText(numRows +
" entries");
400 currPageLabel.setText(Integer.toString(currPage));
401 numPagesLabel.setText(Integer.toString((numRows / ROWS_PER_PAGE) + 1));
403 prevPageButton.setEnabled(
false);
406 exportCsvButton.setEnabled(
true);
407 nextPageButton.setEnabled(((numRows > ROWS_PER_PAGE)));
408 readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE);
410 exportCsvButton.setEnabled(
false);
411 nextPageButton.setEnabled(
false);
413 currentTableHeader =
new ArrayList<>();
414 viewReader.read(tableName);
415 Map<String, Object> columnRow =
new LinkedHashMap<>();
416 for (
int i = 0; i < currentTableHeader.size(); i++) {
417 columnRow.put(currentTableHeader.get(i),
"");
419 selectedTableView.setupTable(Collections.singletonList(columnRow));
421 }
catch (SQLiteTableReaderException ex) {
422 logger.log(Level.WARNING, String.format(
"Failed to load table %s "
423 +
"from DB file '%s' (objId=%d)", tableName, sqliteDbFile.getName(),
424 sqliteDbFile.getId()), ex.getMessage());
425 MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_selectTable_errorText(tableName));
429 @NbBundle.Messages({
"# {0} - tableName",
430 "SQLiteViewer.readTable.errorText=Error getting rows for table: {0}"})
431 private void readTable(String tableName,
int startRow,
int numRowsToRead) {
436 if (!tableName.equals(prevTableName)) {
437 prevTableName = tableName;
439 currentTableHeader =
new ArrayList<>();
440 viewReader.read(tableName, numRowsToRead, startRow - 1);
441 selectedTableView.setupTable(pageOfTableRows);
442 pageOfTableRows =
new ArrayList<>();
443 }
catch (SQLiteTableReaderException ex) {
444 logger.log(Level.WARNING, String.format(
"Failed to read table %s from DB file '%s' "
445 +
"(objId=%d) starting at row [%d] and limit [%d]",
446 tableName, sqliteDbFile.getName(), sqliteDbFile.getId(),
447 startRow - 1, numRowsToRead), ex.getMessage());
448 MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_readTable_errorText(tableName));
458 private void initReader() {
459 viewReader =
new SQLiteTableReader.Builder(sqliteDbFile)
460 .forAllColumnNames((columnName) -> {
461 currentTableHeader.add(columnName);
463 .forAllTableValues(getForAllStrategy()).build();
478 private Consumer<Object> getForAllStrategy() {
479 return new Consumer<Object>() {
480 private int rowIndex = 0;
483 public void accept(Object t) {
485 String objectStr = (t instanceof byte[]) ?
"BLOB Data not shown"
486 : Objects.toString(t,
"");
488 row.put(currentTableHeader.get(rowIndex - 1), objectStr);
492 if (rowIndex == currentTableHeader.size()) {
493 pageOfTableRows.add(row);
494 row =
new LinkedHashMap<>();
496 rowIndex %= currentTableHeader.size();
502 private int totalColumnCount;
504 @NbBundle.Messages({
"SQLiteViewer.exportTableToCsv.write.errText=Failed to export table content to csv file.",
505 "SQLiteViewer.exportTableToCsv.FileName=File name: ",
506 "SQLiteViewer.exportTableToCsv.TableName=Table name: "
508 private void exportTableToCsv(File file) {
509 File csvFile =
new File(file.toString() +
".csv");
510 String tableName = (String) this.tablesDropdownList.getSelectedItem();
511 try (FileOutputStream out =
new FileOutputStream(csvFile,
false)) {
512 try (SQLiteTableReader sqliteStream =
new SQLiteTableReader.Builder(sqliteDbFile)
513 .forAllColumnNames(getColumnNameCSVStrategy(out))
514 .forAllTableValues(getForAllCSVStrategy(out)).build()) {
515 totalColumnCount = sqliteStream.getColumnCount(tableName);
516 sqliteStream.read(tableName);
518 }
catch (IOException | SQLiteTableReaderException | RuntimeException ex) {
519 logger.log(Level.WARNING, String.format(
"Failed to export table [%s]"
520 +
" to CSV in sqlite file '%s' (objId=%d)", tableName, sqliteDbFile.getName(),
521 sqliteDbFile.getId()), ex.getMessage());
522 MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_exportTableToCsv_write_errText());
540 private Consumer<String> getColumnNameCSVStrategy(FileOutputStream out) {
541 return new Consumer<String>() {
542 private int columnIndex = 0;
545 public void accept(String columnName) {
547 String csvString = columnName;
549 if (columnIndex == 1) {
550 csvString =
"\"" + csvString +
"\"";
552 csvString =
",\"" + csvString +
"\"";
554 if (columnIndex == totalColumnCount) {
559 out.write(csvString.getBytes());
560 }
catch (IOException ex) {
566 throw new RuntimeException(ex);
586 private Consumer<Object> getForAllCSVStrategy(FileOutputStream out) {
587 return new Consumer<Object>() {
588 private int rowIndex = 0;
591 public void accept(Object tableValue) {
595 String objectStr = (tableValue instanceof byte[])
596 ?
"BLOB Data not shown" : Objects.toString(tableValue,
"");
597 objectStr =
"\"" + objectStr +
"\"";
600 objectStr =
"," + objectStr;
602 if (rowIndex == totalColumnCount) {
607 out.write(objectStr.getBytes());
608 }
catch (IOException ex) {
614 throw new RuntimeException(ex);
616 rowIndex %= totalColumnCount;
622 public boolean isSupported(AbstractFile file) {