Autopsy 4.22.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
PDFViewer.java
Go to the documentation of this file.
1/*
2 * Autopsy Forensic Browser
3 *
4 * Copyright 2020 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.Container;
24import java.io.IOException;
25import java.lang.reflect.InvocationTargetException;
26import java.util.Arrays;
27import java.util.List;
28import java.util.Properties;
29import java.util.ResourceBundle;
30import java.util.concurrent.ExecutionException;
31import java.util.logging.Level;
32
33import javax.swing.JPanel;
34import javax.swing.SwingUtilities;
35import javax.swing.SwingWorker;
36import org.icepdf.core.SecurityCallback;
37
38import org.openide.util.NbBundle;
39
40import org.sleuthkit.datamodel.AbstractFile;
41import org.sleuthkit.datamodel.ReadContentInputStream;
42
43import org.sleuthkit.autopsy.coreutils.Logger;
44import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
45
46import org.icepdf.core.exceptions.PDFException;
47import org.icepdf.core.exceptions.PDFSecurityException;
48import org.icepdf.core.pobjects.Document;
49
50import org.icepdf.ri.common.ComponentKeyBinding;
51import org.icepdf.ri.common.MyGUISecurityCallback;
52import org.icepdf.ri.common.SwingController;
53import org.icepdf.ri.common.SwingViewBuilder;
54import org.icepdf.ri.common.views.DocumentViewControllerImpl;
55import org.icepdf.ri.common.views.DocumentViewModelImpl;
56import org.icepdf.ri.util.PropertiesManager;
57
61final class PDFViewer implements FileTypeViewer {
62
63 private static final Logger logger = Logger.getLogger(PDFViewer.class.getName());
64
65 private JPanel container;
66 private final PropertiesManager propsManager;
67 private final ResourceBundle messagesBundle;
68
69 PDFViewer() {
70 container = createNewContainer();
71 messagesBundle = getMessagesBundle();
72 propsManager = getCustomProperties();
73 }
74
75 @Override
76 public List<String> getSupportedMIMETypes() {
77 return Arrays.asList("application/pdf");
78 }
79
80 @Override
81 public void setFile(AbstractFile file) {
82 // The 'C' in IcePDFs MVC set up.
83 SwingController controller = new SwingController(messagesBundle);
84
85 // Builder for the 'V' in IcePDFs MVC set up
86 SwingViewBuilder viewBuilder = new SwingViewBuilder(controller, propsManager);
87
88 // The 'V' in IcePDFs MVC set up.
89 JPanel icePdfPanel = viewBuilder.buildViewerPanel();
90
91 // This connects keyboard commands performed on the view to the controller.
92 // The only keyboard commands that the controller supports is Ctrl-C for
93 // copying selected text.
94 ComponentKeyBinding.install(controller, icePdfPanel);
95
96 // Ensure the preferredSize is in sync with the parent container.
97 icePdfPanel.setPreferredSize(this.container.getPreferredSize());
98
99 // Add the IcePDF view to the center of our container.
100 this.container.add(icePdfPanel, BorderLayout.CENTER);
101
102 // Disable all components until the document is ready to view.
103 enableComponents(container, false);
104
105 // Document is the 'M' in IcePDFs MVC set up. Read the data needed to
106 // populate the model in the background.
107 new SwingWorker<Document, Void>() {
108 @Override
109 protected Document doInBackground() throws PDFException, PDFSecurityException, IOException {
110 ReadContentInputStream stream = new ReadContentInputStream(file);
111 Document doc = new Document();
112
113 // Prompts the user for a password if the document is password
114 // protected.
115 doc.setSecurityCallback(createPasswordDialogCallback());
116
117 // This will read the stream into memory and invoke the
118 // security callback if needed.
119 doc.setInputStream(stream, null);
120 return doc;
121 }
122
123 @Override
124 protected void done() {
125 // Customize the view selection modes on the EDT. Each of these
126 // will cause UI widgets to be updated.
127 try {
128 Document doc = get();
129 controller.openDocument(doc, file.getName());
130 // This makes the PDF viewer appear as one continuous
131 // document, which is the default for most popular PDF viewers.
132 controller.setPageViewMode(DocumentViewControllerImpl.ONE_COLUMN_VIEW, true);
133 // This makes it possible to select text by left clicking and dragging.
134 controller.setDisplayTool(DocumentViewModelImpl.DISPLAY_TOOL_TEXT_SELECTION);
135 enableComponents(container, true);
136 } catch (InterruptedException ex) {
137 // Do nothing.
138 } catch (ExecutionException ex) {
139 Throwable exCause = ex.getCause();
140 if (exCause instanceof PDFSecurityException) {
141 showEncryptionDialog();
142 } else {
143 logger.log(Level.WARNING, String.format("PDF content viewer "
144 + "was unable to open document with id %d and name %s",
145 file.getId(), file.getName()), ex);
146 showErrorDialog();
147 }
148 } catch (Throwable ex) {
149 logger.log(Level.WARNING, String.format("PDF content viewer "
150 + "was unable to open document with id %d and name %s",
151 file.getId(), file.getName()), ex);
152 }
153 }
154 }.execute();
155 }
156
161 private void enableComponents(Container container, boolean enabled) {
162 Component[] components = container.getComponents();
163 for(Component component : components) {
164 component.setEnabled(enabled);
165 if (component instanceof Container) {
166 enableComponents((Container)component, enabled);
167 }
168 }
169 }
170
171 @Override
172 public Component getComponent() {
173 return container;
174 }
175
176 @Override
177 public void resetComponent() {
178 container = createNewContainer();
179 }
180
181 // The container should have a BorderLayout otherwise the IcePDF panel may
182 // not be visible.
183 private JPanel createNewContainer() {
184 return new JPanel(new BorderLayout());
185 }
186
187 @Override
188 public boolean isSupported(AbstractFile file) {
189 return getSupportedMIMETypes().contains(file.getMIMEType());
190 }
191
196 private PropertiesManager getCustomProperties() {
197 Properties props = new Properties();
198
199 // See link for available properties. https://www.icesoft.org/wiki/display/PDF/Customizing+the+Viewer
200 props.setProperty(PropertiesManager.PROPERTY_SHOW_UTILITY_SAVE, "false");
201 props.setProperty(PropertiesManager.PROPERTY_SHOW_UTILITY_OPEN, "false");
202 props.setProperty(PropertiesManager.PROPERTY_SHOW_UTILITY_PRINT, "false");
203 props.setProperty(PropertiesManager.PROPERTY_SHOW_TOOLBAR_ANNOTATION, "false");
204 props.setProperty(PropertiesManager.PROPERTY_SHOW_UTILITYPANE_ANNOTATION, "false");
205
206 // This suppresses a pop-up, from IcePDF, that asks if you'd like to
207 // save configuration changes to disk.
208 props.setProperty("application.showLocalStorageDialogs", "false");
209
210 return new PropertiesManager(System.getProperties(), props, messagesBundle);
211 }
212
213 private ResourceBundle getMessagesBundle() {
214 return NbBundle.getBundle(PDFViewer.class);
215 }
216
217 @NbBundle.Messages({
218 "PDFViewer.errorDialog=An error occurred while opening this PDF document. "
219 + "Check the logs for more information. You may continue to use "
220 + "this feature on other PDF documents."
221 })
222 private void showErrorDialog() {
223 MessageNotifyUtil.Message.error(Bundle.PDFViewer_errorDialog());
224 }
225
226 @NbBundle.Messages({
227 "PDFViewer.encryptedDialog=This document is password protected."
228 })
229 private void showEncryptionDialog() {
230 MessageNotifyUtil.Message.error(Bundle.PDFViewer_encryptedDialog());
231 }
232
236 private SecurityCallback createPasswordDialogCallback() {
237 // MyGUISecurityCallback is a reference implementation from IcePDF.
238 return new MyGUISecurityCallback(null, messagesBundle) {
239 private String password;
240
241 @Override
242 public String requestPassword(Document document) {
243 try {
244 SwingUtilities.invokeAndWait(() -> {
245 // Show the password dialog on the EDT.
246 this.password = super.requestPassword(document);
247 });
248 return this.password;
249 } catch (InterruptedException | InvocationTargetException ex) {
250 return null;
251 }
252 }
253 };
254 }
255}

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