Autopsy 4.22.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
SQLiteViewer.java
Go to the documentation of this file.
1/*
2 * Autopsy Forensic Browser
3 *
4 * Copyright 2018-2021 Basis Technology Corp.
5 * Contact: carrier <at> sleuthkit <dot> org
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19package org.sleuthkit.autopsy.contentviewers;
20
21import java.awt.BorderLayout;
22import java.awt.Component;
23import java.awt.Cursor;
24import java.io.File;
25import java.io.FileOutputStream;
26import java.io.IOException;
27import java.util.ArrayList;
28import java.util.Arrays;
29import java.util.Collection;
30import java.util.Collections;
31import java.util.LinkedHashMap;
32import java.util.List;
33import java.util.Map;
34import java.util.Objects;
35import java.util.concurrent.ExecutionException;
36import java.util.function.Consumer;
37import java.util.logging.Level;
38import javax.swing.JComboBox;
39import javax.swing.JFileChooser;
40import javax.swing.JOptionPane;
41import javax.swing.SwingWorker;
42import javax.swing.filechooser.FileNameExtensionFilter;
43import org.apache.commons.io.FilenameUtils;
44import org.openide.util.NbBundle;
45import org.openide.windows.WindowManager;
46import org.sleuthkit.autopsy.casemodule.Case;
47import org.sleuthkit.autopsy.coreutils.SQLiteTableReaderException;
48import org.sleuthkit.autopsy.coreutils.Logger;
49import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
50import org.sleuthkit.datamodel.AbstractFile;
51import org.sleuthkit.autopsy.coreutils.SQLiteTableReader;
52import org.sleuthkit.autopsy.guiutils.JFileChooserFactory;
53
57@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
58class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
59
60 private static final long serialVersionUID = 1L;
61 public static final String[] SUPPORTED_MIMETYPES = new String[]{"application/x-sqlite3"};
62 private static final int ROWS_PER_PAGE = 100;
63 private static final Logger logger = Logger.getLogger(FileViewer.class.getName());
64 private final SQLiteTableView selectedTableView = new SQLiteTableView();
65 private AbstractFile sqliteDbFile;
66
67 private SQLiteTableReader viewReader;
68
69 private Map<String, Object> row = new LinkedHashMap<>();
70 private List<Map<String, Object>> pageOfTableRows = new ArrayList<>();
71 private List<String> currentTableHeader = new ArrayList<>();
72 private String prevTableName;
73
74 private int numRows; // num of rows in the selected table
75 private int currPage = 0; // curr page of rows being displayed
76
77 SwingWorker<?, ?> worker;
78
79 private final JFileChooserFactory chooserHelper = new JFileChooserFactory();
80
84 SQLiteViewer() {
85 initComponents();
86 jTableDataPanel.add(selectedTableView, BorderLayout.CENTER);
87 }
88
94 @SuppressWarnings("unchecked")
95 // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
96 private void initComponents() {
97
98 jHdrPanel = new javax.swing.JPanel();
99 tablesDropdownList = new javax.swing.JComboBox<>();
100 jLabel1 = new javax.swing.JLabel();
101 numEntriesField = new javax.swing.JTextField();
102 jLabel2 = new javax.swing.JLabel();
103 currPageLabel = new javax.swing.JLabel();
104 jLabel3 = new javax.swing.JLabel();
105 numPagesLabel = new javax.swing.JLabel();
106 prevPageButton = new javax.swing.JButton();
107 nextPageButton = new javax.swing.JButton();
108 exportCsvButton = new javax.swing.JButton();
109 jTableDataPanel = new javax.swing.JPanel();
110
111 jHdrPanel.setPreferredSize(new java.awt.Dimension(536, 40));
112
113 tablesDropdownList.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
114 tablesDropdownList.addActionListener(new java.awt.event.ActionListener() {
115 public void actionPerformed(java.awt.event.ActionEvent evt) {
116 tablesDropdownListActionPerformed(evt);
117 }
118 });
119
120 org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.jLabel1.text")); // NOI18N
121
122 numEntriesField.setEditable(false);
123 numEntriesField.setText(org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.numEntriesField.text")); // NOI18N
124 numEntriesField.setBorder(null);
125
126 org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.jLabel2.text")); // NOI18N
127
128 org.openide.awt.Mnemonics.setLocalizedText(currPageLabel, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.currPageLabel.text")); // NOI18N
129
130 org.openide.awt.Mnemonics.setLocalizedText(jLabel3, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.jLabel3.text")); // NOI18N
131
132 org.openide.awt.Mnemonics.setLocalizedText(numPagesLabel, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.numPagesLabel.text")); // NOI18N
133
134 prevPageButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N
135 org.openide.awt.Mnemonics.setLocalizedText(prevPageButton, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.prevPageButton.text")); // NOI18N
136 prevPageButton.setBorderPainted(false);
137 prevPageButton.setContentAreaFilled(false);
138 prevPageButton.setDisabledSelectedIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N
139 prevPageButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
140 prevPageButton.setPreferredSize(new java.awt.Dimension(23, 23));
141 prevPageButton.addActionListener(new java.awt.event.ActionListener() {
142 public void actionPerformed(java.awt.event.ActionEvent evt) {
143 prevPageButtonActionPerformed(evt);
144 }
145 });
146
147 nextPageButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N
148 org.openide.awt.Mnemonics.setLocalizedText(nextPageButton, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.nextPageButton.text")); // NOI18N
149 nextPageButton.setBorderPainted(false);
150 nextPageButton.setContentAreaFilled(false);
151 nextPageButton.setDisabledSelectedIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N
152 nextPageButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
153 nextPageButton.setPreferredSize(new java.awt.Dimension(23, 23));
154 nextPageButton.addActionListener(new java.awt.event.ActionListener() {
155 public void actionPerformed(java.awt.event.ActionEvent evt) {
156 nextPageButtonActionPerformed(evt);
157 }
158 });
159
160 org.openide.awt.Mnemonics.setLocalizedText(exportCsvButton, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.exportCsvButton.text")); // NOI18N
161 exportCsvButton.addActionListener(new java.awt.event.ActionListener() {
162 public void actionPerformed(java.awt.event.ActionEvent evt) {
163 exportCsvButtonActionPerformed(evt);
164 }
165 });
166
167 javax.swing.GroupLayout jHdrPanelLayout = new javax.swing.GroupLayout(jHdrPanel);
168 jHdrPanel.setLayout(jHdrPanelLayout);
169 jHdrPanelLayout.setHorizontalGroup(
170 jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
171 .addGroup(jHdrPanelLayout.createSequentialGroup()
172 .addContainerGap()
173 .addComponent(jLabel1)
174 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
175 .addComponent(tablesDropdownList, javax.swing.GroupLayout.PREFERRED_SIZE, 130, javax.swing.GroupLayout.PREFERRED_SIZE)
176 .addGap(18, 18, 18)
177 .addComponent(numEntriesField, javax.swing.GroupLayout.PREFERRED_SIZE, 71, javax.swing.GroupLayout.PREFERRED_SIZE)
178 .addGap(15, 15, 15)
179 .addComponent(jLabel2)
180 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
181 .addComponent(currPageLabel)
182 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
183 .addComponent(jLabel3)
184 .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
185 .addComponent(numPagesLabel)
186 .addGap(18, 18, 18)
187 .addComponent(prevPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
188 .addGap(0, 0, 0)
189 .addComponent(nextPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
190 .addGap(29, 29, 29)
191 .addComponent(exportCsvButton)
192 .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
193 );
194 jHdrPanelLayout.setVerticalGroup(
195 jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
196 .addGroup(jHdrPanelLayout.createSequentialGroup()
197 .addContainerGap()
198 .addGroup(jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
199 .addComponent(exportCsvButton)
200 .addComponent(nextPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
201 .addComponent(prevPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
202 .addGroup(jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
203 .addComponent(tablesDropdownList, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
204 .addComponent(jLabel1)
205 .addComponent(numEntriesField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
206 .addComponent(jLabel2)
207 .addComponent(currPageLabel)
208 .addComponent(jLabel3)
209 .addComponent(numPagesLabel)))
210 .addContainerGap())
211 );
212
213 jTableDataPanel.setLayout(new java.awt.BorderLayout());
214
215 javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
216 this.setLayout(layout);
217 layout.setHorizontalGroup(
218 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
219 .addComponent(jHdrPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 569, Short.MAX_VALUE)
220 .addComponent(jTableDataPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
221 );
222 layout.setVerticalGroup(
223 layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
224 .addGroup(layout.createSequentialGroup()
225 .addComponent(jHdrPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE)
226 .addGap(0, 0, 0)
227 .addComponent(jTableDataPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 317, Short.MAX_VALUE))
228 );
229 }// </editor-fold>//GEN-END:initComponents
230
231 private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed
232 WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
233 currPage++;
234 if (currPage * ROWS_PER_PAGE > numRows) {
235 nextPageButton.setEnabled(false);
236 }
237 currPageLabel.setText(Integer.toString(currPage));
238 prevPageButton.setEnabled(true);
239
240 // read and display a page of rows
241 String tableName = (String) this.tablesDropdownList.getSelectedItem();
242 readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE);
243 WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
244 }//GEN-LAST:event_nextPageButtonActionPerformed
245
246 private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed
247
248 WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
249 currPage--;
250 if (currPage == 1) {
251 prevPageButton.setEnabled(false);
252 }
253 currPageLabel.setText(Integer.toString(currPage));
254 nextPageButton.setEnabled(true);
255
256 // read and display a page of rows
257 String tableName = (String) this.tablesDropdownList.getSelectedItem();
258 readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE);
259 WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
260 }//GEN-LAST:event_prevPageButtonActionPerformed
261
262 private void tablesDropdownListActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tablesDropdownListActionPerformed
263 JComboBox<?> cb = (JComboBox<?>) evt.getSource();
264 String tableName = (String) cb.getSelectedItem();
265 if (null == tableName) {
266 return;
267 }
268 WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
269 selectTable(tableName);
270 WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
271 }//GEN-LAST:event_tablesDropdownListActionPerformed
272
280 @NbBundle.Messages({"SQLiteViewer.csvExport.fileName.empty=Please input a file name for exporting.",
281 "SQLiteViewer.csvExport.title=Export to csv file",
282 "SQLiteViewer.csvExport.confirm.msg=Do you want to overwrite the existing file?"})
283 private void exportCsvButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportCsvButtonActionPerformed
284 Case openCase = Case.getCurrentCase();
285 File caseDirectory = new File(openCase.getExportDirectory());
286 JFileChooser fileChooser = chooserHelper.getChooser();
287 fileChooser.setDragEnabled(false);
288 fileChooser.setCurrentDirectory(caseDirectory);
289 //Set a filter to let the filechooser only work for csv files
290 FileNameExtensionFilter csvFilter = new FileNameExtensionFilter("*.csv", "csv");
291 fileChooser.addChoosableFileFilter(csvFilter);
292 fileChooser.setAcceptAllFileFilterUsed(true);
293 fileChooser.setFileFilter(csvFilter);
294 fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
295 String defaultFileName = (String) this.tablesDropdownList.getSelectedItem();
296 fileChooser.setSelectedFile(new File(defaultFileName));
297 int choice = fileChooser.showSaveDialog((Component) evt.getSource()); //TODO
298 if (JFileChooser.APPROVE_OPTION == choice) {
299 File file = fileChooser.getSelectedFile();
300 if (file.exists() && FilenameUtils.getExtension(file.getName()).equalsIgnoreCase("csv")) {
301 if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(this,
302 Bundle.SQLiteViewer_csvExport_confirm_msg(),
303 Bundle.SQLiteViewer_csvExport_title(),
304 JOptionPane.YES_NO_OPTION)) {
305 } else {
306 return;
307 }
308 }
309
310 exportTableToCsv(file);
311 }
312 }//GEN-LAST:event_exportCsvButtonActionPerformed
313
314 // Variables declaration - do not modify//GEN-BEGIN:variables
315 private javax.swing.JLabel currPageLabel;
316 private javax.swing.JButton exportCsvButton;
317 private javax.swing.JPanel jHdrPanel;
318 private javax.swing.JLabel jLabel1;
319 private javax.swing.JLabel jLabel2;
320 private javax.swing.JLabel jLabel3;
321 private javax.swing.JPanel jTableDataPanel;
322 private javax.swing.JButton nextPageButton;
323 private javax.swing.JTextField numEntriesField;
324 private javax.swing.JLabel numPagesLabel;
325 private javax.swing.JButton prevPageButton;
326 private javax.swing.JComboBox<String> tablesDropdownList;
327 // End of variables declaration//GEN-END:variables
328
329 @Override
330 public List<String> getSupportedMIMETypes() {
331 return Arrays.asList(SUPPORTED_MIMETYPES);
332 }
333
334 @Override
335 public void setFile(AbstractFile file) {
336 if (worker != null) {
337 worker.cancel(true);
338 worker = null;
339 }
340 resetComponent();
341
342 if (file == null) {
343 return;
344 }
345
346 processSQLiteFile(file);
347 }
348
349 @Override
350 public Component getComponent() {
351 return this;
352 }
353
354 @Override
355 public void resetComponent() {
356 tablesDropdownList.setEnabled(true);
357 tablesDropdownList.removeAllItems();
358 numEntriesField.setText("");
359
360 if(viewReader != null) {
361 try {
362 viewReader.close();
363 } catch (SQLiteTableReaderException ex) {
364 //Could not successfully close the reader, nothing we can do to recover.
365 }
366 }
367 row = new LinkedHashMap<>();
368 pageOfTableRows = new ArrayList<>();
369 currentTableHeader = new ArrayList<>();
370 viewReader = null;
371 sqliteDbFile = null;
372 }
373
377 @NbBundle.Messages({
378 "SQLiteViewer.comboBox.noTableEntry=No tables found",
379 "SQLiteViewer.errorMessage.interrupted=The processing of the file was interrupted.",
380 "SQLiteViewer.errorMessage.noCurrentCase=The case has been closed.",
381 "SQLiteViewer.errorMessage.failedToExtractFile=The file could not be extracted from the data source.",
382 "SQLiteViewer.errorMessage.failedToQueryDatabase=The database tables in the file could not be read.",
383 "SQLiteViewer.errorMessage.failedToinitJDBCDriver=The JDBC driver for SQLite could not be loaded.",
384 "# {0} - exception message", "SQLiteViewer.errorMessage.unexpectedError=An unexpected error occurred:\n{0).",})
385 private void processSQLiteFile(final AbstractFile file) {
386
387 WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
388 worker = new SQLiteViewerWorker(file) {
389 @Override
390 public void done() {
391 if (isCancelled()) {
392 return;
393 }
394
395 WorkerResults results;
396 try {
397 results = get();
398 sqliteDbFile = file;
399 viewReader = results.getReader();
400 tablesDropdownList.removeAllItems();
401 Collection<String> dbTablesMap = results.getDbTablesMap();
402 if (dbTablesMap.isEmpty()) {
403 tablesDropdownList.addItem(Bundle.SQLiteViewer_comboBox_noTableEntry());
404 tablesDropdownList.setEnabled(false);
405 } else {
406 dbTablesMap.forEach((tableName) -> {
407 tablesDropdownList.addItem(tableName);
408 });
409 }
410
411 WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
412 } catch (InterruptedException | ExecutionException ex) {
413 logger.log(Level.SEVERE, String.format("Failed to display SQL Viewer for file (%d)", file.getId()), ex);
414 }
415 }
416 };
417
418 worker.execute();
419 }
420
421 @NbBundle.Messages({"# {0} - tableName",
422 "SQLiteViewer.selectTable.errorText=Error getting row count for table: {0}"
423 })
424 private void selectTable(String tableName) {
425 try {
426 numRows = viewReader.getRowCount(tableName);
427 numEntriesField.setText(numRows + " entries");
428
429 currPage = 1;
430 currPageLabel.setText(Integer.toString(currPage));
431 numPagesLabel.setText(Integer.toString((numRows / ROWS_PER_PAGE) + 1));
432
433 prevPageButton.setEnabled(false);
434
435 if (numRows > 0) {
436 exportCsvButton.setEnabled(true);
437 nextPageButton.setEnabled(((numRows > ROWS_PER_PAGE)));
438 readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE);
439 } else {
440 exportCsvButton.setEnabled(false);
441 nextPageButton.setEnabled(false);
442
443 currentTableHeader = new ArrayList<>();
444 viewReader.read(tableName);
445 Map<String, Object> columnRow = new LinkedHashMap<>();
446 for (int i = 0; i < currentTableHeader.size(); i++) {
447 columnRow.put(currentTableHeader.get(i), "");
448 }
449 selectedTableView.setupTable(Collections.singletonList(columnRow));
450 }
451 } catch (SQLiteTableReaderException ex) {
452 logger.log(Level.WARNING, String.format("Failed to load table %s " //NON-NLS
453 + "from DB file '%s' (objId=%d)", tableName, sqliteDbFile.getName(), //NON-NLS
454 sqliteDbFile.getId()), ex.getMessage());
455 MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_selectTable_errorText(tableName));
456 }
457 }
458
459 @NbBundle.Messages({"# {0} - tableName",
460 "SQLiteViewer.readTable.errorText=Error getting rows for table: {0}"})
461 private void readTable(String tableName, int startRow, int numRowsToRead) {
462 try {
463 //If the table name has changed, then clear our table header. SQLiteTableReader
464 //will also detect the table name has changed and begin reading it as if it
465 //were a brand new table.
466 if (!tableName.equals(prevTableName)) {
467 prevTableName = tableName;
468 }
469 currentTableHeader = new ArrayList<>();
470 viewReader.read(tableName, numRowsToRead, startRow - 1);
471 selectedTableView.setupTable(pageOfTableRows);
472 pageOfTableRows = new ArrayList<>();
473 } catch (SQLiteTableReaderException ex) {
474 logger.log(Level.WARNING, String.format("Failed to read table %s from DB file '%s' " //NON-NLS
475 + "(objId=%d) starting at row [%d] and limit [%d]", //NON-NLS
476 tableName, sqliteDbFile.getName(), sqliteDbFile.getId(),
477 startRow - 1, numRowsToRead), ex.getMessage());
478 MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_readTable_errorText(tableName));
479 }
480 }
481
488 private SQLiteTableReader initReader(AbstractFile sqliteFile) {
489 return new SQLiteTableReader.Builder(sqliteFile)
490 .forAllColumnNames((columnName) -> {
491 currentTableHeader.add(columnName);
492 })
493 .forAllTableValues(getForAllStrategy()).build();
494 }
495
508 private Consumer<Object> getForAllStrategy() {
509 return new Consumer<Object>() {
510 private int rowIndex = 0;
511
512 @Override
513 public void accept(Object t) {
514 rowIndex++;
515 String objectStr = (t instanceof byte[]) ? "BLOB Data not shown"
516 : Objects.toString(t, "");
517
518 row.put(currentTableHeader.get(rowIndex - 1), objectStr);
519
520 //If we have built up a full database row, then add it to our page
521 //of rows to be displayed in the UI.
522 if (rowIndex == currentTableHeader.size()) {
523 pageOfTableRows.add(row);
524 row = new LinkedHashMap<>();
525 }
526 rowIndex %= currentTableHeader.size();
527 }
528
529 };
530 }
531
532 private int totalColumnCount;
533
534 @NbBundle.Messages({"SQLiteViewer.exportTableToCsv.write.errText=Failed to export table content to csv file.",
535 "SQLiteViewer.exportTableToCsv.FileName=File name: ",
536 "SQLiteViewer.exportTableToCsv.TableName=Table name: "
537 })
538 private void exportTableToCsv(File file) {
539 final File csvFile = new File(file.toString() + ".csv");
540 final String tableName = (String) this.tablesDropdownList.getSelectedItem();
541
542 SwingWorker<String, Void> csvWorker = new SwingWorker<String, Void>() {
543 @Override
544 protected String doInBackground() throws Exception {
545 try (FileOutputStream out = new FileOutputStream(csvFile, false)) {
546 try (SQLiteTableReader sqliteStream = new SQLiteTableReader.Builder(sqliteDbFile)
547 .forAllColumnNames(getColumnNameCSVStrategy(out))
548 .forAllTableValues(getForAllCSVStrategy(out)).build()) {
549 totalColumnCount = sqliteStream.getColumnCount(tableName);
550 sqliteStream.read(tableName);
551 }
552 } catch (IOException | SQLiteTableReaderException | RuntimeException ex) {
553 logger.log(Level.WARNING, String.format("Failed to export table [%s]"
554 + " to CSV in sqlite file '%s' (objId=%d)", tableName, sqliteDbFile.getName(),
555 sqliteDbFile.getId()), ex.getMessage()); //NON-NLS
556
557 return Bundle.SQLiteViewer_exportTableToCsv_write_errText();
558 }
559 return "";
560 }
561
562 @Override
563 public void done() {
564 try {
565 String message = get();
566 if (!message.isEmpty()) {
567 MessageNotifyUtil.Message.error(message);
568 }
569 } catch (InterruptedException | ExecutionException ex) {
570 logger.log(Level.SEVERE, "Failure occurred writing sql csv file.", ex);
571 }
572 }
573
574 };
575
576 csvWorker.execute();
577 }
578
593 private Consumer<String> getColumnNameCSVStrategy(FileOutputStream out) {
594 return new Consumer<String>() {
595 private int columnIndex = 0;
596
597 @Override
598 public void accept(String columnName) {
599 columnIndex++;
600 String csvString = columnName;
601 //Format the value to adhere to the format of a CSV file
602 if (columnIndex == 1) {
603 csvString = "\"" + csvString + "\"";
604 } else {
605 csvString = ",\"" + csvString + "\"";
606 }
607 if (columnIndex == totalColumnCount) {
608 csvString += "\n";
609 }
610
611 try {
612 out.write(csvString.getBytes());
613 } catch (IOException ex) {
614 /*
615 * If we can no longer write to the output stream, toss a
616 * runtime exception to get out of iteration. We explicitly
617 * catch this in exportTableToCsv() above.
618 */
619 throw new RuntimeException(ex);
620 }
621 }
622 };
623 }
624
639 private Consumer<Object> getForAllCSVStrategy(FileOutputStream out) {
640 return new Consumer<Object>() {
641 private int rowIndex = 0;
642
643 @Override
644 public void accept(Object tableValue) {
645 rowIndex++;
646 //Substitute string representation of blob with placeholder text.
647 //Automatically wrap the value in quotes in case it contains commas.
648 String objectStr = (tableValue instanceof byte[])
649 ? "BLOB Data not shown" : Objects.toString(tableValue, "");
650 objectStr = "\"" + objectStr + "\"";
651
652 if (rowIndex > 1) {
653 objectStr = "," + objectStr;
654 }
655 if (rowIndex == totalColumnCount) {
656 objectStr += "\n";
657 }
658
659 try {
660 out.write(objectStr.getBytes());
661 } catch (IOException ex) {
662 /*
663 * If we can no longer write to the output stream, toss a
664 * runtime exception to get out of iteration. We explicitly
665 * catch this in exportTableToCsv() above.
666 */
667 throw new RuntimeException(ex);
668 }
669 rowIndex %= totalColumnCount;
670 }
671 };
672 }
673
674 @Override
675 public boolean isSupported(AbstractFile file) {
676 return true;
677 }
678
683 private class SQLiteViewerWorker extends SwingWorker<WorkerResults, Void> {
684
685 private final AbstractFile file;
686
687 SQLiteViewerWorker(AbstractFile file) {
688 this.file = file;
689 }
690
691 @Override
692 protected WorkerResults doInBackground() throws Exception {
693 SQLiteTableReader reader = initReader(file);
694 Collection<String> dbTablesMap = reader.getTableNames();
695
696 return new WorkerResults(reader, dbTablesMap);
697 }
698
699 }
700
701 /*
702 * Stores the data gather from the
703 */
704 private class WorkerResults {
705
707 private final Collection<String> dbTablesMap;
708
709 WorkerResults(SQLiteTableReader reader, Collection<String> dbTablesMap) {
710 this.reader = reader;
711 this.dbTablesMap = dbTablesMap;
712 }
713
714 SQLiteTableReader getReader() {
715 return reader;
716 }
717
718 Collection<String> getDbTablesMap() {
719 return dbTablesMap;
720 }
721 }
722
723}
synchronized static Logger getLogger(String name)
Definition Logger.java:124

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.