Reenable basic deceptive link highlighting.

* bindings/vapi/javascriptcore-4.0.vapi (Object::get_property): Fix
  return type.

* src/client/conversation-viewer/conversation-message.vala (GtkTemplate):
  Hook up to new deceptive_link_clicked signal, remove old DOM-based
  implementation.

* src/client/conversation-viewer/conversation-web-view.vala
  (ConversationWebView): Add new deceptive_link_clicked signal and
  DeceptiveText enum, listen for deceptiveLinkClicked JS message and fire
  signal when received.

* src/client/util/util-webkit.vala (WebKitUtil): Add to_object util function.

* src/engine/util/util-js.vala (Geary.JS): Add to_object and get_property
  util functions.

* ui/conversation-web-view.js (ConversationPageState) Listen for link
  clicks, check for deceptive text and send message if found. Add unit
  tests for deceptive text check.

* test/js/composer-page-state-test.vala: Move ::run_javascript to parent
  class so new ConversationPageStateTest class can use it, adapt call
  sites to different parent signature.
This commit is contained in:
Michael James Gratton 2017-01-24 00:05:44 +11:00
parent 69da046ff3
commit 2b5f94da7d
11 changed files with 371 additions and 140 deletions

View file

@ -25,7 +25,7 @@ class ComposerPageStateTest : ClientWebViewTestCase<ComposerWebView> {
load_body_fixture(html);
try {
assert(run_javascript(@"new EditContext(document.getElementById('test')).encode()")
assert(WebKitUtil.to_string(run_javascript(@"new EditContext(document.getElementById('test')).encode()"))
.has_prefix("1,url,"));
} catch (Geary.JS.Error err) {
print("Geary.JS.Error: %s\n", err.message);
@ -41,8 +41,8 @@ class ComposerPageStateTest : ClientWebViewTestCase<ComposerWebView> {
load_body_fixture(html);
try {
assert(run_javascript(@"new EditContext(document.getElementById('test')).encode()")
== ("0,,Comic Sans,144"));
assert(WebKitUtil.to_string(run_javascript(@"new EditContext(document.getElementById('test')).encode()")) ==
"0,,Comic Sans,144");
} catch (Geary.JS.Error err) {
print("Geary.JS.Error: %s\n", err.message);
assert_not_reached();
@ -56,7 +56,8 @@ class ComposerPageStateTest : ClientWebViewTestCase<ComposerWebView> {
string html = "<p>para</p>";
load_body_fixture(html);
try {
assert(run_javascript(@"window.geary.getHtml();") == html + "<br><br>");
assert(WebKitUtil.to_string(run_javascript(@"window.geary.getHtml();")) ==
html + "<br><br>");
} catch (Geary.JS.Error err) {
print("Geary.JS.Error: %s\n", err.message);
assert_not_reached();
@ -69,7 +70,8 @@ class ComposerPageStateTest : ClientWebViewTestCase<ComposerWebView> {
public void get_text() {
load_body_fixture("<p>para</p>");
try {
assert(run_javascript(@"window.geary.getText();") == "para\n\n\n\n");
assert(WebKitUtil.to_string(run_javascript(@"window.geary.getText();")) ==
"para\n\n\n\n");
} catch (Geary.JS.Error err) {
print("Geary.JS.Error: %s\n", err.message);
assert_not_reached();
@ -83,7 +85,7 @@ class ComposerPageStateTest : ClientWebViewTestCase<ComposerWebView> {
unichar q_marker = Geary.RFC822.Utils.QUOTE_MARKER;
load_body_fixture("<p>pre</p> <blockquote><p>quote</p></blockquote> <p>post</p>");
try {
assert(run_javascript(@"window.geary.getText();") ==
assert(WebKitUtil.to_string(run_javascript(@"window.geary.getText();")) ==
@"pre\n\n$(q_marker)quote\n$(q_marker)\npost\n\n\n\n");
} catch (Geary.JS.Error err) {
print("Geary.JS.Error: %s", err.message);
@ -98,7 +100,7 @@ class ComposerPageStateTest : ClientWebViewTestCase<ComposerWebView> {
unichar q_marker = Geary.RFC822.Utils.QUOTE_MARKER;
load_body_fixture("<p>pre</p> <blockquote><p>quote1</p> <blockquote><p>quote2</p></blockquote></blockquote> <p>post</p>");
try {
assert(run_javascript(@"window.geary.getText();") ==
assert(WebKitUtil.to_string(run_javascript(@"window.geary.getText();")) ==
@"pre\n\n$(q_marker)quote1\n$(q_marker)\n$(q_marker)$(q_marker)quote2\n$(q_marker)$(q_marker)\npost\n\n\n\n");
} catch (Geary.JS.Error err) {
print("Geary.JS.Error: %s\n", err.message);
@ -122,17 +124,17 @@ class ComposerPageStateTest : ClientWebViewTestCase<ComposerWebView> {
string js_cosy_quote2 = @"foo$(q_start)0$(q_end)$(q_start)1$(q_end)bar";
string js_values = "['quote1','quote2']";
try {
assert(run_javascript(@"ComposerPageState.resolveNesting('$(js_no_quote)', $(js_values));") ==
assert(WebKitUtil.to_string(run_javascript(@"ComposerPageState.resolveNesting('$(js_no_quote)', $(js_values));")) ==
@"foo");
assert(run_javascript(@"ComposerPageState.resolveNesting('$(js_spaced_quote)', $(js_values));") ==
assert(WebKitUtil.to_string(run_javascript(@"ComposerPageState.resolveNesting('$(js_spaced_quote)', $(js_values));")) ==
@"foo \n$(q_marker)quote1\n bar");
assert(run_javascript(@"ComposerPageState.resolveNesting('$(js_leading_quote)', $(js_values));") ==
assert(WebKitUtil.to_string(run_javascript(@"ComposerPageState.resolveNesting('$(js_leading_quote)', $(js_values));")) ==
@"$(q_marker)quote1\n bar");
assert(run_javascript(@"ComposerPageState.resolveNesting('$(js_hanging_quote)', $(js_values));") ==
assert(WebKitUtil.to_string(run_javascript(@"ComposerPageState.resolveNesting('$(js_hanging_quote)', $(js_values));")) ==
@"foo \n$(q_marker)quote1");
assert(run_javascript(@"ComposerPageState.resolveNesting('$(js_cosy_quote1)', $(js_values));") ==
assert(WebKitUtil.to_string(run_javascript(@"ComposerPageState.resolveNesting('$(js_cosy_quote1)', $(js_values));")) ==
@"foo\n$(q_marker)quote1\nbar");
assert(run_javascript(@"ComposerPageState.resolveNesting('$(js_cosy_quote2)', $(js_values));") ==
assert(WebKitUtil.to_string(run_javascript(@"ComposerPageState.resolveNesting('$(js_cosy_quote2)', $(js_values));")) ==
@"foo\n$(q_marker)quote1\n$(q_marker)quote2\nbar");
} catch (Geary.JS.Error err) {
print("Geary.JS.Error: %s\n", err.message);
@ -147,11 +149,11 @@ class ComposerPageStateTest : ClientWebViewTestCase<ComposerWebView> {
load_body_fixture();
unichar q_marker = Geary.RFC822.Utils.QUOTE_MARKER;
try {
assert(run_javascript("ComposerPageState.quoteLines('');") ==
assert(WebKitUtil.to_string(run_javascript("ComposerPageState.quoteLines('');")) ==
@"$(q_marker)");
assert(run_javascript("ComposerPageState.quoteLines('line1');") ==
assert(WebKitUtil.to_string(run_javascript("ComposerPageState.quoteLines('line1');")) ==
@"$(q_marker)line1");
assert(run_javascript("ComposerPageState.quoteLines('line1\\nline2');") ==
assert(WebKitUtil.to_string(run_javascript("ComposerPageState.quoteLines('line1\\nline2');")) ==
@"$(q_marker)line1\n$(q_marker)line2");
} catch (Geary.JS.Error err) {
print("Geary.JS.Error: %s\n", err.message);
@ -167,9 +169,9 @@ class ComposerPageStateTest : ClientWebViewTestCase<ComposerWebView> {
string single_nbsp = "a b";
string multiple_nbsp = "a b c";
try {
assert(run_javascript(@"ComposerPageState.replaceNonBreakingSpace('$(single_nbsp)');") ==
assert(WebKitUtil.to_string(run_javascript(@"ComposerPageState.replaceNonBreakingSpace('$(single_nbsp)');")) ==
"a b");
assert(run_javascript(@"ComposerPageState.replaceNonBreakingSpace('$(multiple_nbsp)');") ==
assert(WebKitUtil.to_string(run_javascript(@"ComposerPageState.replaceNonBreakingSpace('$(multiple_nbsp)');")) ==
"a b c");
} catch (Geary.JS.Error err) {
print("Geary.JS.Error: %s\n", err.message);
@ -196,14 +198,4 @@ class ComposerPageStateTest : ClientWebViewTestCase<ComposerWebView> {
}
}
protected string run_javascript(string command) throws Error {
this.test_view.run_javascript.begin(
command, null, (obj, res) => { async_complete(res); }
);
WebKit.JavascriptResult result =
this.test_view.run_javascript.end(async_result());
return WebKitUtil.to_string(result);
}
}

View file

@ -0,0 +1,93 @@
/*
* Copyright 2017 Michael Gratton <mike@vee.net>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
class ConversationPageStateTest : ClientWebViewTestCase<ConversationWebView> {
public ConversationPageStateTest() {
base("ConversationPageStateTest");
add_test("is_deceptive_text_not_url", is_deceptive_text_not_url);
add_test("is_deceptive_text_identical_text", is_deceptive_text_identical_text);
add_test("is_deceptive_text_matching_url", is_deceptive_text_matching_url);
add_test("is_deceptive_text_common_href_subdomain", is_deceptive_text_common_href_subdomain);
add_test("is_deceptive_text_common_text_subdomain", is_deceptive_text_common_text_subdomain);
add_test("is_deceptive_text_deceptive_href", is_deceptive_text_deceptive_href);
add_test("is_deceptive_text_non_matching_subdomain", is_deceptive_text_non_matching_subdomain);
add_test("is_deceptive_text_different_domain", is_deceptive_text_different_domain);
}
public void is_deceptive_text_not_url() {
load_body_fixture("<p>my hovercraft is full of eels</p>");
assert(exec_is_deceptive_text("ohhai!", "http://example.com") ==
ConversationWebView.DeceptiveText.NOT_DECEPTIVE);
}
public void is_deceptive_text_identical_text() {
load_body_fixture("<p>my hovercraft is full of eels</p>");
assert(exec_is_deceptive_text("http://example.com", "http://example.com") ==
ConversationWebView.DeceptiveText.NOT_DECEPTIVE);
}
public void is_deceptive_text_matching_url() {
load_body_fixture("<p>my hovercraft is full of eels</p>");
assert(exec_is_deceptive_text("example.com", "http://example.com") ==
ConversationWebView.DeceptiveText.NOT_DECEPTIVE);
}
public void is_deceptive_text_common_href_subdomain() {
load_body_fixture("<p>my hovercraft is full of eels</p>");
assert(exec_is_deceptive_text("example.com", "http://foo.example.com") ==
ConversationWebView.DeceptiveText.NOT_DECEPTIVE);
}
public void is_deceptive_text_common_text_subdomain() {
load_body_fixture("<p>my hovercraft is full of eels</p>");
assert(exec_is_deceptive_text("www.example.com", "http://example.com") ==
ConversationWebView.DeceptiveText.NOT_DECEPTIVE);
}
public void is_deceptive_text_deceptive_href() {
load_body_fixture("<p>my hovercraft is full of eels</p>");
assert(exec_is_deceptive_text("www.example.com", "ohhai!") ==
ConversationWebView.DeceptiveText.DECEPTIVE_HREF);
}
public void is_deceptive_text_non_matching_subdomain() {
load_body_fixture("<p>my hovercraft is full of eels</p>");
assert(exec_is_deceptive_text("www.example.com", "phishing.com") ==
ConversationWebView.DeceptiveText.DECEPTIVE_DOMAIN);
}
public void is_deceptive_text_different_domain() {
load_body_fixture("<p>my hovercraft is full of eels</p>");
assert(exec_is_deceptive_text("www.example.com", "phishing.net") ==
ConversationWebView.DeceptiveText.DECEPTIVE_DOMAIN);
}
protected override ConversationWebView set_up_test_view() {
try {
ConversationWebView.load_resources(File.new_for_path(""));
} catch (Error err) {
assert_not_reached();
}
return new ConversationWebView(this.config);
}
private uint exec_is_deceptive_text(string text, string href) {
try {
return (uint) WebKitUtil.to_number(
run_javascript(@"ConversationPageState.isDeceptiveText(\"$text\", \"$href\")")
);
} catch (Geary.JS.Error err) {
print("Geary.JS.Error: %s\n", err.message);
assert_not_reached();
} catch (Error err) {
print("WKError: %s\n", err.message);
assert_not_reached();
}
}
}