diff --git a/Core/ivy.xml b/Core/ivy.xml
index 9dfd0fcf85..54ed532feb 100644
--- a/Core/ivy.xml
+++ b/Core/ivy.xml
@@ -45,6 +45,9 @@
+
+
+
diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties
index 955079d04a..8209da0221 100644
--- a/Core/nbproject/project.properties
+++ b/Core/nbproject/project.properties
@@ -111,6 +111,10 @@ file.reference.grpc-context-1.19.0.jar=release/modules/ext/grpc-context-1.19.0.j
file.reference.opencensus-api-0.19.2.jar=release/modules/ext/opencensus-api-0.19.2.jar
file.reference.opencensus-contrib-http-util-0.19.2.jar=release/modules/ext/opencensus-contrib-http-util-0.19.2.jar
file.reference.threetenbp-1.3.3.jar=release/modules/ext/threetenbp-1.3.3.jar
+file.reference.okhttp-2.7.5-javadoc.jar=release/modules/ext/okhttp-2.7.5-javadoc.jar
+file.reference.okhttp-2.7.5-sources.jar=release/modules/ext/okhttp-2.7.5-sources.jar
+file.reference.okhttp-2.7.5.jar=release/modules/ext/okhttp-2.7.5.jar
+file.reference.okio-1.6.0.jar=release/modules/ext/okio-1.6.0.jar
javac.source=1.8
javac.compilerargs=-Xlint -Xlint:-serial
license.file=../LICENSE-2.0.txt
diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml
index 1ac3c9d2fd..3dbc3069e3 100644
--- a/Core/nbproject/project.xml
+++ b/Core/nbproject/project.xml
@@ -773,6 +773,14 @@
ext/google-api-services-translate-v2-rev20170525-1.27.0.jar
release/modules/ext/google-api-services-translate-v2-rev20170525-1.27.0.jar
+
+ ext/okhttp-2.7.5.jar
+ release/modules/ext/okhttp-2.7.5.jar
+
+
+ ext/okio-1.6.0.jar
+ release/modules/ext/okio-1.6.0.jar
+
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties-MERGED
index 508e04b76a..fe998b0e57 100755
--- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties-MERGED
@@ -66,6 +66,7 @@ DataSourceSummaryDialog.window.title=Data Sources Summary
DataSourceSummaryNode.column.dataSourceName.header=Data Source Name
DataSourceSummaryNode.column.files.header=Files
DataSourceSummaryNode.column.results.header=Results
+DataSourceSummaryNode.column.status.header=Ingest Status
DataSourceSummaryNode.column.tags.header=Tags
DataSourceSummaryNode.column.type.header=Type
DataSourceSummaryNode.viewDataSourceAction.text=Go to Data Source
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java
index e3b9fa0e93..ac09010de1 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceBrowser.java
@@ -37,8 +37,10 @@ import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.datasourcesummary.DataSourceSummaryNode.DataSourceSummaryEntryNode;
import static javax.swing.SwingConstants.RIGHT;
+import javax.swing.SwingUtilities;
import javax.swing.table.TableColumn;
import org.sleuthkit.datamodel.DataSource;
+import org.sleuthkit.datamodel.IngestJobInfo;
import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException;
@@ -50,9 +52,10 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana
private static final long serialVersionUID = 1L;
private static final Logger logger = Logger.getLogger(DataSourceBrowser.class.getName());
- private static final int COUNT_COLUMN_WIDTH = 25;
- private static final int USAGE_COLUMN_WIDTH = 120;
- private static final int DATA_SOURCE_COLUMN_WIDTH = 325;
+ private static final int COUNT_COLUMN_WIDTH = 20;
+ private static final int INGEST_STATUS_WIDTH = 50;
+ private static final int USAGE_COLUMN_WIDTH = 110;
+ private static final int DATA_SOURCE_COLUMN_WIDTH = 280;
private final Outline outline;
private final org.openide.explorer.view.OutlineView outlineView;
private final ExplorerManager explorerManager;
@@ -69,6 +72,7 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana
outlineView = new org.openide.explorer.view.OutlineView();
this.setVisible(true);
outlineView.setPropertyColumns(
+ Bundle.DataSourceSummaryNode_column_status_header(), Bundle.DataSourceSummaryNode_column_status_header(),
Bundle.DataSourceSummaryNode_column_type_header(), Bundle.DataSourceSummaryNode_column_type_header(),
Bundle.DataSourceSummaryNode_column_files_header(), Bundle.DataSourceSummaryNode_column_files_header(),
Bundle.DataSourceSummaryNode_column_results_header(), Bundle.DataSourceSummaryNode_column_results_header(),
@@ -90,6 +94,8 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana
column.setPreferredWidth(COUNT_COLUMN_WIDTH);
} else if (column.getHeaderValue().toString().equals(Bundle.DataSourceSummaryNode_column_type_header())) {
column.setPreferredWidth(USAGE_COLUMN_WIDTH);
+ } else if (column.getHeaderValue().toString().equals(Bundle.DataSourceSummaryNode_column_status_header())) {
+ column.setPreferredWidth(INGEST_STATUS_WIDTH);
} else {
column.setPreferredWidth(DATA_SOURCE_COLUMN_WIDTH);
}
@@ -182,6 +188,47 @@ final class DataSourceBrowser extends javax.swing.JPanel implements ExplorerMana
return null;
}
+ /**
+ * Update the DataSourceBrowser to display up to date status information for
+ * the data sources.
+ *
+ * @param dataSourceId the ID of the data source which should be updated
+ * @param newStatus the new status which the data source should have
+ */
+ void refresh(long dataSourceId, IngestJobInfo.IngestJobStatusType newStatus) {
+
+ //attempt to update the status of any datasources that had status which was STARTED
+ for (DataSourceSummary summary : dataSourceSummaryList) {
+ if (summary.getDataSource().getId() == dataSourceId) {
+ summary.setIngestStatus(newStatus);
+ }
+ }
+ //figure out which nodes were previously selected
+ Node[] selectedNodes = explorerManager.getSelectedNodes();
+ SwingUtilities.invokeLater(() -> {
+ explorerManager.setRootContext(new DataSourceSummaryNode(dataSourceSummaryList));
+ List nodesToSelect = new ArrayList<>();
+ for (Node node : explorerManager.getRootContext().getChildren().getNodes()) {
+ if (node instanceof DataSourceSummaryEntryNode) {
+ //there should only be one selected node as multi-select is disabled
+ for (Node selectedNode : selectedNodes) {
+ if (((DataSourceSummaryEntryNode) node).getDataSource().equals(((DataSourceSummaryEntryNode) selectedNode).getDataSource())) {
+ nodesToSelect.add(node);
+ }
+ }
+ }
+ }
+ //reselect the previously selected Nodes
+ try {
+ explorerManager.setSelectedNodes(nodesToSelect.toArray(new Node[nodesToSelect.size()]));
+ } catch (PropertyVetoException ex) {
+ logger.log(Level.WARNING, "Error selecting previously selected nodes", ex);
+ }
+
+ });
+
+ }
+
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java
index 1aa55d95b5..390dce1afe 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummary.java
@@ -18,15 +18,27 @@
*/
package org.sleuthkit.autopsy.casemodule.datasourcesummary;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.sleuthkit.autopsy.casemodule.Case;
+import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
+import org.sleuthkit.datamodel.CaseDbAccessManager;
import org.sleuthkit.datamodel.DataSource;
+import org.sleuthkit.datamodel.IngestJobInfo.IngestJobStatusType;
+import org.sleuthkit.datamodel.TskCoreException;
/**
* Wrapper object for a DataSource and the information associated with it.
*
*/
class DataSourceSummary {
-
+
+ private static final Logger logger = Logger.getLogger(DataSourceSummary.class.getName());
+ private static final String INGEST_JOB_STATUS_QUERY = "status_id FROM ingest_jobs WHERE obj_id=";
private final DataSource dataSource;
+ private IngestJobStatusType status = null;
private final String type;
private final long filesCount;
private final long resultsCount;
@@ -45,12 +57,26 @@ class DataSourceSummary {
*/
DataSourceSummary(DataSource dSource, String typeValue, Long numberOfFiles, Long numberOfResults, Long numberOfTags) {
dataSource = dSource;
+ getStatusFromDatabase();
type = typeValue == null ? "" : typeValue;
filesCount = numberOfFiles == null ? 0 : numberOfFiles;
resultsCount = numberOfResults == null ? 0 : numberOfResults;
tagsCount = numberOfTags == null ? 0 : numberOfTags;
}
+ /**
+ * Get the status of the ingest job from the case database
+ */
+ private void getStatusFromDatabase() {
+ try {
+ IngestJobQueryCallback callback = new IngestJobQueryCallback();
+ Case.getCurrentCaseThrows().getSleuthkitCase().getCaseDbAccessManager().select(INGEST_JOB_STATUS_QUERY + dataSource.getId(), callback);
+ status = callback.getStatus();
+ } catch (NoCurrentCaseException | TskCoreException ex) {
+ logger.log(Level.WARNING, "Error getting status for data source from case database", ex);
+ }
+ }
+
/**
* Get the DataSource
*
@@ -60,6 +86,16 @@ class DataSourceSummary {
return dataSource;
}
+ /**
+ * Manually set the ingest job status
+ *
+ * @param ingestStatus the status which the ingest job should have
+ * currently, null to display empty string
+ */
+ void setIngestStatus(IngestJobStatusType ingestStatus) {
+ status = ingestStatus;
+ }
+
/**
* Get the type of this DataSource
*
@@ -87,6 +123,16 @@ class DataSourceSummary {
return resultsCount;
}
+ /**
+ * Get the IngestJobStatusType associated with this data source.
+ *
+ * @return the IngestJobStatusType associated with this data source. Can be
+ * null if the IngestJobStatusType is not STARTED or COMPLETED.
+ */
+ IngestJobStatusType getIngestStatus() {
+ return status;
+ }
+
/**
* Get the number of tagged content objects in this DataSource
*
@@ -95,4 +141,40 @@ class DataSourceSummary {
long getTagsCount() {
return tagsCount;
}
+
+ /**
+ * Callback to parse result set, getting the status to be associated with
+ * this data source
+ */
+ class IngestJobQueryCallback implements CaseDbAccessManager.CaseDbAccessQueryCallback {
+
+ private IngestJobStatusType jobStatus = null;
+
+ @Override
+ public void process(ResultSet rs) {
+ try {
+ while (rs.next()) {
+ IngestJobStatusType currentStatus = IngestJobStatusType.fromID(rs.getInt("status_id"));
+ if (currentStatus == IngestJobStatusType.COMPLETED) {
+ jobStatus = currentStatus;
+ } else if (currentStatus == IngestJobStatusType.STARTED) {
+ jobStatus = currentStatus;
+ return;
+ }
+ }
+ } catch (SQLException ex) {
+ logger.log(Level.WARNING, "Error getting status for ingest job", ex);
+ }
+ }
+
+ /**
+ * Get the status which was determined for this callback
+ *
+ * @return the status of the data source which was queried for
+ */
+ IngestJobStatusType getStatus() {
+ return jobStatus;
+ }
+
+ }
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java
index 3fd86c8de8..1535cbefe0 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryDialog.java
@@ -19,14 +19,18 @@
package org.sleuthkit.autopsy.casemodule.datasourcesummary;
import java.awt.Frame;
+import java.beans.PropertyChangeEvent;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
-import java.util.logging.Logger;
import javax.swing.event.ListSelectionEvent;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.IngestJobInfoPanel;
+import org.sleuthkit.autopsy.ingest.IngestManager;
+import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent;
+import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent.Reason;
import org.sleuthkit.datamodel.DataSource;
+import org.sleuthkit.datamodel.IngestJobInfo;
/**
* Dialog for displaying the Data Sources Summary information
@@ -38,7 +42,6 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
private final DataSourceSummaryDetailsPanel detailsPanel;
private final DataSourceBrowser dataSourcesPanel;
private final IngestJobInfoPanel ingestHistoryPanel;
- private static final Logger logger = Logger.getLogger(DataSourceSummaryDialog.class.getName());
/**
* Creates new form DataSourceSummaryDialog for displaying a summary of the
@@ -73,6 +76,17 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
this.repaint();
}
});
+ //add listener to refresh jobs with Started status when they complete
+ IngestManager.getInstance().addIngestJobEventListener((PropertyChangeEvent evt) -> {
+ if (evt instanceof DataSourceAnalysisCompletedEvent) {
+ DataSourceAnalysisCompletedEvent dsEvent = (DataSourceAnalysisCompletedEvent) evt;
+ if (dsEvent.getResult() == Reason.ANALYSIS_COMPLETED) {
+ dataSourcesPanel.refresh(dsEvent.getDataSource().getId(), IngestJobInfo.IngestJobStatusType.COMPLETED);
+ } else if (dsEvent.getResult() == Reason.ANALYSIS_CANCELLED) {
+ dataSourcesPanel.refresh(dsEvent.getDataSource().getId(), null);
+ }
+ }
+ });
this.pack();
}
diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java
index 8323061a66..0411d94bb2 100644
--- a/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java
+++ b/Core/src/org/sleuthkit/autopsy/casemodule/datasourcesummary/DataSourceSummaryNode.java
@@ -109,6 +109,7 @@ final class DataSourceSummaryNode extends AbstractNode {
static final class DataSourceSummaryEntryNode extends AbstractNode {
private final DataSource dataSource;
+ private final String status;
private final String type;
private final long filesCount;
private final long resultsCount;
@@ -124,6 +125,7 @@ final class DataSourceSummaryNode extends AbstractNode {
DataSourceSummaryEntryNode(DataSourceSummary dataSourceSummary) {
super(Children.LEAF);
dataSource = dataSourceSummary.getDataSource();
+ status = dataSourceSummary.getIngestStatus() == null ? "" : dataSourceSummary.getIngestStatus().getDisplayName();
type = dataSourceSummary.getType();
filesCount = dataSourceSummary.getFilesCount();
resultsCount = dataSourceSummary.getResultsCount();
@@ -143,6 +145,7 @@ final class DataSourceSummaryNode extends AbstractNode {
}
@Messages({"DataSourceSummaryNode.column.dataSourceName.header=Data Source Name",
+ "DataSourceSummaryNode.column.status.header=Ingest Status",
"DataSourceSummaryNode.column.type.header=Type",
"DataSourceSummaryNode.column.files.header=Files",
"DataSourceSummaryNode.column.results.header=Results",
@@ -157,6 +160,7 @@ final class DataSourceSummaryNode extends AbstractNode {
}
sheetSet.put(new NodeProperty<>(Bundle.DataSourceSummaryNode_column_dataSourceName_header(), Bundle.DataSourceSummaryNode_column_dataSourceName_header(), Bundle.DataSourceSummaryNode_column_dataSourceName_header(),
dataSource));
+ sheetSet.put(new NodeProperty<>(Bundle.DataSourceSummaryNode_column_status_header(), Bundle.DataSourceSummaryNode_column_status_header(), Bundle.DataSourceSummaryNode_column_status_header(), status));
sheetSet.put(new NodeProperty<>(Bundle.DataSourceSummaryNode_column_type_header(), Bundle.DataSourceSummaryNode_column_type_header(), Bundle.DataSourceSummaryNode_column_type_header(),
type));
sheetSet.put(new NodeProperty<>(Bundle.DataSourceSummaryNode_column_files_header(), Bundle.DataSourceSummaryNode_column_files_header(), Bundle.DataSourceSummaryNode_column_files_header(),
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties
index 652f8781ba..fd3bbc833f 100644
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties
@@ -83,7 +83,7 @@ MediaViewImagePanel.zoomResetButton.text=Reset
MediaViewImagePanel.zoomTextField.text=
MediaViewImagePanel.rotationTextField.text=
MediaViewImagePanel.rotateLeftButton.toolTipText=
-HtmlPanel.showImagesToggleButton.text=Show Images
+HtmlPanel.showImagesToggleButton.text=Download Images
MediaPlayerPanel.audioSlider.toolTipText=
MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume
MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED
index 236fddfada..7b8fa6ad6b 100755
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/Bundle.properties-MERGED
@@ -22,7 +22,7 @@ GstVideoPanel.cannotProcFile.err=The media player cannot process this file.
GstVideoPanel.noOpenCase.errMsg=No open case available.
Html_text_display_error=The HTML text cannot be displayed, it may not be correctly formed HTML.
HtmlPanel_showImagesToggleButton_hide=Hide Images
-HtmlPanel_showImagesToggleButton_show=Show Images
+HtmlPanel_showImagesToggleButton_show=Download Images
HtmlViewer_file_error=This file is missing or unreadable.
MediaFileViewer.initGst.gstException.msg=Error initializing gstreamer for audio/video viewing and frame extraction capabilities. Video and audio viewing will be disabled.
GstVideoPanel.setupVideo.infoLabel.text=Playback of deleted videos is not supported, use an external player.
@@ -145,7 +145,7 @@ MediaViewImagePanel.zoomResetButton.text=Reset
MediaViewImagePanel.zoomTextField.text=
MediaViewImagePanel.rotationTextField.text=
MediaViewImagePanel.rotateLeftButton.toolTipText=
-HtmlPanel.showImagesToggleButton.text=Show Images
+HtmlPanel.showImagesToggleButton.text=Download Images
MediaPlayerPanel.audioSlider.toolTipText=
MediaPlayerPanel.VolumeIcon.text=\ \ \ \ \ Volume
MediaPlayerPanel.progressLabel.text=00:00:00/00:00:00
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.form b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.form
index 97b3c67f72..407f0e6ec7 100755
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.form
@@ -18,7 +18,7 @@
-
+
diff --git a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java
index f915b8d5a6..b53b1dc258 100755
--- a/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/contentviewers/HtmlPanel.java
@@ -18,6 +18,11 @@
*/
package org.sleuthkit.autopsy.contentviewers;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
@@ -25,8 +30,9 @@ import javafx.concurrent.Worker;
import javafx.scene.web.WebView;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
-import org.jsoup.Jsoup;
-import org.jsoup.nodes.Node;
+import net.htmlparser.jericho.Attribute;
+import net.htmlparser.jericho.OutputDocument;
+import net.htmlparser.jericho.Source;
import org.openide.util.NbBundle.Messages;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
@@ -38,6 +44,7 @@ import org.w3c.dom.events.EventTarget;
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
final class HtmlPanel extends javax.swing.JPanel {
+ private static final Logger logger = Logger.getLogger(HtmlPanel.class.getName());
private static final long serialVersionUID = 1L;
private static final String TEXT_TYPE = "text/plain";
private final JFXPanel jfxPanel = new JFXPanel();
@@ -92,26 +99,76 @@ final class HtmlPanel extends javax.swing.JPanel {
}
/**
- * Cleans out input HTML string
+ * Cleans out input HTML string so it will not access resources over the internet
*
* @param htmlInString The HTML string to cleanse
*
* @return The cleansed HTML String
*/
private String cleanseHTML(String htmlInString) {
- org.jsoup.nodes.Document doc = Jsoup.parse(htmlInString);
- // remove all 'img' tags.
- doc.select("img").stream().forEach(Node::remove);
- // remove all 'span' tags, these are often images which are ads
- doc.select("span").stream().forEach(Node::remove);
- return doc.html();
+ String returnString = "";
+ try {
+ Source source = new Source(new StringReader(htmlInString));
+ OutputDocument document = new OutputDocument(source);
+ //remove background images
+ source.getAllTags().stream().filter((tag) -> (tag.toString().contains("background-image"))).forEachOrdered((tag) -> {
+ document.remove(tag);
+ });
+ //remove images
+ source.getAllElements("img").forEach((element) -> {
+ document.remove(element.getAllTags());
+ });
+ //remove frames
+ source.getAllElements("frame").forEach((element) -> {
+ document.remove(element.getAllTags());
+ });
+ //remove iframes
+ source.getAllElements("iframe").forEach((element) -> {
+ document.remove(element.getAllTags());
+ });
+ //remove pictures
+ source.getAllElements("picture").forEach((element) -> {
+ document.remove(element.getAllTags());
+ });
+ //remove svg
+ source.getAllElements("svg").forEach((element) -> {
+ document.remove(element.getAllTags());
+ });
+ //remove audio
+ source.getAllElements("audio").forEach((element) -> {
+ document.remove(element.getAllTags());
+ });
+ //remove video
+ source.getAllElements("video").forEach((element) -> {
+ document.remove(element.getAllTags());
+ });
+ //remove tracks
+ source.getAllElements("track").forEach((element) -> {
+ document.remove(element.getAllTags());
+ });
+ //remove embeded external elements
+ source.getAllElements("embed").forEach((element) -> {
+ document.remove(element.getAllTags());
+ });
+ //remove linked elements
+ source.getAllElements("link").forEach((element) -> {
+ document.remove(element.getAllTags());
+ });
+ //remove other URI elements such as input boxes
+ List attributesToRemove = source.getURIAttributes();
+ document.remove(attributesToRemove);
+ returnString = document.toString();
+ } catch (IOException ex) {
+ logger.log(Level.WARNING, "Unable to read html for cleaning out URI elements with Jericho", ex);
+ }
+ return returnString;
}
/**
* Refresh the panel to reflect the current show/hide images setting.
*/
@Messages({
- "HtmlPanel_showImagesToggleButton_show=Show Images",
+ "HtmlPanel_showImagesToggleButton_show=Download Images",
"HtmlPanel_showImagesToggleButton_hide=Hide Images",
"Html_text_display_error=The HTML text cannot be displayed, it may not be correctly formed HTML.",})
private void refresh() {
@@ -164,7 +221,7 @@ final class HtmlPanel extends javax.swing.JPanel {
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(showImagesToggleButton)
- .addGap(0, 95, Short.MAX_VALUE))
+ .addGap(0, 75, Short.MAX_VALUE))
.addComponent(htmlJPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
);
layout.setVerticalGroup(
diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java
new file mode 100644
index 0000000000..2b234f71d3
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslator.java
@@ -0,0 +1,175 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2019 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.texttranslation.translators;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.squareup.okhttp.MediaType;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Request;
+import com.squareup.okhttp.RequestBody;
+import com.squareup.okhttp.Response;
+import java.awt.Component;
+import java.io.IOException;
+import org.openide.util.NbBundle.Messages;
+import org.openide.util.lookup.ServiceProvider;
+import org.sleuthkit.autopsy.texttranslation.TextTranslator;
+import org.sleuthkit.autopsy.texttranslation.TranslationException;
+
+/**
+ * Translates text by making HTTP requests to Bing Translator. This requires a
+ * valid subscription key for a Microsoft Azure account.
+ */
+@ServiceProvider(service = TextTranslator.class)
+public class BingTranslator implements TextTranslator {
+
+ //The target language follows the to= in the string below. You can include multiple target
+ //languages separated by commas. A full list of supported languages is here:
+ //https://docs.microsoft.com/en-us/azure/cognitive-services/translator/language-support
+ private static final String BASE_URL = "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&to=";
+ private static final int MAX_STRING_LENGTH = 5000;
+ private final BingTranslatorSettingsPanel settingsPanel;
+ private final BingTranslatorSettings settings = new BingTranslatorSettings();
+ // This sends messages to Microsoft.
+ private final OkHttpClient CLIENT = new OkHttpClient();
+
+ /**
+ * Create a Bing Translator
+ */
+ public BingTranslator() {
+ settingsPanel = new BingTranslatorSettingsPanel(settings.getAuthenticationKey(), settings.getTargetLanguageCode());
+ }
+
+ /**
+ * Get the tranlationurl for the specified language code
+ *
+ *
+ *
+ * @param languageCode language code for language to translate to
+ *
+ * @return a string representation of the url to request translation from
+ */
+ static String getTranlatorUrl(String languageCode) {
+ return BASE_URL + languageCode;
+ }
+
+ /**
+ * Converts an input text to the JSON format required by Bing Translator,
+ * posts it to Microsoft, and returns the JSON text response.
+ *
+ * @param string The input text to be translated.
+ *
+ * @return The translation response as a JSON string
+ *
+ * @throws IOException if the request could not be executed due to
+ * cancellation, a connectivity problem or timeout.
+ */
+ public String postTranslationRequest(String string) throws IOException {
+ MediaType mediaType = MediaType.parse("application/json");
+
+ JsonArray jsonArray = new JsonArray();
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("Text", string);
+ jsonArray.add(jsonObject);
+ String bodyString = jsonArray.toString();
+
+ RequestBody body = RequestBody.create(mediaType,
+ bodyString);
+ Request request = new Request.Builder()
+ .url(getTranlatorUrl(settings.getTargetLanguageCode())).post(body)
+ .addHeader("Ocp-Apim-Subscription-Key", settings.getAuthenticationKey())
+ .addHeader("Content-type", "application/json").build();
+ Response response = CLIENT.newCall(request).execute();
+ return response.body().string();
+ }
+
+ @Override
+ public String translate(String string) throws TranslationException {
+ if (settings.getAuthenticationKey() == null || settings.getAuthenticationKey().isEmpty()) {
+ throw new TranslationException("Bing Translator has not been configured, authentication key needs to be specified");
+ }
+ String toTranslate = string.trim();
+ //Translates some text into English, without specifying the source langauge.
+
+ // HTML files were producing lots of white space at the end
+ //Google Translate required us to replace (\r\n|\n) with
+ //but Bing Translator doesn not have that requirement.
+ //The free account has a maximum file size. If you have a paid account,
+ //you probably still want to limit file size to prevent accidentally
+ //translating very large documents.
+ if (toTranslate.length() > MAX_STRING_LENGTH) {
+ toTranslate = toTranslate.substring(0, MAX_STRING_LENGTH);
+ }
+
+ try {
+ String response = postTranslationRequest(toTranslate);
+ return parseJSONResponse(response);
+ } catch (IOException | TranslationException ex) {
+ throw new TranslationException("Exception while attempting to translate using BingTranslator", ex);
+ }
+ }
+
+ @Messages({"BingTranslator.name.text=Bing Translator"})
+ @Override
+ public String getName() {
+ return Bundle.BingTranslator_name_text();
+ }
+
+ @Override
+ public Component getComponent() {
+ return settingsPanel;
+ }
+
+ @Override
+ public void saveSettings() {
+ settings.setAuthenticationKey(settingsPanel.getAuthenticationKey());
+ settings.setTargetLanguageCode(settingsPanel.getTargetLanguageCode());
+ settings.saveSettings();
+ }
+
+ /**
+ * Parse the response to get the translated text
+ *
+ * @param json_text the json which was received as a response to a
+ * translation request
+ *
+ * @return the translated text
+ *
+ * @throws TranslationException
+ */
+ private String parseJSONResponse(String json_text) throws TranslationException {
+ /*
+ * Here is an example of the text we get from Bing when input is "gato",
+ * the Spanish word for cat: [ { "detectedLanguage": { "language": "es",
+ * "score": 1.0 }, "translations": [ { "text": "cat", "to": "en" } ] } ]
+ */
+ JsonParser parser = new JsonParser();
+ try {
+ JsonArray responses = parser.parse(json_text).getAsJsonArray();
+ //As far as I know, there's always exactly one item in the array.
+ JsonObject response0 = responses.get(0).getAsJsonObject();
+ JsonArray translations = response0.getAsJsonArray("translations");
+ JsonObject translation0 = translations.get(0).getAsJsonObject();
+ return translation0.get("text").getAsString();
+ } catch (IllegalStateException | ClassCastException | NullPointerException | IndexOutOfBoundsException e) {
+ throw new TranslationException("JSON text does not match Bing Translator scheme: " + e);
+ }
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettings.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettings.java
new file mode 100644
index 0000000000..d00ed61f77
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettings.java
@@ -0,0 +1,115 @@
+/*
+ * Autopsy
+ *
+ * Copyright 2019 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.texttranslation.translators;
+
+import org.apache.commons.lang3.StringUtils;
+import org.sleuthkit.autopsy.coreutils.ModuleSettings;
+
+/**
+ * Class to handle the settings associated with the BingTranslator
+ */
+public final class BingTranslatorSettings {
+
+ private static final String AUTHENTICATION_KEY = "Credentials";
+ private static final String BING_TRANSLATE_NAME = "BingTranslate";
+ private static final String DEFAULT_AUTHENTICATION = "";
+ private static final String DEFAULT_TARGET_LANGUAGE = "en";
+ private static final String TARGET_LANGUAGE_CODE_KEY = "TargetLanguageCode";
+ private String authenticationKey;
+ private String targetLanguageCode;
+
+ /**
+ * Construct a new BingTranslatorSettings object
+ */
+ BingTranslatorSettings() {
+ loadSettings();
+ }
+
+ /**
+ * Get the Authentication key to be used for the Microsoft translation
+ * service
+ *
+ * @return the Authentication key for the service
+ */
+ String getAuthenticationKey() {
+ return authenticationKey;
+ }
+
+ /**
+ * Set the Authentication key to be used for the Microsoft translation
+ * service
+ *
+ * @param authKey the Authentication key for the service
+ */
+ void setAuthenticationKey(String authKey) {
+ authenticationKey = authKey;
+ }
+
+ /**
+ * Load the settings into memory from their on disk storage
+ */
+ void loadSettings() {
+ if (!ModuleSettings.configExists(BING_TRANSLATE_NAME)) {
+ ModuleSettings.makeConfigFile(BING_TRANSLATE_NAME);
+ }
+ if (ModuleSettings.settingExists(BING_TRANSLATE_NAME, AUTHENTICATION_KEY)) {
+ authenticationKey = ModuleSettings.getConfigSetting(BING_TRANSLATE_NAME, AUTHENTICATION_KEY);
+ }
+ if (authenticationKey == null || StringUtils.isBlank(authenticationKey)) {
+ authenticationKey = DEFAULT_AUTHENTICATION;
+ }
+ if (ModuleSettings.settingExists(BING_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY)) {
+ targetLanguageCode = ModuleSettings.getConfigSetting(BING_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY);
+ }
+ if (targetLanguageCode == null || StringUtils.isBlank(targetLanguageCode)) {
+ targetLanguageCode = DEFAULT_TARGET_LANGUAGE;
+ }
+ }
+
+ /**
+ * Get the target language code
+ *
+ * @return the code used to identify the target language
+ */
+ String getTargetLanguageCode() {
+ return targetLanguageCode;
+ }
+
+ /**
+ * Set the target language code. If a blank code is specified it sets the
+ * default code instead.
+ *
+ * @param code the target language code to set
+ */
+ void setTargetLanguageCode(String code) {
+ if (StringUtils.isBlank(code)) {
+ targetLanguageCode = DEFAULT_TARGET_LANGUAGE;
+ } else {
+ targetLanguageCode = code;
+ }
+ }
+
+ /**
+ * Save the setting from memory to their location on disk
+ */
+ void saveSettings() {
+ ModuleSettings.setConfigSetting(BING_TRANSLATE_NAME, AUTHENTICATION_KEY, authenticationKey);
+ ModuleSettings.setConfigSetting(BING_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY, targetLanguageCode);
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form
new file mode 100644
index 0000000000..cd855ac419
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.form
@@ -0,0 +1,172 @@
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java
new file mode 100644
index 0000000000..1be932873c
--- /dev/null
+++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorSettingsPanel.java
@@ -0,0 +1,314 @@
+/*
+ * Autopsy
+ *
+ * Copyright 2019 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.texttranslation.translators;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonObject;
+import com.squareup.okhttp.MediaType;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Request;
+import com.squareup.okhttp.RequestBody;
+import com.squareup.okhttp.Response;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import org.apache.commons.lang3.StringUtils;
+import org.openide.util.NbBundle.Messages;
+
+/**
+ * Settings panel for the BingTranslator
+ */
+public class BingTranslatorSettingsPanel extends javax.swing.JPanel {
+
+ private static final Logger logger = Logger.getLogger(BingTranslatorSettingsPanel.class.getName());
+ private static final long serialVersionUID = 1L;
+ private static final String GET_TARGET_LANGUAGES_URL = "https://api.cognitive.microsofttranslator.com/languages?api-version=3.0&scope=translation";
+ private static final String DEFUALT_TEST_STRING = "traducción exitoso"; //spanish which should translate to something along the lines of "successful translation"
+ private String targetLanguageCode = "";
+
+ /**
+ * Creates new form BingTranslatorSettingsPanel
+ */
+ public BingTranslatorSettingsPanel(String authenticationKey, String code) {
+ initComponents();
+ authenticationKeyField.setText(authenticationKey);
+ authenticationKeyField.getDocument().addDocumentListener(new DocumentListener() {
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ firePropertyChange("SettingChanged", true, false);
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ firePropertyChange("SettingChanged", true, false);
+ }
+
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ firePropertyChange("SettingChanged", true, false);
+ }
+
+ });
+ populateComboBox();
+ selectLanguageByCode(code);
+ targetLanguageCode = code;
+ }
+
+ /**
+ * Populate the target language combo box with available target languages
+ */
+ @Messages({"BingTranslatorSettingsPanel.warning.targetLanguageFailure=Unable to get list of target languages or parse the result that was received"})
+ private void populateComboBox() {
+ Request get_request = new Request.Builder()
+ .url(GET_TARGET_LANGUAGES_URL).build();
+ try {
+ Response response = new OkHttpClient().newCall(get_request).execute();
+ JsonParser parser = new JsonParser();
+ String responseBody = response.body().string();
+ JsonElement elementBody = parser.parse(responseBody);
+ JsonObject asObject = elementBody.getAsJsonObject();
+ JsonElement translationElement = asObject.get("translation");
+ JsonObject responses = translationElement.getAsJsonObject();
+ responses.entrySet().forEach((entry) -> {
+ targetLanguageComboBox.addItem(new LanguageWrapper(entry.getKey(), entry.getValue().getAsJsonObject().get("name").getAsString()));
+ });
+ targetLanguageComboBox.setEnabled(true);
+ } catch (IOException | IllegalStateException | ClassCastException | NullPointerException | IndexOutOfBoundsException ex) {
+ logger.log(Level.SEVERE, Bundle.BingTranslatorSettingsPanel_warning_targetLanguageFailure(), ex);
+ warningLabel.setText(Bundle.BingTranslatorSettingsPanel_warning_targetLanguageFailure());
+ targetLanguageComboBox.setEnabled(false);
+ }
+
+ }
+
+ /**
+ * Given a language code select the corresponding language in the combo box
+ * if it is present
+ *
+ * @param code language code such as "en" for English
+ */
+ private void selectLanguageByCode(String code) {
+ for (int i = 0; i < targetLanguageComboBox.getModel().getSize(); i++) {
+ if (targetLanguageComboBox.getModel().getElementAt(i).getLanguageCode().equals(code)) {
+ targetLanguageComboBox.setSelectedIndex(i);
+ break;
+ }
+ }
+ }
+
+ /**
+ * This method is called from within the constructor to initialize the form.
+ * WARNING: Do NOT modify this code. The content of this method is always
+ * regenerated by the Form Editor.
+ */
+ @SuppressWarnings("unchecked")
+ // //GEN-BEGIN:initComponents
+ private void initComponents() {
+
+ authenticationKeyField = new javax.swing.JTextField();
+ warningLabel = new javax.swing.JLabel();
+ testButton = new javax.swing.JButton();
+ targetLanguageLabel = new javax.swing.JLabel();
+ targetLanguageComboBox = new javax.swing.JComboBox<>();
+ testUntranslatedTextField = new javax.swing.JTextField();
+ untranslatedLabel = new javax.swing.JLabel();
+ resultLabel = new javax.swing.JLabel();
+ testResultValueLabel = new javax.swing.JLabel();
+ authenticationKeyLabel = new javax.swing.JLabel();
+
+ authenticationKeyField.setToolTipText(org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.authenticationKeyField.toolTipText")); // NOI18N
+
+ warningLabel.setForeground(new java.awt.Color(255, 0, 0));
+ org.openide.awt.Mnemonics.setLocalizedText(warningLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.warningLabel.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(testButton, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.testButton.text")); // NOI18N
+ testButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ testButtonActionPerformed(evt);
+ }
+ });
+
+ org.openide.awt.Mnemonics.setLocalizedText(targetLanguageLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.targetLanguageLabel.text")); // NOI18N
+
+ targetLanguageComboBox.setEnabled(false);
+ targetLanguageComboBox.addItemListener(new java.awt.event.ItemListener() {
+ public void itemStateChanged(java.awt.event.ItemEvent evt) {
+ targetLanguageComboBoxSelected(evt);
+ }
+ });
+
+ testUntranslatedTextField.setText(DEFUALT_TEST_STRING);
+
+ org.openide.awt.Mnemonics.setLocalizedText(untranslatedLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.untranslatedLabel.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(resultLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.resultLabel.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(testResultValueLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.testResultValueLabel.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(authenticationKeyLabel, org.openide.util.NbBundle.getMessage(BingTranslatorSettingsPanel.class, "BingTranslatorSettingsPanel.authenticationKeyLabel.text")); // NOI18N
+
+ javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
+ this.setLayout(layout);
+ layout.setHorizontalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
+ .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 551, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(targetLanguageLabel)
+ .addGap(18, 18, 18)
+ .addComponent(targetLanguageComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 192, javax.swing.GroupLayout.PREFERRED_SIZE)))
+ .addGap(0, 0, Short.MAX_VALUE))
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(authenticationKeyLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(authenticationKeyField, javax.swing.GroupLayout.PREFERRED_SIZE, 486, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGap(0, 20, Short.MAX_VALUE))
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(testButton, javax.swing.GroupLayout.PREFERRED_SIZE, 79, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGap(25, 25, 25)
+ .addComponent(untranslatedLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(testUntranslatedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 140, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(resultLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(testResultValueLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
+ .addContainerGap())))
+ );
+ layout.setVerticalGroup(
+ layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addContainerGap()
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(authenticationKeyField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(authenticationKeyLabel))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(targetLanguageLabel)
+ .addComponent(targetLanguageComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(testButton)
+ .addComponent(testUntranslatedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(untranslatedLabel)
+ .addComponent(resultLabel)
+ .addComponent(testResultValueLabel))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addContainerGap())
+ );
+ }// //GEN-END:initComponents
+
+ @Messages({"BingTranslatorSettingsPanel.warning.invalidKey=Invalid translation authentication key"})
+ private void testButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_testButtonActionPerformed
+ if (testTranslationSetup()) {
+ warningLabel.setText("");
+ } else {
+ warningLabel.setText(Bundle.BingTranslatorSettingsPanel_warning_invalidKey());
+ }
+ }//GEN-LAST:event_testButtonActionPerformed
+
+ private void targetLanguageComboBoxSelected(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_targetLanguageComboBoxSelected
+ String selectedCode = ((LanguageWrapper) targetLanguageComboBox.getSelectedItem()).getLanguageCode();
+ if (!StringUtils.isBlank(selectedCode) && !selectedCode.equals(targetLanguageCode)) {
+ targetLanguageCode = selectedCode;
+ firePropertyChange("SettingChanged", true, false);
+ }
+ }//GEN-LAST:event_targetLanguageComboBoxSelected
+
+ // Variables declaration - do not modify//GEN-BEGIN:variables
+ private javax.swing.JTextField authenticationKeyField;
+ private javax.swing.JLabel authenticationKeyLabel;
+ private javax.swing.JLabel resultLabel;
+ private javax.swing.JComboBox targetLanguageComboBox;
+ private javax.swing.JLabel targetLanguageLabel;
+ private javax.swing.JButton testButton;
+ private javax.swing.JLabel testResultValueLabel;
+ private javax.swing.JTextField testUntranslatedTextField;
+ private javax.swing.JLabel untranslatedLabel;
+ private javax.swing.JLabel warningLabel;
+ // End of variables declaration//GEN-END:variables
+
+ /**
+ * Attempts to translate the text specified in the Untranslated field using
+ * the settings currently specified but not necessarily saved
+ *
+ * @return true if the translation was able to be performed, false otherwise
+ *
+ */
+ private boolean testTranslationSetup() {
+ testResultValueLabel.setText("");
+ MediaType mediaType = MediaType.parse("application/json");
+ JsonArray jsonArray = new JsonArray();
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("Text", testUntranslatedTextField.getText());
+ jsonArray.add(jsonObject);
+ String bodyString = jsonArray.toString();
+
+ RequestBody body = RequestBody.create(mediaType,
+ bodyString);
+ Request request = new Request.Builder()
+ .url(BingTranslator.getTranlatorUrl(targetLanguageCode)).post(body)
+ .addHeader("Ocp-Apim-Subscription-Key", authenticationKeyField.getText())
+ .addHeader("Content-type", "application/json").build();
+ try {
+ Response response = new OkHttpClient().newCall(request).execute();
+ JsonParser parser = new JsonParser();
+ JsonArray responses = parser.parse(response.body().string()).getAsJsonArray();
+ //As far as I know, there's always exactly one item in the array.
+ JsonObject response0 = responses.get(0).getAsJsonObject();
+ JsonArray translations = response0.getAsJsonArray("translations");
+ JsonObject translation0 = translations.get(0).getAsJsonObject();
+ testResultValueLabel.setText(translation0.get("text").getAsString());
+ return true;
+ } catch (IOException | IllegalStateException | ClassCastException | NullPointerException | IndexOutOfBoundsException e) {
+ logger.log(Level.WARNING, "Test of Bing Translator failed due to exception", e);
+ return false;
+ }
+ }
+
+ /**
+ * Get the currently set authentication key to be used for the Microsoft
+ * translation service
+ *
+ * @return the authentication key specified in the textarea
+ */
+ String getAuthenticationKey() {
+ return authenticationKeyField.getText();
+ }
+
+ /**
+ * Get the currently selected target language code
+ *
+ * @return the target language code of the language selected in the combobox
+ */
+ String getTargetLanguageCode() {
+ return targetLanguageCode;
+ }
+}
diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties
index 56782cdf33..0021118e14 100644
--- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties
+++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties
@@ -1,4 +1,15 @@
GoogleTranslatorSettingsPanel.browseButton.text=Browse
-GoogleTranslatorSettingsPanel.credentialsLabel.text=Credentials:
+GoogleTranslatorSettingsPanel.credentialsLabel.text=Credentials Path:
GoogleTranslatorSettingsPanel.warningLabel.text=
GoogleTranslatorSettingsPanel.targetLanguageLabel.text=Target Language:
+BingTranslatorSettingsPanel.testButton.text=Test
+BingTranslatorSettingsPanel.testResultValueLabel.text=
+BingTranslatorSettingsPanel.resultLabel.text=Result:
+BingTranslatorSettingsPanel.untranslatedLabel.text=Untranslated:
+BingTranslatorSettingsPanel.targetLanguageLabel.text=Target Language:
+BingTranslatorSettingsPanel.authenticationKeyField.toolTipText=Enter the hash for the
+GoogleTranslatorSettingsPanel.testButton.text=Test
+GoogleTranslatorSettingsPanel.untranslatedLabel.text=Untranslated:
+GoogleTranslatorSettingsPanel.resultLabel.text=Result:
+GoogleTranslatorSettingsPanel.testResultValueLabel.text=
+BingTranslatorSettingsPanel.authenticationKeyLabel.text=Authentication Key:
diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED
index b0e30f632c..51cbb9a26f 100644
--- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED
+++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/Bundle.properties-MERGED
@@ -1,8 +1,12 @@
+BingTranslator.name.text=Bing Translator
+BingTranslatorSettingsPanel.warning.invalidKey=Invalid translation authentication key
+BingTranslatorSettingsPanel.warning.targetLanguageFailure=Unable to get list of target languages or parse the result that was received
GoogleTranslator.name.text=Google Translate
GoogleTranslatorSettingsPanel.browseButton.text=Browse
-GoogleTranslatorSettingsPanel.credentialsLabel.text=Credentials:
+GoogleTranslatorSettingsPanel.credentialsLabel.text=Credentials Path:
GoogleTranslatorSettingsPanel.errorMessage.fileNotFound=Credentials file not found, please set the location to be a valid JSON credentials file.
GoogleTranslatorSettingsPanel.errorMessage.noFileSelected=A JSON file must be selected to provide your credentials for Google Translate.
+GoogleTranslatorSettingsPanel.errorMessage.translationFailure=Translation failure with specified credentials
GoogleTranslatorSettingsPanel.errorMessage.unableToMakeCredentials=Unable to construct credentials object from credentials file, please set the location to be a valid JSON credentials file.
GoogleTranslatorSettingsPanel.errorMessage.unableToReadCredentials=Unable to read credentials from credentials file, please set the location to be a valid JSON credentials file.
GoogleTranslatorSettingsPanel.errorMessage.unknownFailureGetting=Failure getting list of supported languages with current credentials file.
@@ -11,3 +15,14 @@ GoogleTranslatorSettingsPanel.fileChooser.confirmButton=Select
GoogleTranslatorSettingsPanel.json.description=JSON Files
GoogleTranslatorSettingsPanel.warningLabel.text=
GoogleTranslatorSettingsPanel.targetLanguageLabel.text=Target Language:
+BingTranslatorSettingsPanel.testButton.text=Test
+BingTranslatorSettingsPanel.testResultValueLabel.text=
+BingTranslatorSettingsPanel.resultLabel.text=Result:
+BingTranslatorSettingsPanel.untranslatedLabel.text=Untranslated:
+BingTranslatorSettingsPanel.targetLanguageLabel.text=Target Language:
+BingTranslatorSettingsPanel.authenticationKeyField.toolTipText=Enter the hash for the
+GoogleTranslatorSettingsPanel.testButton.text=Test
+GoogleTranslatorSettingsPanel.untranslatedLabel.text=Untranslated:
+GoogleTranslatorSettingsPanel.resultLabel.text=Result:
+GoogleTranslatorSettingsPanel.testResultValueLabel.text=
+BingTranslatorSettingsPanel.authenticationKeyLabel.text=Authentication Key:
diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java
index 318191e713..46bdd6da67 100644
--- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java
+++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslator.java
@@ -32,8 +32,10 @@ import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.logging.Level;
import java.util.logging.Logger;
+import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.ServiceProvider;
+import org.sleuthkit.autopsy.coreutils.EscapeUtil;
import org.sleuthkit.autopsy.texttranslation.TextTranslator;
import org.sleuthkit.autopsy.texttranslation.TranslationException;
@@ -58,26 +60,33 @@ public final class GoogleTranslator implements TextTranslator {
settingsPanel = new GoogleTranslatorSettingsPanel(settings.getCredentialPath(), settings.getTargetLanguageCode());
loadTranslator();
}
-
+
+ /**
+ * Check if google is able to be reached
+ *
+ * @return true if it can be, false otherwise
+ */
private static boolean googleIsReachable() {
String host = "www.google.com";
InetAddress address;
try {
address = InetAddress.getByName(host);
return address.isReachable(1500);
- }catch (UnknownHostException ex) {
+ } catch (UnknownHostException ex) {
+ logger.log(Level.WARNING, "Unable to reach google.com due to unknown host", ex);
return false;
} catch (IOException ex) {
+ logger.log(Level.WARNING, "Unable to reach google.com due IOException", ex);
return false;
}
}
-
+
@Override
public String translate(String string) throws TranslationException {
if (!googleIsReachable()) {
throw new TranslationException("Failure translating using GoogleTranslator: Cannot connect to Google");
}
-
+
if (googleTranslate != null) {
try {
// Translates some text into English, without specifying the source language.
@@ -88,7 +97,7 @@ public final class GoogleTranslator implements TextTranslator {
// We can't currently set parameters, so we are using the default behavior of
// assuming the input is HTML. We need to replace newlines with
for Google to preserve them
substring = substring.replaceAll("(\r\n|\n)", "
");
-
+
// The API complains if the "Payload" is over 204800 bytes. I'm assuming that
// deals with the full request. At some point, we get different errors about too
// much text. Officially, Google says they will googleTranslate only 5k chars,
@@ -100,11 +109,15 @@ public final class GoogleTranslator implements TextTranslator {
Translation translation
= googleTranslate.translate(substring);
String translatedString = translation.getTranslatedText();
-
+
// put back the newlines
translatedString = translatedString.replaceAll("
", "\n");
+
+ // With our current settings, Google Translate outputs HTML
+ // so we need to undo the escape characters.
+ translatedString = EscapeUtil.unEscapeHtml(translatedString);
return translatedString;
- } catch (Throwable ex) {
+ } catch (Throwable ex) {
//Catching throwables because some of this Google Translate code throws throwables
throw new TranslationException("Failure translating using GoogleTranslator", ex);
}
@@ -112,7 +125,7 @@ public final class GoogleTranslator implements TextTranslator {
throw new TranslationException("Google Translator has not been configured, credentials need to be specified");
}
}
-
+
@Messages({"GoogleTranslator.name.text=Google Translate"})
@Override
public String getName() {
@@ -125,16 +138,20 @@ public final class GoogleTranslator implements TextTranslator {
}
/**
- * Load the Google Cloud Translation service give the currently saved
+ * Load the Google Cloud Translation service given the currently saved
* settings
*/
private void loadTranslator() {
InputStream credentialStream = null;
Credentials creds = null;
- try {
- credentialStream = new FileInputStream(settings.getCredentialPath());
- } catch (FileNotFoundException ex) {
- logger.log(Level.WARNING, "JSON file for GoogleTranslator credentials not found", ex);
+ if (StringUtils.isBlank(settings.getCredentialPath())) {
+ logger.log(Level.INFO, "No credentials file has been provided for Google Translator");
+ } else {
+ try {
+ credentialStream = new FileInputStream(settings.getCredentialPath());
+ } catch (FileNotFoundException ex) {
+ logger.log(Level.WARNING, "JSON file for GoogleTranslator credentials not found", ex);
+ }
}
if (credentialStream != null) {
try {
diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettings.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettings.java
index 55771b8c7a..34ebfd67d4 100644
--- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettings.java
+++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettings.java
@@ -92,12 +92,14 @@ public final class GoogleTranslatorSettings {
}
if (ModuleSettings.settingExists(GOOGLE_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY)) {
targetLanguageCode = ModuleSettings.getConfigSetting(GOOGLE_TRANSLATE_NAME, TARGET_LANGUAGE_CODE_KEY);
- } else {
+ }
+ if (targetLanguageCode == null || StringUtils.isBlank(targetLanguageCode)) {
targetLanguageCode = DEFAULT_TARGET_LANGUAGE;
}
if (ModuleSettings.settingExists(GOOGLE_TRANSLATE_NAME, CREDENTIAL_PATH_KEY)) {
credentialPath = ModuleSettings.getConfigSetting(GOOGLE_TRANSLATE_NAME, CREDENTIAL_PATH_KEY);
- } else {
+ }
+ if (credentialPath == null) {
credentialPath = DEFAULT_CREDENTIAL_PATH;
}
}
diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.form b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.form
index 97a10b500f..b55a40b30f 100644
--- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.form
+++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.form
@@ -16,13 +16,9 @@
-
+
-
-
-
-
@@ -31,7 +27,7 @@
-
+
@@ -42,6 +38,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -60,9 +68,16 @@
-
+
+
+
+
+
+
+
+
+
-
@@ -98,7 +113,7 @@
-
+
@@ -118,5 +133,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java
index fbb957413b..84fad9e8e1 100644
--- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java
+++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleTranslatorSettingsPanel.java
@@ -21,7 +21,9 @@ package org.sleuthkit.autopsy.texttranslation.translators;
import com.google.auth.Credentials;
import com.google.auth.oauth2.ServiceAccountCredentials;
import com.google.cloud.translate.Language;
+import com.google.cloud.translate.Translate;
import com.google.cloud.translate.TranslateOptions;
+import com.google.cloud.translate.Translation;
import java.awt.event.ItemListener;
import java.io.File;
import java.io.FileInputStream;
@@ -44,6 +46,7 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
private static final Logger logger = Logger.getLogger(GoogleTranslatorSettingsPanel.class.getName());
private static final String JSON_EXTENSION = "json";
+ private static final String DEFUALT_TEST_STRING = "traducción exitoso"; //spanish which should translate to something along the lines of "successful translation"
private static final long serialVersionUID = 1L;
private final ItemListener listener = new ComboBoxSelectionListener();
private String targetLanguageCode = "";
@@ -60,16 +63,15 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
/**
* Private method to make a temporary translation service given the current
- * settings and use it to retrieve the available target languages for
- * population of combobox with target language with unsaved settings.
+ * settings with unsaved settings.
*
- * @return A list of Languages
+ * @return A Translate object which is the translation service
*/
@Messages({"GoogleTranslatorSettingsPanel.errorMessage.fileNotFound=Credentials file not found, please set the location to be a valid JSON credentials file.",
"GoogleTranslatorSettingsPanel.errorMessage.unableToReadCredentials=Unable to read credentials from credentials file, please set the location to be a valid JSON credentials file.",
"GoogleTranslatorSettingsPanel.errorMessage.unableToMakeCredentials=Unable to construct credentials object from credentials file, please set the location to be a valid JSON credentials file.",
"GoogleTranslatorSettingsPanel.errorMessage.unknownFailureGetting=Failure getting list of supported languages with current credentials file.",})
- private List getListOfTargetLanguages() {
+ private Translate getTemporaryTranslationService() {
//This method also has the side effect of more or less validating the JSON file which was selected as it is necessary to get the list of target languages
try {
InputStream credentialStream;
@@ -77,31 +79,31 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
credentialStream = new FileInputStream(credentialsPathField.getText());
} catch (FileNotFoundException ignored) {
warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_fileNotFound());
- return new ArrayList<>();
+ return null;
}
Credentials creds;
try {
creds = ServiceAccountCredentials.fromStream(credentialStream);
} catch (IOException ignored) {
warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_unableToMakeCredentials());
- return new ArrayList<>();
+ return null;
}
if (creds == null) {
warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_unableToReadCredentials());
logger.log(Level.WARNING, "Credentials were not successfully made, no translations will be available from the GoogleTranslator");
- return new ArrayList<>();
+ return null;
} else {
TranslateOptions.Builder builder = TranslateOptions.newBuilder();
builder.setCredentials(creds);
builder.setTargetLanguage(targetLanguageCode); //localize the list to the currently selected target language
warningLabel.setText(""); //clear any previous warning text
- return builder.build().getService().listSupportedLanguages();
+ return builder.build().getService();
}
} catch (Throwable throwable) {
//Catching throwables because some of this Google Translate code throws throwables
warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_unknownFailureGetting());
logger.log(Level.WARNING, "Throwable caught while getting list of supported languages", throwable);
- return new ArrayList<>();
+ return null;
}
}
@@ -114,29 +116,51 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
targetLanguageComboBox.removeItemListener(listener);
try {
if (!StringUtils.isBlank(credentialsPathField.getText())) {
- List listSupportedLanguages = getListOfTargetLanguages();
+ List listSupportedLanguages;
+ Translate tempService = getTemporaryTranslationService();
+ if (tempService != null) {
+ listSupportedLanguages = tempService.listSupportedLanguages();
+ } else {
+ listSupportedLanguages = new ArrayList<>();
+ }
targetLanguageComboBox.removeAllItems();
if (!listSupportedLanguages.isEmpty()) {
listSupportedLanguages.forEach((lang) -> {
- targetLanguageComboBox.addItem(new GoogleLanguageWrapper(lang));
+ targetLanguageComboBox.addItem(new LanguageWrapper(lang));
});
selectLanguageByCode(targetLanguageCode);
targetLanguageComboBox.addItemListener(listener);
- targetLanguageComboBox.setEnabled(true);
+ enableControls(true);
+
} else {
- targetLanguageComboBox.setEnabled(false);
+ enableControls(false);
}
} else {
warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_noFileSelected());
- targetLanguageComboBox.setEnabled(false);
+ enableControls(false);
}
} catch (Throwable throwable) {
warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_unknownFailurePopulating());
logger.log(Level.WARNING, "Throwable caught while populating list of supported languages", throwable);
- targetLanguageComboBox.setEnabled(false);
+ enableControls(false);
}
}
+ /**
+ * Helper method to enable/disable all controls which are dependent on valid
+ * credentials having been provided
+ *
+ * @param enabled true to enable the controls, false to disable them
+ */
+ private void enableControls(boolean enabled) {
+ targetLanguageComboBox.setEnabled(enabled);
+ testButton.setEnabled(enabled);
+ testResultValueLabel.setEnabled(enabled);
+ testUntranslatedTextField.setEnabled(enabled);
+ untranslatedLabel.setEnabled(enabled);
+ resultLabel.setEnabled(enabled);
+ }
+
/**
* Given a language code select the corresponding language in the combo box
* if it is present
@@ -145,7 +169,7 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
*/
private void selectLanguageByCode(String code) {
for (int i = 0; i < targetLanguageComboBox.getModel().getSize(); i++) {
- if (targetLanguageComboBox.getItemAt(i).getLanguage().getCode().equals(code)) {
+ if (targetLanguageComboBox.getItemAt(i).getLanguageCode().equals(code)) {
targetLanguageComboBox.setSelectedIndex(i);
return;
}
@@ -167,6 +191,11 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
targetLanguageComboBox = new javax.swing.JComboBox<>();
targetLanguageLabel = new javax.swing.JLabel();
warningLabel = new javax.swing.JLabel();
+ testResultValueLabel = new javax.swing.JLabel();
+ resultLabel = new javax.swing.JLabel();
+ untranslatedLabel = new javax.swing.JLabel();
+ testUntranslatedTextField = new javax.swing.JTextField();
+ testButton = new javax.swing.JButton();
org.openide.awt.Mnemonics.setLocalizedText(credentialsLabel, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.credentialsLabel.text")); // NOI18N
@@ -186,6 +215,25 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
warningLabel.setForeground(new java.awt.Color(255, 0, 0));
org.openide.awt.Mnemonics.setLocalizedText(warningLabel, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.warningLabel.text")); // NOI18N
+ org.openide.awt.Mnemonics.setLocalizedText(testResultValueLabel, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.testResultValueLabel.text")); // NOI18N
+
+ org.openide.awt.Mnemonics.setLocalizedText(resultLabel, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.resultLabel.text")); // NOI18N
+ resultLabel.setEnabled(false);
+
+ org.openide.awt.Mnemonics.setLocalizedText(untranslatedLabel, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.untranslatedLabel.text")); // NOI18N
+ untranslatedLabel.setEnabled(false);
+
+ testUntranslatedTextField.setText(DEFUALT_TEST_STRING);
+ testUntranslatedTextField.setEnabled(false);
+
+ org.openide.awt.Mnemonics.setLocalizedText(testButton, org.openide.util.NbBundle.getMessage(GoogleTranslatorSettingsPanel.class, "GoogleTranslatorSettingsPanel.testButton.text")); // NOI18N
+ testButton.setEnabled(false);
+ testButton.addActionListener(new java.awt.event.ActionListener() {
+ public void actionPerformed(java.awt.event.ActionEvent evt) {
+ testButtonActionPerformed(evt);
+ }
+ });
+
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
@@ -193,9 +241,6 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
- .addGroup(layout.createSequentialGroup()
- .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 551, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addGap(0, 0, Short.MAX_VALUE))
.addGroup(layout.createSequentialGroup()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addComponent(credentialsLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
@@ -203,13 +248,24 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
- .addComponent(credentialsPathField, javax.swing.GroupLayout.DEFAULT_SIZE, 443, Short.MAX_VALUE)
+ .addComponent(credentialsPathField, javax.swing.GroupLayout.DEFAULT_SIZE, 451, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(browseButton)
.addGap(14, 14, 14))
.addGroup(layout.createSequentialGroup()
.addComponent(targetLanguageComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, 317, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addGap(0, 0, Short.MAX_VALUE))))))
+ .addGap(0, 0, Short.MAX_VALUE))))
+ .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 551, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addGroup(layout.createSequentialGroup()
+ .addComponent(testButton, javax.swing.GroupLayout.PREFERRED_SIZE, 83, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(untranslatedLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(testUntranslatedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, 140, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(resultLabel)
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
+ .addComponent(testResultValueLabel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
@@ -223,9 +279,15 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(targetLanguageLabel)
.addComponent(targetLanguageComboBox, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
- .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
- .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE)
- .addContainerGap(23, Short.MAX_VALUE))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 15, Short.MAX_VALUE)
+ .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
+ .addComponent(testButton)
+ .addComponent(testUntranslatedTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
+ .addComponent(untranslatedLabel)
+ .addComponent(resultLabel)
+ .addComponent(testResultValueLabel))
+ .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
+ .addComponent(warningLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 18, javax.swing.GroupLayout.PREFERRED_SIZE))
);
}// //GEN-END:initComponents
@@ -249,12 +311,33 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
}
}//GEN-LAST:event_browseButtonActionPerformed
+ @Messages({"GoogleTranslatorSettingsPanel.errorMessage.translationFailure=Translation failure with specified credentials"})
+ private void testButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_testButtonActionPerformed
+ testResultValueLabel.setText("");
+ Translate tempTranslate = getTemporaryTranslationService();
+ if (tempTranslate != null) {
+ try {
+ Translation translation = tempTranslate.translate(testUntranslatedTextField.getText());
+ testResultValueLabel.setText(translation.getTranslatedText());
+ warningLabel.setText("");
+ } catch (Exception ex) {
+ warningLabel.setText(Bundle.GoogleTranslatorSettingsPanel_errorMessage_translationFailure());
+ logger.log(Level.WARNING, Bundle.GoogleTranslatorSettingsPanel_errorMessage_translationFailure(), ex);
+ }
+ }
+ }//GEN-LAST:event_testButtonActionPerformed
+
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton browseButton;
private javax.swing.JLabel credentialsLabel;
private javax.swing.JTextField credentialsPathField;
- private javax.swing.JComboBox targetLanguageComboBox;
+ private javax.swing.JLabel resultLabel;
+ private javax.swing.JComboBox targetLanguageComboBox;
private javax.swing.JLabel targetLanguageLabel;
+ private javax.swing.JButton testButton;
+ private javax.swing.JLabel testResultValueLabel;
+ private javax.swing.JTextField testUntranslatedTextField;
+ private javax.swing.JLabel untranslatedLabel;
private javax.swing.JLabel warningLabel;
// End of variables declaration//GEN-END:variables
@@ -284,7 +367,7 @@ public class GoogleTranslatorSettingsPanel extends javax.swing.JPanel {
@Override
public void itemStateChanged(java.awt.event.ItemEvent evt) {
- String selectedCode = ((GoogleLanguageWrapper) targetLanguageComboBox.getSelectedItem()).getLanguage().getCode();
+ String selectedCode = ((LanguageWrapper) targetLanguageComboBox.getSelectedItem()).getLanguageCode();
if (!StringUtils.isBlank(selectedCode) && !selectedCode.equals(targetLanguageCode)) {
targetLanguageCode = selectedCode;
populateTargetLanguageComboBox();
diff --git a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleLanguageWrapper.java b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/LanguageWrapper.java
similarity index 51%
rename from Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleLanguageWrapper.java
rename to Core/src/org/sleuthkit/autopsy/texttranslation/translators/LanguageWrapper.java
index c61ed1c948..2fa308291b 100644
--- a/Core/src/org/sleuthkit/autopsy/texttranslation/translators/GoogleLanguageWrapper.java
+++ b/Core/src/org/sleuthkit/autopsy/texttranslation/translators/LanguageWrapper.java
@@ -21,19 +21,33 @@ package org.sleuthkit.autopsy.texttranslation.translators;
import com.google.cloud.translate.Language;
/**
- * Wrapper for the Language class
+ * Wrapper for Language definitions used by translators
*/
-class GoogleLanguageWrapper {
+class LanguageWrapper {
- private final Language language;
+ private final String languageCode;
+ private final String languageDisplayName;
/**
- * Create a new GoogleLanguageWrapper
+ * Create a new LanguageWrapper to wrap the google language object
*
* @param lang the Language object to wrap
*/
- GoogleLanguageWrapper(Language lang) {
- language = lang;
+ LanguageWrapper(Language language) {
+ languageCode = language.getCode();
+ languageDisplayName = language.getName();
+ }
+
+ /**
+ * Create a new LanguageWrapper to wrap json elements that identify a
+ * language for microsofts translation service
+ *
+ * @param code the code which uniquely identifies a language
+ * @param name the name of the language
+ */
+ LanguageWrapper(String code, String name) {
+ languageCode = code;
+ languageDisplayName = name;
}
/**
@@ -41,14 +55,14 @@ class GoogleLanguageWrapper {
*
* @return the wrapped Language
*/
- Language getLanguage() {
- return language;
+ String getLanguageCode() {
+ return languageCode;
}
@Override
public String toString() {
- //toString overridden so that the jComboBox in the GoogleTranslatorSettingsPanel will display the name of the language
- return language.getName();
+ //toString overridden so that the jComboBox in the TranslatorSettingsPanels will display the name of the language
+ return languageDisplayName;
}
}
diff --git a/Core/test/unit/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorTest.java b/Core/test/unit/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorTest.java
new file mode 100644
index 0000000000..25c78ba049
--- /dev/null
+++ b/Core/test/unit/src/org/sleuthkit/autopsy/texttranslation/translators/BingTranslatorTest.java
@@ -0,0 +1,92 @@
+/*
+ * Autopsy Forensic Browser
+ *
+ * Copyright 2019 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.texttranslation.translators;
+
+//import java.io.IOException;
+//import org.junit.After;
+//import org.junit.AfterClass;
+//import org.junit.Before;
+//import org.junit.BeforeClass;
+import org.junit.Test;
+//import static org.junit.Assert.*;
+
+/**
+ * Tests for the BingTranslator translation service, these tests have been
+ * commented out because they require credentials to perform
+ */
+public class BingTranslatorTest {
+
+ @Test
+ public void testTranslate() {
+// BingTranslator translator = new BingTranslator();
+// String input = "gato";
+// String expectedTranslation = "cat";
+// runTest(translator, input, expectedTranslation);
+ }
+
+// @Test
+// public void testQuickStartSentence() throws Exception {
+// BingTranslator translator = new BingTranslator();
+// String input = "Willkommen bei Microsoft Translator. Raten Sie mal, wie viele Sprachen ich spreche.";
+// String expectedTranslation = "Welcome to Microsoft Translator. Guess how many languages I speak.";
+// runTest(translator, input, expectedTranslation);
+// }
+//
+// @Test
+// public void testCharacterEscapes() throws Exception {
+// BingTranslator translator = new BingTranslator();
+// String input = "\"gato\"";;
+// String expectedTranslation = "Cat";
+// runTest(translator, input, expectedTranslation);
+// }
+//
+// @Test
+// public void testLineBreaks() throws Exception {
+// BingTranslator translator = new BingTranslator();
+// String input = "gato\nperro";;
+// String expectedTranslation = "cat\nDog";
+// runTest(translator, input, expectedTranslation);
+// }
+//
+// /**
+// * Test whether translator throws an error. This should not be part of our
+// * regular testing, because we are limited to only 2MB of free translations
+// * ever.
+// * @param translator A BingTranslator
+// * @param input Text to translate
+// * @param expectedTranslation Not used unless you uncomment those lines.
+// */
+// public void runTest(BingTranslator translator, String input, String expectedTranslation) {
+// String translation;
+// try {
+// translation = translator.translate(input);
+// }
+// catch (Throwable e) {
+// fail("Bing translation produced an exception: " + e.getMessage());
+// return;
+// };
+//
+// /*
+// //It's unrealistic to expect the same answer every time, but sometimes
+// //it's helpful to have this in your debug process.
+// System.out.println(translation);
+// assertEquals(expectedTranslation, translation);
+// */
+// }
+}