summaryrefslogtreecommitdiffstats
path: root/comm/suite/mailnews/content/phishingDetector.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/suite/mailnews/content/phishingDetector.js')
-rw-r--r--comm/suite/mailnews/content/phishingDetector.js173
1 files changed, 173 insertions, 0 deletions
diff --git a/comm/suite/mailnews/content/phishingDetector.js b/comm/suite/mailnews/content/phishingDetector.js
new file mode 100644
index 0000000000..04d2910753
--- /dev/null
+++ b/comm/suite/mailnews/content/phishingDetector.js
@@ -0,0 +1,173 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Dependencies:
+// gBrandBundle, gMessengerBundle should already be defined
+// gatherTextUnder from utilityOverlay.js
+
+ChromeUtils.import("resource:///modules/hostnameUtils.jsm");
+
+const kPhishingNotSuspicious = 0;
+const kPhishingWithIPAddress = 1;
+const kPhishingWithMismatchedHosts = 2;
+
+//////////////////////////////////////////////////////////////////////////////
+// isEmailScam --> examines the message currently loaded in the message pane
+// and returns true if we think that message is an e-mail scam.
+// Assumes the message has been completely loaded in the message pane (i.e. OnMsgParsed has fired)
+// aUrl: nsIURI object for the msg we want to examine...
+//////////////////////////////////////////////////////////////////////////////
+function isMsgEmailScam(aUrl)
+{
+ var isEmailScam = false;
+ if (!aUrl || !Services.prefs.getBoolPref("mail.phishing.detection.enabled"))
+ return isEmailScam;
+
+ try {
+ // nsIMsgMailNewsUrl.folder can throw an NS_ERROR_FAILURE, especially if
+ // we are opening an .eml file.
+ var folder = aUrl.folder;
+
+ // Ignore NNTP and RSS messages.
+ if (folder.server.type == 'nntp' || folder.server.type == 'rss')
+ return isEmailScam;
+
+ // Also ignore messages in Sent/Drafts/Templates/Outbox.
+ let outgoingFlags = Ci.nsMsgFolderFlags.SentMail |
+ Ci.nsMsgFolderFlags.Drafts |
+ Ci.nsMsgFolderFlags.Templates |
+ Ci.nsMsgFolderFlags.Queue;
+ if (folder.isSpecialFolder(outgoingFlags, true))
+ return isEmailScam;
+
+ } catch (ex) {
+ if (ex.result != Cr.NS_ERROR_FAILURE)
+ throw ex;
+ }
+
+ // loop through all of the link nodes in the message's DOM, looking for phishing URLs...
+ var msgDocument = document.getElementById('messagepane').contentDocument;
+ var index;
+
+ // examine all links...
+ var linkNodes = msgDocument.links;
+ for (index = 0; index < linkNodes.length && !isEmailScam; index++)
+ isEmailScam = isPhishingURL(linkNodes[index], true);
+
+ // if an e-mail contains a non-addressbook form element, then assume the message is
+ // a phishing attack. Legitimate sites should not be using forms inside of e-mail
+ if (!isEmailScam)
+ {
+ var forms = msgDocument.getElementsByTagName("form");
+ for (index = 0; index < forms.length && !isEmailScam; index++)
+ isEmailScam = forms[index].action != "" && !/^addbook:/.test(forms[index].action);
+ }
+
+ // we'll add more checks here as our detector matures....
+ return isEmailScam;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// isPhishingURL --> examines the passed in linkNode and returns true if we think
+// the URL is an email scam.
+// aLinkNode: the link node to examine
+// aSilentMode: don't prompt the user to confirm
+// aHref: optional href for XLinks
+//////////////////////////////////////////////////////////////////////////////
+
+function isPhishingURL(aLinkNode, aSilentMode, aHref)
+{
+ if (!Services.prefs.getBoolPref("mail.phishing.detection.enabled"))
+ return false;
+
+ var phishingType = kPhishingNotSuspicious;
+ var aLinkText = gatherTextUnder(aLinkNode);
+ var href = aHref || aLinkNode.href;
+ if (!href)
+ return false;
+
+ var linkTextURL = {};
+ var isPhishingURL = false;
+
+ var hrefURL;
+ // Make sure relative link urls don't make us bail out.
+ try {
+ hrefURL = Services.io.newURI(href);
+ } catch(ex) { return false; }
+
+ // only check for phishing urls if the url is an http or https link.
+ // this prevents us from flagging imap and other internally handled urls
+ if (hrefURL.schemeIs('http') || hrefURL.schemeIs('https'))
+ {
+
+ if (aLinkText)
+ aLinkText = aLinkText.replace(/^<(.+)>$|^"(.+)"$/, "$1$2");
+ if (aLinkText != aLinkNode.href &&
+ aLinkText.replace(/\/+$/, "") != aLinkNode.href.replace(/\/+$/, ""))
+ {
+ let ipAddress = isLegalIPAddress(hrefURL.host, true);
+ if (ipAddress && !isLegalLocalIPAddress(ipAddress))
+ phishingType = kPhishingWithIPAddress;
+ else if (misMatchedHostWithLinkText(aLinkNode, hrefURL))
+ phishingType = kPhishingWithMismatchedHosts;
+
+ isPhishingURL = phishingType != kPhishingNotSuspicious;
+
+ if (!aSilentMode && isPhishingURL) // allow the user to override the decision
+ isPhishingURL = confirmSuspiciousURL(phishingType, hrefURL.host);
+ }
+ }
+
+ return isPhishingURL;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// helper methods in support of isPhishingURL
+//////////////////////////////////////////////////////////////////////////////
+
+function misMatchedHostWithLinkText(aLinkNode, aHrefURL)
+{
+ var linkNodeText = gatherTextUnder(aLinkNode);
+
+ // gatherTextUnder puts a space between each piece of text it gathers,
+ // so strip the spaces out (see bug 326082 for details).
+ linkNodeText = linkNodeText.replace(/ /g, "");
+
+ // only worry about http and https urls
+ if (linkNodeText)
+ {
+ // does the link text look like a http url?
+ if (linkNodeText.search(/(^http:|^https:)/) != -1)
+ {
+ var linkURI = Services.io.newURI(linkNodeText);
+ // compare hosts, but ignore possible www. prefix
+ return !(aHrefURL.host.replace(/^www\./, "") == linkURI.host.replace(/^www\./, ""));
+ }
+ }
+
+ return false;
+}
+
+// returns true if the user confirms the URL is a scam
+function confirmSuspiciousURL(aPhishingType, aSuspiciousHostName)
+{
+ var brandShortName = gBrandBundle.getString("brandShortName");
+ var titleMsg = gMessengerBundle.getString("confirmPhishingTitle");
+ var dialogMsg;
+
+ switch (aPhishingType)
+ {
+ case kPhishingWithIPAddress:
+ case kPhishingWithMismatchedHosts:
+ dialogMsg = gMessengerBundle.getFormattedString("confirmPhishingUrl" + aPhishingType, [brandShortName, aSuspiciousHostName], 2);
+ break;
+ default:
+ return false;
+ }
+
+ var buttons = Services.prompt.STD_YES_NO_BUTTONS +
+ Services.prompt.BUTTON_POS_1_DEFAULT;
+ return Services.prompt.confirmEx(window, titleMsg, dialogMsg, buttons, "", "", "", "", {}); /* the yes button is in position 0 */
+} \ No newline at end of file