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); +// */ +// } +}