diff --git a/Core/build.xml b/Core/build.xml index 5d91ade7e1..df747ff257 100644 --- a/Core/build.xml +++ b/Core/build.xml @@ -72,6 +72,9 @@ + + + diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index 22af0f87c8..9db5951f69 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -2,8 +2,15 @@ file.reference.activemq-all-5.11.1.jar=release/modules/ext/activemq-all-5.11.1.j file.reference.apache-mime4j-core-0.8.2.jar=release\\modules\\ext\\apache-mime4j-core-0.8.2.jar file.reference.apache-mime4j-dom-0.8.2.jar=release\\modules\\ext\\apache-mime4j-dom-0.8.2.jar file.reference.asm-7.0.jar=release\\modules\\ext\\asm-7.0.jar +file.reference.batik-awt-util-1.6.jar=release/modules/ext/batik-awt-util-1.6.jar +file.reference.batik-dom-1.6.jar=release/modules/ext/batik-dom-1.6.jar +file.reference.batik-svg-dom-1.6.jar=release/modules/ext/batik-svg-dom-1.6.jar +file.reference.batik-svggen-1.6.jar=release/modules/ext/batik-svggen-1.6.jar +file.reference.batik-util-1.6.jar=release/modules/ext/batik-util-1.6.jar +file.reference.batik-xml-1.6.jar=release/modules/ext/batik-xml-1.6.jar file.reference.bcmail-jdk15on-1.60.jar=release\\modules\\ext\\bcmail-jdk15on-1.60.jar file.reference.bcpkix-jdk15on-1.60.jar=release\\modules\\ext\\bcpkix-jdk15on-1.60.jar +file.reference.bcprov-ext-jdk15on-1.54.jar=release/modules/ext/bcprov-ext-jdk15on-1.54.jar file.reference.bcprov-jdk15on-1.60.jar=release\\modules\\ext\\bcprov-jdk15on-1.60.jar file.reference.boilerpipe-1.1.0.jar=release\\modules\\ext\\boilerpipe-1.1.0.jar file.reference.c3p0-0.9.5.jar=release/modules/ext/c3p0-0.9.5.jar @@ -17,14 +24,17 @@ file.reference.commons-io-2.6.jar=release\\modules\\ext\\commons-io-2.6.jar file.reference.commons-lang3-3.8.1.jar=release\\modules\\ext\\commons-lang3-3.8.1.jar file.reference.commons-pool2-2.4.2.jar=release/modules/ext/commons-pool2-2.4.2.jar file.reference.cxf-rt-rs-client-3.3.0.jar=release\\modules\\ext\\cxf-rt-rs-client-3.3.0.jar +file.reference.DatCon.jar=release/modules/ext/DatCon.jar file.reference.dec-0.1.2.jar=release\\modules\\ext\\dec-0.1.2.jar -file.reference.decodetect-core-0.3.jar=release\\modules\\ext\\decodetect-core-0.3.jar +file.reference.decodetect-core-0.3.jar=release/modules/ext/decodetect-core-0.3.jar file.reference.fontbox-2.0.13.jar=release\\modules\\ext\\fontbox-2.0.13.jar file.reference.geoapi-3.0.1.jar=release\\modules\\ext\\geoapi-3.0.1.jar file.reference.grib-4.5.5.jar=release\\modules\\ext\\grib-4.5.5.jar file.reference.httpclient-4.5.6.jar=release\\modules\\ext\\httpclient-4.5.6.jar file.reference.httpmime-4.5.6.jar=release\\modules\\ext\\httpmime-4.5.6.jar file.reference.httpservices-4.5.5.jar=release\\modules\\ext\\httpservices-4.5.5.jar +file.reference.icepdf-core-6.2.2.jar=release/modules/ext/icepdf-core-6.2.2.jar +file.reference.icepdf-viewer-6.2.2.jar=release/modules/ext/icepdf-viewer-6.2.2.jar file.reference.isoparser-1.1.22.jar=release\\modules\\ext\\isoparser-1.1.22.jar file.reference.jackcess-2.2.0.jar=release\\modules\\ext\\jackcess-2.2.0.jar file.reference.jackcess-encrypt-2.1.4.jar=release\\modules\\ext\\jackcess-encrypt-2.1.4.jar @@ -32,6 +42,8 @@ file.reference.jackson-annotations-2.9.7.jar=release\\modules\\ext\\jackson-anno file.reference.jackson-core-2.9.7.jar=release\\modules\\ext\\jackson-core-2.9.7.jar file.reference.jackson-databind-2.9.7.jar=release\\modules\\ext\\jackson-databind-2.9.7.jar file.reference.jai-imageio-core-1.4.0.jar=release\\modules\\ext\\jai-imageio-core-1.4.0.jar +file.reference.jai_core-1.1.3.jar=release/modules/ext/jai_core-1.1.3.jar +file.reference.jai_imageio-1.1.jar=release/modules/ext/jai_imageio-1.1.jar file.reference.java-libpst-0.8.1.jar=release\\modules\\ext\\java-libpst-0.8.1.jar file.reference.javax.activation-1.2.0.jar=release\\modules\\ext\\javax.activation-1.2.0.jar file.reference.javax.annotation-api-1.3.2.jar=release\\modules\\ext\\javax.annotation-api-1.3.2.jar @@ -50,7 +62,7 @@ file.reference.jsoup-1.11.3.jar=release\\modules\\ext\\jsoup-1.11.3.jar file.reference.jul-to-slf4j-1.7.25.jar=release\\modules\\ext\\jul-to-slf4j-1.7.25.jar file.reference.juniversalchardet-1.0.3.jar=release\\modules\\ext\\juniversalchardet-1.0.3.jar file.reference.junrar-2.0.0.jar=release\\modules\\ext\\junrar-2.0.0.jar -file.reference.jutf7-1.0.0.jar=release\\modules\\ext\\jutf7-1.0.0.jar +file.reference.jutf7-1.0.0.jar=release/modules/ext/jutf7-1.0.0.jar file.reference.jxmapviewer2-2.4.jar=release/modules/ext/jxmapviewer2-2.4.jar file.reference.jython-standalone-2.7.0.jar=release/modules/ext/jython-standalone-2.7.0.jar file.reference.libphonenumber-3.5.jar=release/modules/ext/libphonenumber-3.5.jar diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 79c518eeaa..bf917fc039 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -355,6 +355,14 @@ ext/commons-lang3-3.8.1.jar release\modules\ext\commons-lang3-3.8.1.jar + + ext/batik-xml-1.6.jar + release/modules/ext/batik-xml-1.6.jar + + + ext/jai_core-1.1.3.jar + release/modules/ext/jai_core-1.1.3.jar + ext/gax-grpc-1.44.0.jar release/modules/ext/gax-grpc-1.44.0.jar @@ -371,6 +379,10 @@ ext/opencensus-api-0.19.2.jar release/modules/ext/opencensus-api-0.19.2.jar + + ext/batik-svg-dom-1.6.jar + release/modules/ext/batik-svg-dom-1.6.jar + ext/gax-httpjson-0.61.0.jar release/modules/ext/gax-httpjson-0.61.0.jar @@ -479,6 +491,10 @@ ext/xmpcore-5.1.3.jar release/modules/ext/xmpcore-5.1.3.jar + + ext/batik-util-1.6.jar + release/modules/ext/batik-util-1.6.jar + ext/javax.activation-1.2.0.jar release\modules\ext\javax.activation-1.2.0.jar @@ -499,6 +515,10 @@ ext/jgraphx-4.1.0.jar release/modules/ext/jgraphx-4.1.0.jar + + ext/DatCon.jar + release/modules/ext/DatCon.jar + ext/java-libpst-0.8.1.jar release\modules\ext\java-libpst-0.8.1.jar @@ -535,10 +555,6 @@ ext/google-http-client-1.29.0.jar release/modules/ext/google-http-client-1.29.0.jar - - ext/sleuthkit-postgresql-4.9.0.jar - release/modules/ext/sleuthkit-postgresql-4.9.0.jar - ext/bcpkix-jdk15on-1.60.jar release\modules\ext\bcpkix-jdk15on-1.60.jar @@ -551,6 +567,10 @@ ext/slf4j-api-1.7.25.jar release\modules\ext\slf4j-api-1.7.25.jar + + ext/bcprov-ext-jdk15on-1.54.jar + release/modules/ext/bcprov-ext-jdk15on-1.54.jar + ext/google-cloud-core-1.70.0.jar release/modules/ext/google-cloud-core-1.70.0.jar @@ -619,6 +639,14 @@ ext/commons-validator-1.6.jar release/modules/ext/commons-validator-1.6.jar + + ext/sleuthkit-postgresql-4.9.0.jar + release/modules/ext/sleuthkit-postgresql-4.9.0.jar + + + ext/decodetect-core-0.3.jar + release/modules/ext/decodetect-core-0.3.jar + ext/jbig2-imageio-3.0.2.jar release\modules\ext\jbig2-imageio-3.0.2.jar @@ -667,6 +695,10 @@ ext/SparseBitSet-1.1.jar release/modules/ext/SparseBitSet-1.1.jar + + ext/batik-svggen-1.6.jar + release/modules/ext/batik-svggen-1.6.jar + ext/c3p0-0.9.5.jar release/modules/ext/c3p0-0.9.5.jar @@ -735,6 +767,10 @@ ext/postgresql-9.4.1211.jre7.jar release/modules/ext/postgresql-9.4.1211.jre7.jar + + ext/jai_imageio-1.1.jar + release/modules/ext/jai_imageio-1.1.jar + ext/httpclient-4.5.6.jar release\modules\ext\httpclient-4.5.6.jar @@ -747,6 +783,10 @@ ext/fontbox-2.0.13.jar release\modules\ext\fontbox-2.0.13.jar + + ext/icepdf-core-6.2.2.jar + release/modules/ext/icepdf-core-6.2.2.jar + ext/activemq-all-5.11.1.jar release/modules/ext/activemq-all-5.11.1.jar @@ -763,6 +803,10 @@ ext/dec-0.1.2.jar release\modules\ext\dec-0.1.2.jar + + ext/batik-dom-1.6.jar + release/modules/ext/batik-dom-1.6.jar + ext/google-http-client-jackson2-1.29.0.jar release/modules/ext/google-http-client-jackson2-1.29.0.jar @@ -779,6 +823,14 @@ ext/sevenzipjbinding-AllPlatforms.jar release/modules/ext/sevenzipjbinding-AllPlatforms.jar + + ext/jutf7-1.0.0.jar + release/modules/ext/jutf7-1.0.0.jar + + + ext/batik-awt-util-1.6.jar + release/modules/ext/batik-awt-util-1.6.jar + ext/google-api-services-translate-v2-rev20170525-1.27.0.jar release/modules/ext/google-api-services-translate-v2-rev20170525-1.27.0.jar @@ -787,6 +839,10 @@ ext/webp-imageio-sejda-0.1.0.jar release/modules/ext/webp-imageio-sejda-0.1.0.jar + + ext/icepdf-viewer-6.2.2.jar + release/modules/ext/icepdf-viewer-6.2.2.jar + ext/bcmail-jdk15on-1.60.jar release\modules\ext\bcmail-jdk15on-1.60.jar @@ -795,18 +851,6 @@ ext/vorbis-java-tika-0.8.jar release\modules\ext\vorbis-java-tika-0.8.jar - - ext/decodetect-core-0.3.jar - release/modules/ext/decodetect-core-0.3.jar - - - ext/jutf7-1.0.0.jar - release/modules/ext/jutf7-1.0.0.jar - - - ext/DatCon.jar - release/modules/ext/DatCon.jar - diff --git a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java index 25d7bf5519..8bf29e70ac 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java +++ b/Core/src/org/sleuthkit/autopsy/communications/AccountDeviceInstanceNode.java @@ -48,7 +48,8 @@ final class AccountDeviceInstanceNode extends AbstractNode { this.account = accountDeviceInstanceKey.getAccountDeviceInstance().getAccount(); setName(account.getTypeSpecificID()); setDisplayName(getName()); - setIconBaseWithExtension(Utils.getIconFilePath(account.getAccountType())); + String iconPath = Utils.getIconFilePath(account.getAccountType()); + this.setIconBaseWithExtension(iconPath != null && iconPath.charAt(0) == '/' ? iconPath.substring(1) : iconPath); } AccountDeviceInstance getAccountDeviceInstance() { diff --git a/Core/src/org/sleuthkit/autopsy/communications/PinAccountsAction.java b/Core/src/org/sleuthkit/autopsy/communications/PinAccountsAction.java index 52136d1b1a..34e6339b68 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/PinAccountsAction.java +++ b/Core/src/org/sleuthkit/autopsy/communications/PinAccountsAction.java @@ -32,7 +32,7 @@ import org.openide.util.NbBundle; final class PinAccountsAction extends AbstractCVTAction { static private final ImageIcon ICON = ImageUtilities.loadImageIcon( - "/org/sleuthkit/autopsy/communications/images/marker--plus.png", false); + "org/sleuthkit/autopsy/communications/images/marker--plus.png", false); private static final String SINGULAR_TEXT = Bundle.PinAccountsAction_singularText(); private static final String PLURAL_TEXT = Bundle.PinAccountsAction_pluralText(); diff --git a/Core/src/org/sleuthkit/autopsy/communications/ResetAndPinAccountsAction.java b/Core/src/org/sleuthkit/autopsy/communications/ResetAndPinAccountsAction.java index 385ac3348b..ee71508162 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/ResetAndPinAccountsAction.java +++ b/Core/src/org/sleuthkit/autopsy/communications/ResetAndPinAccountsAction.java @@ -32,7 +32,7 @@ import org.openide.util.NbBundle; final class ResetAndPinAccountsAction extends AbstractCVTAction { private static final ImageIcon ICON = ImageUtilities.loadImageIcon( - "/org/sleuthkit/autopsy/communications/images/marker--pin.png", false); + "org/sleuthkit/autopsy/communications/images/marker--pin.png", false); private static final String SINGULAR_TEXT = Bundle.ResetAndPinAccountsAction_singularText(); private static final String PLURAL_TEXT = Bundle.ResetAndPinAccountsAction_pluralText(); diff --git a/Core/src/org/sleuthkit/autopsy/communications/UnpinAccountsAction.java b/Core/src/org/sleuthkit/autopsy/communications/UnpinAccountsAction.java index 58ace503d9..0342d90310 100644 --- a/Core/src/org/sleuthkit/autopsy/communications/UnpinAccountsAction.java +++ b/Core/src/org/sleuthkit/autopsy/communications/UnpinAccountsAction.java @@ -32,7 +32,7 @@ import org.openide.util.NbBundle; final class UnpinAccountsAction extends AbstractCVTAction { static final private ImageIcon ICON = ImageUtilities.loadImageIcon( - "/org/sleuthkit/autopsy/communications/images/marker--minus.png", false); + "org/sleuthkit/autopsy/communications/images/marker--minus.png", false); private static final String SINGULAR_TEXT = Bundle.UnpinAccountsAction_singularText(); private static final String PLURAL_TEXT = Bundle.UnpinAccountsAction_pluralText(); diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED index c0a6f73aaf..0c33d92daf 100755 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED @@ -112,6 +112,8 @@ 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 +PDFViewer.encryptedDialog=This document is password protected. +PDFViewer.errorDialog=An error occurred while opening this PDF document. Check the logs for more information. You may continue to use this feature on other PDF documents. PListNode.KeyCol=Key PListNode.TypeCol=Type PListNode.ValueCol=Value diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java index 1b0d048f35..0a50fb0ec9 100644 --- a/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/FileViewer.java @@ -50,7 +50,8 @@ public class FileViewer extends javax.swing.JPanel implements DataContentViewer new PListViewer(), new MediaFileViewer(), new HtmlViewer(), - new WindowsRegistryViewer() + new WindowsRegistryViewer(), + new PDFViewer() }; private FileTypeViewer lastViewer; diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/PDFViewer.java b/Core/src/org/sleuthkit/autopsy/contentviewers/PDFViewer.java new file mode 100755 index 0000000000..52c6a93558 --- /dev/null +++ b/Core/src/org/sleuthkit/autopsy/contentviewers/PDFViewer.java @@ -0,0 +1,192 @@ +/* + * Autopsy Forensic Browser + * + * Copyright 2020 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.BorderLayout; +import java.awt.Component; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import javax.swing.JPanel; +import javax.swing.SwingWorker; + +import org.openide.util.NbBundle; + +import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.ReadContentInputStream; +import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; + +import org.icepdf.core.exceptions.PDFException; +import org.icepdf.core.exceptions.PDFSecurityException; +import org.icepdf.core.pobjects.Document; + +import org.icepdf.ri.common.ComponentKeyBinding; +import org.icepdf.ri.common.SwingController; +import org.icepdf.ri.common.SwingViewBuilder; +import org.icepdf.ri.common.views.DocumentViewControllerImpl; +import org.icepdf.ri.common.views.DocumentViewModelImpl; +import org.icepdf.ri.util.PropertiesManager; + +/** + * Application content viewer for PDF files. + */ +public class PDFViewer implements FileTypeViewer { + + private static final Logger logger = Logger.getLogger(PDFViewer.class.getName()); + + private JPanel container; + private final PropertiesManager propsManager; + + public PDFViewer() { + container = createNewContainer(); + propsManager = getCustomProperties(); + } + + @Override + public List getSupportedMIMETypes() { + return Arrays.asList("application/pdf"); + } + + @Override + public void setFile(AbstractFile file) { + // The 'C' in IcePDFs MVC set up. + SwingController controller = new SwingController(); + + // Builder for the 'V' in IcePDFs MVC set up + SwingViewBuilder viewBuilder = new SwingViewBuilder(controller, propsManager); + + // The 'V' in IcePDFs MVC set up. + JPanel icePdfPanel = viewBuilder.buildViewerPanel(); + + // This connects keyboard commands performed on the view to the controller. + // The only keyboard commands that the controller supports is Ctrl-C for + // copying selected text. + ComponentKeyBinding.install(controller, icePdfPanel); + + // Ensure the preferredSize is in sync with the parent container. + icePdfPanel.setPreferredSize(this.container.getPreferredSize()); + + // Add the IcePDF view to the center of our container. + this.container.add(icePdfPanel, BorderLayout.CENTER); + + // Document is the 'M' in IcePDFs MVC set up. Read the data needed to + // populate the model in the background. + new SwingWorker() { + @Override + protected Document doInBackground() throws PDFException, PDFSecurityException, IOException { + ReadContentInputStream stream = new ReadContentInputStream(file); + Document doc = new Document(); + // This will read the stream into memory. + doc.setInputStream(stream, null); + return doc; + } + + @Override + protected void done() { + // Customize the view selection modes on the EDT. Each of these + // will cause UI widgets to be updated. + try { + Document doc = get(); + controller.openDocument(doc, null); + // This makes the PDF viewer appear as one continuous + // document, which is the default for most popular PDF viewers. + controller.setPageViewMode(DocumentViewControllerImpl.ONE_COLUMN_VIEW, true); + // This makes it possible to select text by left clicking and dragging. + controller.setDisplayTool(DocumentViewModelImpl.DISPLAY_TOOL_TEXT_SELECTION); + } catch (InterruptedException ex) { + // Do nothing. + } catch (ExecutionException ex) { + Throwable exCause = ex.getCause(); + if (exCause instanceof PDFSecurityException) { + showEncryptionDialog(); + } else { + logger.log(Level.WARNING, String.format("PDF content viewer " + + "was unable to open document with id %d and name %s", + file.getId(), file.getName()), ex); + showErrorDialog(); + } + } + } + }.execute(); + } + + @Override + public Component getComponent() { + return container; + } + + @Override + public void resetComponent() { + container = createNewContainer(); + } + + // The container should have a BorderLayout otherwise the IcePDF panel may + // not be visible. + private JPanel createNewContainer() { + return new JPanel(new BorderLayout()); + } + + @Override + public boolean isSupported(AbstractFile file) { + return getSupportedMIMETypes().contains(file.getMIMEType()); + } + + /** + * Sets property values that will control how the view will be constructed + * in IcePDFs MVC set up. + */ + private PropertiesManager getCustomProperties() { + Properties props = new Properties(); + + // See link for available properties. https://www.icesoft.org/wiki/display/PDF/Customizing+the+Viewer + props.setProperty(PropertiesManager.PROPERTY_SHOW_UTILITY_SAVE, "false"); + props.setProperty(PropertiesManager.PROPERTY_SHOW_UTILITY_OPEN, "false"); + props.setProperty(PropertiesManager.PROPERTY_SHOW_UTILITY_PRINT, "false"); + props.setProperty(PropertiesManager.PROPERTY_SHOW_TOOLBAR_ANNOTATION, "false"); + props.setProperty(PropertiesManager.PROPERTY_SHOW_UTILITYPANE_ANNOTATION, "false"); + + // This suppresses a pop-up, from IcePDF, that asks if you'd like to + // save configuration changes to disk. + props.setProperty("application.showLocalStorageDialogs", "false"); + + ResourceBundle defaultMessageBundle = ResourceBundle.getBundle(PropertiesManager.DEFAULT_MESSAGE_BUNDLE); + return new PropertiesManager(System.getProperties(), props, defaultMessageBundle); + } + + @NbBundle.Messages({ + "PDFViewer.errorDialog=An error occurred while opening this PDF document. " + + "Check the logs for more information. You may continue to use " + + "this feature on other PDF documents." + }) + private void showErrorDialog() { + MessageNotifyUtil.Message.error(Bundle.PDFViewer_errorDialog()); + } + + @NbBundle.Messages({ + "PDFViewer.encryptedDialog=This document is password protected." + }) + private void showEncryptionDialog() { + MessageNotifyUtil.Message.error(Bundle.PDFViewer_encryptedDialog()); + } +} diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index 15bc46a8da..c7ccf32099 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -260,7 +260,7 @@ public class BlackboardArtifactNode extends AbstractContentNode counts = new HashMap<>(); + + AccountTypeResults() { + update(); + } + + /** + * Given the type name of the Account.Type, provides the count of those type. + * @param accountType The type name of the Account.Type. + * @return The number of results found for the given account type. + */ + Long getCount(String accountType) { + return counts.get(accountType); + } + + /** + * Retrieves an alphabetically organized list of all the account types. + * @return An alphabetically organized list of all the account types. + */ + List getTypes() { + List types = new ArrayList<>(counts.keySet()); + Collections.sort(types); + return types; + } + + /** + * Queries the database and updates the counts for each account type. + */ + private void update() { + String accountTypesInUseQuery + = "SELECT blackboard_attributes.value_text as account_type, COUNT(*) as count " + + " FROM blackboard_artifacts " //NON-NLS + + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS + + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS + + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS + + getFilterByDataSourceClause() + + " GROUP BY blackboard_attributes.value_text "; + + try (SleuthkitCase.CaseDbQuery executeQuery = skCase.executeQuery(accountTypesInUseQuery); + ResultSet resultSet = executeQuery.getResultSet()) { + + counts.clear(); + while (resultSet.next()) { + String accountType = resultSet.getString("account_type"); + Long count = resultSet.getLong("count"); + counts.put(accountType, count); + } + } catch (TskCoreException | SQLException ex) { + LOGGER.log(Level.SEVERE, "Error querying for account_types", ex); + } + } + } /** * Creates child nodes for each account type in the db. */ private class AccountTypeFactory extends ObservingChildren { - + /* * The pcl is in this class because it has the easiest mechanisms to add * and remove itself during its life cycles. @@ -281,6 +344,7 @@ final public class Accounts implements AutopsyVisitableItem { ModuleDataEvent eventData = (ModuleDataEvent) evt.getOldValue(); if (null != eventData && eventData.getBlackboardArtifactType().getTypeID() == ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID()) { + accountTypeResults.update(); reviewStatusBus.post(eventData); } } catch (NoCurrentCaseException notUsed) { @@ -324,37 +388,31 @@ final public class Accounts implements AutopsyVisitableItem { @Override protected boolean createKeys(List list) { - String accountTypesInUseQuery - = "SELECT DISTINCT blackboard_attributes.value_text as account_type " - + " FROM blackboard_artifacts " //NON-NLS - + " JOIN blackboard_attributes ON blackboard_artifacts.artifact_id = blackboard_attributes.artifact_id " //NON-NLS - + " WHERE blackboard_artifacts.artifact_type_id = " + BlackboardArtifact.ARTIFACT_TYPE.TSK_ACCOUNT.getTypeID() //NON-NLS - + " AND blackboard_attributes.attribute_type_id = " + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ACCOUNT_TYPE.getTypeID() //NON-NLS - + getFilterByDataSourceClause(); - - try (SleuthkitCase.CaseDbQuery executeQuery = skCase.executeQuery(accountTypesInUseQuery); - ResultSet resultSet = executeQuery.getResultSet()) { - while (resultSet.next()) { - String accountType = resultSet.getString("account_type"); - list.add(accountType); - } - } catch (TskCoreException | SQLException ex) { - LOGGER.log(Level.SEVERE, "Error querying for account_types", ex); - } - + list.addAll(accountTypeResults.getTypes()); return true; } + + /** + * Registers the given node with the reviewStatusBus and returns + * the node wrapped in an array. + * @param node The node to be wrapped. + * @return The array containing this node. + */ + private Node[] getNodeArr(Node node) { + reviewStatusBus.register(node); + return new Node[]{node}; + } @Override protected Node[] createNodesForKey(String acountTypeName) { if (Account.Type.CREDIT_CARD.getTypeName().equals(acountTypeName)) { - return new Node[]{new CreditCardNumberAccountTypeNode()}; + return getNodeArr(new CreditCardNumberAccountTypeNode()); } else { try { Account.Type accountType = skCase.getCommunicationsManager().getAccountType(acountTypeName); - return new Node[]{new DefaultAccountTypeNode(accountType)}; + return getNodeArr(new DefaultAccountTypeNode(accountType)); } catch (TskCoreException ex) { LOGGER.log(Level.SEVERE, "Error getting display name for account type. ", ex); } @@ -509,11 +567,14 @@ final public class Accounts implements AutopsyVisitableItem { * no special behavior. */ final public class DefaultAccountTypeNode extends DisplayableItemNode { - + private final Account.Type accountType; + private DefaultAccountTypeNode(Account.Type accountType) { super(Children.create(new DefaultAccountFactory(accountType), true), Lookups.singleton(accountType)); - setName(accountType.getDisplayName()); - this.setIconBaseWithExtension(getIconFilePath(accountType)); //NON-NLS + this.accountType = accountType; + String iconPath = getIconFilePath(accountType); + this.setIconBaseWithExtension(iconPath != null && iconPath.charAt(0) == '/' ? iconPath.substring(1) : iconPath); //NON-NLS + updateName(); } @Override @@ -530,6 +591,24 @@ final public class Accounts implements AutopsyVisitableItem { public String getItemType() { return getClass().getName(); } + + + @Subscribe + void handleReviewStatusChange(ReviewStatusChangeEvent event) { + updateName(); + } + + @Subscribe + void handleDataAdded(ModuleDataEvent event) { + updateName(); + } + + /** + * Gets the latest counts for the account type and then updates the name. + */ + public void updateName() { + setName(String.format("%s (%d)", accountType.getDisplayName(), accountTypeResults.getCount(accountType.getTypeName()))); + } } /** @@ -659,6 +738,23 @@ final public class Accounts implements AutopsyVisitableItem { setName(Account.Type.CREDIT_CARD.getDisplayName()); this.setIconBaseWithExtension("org/sleuthkit/autopsy/images/credit-cards.png"); //NON-NLS } + + /** + * Gets the latest counts for the account type and then updates the name. + */ + public void updateName() { + setName(String.format("%s (%d)", Account.Type.CREDIT_CARD.getDisplayName(), accountTypeResults.getCount(Account.Type.CREDIT_CARD.getTypeName()))); + } + + @Subscribe + void handleReviewStatusChange(ReviewStatusChangeEvent event) { + updateName(); + } + + @Subscribe + void handleDataAdded(ModuleDataEvent event) { + updateName(); + } @Override public boolean isLeafTypeNode() { diff --git a/Core/src/org/sleuthkit/autopsy/filequery/DiscoveryUiUtils.java b/Core/src/org/sleuthkit/autopsy/filequery/DiscoveryUiUtils.java index c9ad380a32..0a8f93fa92 100644 --- a/Core/src/org/sleuthkit/autopsy/filequery/DiscoveryUiUtils.java +++ b/Core/src/org/sleuthkit/autopsy/filequery/DiscoveryUiUtils.java @@ -34,8 +34,8 @@ final class DiscoveryUiUtils { private static final int ICON_SIZE = 16; private static final String RED_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/red-circle-exclamation.png"; private static final String YELLOW_CIRCLE_ICON_PATH = "org/sleuthkit/autopsy/images/yellow-circle-yield.png"; - private static final String DELETE_ICON_PATH = "/org/sleuthkit/autopsy/images/file-icon-deleted.png"; - private static final String UNSUPPORTED_DOC_PATH = "/org/sleuthkit/autopsy/images/image-extraction-not-supported.png"; + private static final String DELETE_ICON_PATH = "org/sleuthkit/autopsy/images/file-icon-deleted.png"; + private static final String UNSUPPORTED_DOC_PATH = "org/sleuthkit/autopsy/images/image-extraction-not-supported.png"; private static final ImageIcon INTERESTING_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(YELLOW_CIRCLE_ICON_PATH, false)); private static final ImageIcon NOTABLE_SCORE_ICON = new ImageIcon(ImageUtilities.loadImage(RED_CIRCLE_ICON_PATH, false)); private static final ImageIcon DELETED_ICON = new ImageIcon(ImageUtilities.loadImage(DELETE_ICON_PATH, false)); diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/AbstractWaypointFetcher.java b/Core/src/org/sleuthkit/autopsy/geolocation/AbstractWaypointFetcher.java index bc56968f99..e923d6dbd6 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/AbstractWaypointFetcher.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/AbstractWaypointFetcher.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.logging.Level; +import javafx.util.Pair; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException; @@ -75,11 +76,11 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter * * @param mapWaypoints List of filtered MapWaypoints. */ - abstract void handleFilteredWaypointSet(Set mapWaypoints); + abstract void handleFilteredWaypointSet(Set mapWaypoints, List> tracks); @Override public void process(List waypoints) { - List tracks = null; + List tracks = new ArrayList<>(); if (filters.getArtifactTypes().contains(ARTIFACT_TYPE.TSK_GPS_TRACK)) { try { tracks = Track.getTracks(Case.getCurrentCase().getSleuthkitCase(), filters.getDataSources()); @@ -87,11 +88,15 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter logger.log(Level.WARNING, "Exception thrown while retrieving list of Tracks", ex); } } + Pair, List>> waypointsAndTracks = createWaypointList(waypoints, tracks); + + final Set pointSet = MapWaypoint.getWaypoints(waypointsAndTracks.getKey()); + final List> trackSets = new ArrayList<>(); + for (List t : waypointsAndTracks.getValue()) { + trackSets.add(MapWaypoint.getWaypoints(t)); + } - List completeList = createWaypointList(waypoints, tracks); - final Set pointSet = MapWaypoint.getWaypoints(completeList); - - handleFilteredWaypointSet(pointSet); + handleFilteredWaypointSet(pointSet, trackSets); } /** @@ -104,8 +109,9 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter * @return A list of waypoints including the tracks based on the current * filters. */ - private List createWaypointList(List waypoints, List tracks) { + private Pair, List>> createWaypointList(List waypoints, List tracks) { final List completeList = new ArrayList<>(); + List> filteredTracks = new ArrayList<>(); if (tracks != null) { Long timeRangeEnd; @@ -117,19 +123,22 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter timeRangeStart = timeRangeEnd - (86400 * filters.getMostRecentNumDays()); completeList.addAll(getWaypointsInRange(timeRangeStart, timeRangeEnd, waypoints)); - completeList.addAll(getTracksInRange(timeRangeStart, timeRangeEnd, tracks)); - + + filteredTracks = getTracksInRange(timeRangeStart, timeRangeEnd, tracks); + for (List filteredTrack : filteredTracks) { + completeList.addAll(filteredTrack); + } } else { completeList.addAll(waypoints); for (Track track : tracks) { completeList.addAll(track.getPath()); + filteredTracks.add(track.getPath()); } } } else { completeList.addAll(waypoints); } - - return completeList; + return new Pair<>(completeList, filteredTracks); } /** @@ -158,31 +167,30 @@ abstract class AbstractWaypointFetcher implements WaypointBuilder.WaypointFilter } /** - * Return a list of waypoints from the given tracks that fall into for - * tracks that fall into the given time range. The track start time will - * used for determining if the whole track falls into the range. + * Return a list of lists of waypoints from the given tracks that fall into + * the given time range. The track start time will used for determining if + * the whole track falls into the range. * * @param timeRangeStart start timestamp of range (seconds from java epoch) * @param timeRangeEnd start timestamp of range (seconds from java epoch) * @param tracks Track list. * - * @return A list of waypoints that that belong to tracks that fall into the - * time range. + * @return A list of lists of waypoints corresponding to belong to tracks + * that exist within the time range. */ - private List getTracksInRange(Long timeRangeStart, Long timeRangeEnd, List tracks) { - List completeList = new ArrayList<>(); + private List> getTracksInRange(Long timeRangeStart, Long timeRangeEnd, List tracks) { + List> ret = new ArrayList<>(); if (tracks != null) { for (Track track : tracks) { Long trackTime = track.getStartTime(); if ((trackTime == null && filters.showWaypointsWithoutTimeStamp()) || (trackTime != null && (trackTime >= timeRangeStart && trackTime <= timeRangeEnd))) { - - completeList.addAll(track.getPath()); + ret.add(track.getPath()); } } } - return completeList; + return ret; } /** diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java index da8e14a65b..ae387c7847 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/GeolocationTopComponent.java @@ -330,7 +330,7 @@ public final class GeolocationTopComponent extends TopComponent { * * @param waypointList */ - void addWaypointsToMap(Set waypointList) { + void addWaypointsToMap(Set waypointList, List> tracks) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { @@ -347,6 +347,8 @@ public final class GeolocationTopComponent extends TopComponent { } mapPanel.clearWaypoints(); mapPanel.setWaypoints(waypointList); + mapPanel.setTracks(tracks); + mapPanel.initializePainter(); setWaypointLoading(false); geoFilterPanel.setEnabled(true); } @@ -499,8 +501,8 @@ public final class GeolocationTopComponent extends TopComponent { } @Override - void handleFilteredWaypointSet(Set mapWaypoints) { - addWaypointsToMap(mapWaypoints); + void handleFilteredWaypointSet(Set mapWaypoints, List> tracks) { + addWaypointsToMap(mapWaypoints, tracks); } } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java index 46600f2c86..44de21ff81 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapPanel.java @@ -19,11 +19,13 @@ package org.sleuthkit.autopsy.geolocation; import java.awt.AlphaComposite; +import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; +import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; @@ -71,6 +73,9 @@ import org.sleuthkit.autopsy.geolocation.datamodel.GeoLocationDataException; import org.sleuthkit.datamodel.TskCoreException; import javax.imageio.ImageIO; import javax.swing.SwingUtilities; +import org.jxmapviewer.painter.CompoundPainter; +import org.jxmapviewer.painter.Painter; +import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; /** * The map panel. This panel contains the jxmapviewer MapViewer @@ -84,6 +89,7 @@ final public class MapPanel extends javax.swing.JPanel { private boolean zoomChanging; private KdTree waypointTree; private Set waypointSet; + private List> tracks = new ArrayList<>(); private Popup currentPopup; private final PopupFactory popupFactory; @@ -96,6 +102,7 @@ final public class MapPanel extends javax.swing.JPanel { private BufferedImage transparentWaypointImage; private MapWaypoint currentlySelectedWaypoint; + private Set currentlySelectedTrack; /** * Creates new form MapPanel @@ -204,6 +211,10 @@ final public class MapPanel extends javax.swing.JPanel { mapViewer.setCenterPosition(new GeoPosition(0, 0)); + initializePainter(); + } + + void initializePainter() { // Basic painters for the way points. WaypointPainter waypointPainter = new WaypointPainter() { @Override @@ -217,7 +228,12 @@ final public class MapPanel extends javax.swing.JPanel { }; waypointPainter.setRenderer(new MapWaypointRenderer()); - mapViewer.setOverlayPainter(waypointPainter); + ArrayList> painters = new ArrayList<>(); + painters.add(new MapTrackRenderer(tracks)); + painters.add(waypointPainter); + + CompoundPainter compoundPainter = new CompoundPainter<>(painters); + mapViewer.setOverlayPainter(compoundPainter); } /** @@ -306,6 +322,15 @@ final public class MapPanel extends javax.swing.JPanel { mapViewer.repaint(); } + /** + * Stores the given List of tracks from which to draw paths later + * + * @param tracks + */ + void setTracks(List> tracks) { + this.tracks = tracks; + } + /** * Set the current zoom level. * @@ -324,6 +349,7 @@ final public class MapPanel extends javax.swing.JPanel { void clearWaypoints() { waypointTree = null; currentlySelectedWaypoint = null; + currentlySelectedTrack = null; if (currentPopup != null) { currentPopup.hide(); } @@ -661,9 +687,17 @@ final public class MapPanel extends javax.swing.JPanel { if (!evt.isPopupTrigger() && SwingUtilities.isLeftMouseButton(evt)) { List waypoints = findClosestWaypoint(evt.getPoint()); if (waypoints.size() > 0) { - currentlySelectedWaypoint = waypoints.get(0); + MapWaypoint selection = waypoints.get(0); + currentlySelectedWaypoint = selection; + for (Set track : tracks) { + if (track.contains(selection)) { + currentlySelectedTrack = track; + break; + } + } } else { currentlySelectedWaypoint = null; + currentlySelectedTrack = null; } showDetailsPopup(); } @@ -691,18 +725,18 @@ final public class MapPanel extends javax.swing.JPanel { */ private class MapWaypointRenderer implements WaypointRenderer { - private final Map imageCache = new HashMap<>(); + private final Map dotImageCache = new HashMap<>(); + private final Map waypointImageCache = new HashMap<>(); /** * - * @param waypoint the waypoint for which to get the color - * @param currentlySelectedWaypoint the waypoint that is currently - * selected + * @param waypoint the waypoint for which to get the color selected * @return the color that this waypoint should be rendered */ - private Color getColor(MapWaypoint waypoint, MapWaypoint currentlySelectedWaypoint) { + private Color getColor(MapWaypoint waypoint) { Color baseColor = waypoint.getColor(); - if (waypoint.equals(currentlySelectedWaypoint)) { + if (waypoint.equals(currentlySelectedWaypoint) + || (currentlySelectedTrack != null && currentlySelectedTrack.contains(waypoint))) { // Highlight this waypoint since it is selected return Color.YELLOW; } else { @@ -710,10 +744,32 @@ final public class MapPanel extends javax.swing.JPanel { } } + /** + * Creates a dot image with the specified color + * + * @param color the color of the new image + * @return the new dot image + */ + private BufferedImage createTrackDotImage(Color color) { + int w = 10; + int h = 10; + + BufferedImage ret = new BufferedImage(w + 2, h + 2, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = ret.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setColor(color); + g.fillOval(1, 1, w, h); + g.setColor(Color.BLACK); + g.setStroke(new BasicStroke(1)); + g.drawOval(1, 1, w, h); + g.dispose(); + return ret; + } + /** * Creates a waypoint image with the specified color * - * @param color the color of the new waypoint image + * @param color the color of the new image * @return the new waypoint image */ private BufferedImage createWaypointImage(Color color) { @@ -723,6 +779,7 @@ final public class MapPanel extends javax.swing.JPanel { BufferedImage ret = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); Graphics2D g = ret.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.drawImage(whiteWaypointImage, 0, 0, null); g.setComposite(AlphaComposite.SrcIn); g.setColor(color); @@ -734,22 +791,88 @@ final public class MapPanel extends javax.swing.JPanel { } @Override - public void paintWaypoint(Graphics2D gd, JXMapViewer jxmv, MapWaypoint waypoint) { - Color color = getColor(waypoint, currentlySelectedWaypoint); - - // Store computed images in cache for later use - BufferedImage image = imageCache.computeIfAbsent(color, k -> { - return createWaypointImage(color); - }); - - Point2D point = jxmv.getTileFactory().geoToPixel(waypoint.getPosition(), jxmv.getZoom()); - + public void paintWaypoint(Graphics2D g, JXMapViewer map, MapWaypoint waypoint) { + Color color = getColor(waypoint); + BufferedImage image; + int artifactType = waypoint.getArtifactTypeID(); + Point2D point = map.getTileFactory().geoToPixel(waypoint.getPosition(), map.getZoom()); int x = (int) point.getX(); int y = (int) point.getY(); - gd = (Graphics2D) gd.create(); - gd.drawImage(image, x - image.getWidth() / 2, y - image.getHeight(), null); - gd.dispose(); + if (artifactType == ARTIFACT_TYPE.TSK_GPS_TRACKPOINT.getTypeID() + || artifactType == ARTIFACT_TYPE.TSK_GPS_TRACK.getTypeID() + || artifactType == ARTIFACT_TYPE.TSK_GPS_ROUTE.getTypeID()) { + image = dotImageCache.computeIfAbsent(color, k -> { + return createTrackDotImage(color); + }); + // Center the dot on the GPS coordinate + y -= image.getHeight() / 2; + } else { + image = waypointImageCache.computeIfAbsent(color, k -> { + return createWaypointImage(color); + }); + // Align the bottom of the pin with the GPS coordinate + y -= image.getHeight(); + } + // Center image horizontally on image + x -= image.getWidth() / 2; + + Graphics2D g2d = (Graphics2D) g.create(); + g2d.drawImage(image, x, y, null); + g2d.dispose(); + } + } + + /** + * Renderer for map track routes + */ + private class MapTrackRenderer implements Painter { + + private final List> tracks; + + MapTrackRenderer(List> tracks) { + this.tracks = tracks; + } + + private void drawRoute(Set track, Graphics2D g, JXMapViewer map) { + int lastX = 0; + int lastY = 0; + + boolean first = true; + + for (MapWaypoint wp : track) { + Point2D p = map.getTileFactory().geoToPixel(wp.getPosition(), map.getZoom()); + int thisX = (int) p.getX(); + int thisY = (int) p.getY(); + + if (first) { + first = false; + } else { + g.drawLine(lastX, lastY, thisX, thisY); + } + + lastX = thisX; + lastY = thisY; + } + } + + @Override + public void paint(Graphics2D g, JXMapViewer map, int w, int h) { + Graphics2D g2d = (Graphics2D) g.create(); + + Rectangle bounds = map.getViewportBounds(); + g2d.translate(-bounds.x, -bounds.y); + + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + g2d.setColor(Color.BLACK); + g2d.setStroke(new BasicStroke(2)); + + for (Set track : tracks) { + drawRoute(track, g2d, map); + } + + g2d.dispose(); } } } diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java index f9e4ed86ee..f7673338b1 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/MapWaypoint.java @@ -84,7 +84,7 @@ final class MapWaypoint extends KdTree.XYZPoint implements org.jxmapviewer.viewe private final GeoPosition position; /** - * Returns a list of of MapWaypoint objects for the given list of + * Returns a list of MapWaypoint objects for the given list of * datamodel.Waypoint objects. * * @param dmWaypoints @@ -199,6 +199,13 @@ final class MapWaypoint extends KdTree.XYZPoint implements org.jxmapviewer.viewe return getFormattedDetails(dataModelWaypoint); } + /** + * Returns the artifact type for this waypoint's data source + */ + int getArtifactTypeID() { + return dataModelWaypoint.getArtifact().getArtifactTypeID(); + } + /** * Returns a list of JMenuItems for the waypoint. The list list may contain * nulls which should be removed or replaced with JSeparators. diff --git a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java index 1f8f25452e..11ff0107dd 100755 --- a/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java +++ b/Core/src/org/sleuthkit/autopsy/geolocation/datamodel/WaypointBuilder.java @@ -98,7 +98,7 @@ public final class WaypointBuilder { * * @param wwaypoints This of waypoints. */ - void process(List wwaypoints); + void process(List waypoints); } /** diff --git a/InternalPythonModules/GPX_Module/GPX_Parser_Module.py b/InternalPythonModules/GPX_Module/GPX_Parser_Module.py index 3d202a963b..0e4face2bc 100644 --- a/InternalPythonModules/GPX_Module/GPX_Parser_Module.py +++ b/InternalPythonModules/GPX_Module/GPX_Parser_Module.py @@ -44,7 +44,6 @@ from org.sleuthkit.datamodel.blackboardutils.attributes.GeoTrackPoints import Tr from org.sleuthkit.autopsy.datamodel import ContentUtils from org.sleuthkit.autopsy.ingest import IngestModule from org.sleuthkit.autopsy.ingest.IngestModule import IngestModuleException -from org.sleuthkit.autopsy.ingest import DataSourceIngestModule from org.sleuthkit.autopsy.ingest import FileIngestModule from org.sleuthkit.autopsy.ingest import IngestModuleFactoryAdapter from org.sleuthkit.autopsy.ingest import IngestMessage @@ -60,12 +59,17 @@ import gpxpy import gpxpy.gpx import gpxpy.parser +# to get a random filename to prevent race conditions +import uuid + # Factory that defines the name and details of the module and allows Autopsy # to create instances of the modules that will do the analysis. -class GPXParserDataSourceIngestModuleFactory(IngestModuleFactoryAdapter): + + +class GPXParserFileIngestModuleFactory(IngestModuleFactoryAdapter): moduleName = "GPX Parser" - + def getModuleDisplayName(self): return self.moduleName @@ -75,158 +79,176 @@ class GPXParserDataSourceIngestModuleFactory(IngestModuleFactoryAdapter): def getModuleVersionNumber(self): return "1.2" - def isDataSourceIngestModuleFactory(self): + def isFileIngestModuleFactory(self): return True - def createDataSourceIngestModule(self, ingestOptions): - return GPXParserDataSourceIngestModule() + def createFileIngestModule(self, ingestOptions): + return GPXParserFileIngestModule() - -# Data Source-level ingest module. One gets created per data source. -class GPXParserDataSourceIngestModule(DataSourceIngestModule): - logger = Logger.getLogger(GPXParserDataSourceIngestModuleFactory.moduleName) +# File level ingest module. +class GPXParserFileIngestModule(FileIngestModule): + + logger = Logger.getLogger( + GPXParserFileIngestModuleFactory.moduleName) writeDebugMsgs = False def log(self, level, msg): - self.logger.logp(level, self.__class__.__name__, inspect.stack()[1][3], msg) + self.logger.logp(level, self.__class__.__name__, + inspect.stack()[1][3], msg) def __init__(self): self.context = None - - # Where any setup and configuration is done. - def startUp(self, context): - self.context = context - - # Where the analysis is done. - def process(self, dataSource, progressBar): - - # We don't know how much work there is yet. - progressBar.switchToIndeterminate() - - # Get the case database and its blackboard. - skCase = Case.getCurrentCase().getSleuthkitCase() - blackboard = skCase.getBlackboard() - - # Get any files with a .gpx extension. - # It would perhaps be better to get these files by MIME type instead. - # RC: It would also be better if this were a file level ingest module so it could process files extracted from archives. - fileManager = Case.getCurrentCase().getServices().getFileManager() - files = fileManager.findFiles(dataSource, "%.gpx") - - # Update the progress bar now that we know how much work there is to do. - numFiles = len(files) - if self.writeDebugMsgs: self.log(Level.INFO, "Found " + str(numFiles) + " GPX files") - progressBar.switchToDeterminate(numFiles) + self.fileCount = 0 # Get the module name, it will be needed for adding attributes - moduleName = GPXParserDataSourceIngestModuleFactory.moduleName + self.moduleName = GPXParserFileIngestModuleFactory.moduleName - # Check if a folder for this module is present in the case Temp directory. + # Get the case database and its blackboard. + self.skCase = Case.getCurrentCase().getSleuthkitCase() + self.blackboard = self.skCase.getBlackboard() + + # Check if a folder for this module is present in the case Temp directory. # If not, create it. - dirName = os.path.join(Case.getCurrentCase().getTempDirectory(), "GPX_Parser_Module") + self.dirName = os.path.join( + Case.getCurrentCase().getTempDirectory(), "GPX_Parser_Module") try: - os.stat(dirName) + os.stat(self.dirName) except: - os.mkdir(dirName) + os.mkdir(self.dirName) - # Create a temp file name. It appears that we cannot close and delete + # Where any setup and configuration is done. + + def startUp(self, context): + self.context = context + self.fileCount = 0 + + # Where the file analysis is done. + def process(self, file): + if not file.getName().lower().endswith(".gpx"): + return IngestModule.ProcessResult.OK + + # Create a temp file name. It appears that we cannot close and delete # this file, but we can overwrite it for each file we need to process. - fileName = os.path.join(dirName, "tmp.gpx") - - fileCount = 0; - for file in files: + fileName = os.path.join(self.dirName, uuid.uuid4().hex + ".gpx") - # Create a GeoArtifactsHelper for this file. - geoArtifactHelper = GeoArtifactsHelper(skCase, moduleName, None, file) - - # Check if the user pressed cancel while we were busy. - if self.context.isJobCancelled(): - return IngestModule.ProcessResult.OK + # Create a GeoArtifactsHelper for this file. + geoArtifactHelper = GeoArtifactsHelper( + self.skCase, self.moduleName, None, file) - if self.writeDebugMsgs: self.log(Level.INFO, "Processing " + file.getUniquePath() + " (objID = " + str(file.getId()) + ")") - fileCount += 1 + if self.writeDebugMsgs: + self.log(Level.INFO, "Processing " + file.getUniquePath() + + " (objID = " + str(file.getId()) + ")") - # Write the file so that it can be parsed by gpxpy. - localFile = File(fileName) - ContentUtils.writeToFile(file, localFile) + # Write the file so that it can be parsed by gpxpy. + localFile = File(fileName) + ContentUtils.writeToFile(file, localFile) - # Send the file to gpxpy for parsing. - gpxfile = open(fileName) - try: - gpx = gpxpy.parse(gpxfile) - if self.writeDebugMsgs: self.log(Level.INFO, "Parsed " + file.getUniquePath() + " (objID = " + str(file.getId()) + ")") - except Exception as e: - self.log(Level.WARNING, "Error parsing file " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + str(e)) - continue - - if gpx: - if self.writeDebugMsgs: self.log(Level.INFO, "Processing tracks from " + file.getUniquePath() + " (objID = " + str(file.getId()) + ")") - for track in gpx.tracks: - for segment in track.segments: - geoPointList = GeoTrackPoints() - for point in segment.points: + # Send the file to gpxpy for parsing. + gpxfile = open(fileName) + try: + gpx = gpxpy.parse(gpxfile) + if self.writeDebugMsgs: + self.log(Level.INFO, "Parsed " + file.getUniquePath() + + " (objID = " + str(file.getId()) + ")") + except Exception as e: + self.log(Level.WARNING, "Error parsing file " + file.getUniquePath() + + " (objID = " + str(file.getId()) + "):" + str(e)) + return IngestModule.ProcessResult.ERROR - elevation = 0 - if point.elevation != None: - elevation = point.elevation - - timeStamp = 0 - try: - if (point.time != None): - timeStamp = long(time.mktime(point.time.timetuple())) - except Exception as e: - self.log(Level.WARNING, "Error getting track timestamp from " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + str(e)) + if gpx: + if self.writeDebugMsgs: + self.log(Level.INFO, "Processing tracks from " + + file.getUniquePath() + " (objID = " + str(file.getId()) + ")") - geoPointList.addPoint(TrackPoint(point.latitude, point.longitude, elevation, None, 0, 0, 0, timeStamp)) - + for track in gpx.tracks: + for segment in track.segments: + geoPointList = GeoTrackPoints() + for point in segment.points: + + elevation = 0 + if point.elevation != None: + elevation = point.elevation + + timeStamp = 0 try: - geoArtifactHelper.addTrack("Track", geoPointList, None) - except Blackboard.BlackboardException as e: - self.log(Level.SEVERE, "Error posting GPS track artifact for " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage()) - except TskCoreException as e: - self.log(Level.SEVERE, "Error creating GPS track artifact for " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage()) - - if self.writeDebugMsgs: self.log(Level.INFO, "Processing waypoints from " + file.getUniquePath() + " (objID = " + str(file.getId()) + ")") - for waypoint in gpx.waypoints: - + if (point.time != None): + timeStamp = long(time.mktime( + point.time.timetuple())) + except Exception as e: + self.log(Level.WARNING, "Error getting track timestamp from " + + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + str(e)) + + geoPointList.addPoint(TrackPoint( + point.latitude, point.longitude, elevation, None, 0, 0, 0, timeStamp)) + try: - art = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK) - - attributes = ArrayList() - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(), moduleName, waypoint.latitude)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID(), moduleName, waypoint.longitude)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG.getTypeID(), moduleName, "Waypoint")) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME.getTypeID(), moduleName, waypoint.name)) - attributes.add(BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID(), moduleName, "GPXParser")) - art.addAttributes(attributes) - - blackboard.postArtifact(art, moduleName) - + geoArtifactHelper.addTrack("Track", geoPointList, None) except Blackboard.BlackboardException as e: - self.log(Level.SEVERE, "Error posting GPS bookmark artifact for " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage()) + self.log(Level.SEVERE, "Error posting GPS track artifact for " + + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage()) except TskCoreException as e: - self.log(Level.SEVERE, "Error creating GPS bookmark artifact for " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage()) + self.log(Level.SEVERE, "Error creating GPS track artifact for " + + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage()) - if self.writeDebugMsgs: self.log(Level.INFO, "Processing routes from " + file.getUniquePath() + " (objID = " + str(file.getId()) + ")") - for route in gpx.routes: + if self.writeDebugMsgs: + self.log(Level.INFO, "Processing waypoints from " + + file.getUniquePath() + " (objID = " + str(file.getId()) + ")") - geoWaypoints = GeoWaypoints() + for waypoint in gpx.waypoints: - for point in route.points: - geoWaypoints.addPoint(Waypoint(point.latitude, point.longitude, point.elevation, point.name)) - - try: - geoArtifactHelper.addRoute(None, None, geoWaypoints, None) - except Blackboard.BlackboardException as e: - self.log("Error posting GPS route artifact for " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage()) - except TskCoreException as e: - self.log(Level.SEVERE, "Error creating GPS route artifact for " + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage()) - - # Update the progress bar. - progressBar.progress(fileCount) + try: + art = file.newArtifact( + BlackboardArtifact.ARTIFACT_TYPE.TSK_GPS_BOOKMARK) - # Post a message to the ingest messages inbox. - message = IngestMessage.createMessage(IngestMessage.MessageType.DATA, moduleName, "Processed %d files" % fileCount) - IngestServices.getInstance().postMessage(message) - return IngestModule.ProcessResult.OK; + attributes = ArrayList() + attributes.add(BlackboardAttribute( + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(), self.moduleName, waypoint.latitude)) + attributes.add(BlackboardAttribute( + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID(), self.moduleName, waypoint.longitude)) + attributes.add(BlackboardAttribute( + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_FLAG.getTypeID(), self.moduleName, "Waypoint")) + attributes.add(BlackboardAttribute( + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME.getTypeID(), self.moduleName, waypoint.name)) + attributes.add(BlackboardAttribute( + BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID(), self.moduleName, "GPXParser")) + art.addAttributes(attributes) + + self.blackboard.postArtifact(art, self.moduleName) + + except Blackboard.BlackboardException as e: + self.log(Level.SEVERE, "Error posting GPS bookmark artifact for " + + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage()) + except TskCoreException as e: + self.log(Level.SEVERE, "Error creating GPS bookmark artifact for " + + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage()) + + if self.writeDebugMsgs: + self.log(Level.INFO, "Processing routes from " + + file.getUniquePath() + " (objID = " + str(file.getId()) + ")") + + for route in gpx.routes: + + geoWaypoints = GeoWaypoints() + + for point in route.points: + geoWaypoints.addPoint( + Waypoint(point.latitude, point.longitude, point.elevation, point.name)) + + try: + geoArtifactHelper.addRoute(None, None, geoWaypoints, None) + except Blackboard.BlackboardException as e: + self.log("Error posting GPS route artifact for " + file.getUniquePath() + + " (objID = " + str(file.getId()) + "):" + e.getMessage()) + except TskCoreException as e: + self.log(Level.SEVERE, "Error creating GPS route artifact for " + + file.getUniquePath() + " (objID = " + str(file.getId()) + "):" + e.getMessage()) + + self.fileCount += 1 + return IngestModule.ProcessResult.OK + + def shutDown(self): + message = IngestMessage.createMessage( + IngestMessage.MessageType.DATA, GPXParserFileIngestModuleFactory.moduleName, + str(self.fileCount) + " files found") + ingestServices = IngestServices.getInstance().postMessage(message) diff --git a/README.txt b/README.txt index 3ed2dda963..7917b66382 100644 --- a/README.txt +++ b/README.txt @@ -23,8 +23,6 @@ There is no need for manual installation of additional dependencies if the Windo If you want the Japanese localized version, you must have the Japanese language pack (http://support.microsoft.com/kb/972813) installed and the default locale set to JA. (http://windows.microsoft.com/en-us/windows/change-system-locale#1TC=windows-7). -Refer to the KNOWN_ISSUES.txt file for known bugs that could cause investigation problems. - SUPPORT @@ -151,4 +149,4 @@ WebHostingHub Glyphs - License: http://creativecommons.org/licenses/by/3.0/ Splashy Icons (free as in free) -- Web page: http://splashyfish.com/icons/ \ No newline at end of file +- Web page: http://splashyfish.com/icons/ diff --git a/thirdparty/IcePDF 6.2.2/batik-awt-util-1.6.jar b/thirdparty/IcePDF 6.2.2/batik-awt-util-1.6.jar new file mode 100755 index 0000000000..b62ac15866 Binary files /dev/null and b/thirdparty/IcePDF 6.2.2/batik-awt-util-1.6.jar differ diff --git a/thirdparty/IcePDF 6.2.2/batik-dom-1.6.jar b/thirdparty/IcePDF 6.2.2/batik-dom-1.6.jar new file mode 100755 index 0000000000..bf144ab070 Binary files /dev/null and b/thirdparty/IcePDF 6.2.2/batik-dom-1.6.jar differ diff --git a/thirdparty/IcePDF 6.2.2/batik-svg-dom-1.6.jar b/thirdparty/IcePDF 6.2.2/batik-svg-dom-1.6.jar new file mode 100755 index 0000000000..c8970fbb4d Binary files /dev/null and b/thirdparty/IcePDF 6.2.2/batik-svg-dom-1.6.jar differ diff --git a/thirdparty/IcePDF 6.2.2/batik-svggen-1.6.jar b/thirdparty/IcePDF 6.2.2/batik-svggen-1.6.jar new file mode 100755 index 0000000000..819f2da07a Binary files /dev/null and b/thirdparty/IcePDF 6.2.2/batik-svggen-1.6.jar differ diff --git a/thirdparty/IcePDF 6.2.2/batik-util-1.6.jar b/thirdparty/IcePDF 6.2.2/batik-util-1.6.jar new file mode 100755 index 0000000000..7550b4802e Binary files /dev/null and b/thirdparty/IcePDF 6.2.2/batik-util-1.6.jar differ diff --git a/thirdparty/IcePDF 6.2.2/batik-xml-1.6.jar b/thirdparty/IcePDF 6.2.2/batik-xml-1.6.jar new file mode 100755 index 0000000000..1b915a0503 Binary files /dev/null and b/thirdparty/IcePDF 6.2.2/batik-xml-1.6.jar differ diff --git a/thirdparty/IcePDF 6.2.2/bcpkix-jdk15on-1.54.jar b/thirdparty/IcePDF 6.2.2/bcpkix-jdk15on-1.54.jar new file mode 100755 index 0000000000..86f7f0be19 Binary files /dev/null and b/thirdparty/IcePDF 6.2.2/bcpkix-jdk15on-1.54.jar differ diff --git a/thirdparty/IcePDF 6.2.2/bcprov-ext-jdk15on-1.54.jar b/thirdparty/IcePDF 6.2.2/bcprov-ext-jdk15on-1.54.jar new file mode 100755 index 0000000000..f31c1c1c45 Binary files /dev/null and b/thirdparty/IcePDF 6.2.2/bcprov-ext-jdk15on-1.54.jar differ diff --git a/thirdparty/IcePDF 6.2.2/bcprov-jdk15on-1.54.jar b/thirdparty/IcePDF 6.2.2/bcprov-jdk15on-1.54.jar new file mode 100755 index 0000000000..bd95185ae8 Binary files /dev/null and b/thirdparty/IcePDF 6.2.2/bcprov-jdk15on-1.54.jar differ diff --git a/thirdparty/IcePDF 6.2.2/icepdf-core-6.2.2.jar b/thirdparty/IcePDF 6.2.2/icepdf-core-6.2.2.jar new file mode 100755 index 0000000000..b5555280e1 Binary files /dev/null and b/thirdparty/IcePDF 6.2.2/icepdf-core-6.2.2.jar differ diff --git a/thirdparty/IcePDF 6.2.2/icepdf-viewer-6.2.2.jar b/thirdparty/IcePDF 6.2.2/icepdf-viewer-6.2.2.jar new file mode 100755 index 0000000000..d58fcc9de3 Binary files /dev/null and b/thirdparty/IcePDF 6.2.2/icepdf-viewer-6.2.2.jar differ diff --git a/thirdparty/IcePDF 6.2.2/jai_core-1.1.3.jar b/thirdparty/IcePDF 6.2.2/jai_core-1.1.3.jar new file mode 100755 index 0000000000..b29b8eed5b Binary files /dev/null and b/thirdparty/IcePDF 6.2.2/jai_core-1.1.3.jar differ diff --git a/thirdparty/IcePDF 6.2.2/jai_imageio-1.1.jar b/thirdparty/IcePDF 6.2.2/jai_imageio-1.1.jar new file mode 100755 index 0000000000..571aa199fa Binary files /dev/null and b/thirdparty/IcePDF 6.2.2/jai_imageio-1.1.jar differ