Bug 336288 (for tony@ponderer.org) r+a=ben Merge safe browsing UI into browser

git-svn-id: svn://10.0.0.236/branches/MOZILLA_1_8_BRANCH@195925 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
brettw%gmail.com
2006-05-03 20:36:34 +00:00
parent 4bda26a564
commit a67ba0f229
57 changed files with 5418 additions and 913 deletions

View File

@@ -74,6 +74,10 @@ ifdef MOZ_FEEDS
DIRS += feeds
endif
ifdef MOZ_SAFE_BROWSING
DIRS += safebrowsing
endif
DIRS += build
include $(topsrcdir)/config/rules.mk

View File

@@ -73,6 +73,7 @@ REQUIRES = xpcom \
js \
nkcache \
widget \
url-classifier \
$(NULL)
LOCAL_INCLUDES = -I$(srcdir)/../../build

View File

@@ -0,0 +1,52 @@
#
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (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.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is mozilla.org code.
#
# The Initial Developer of the Original Code is
# Netscape Communications Corporation.
# Portions created by the Initial Developer are Copyright (C) 1998
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
DEPTH = ../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
DIRS = src
ifdef ENABLE_TESTS
DIRS += tests
endif
include $(topsrcdir)/config/rules.mk

View File

@@ -0,0 +1,93 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// An instance of our application is a PROT_Application object. It
// basically just populates a few globals and instantiates wardens and
// the listmanager.
/**
* An instance of our application. There should be exactly one of these.
*
* Note: This object should instantiated only at profile-after-change
* or later because the listmanager and the cryptokeymanager need to
* read and write data files. Additionally, NSS isn't loaded until
* some time around then (Moz bug #321024).
*
* @constructor
*/
function PROT_Application() {
this.debugZone= "application";
// TODO This is truly lame; we definitely want something better
function runUnittests() {
if (false) {
G_DebugL("UNITTESTS", "STARTING UNITTESTS");
TEST_G_Protocol4Parser();
TEST_G_Base64();
TEST_G_CryptoHasher();
TEST_PROT_EnchashDecrypter();
TEST_PROT_TRTable();
TEST_PROT_ListManager();
TEST_PROT_PhishingWarden();
TEST_PROT_TRFetcher();
TEST_G_ObjectSafeMap();
TEST_PROT_URLCanonicalizer();
TEST_G_Preferences();
TEST_G_Observer();
TEST_G_File();
TEST_PROT_WireFormat();
// UrlCrypto's test should come before the key manager's
TEST_PROT_UrlCrypto();
TEST_PROT_UrlCryptoKeyManager();
TEST_G_MozVersionNumber();
TEST_G_ThisFirefoxVersion();
G_DebugL("UNITTESTS", "END UNITTESTS");
}
};
runUnittests();
// expose some classes
this.G_TabbedBrowserWatcher = G_TabbedBrowserWatcher;
this.PROT_Controller = PROT_Controller;
this.PROT_GlobalStore = PROT_GlobalStore;
this.PROT_PhishingWarden = PROT_PhishingWarden;
// expose the object
this.wrappedJSObject = this;
}

View File

@@ -0,0 +1,615 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// There is one BrowserView per browser window, and each BrowserView
// is responsible for keeping track of problems (phishy documents)
// within that window. The BrowserView is also responsible for
// figuring out what to do about such problems, for example, whether
// the tab with a phishy page is currently showing and therefore if we
// should be showing a warning.
//
// The BrowserView receives information from three places:
//
// - from the phishing warden. When the phishing warden notices a
// problem, it queries all browser views to see which one (if any)
// has the Document that is problematic. It then hands the problem
// off to the appropriate BrowserView.
//
// - from the controller. The controller responds to explicit user
// actions (tab switches, requests to hide the warning message,
// etc.) and let's the BrowserView know about any user action
// having to do with the problems it is tracking.
//
// - from the TabbedBrowserWatcher. When the BrowserView is keeping
// track of a problematic document it listens for interesting
// events affecting it, for example pagehide (at which point
// we presumably hide the warning if we're showing it).
//
// The BrowserView associates at most one "problem" with each Document
// in the browser window. It keeps state about which Documents are
// problematic by storing a "problem queue" on each browser (tab).
// At most one problematic document per browser (tab) is active
// at any time. That is, we show the warning for at most one phishy
// document at any one time. If another phishy doc loads in that tab,
// it goes onto the end of the queue to be activated only when the
// currently active document goes away.
//
// If we had multiple types of warnings (one for after the page had
// loaded, one for when the user clicked a link, etc) here's where
// we'd select the appropate one to use. As it stands, we only have
// one displayer (an "afterload" displayer). A displayer knows _how_
// to display a warning, whereas as the BrowserView knows _what_ and
// _when_.
//
// To keep things (relatively) easy to reason about and efficient (the
// phishwarden could be querying us inside a progresslistener
// notification, or the controller inside an event handler), we have
// the following rules:
//
// - at most one of a displayer's start() or stop() methods is called
// in any iteration (if calling two is required, the second is run in
// the next event loop)
// - displayers should run their operations synchronously so we don't have
// to look two places (here and in the displayer) to see what is happening
// when
// - displayer actions are run after cleaning up the browser view state
// in case they have consequences
//
// TODO: this could use some redesign, but I don't have time.
// TODO: the queue needs to be abstracted, but we want another release fast,
// so I'm not going to touch it for the time being
// TODO: IDN issues and canonical URLs?
// TODO: Perhaps we should blur the page before showing a warning in order
// to prevent stray keystrokes?
/**
* The BrowerView is responsible for keeping track of and figuring out
* what to do with problems within a single browser window.
*
* TODO
* Unify all browser-related state here. Currently it's split
* between two objects, this object and the controller. We could have
* this object be solely responsible for UI hide/show decisions, which
* would probably make it easier to reason about what's going on.
*
* TODO
* Investigate an alternative model. For example, we could factor out
* the problem signaling stuff from the tab/UI logic into a
* ProblemRegistry. Attach listeners to new docs/requests as they go
* by and have these listeners periodically check in with a
* ProblemRegistry to see if they're watching a problematic
* doc/request. If so, then have them flag the browser view to be
* aware of the problem.
*
* @constructor
* @param tabWatcher Reference to the TabbedBrowserWatcher we'll use to query
* for information about active tabs/browsers.
* @param doc Reference to the XUL Document (browser window) in which the
* tabwatcher is watching
*/
function PROT_BrowserView(tabWatcher, doc) {
this.debugZone = "browserview";
this.tabWatcher_ = tabWatcher;
this.doc_ = doc;
}
/**
* See if we have any Documents with a given (problematic) URL that
* haven't yet been marked as problems. Called as a subroutine by
* tryToHandleProblemRequest().
*
* @param url String containing the URL to look for
*
* @returns Reference to an unhandled Document with the problem URL or null
*/
PROT_BrowserView.prototype.getFirstUnhandledDocWithURL_ = function(url) {
var docs = this.tabWatcher_.getDocumentsFromURL(url);
if (!docs.length)
return null;
for (var i = 0; i < docs.length; i++) {
var browser = this.tabWatcher_.getBrowserFromDocument(docs[i]);
G_Assert(this, !!browser, "Found doc but can't find browser???");
var alreadyHandled = this.getProblem_(docs[i], browser);
if (!alreadyHandled)
return docs[i];
}
return null;
}
/**
* Invoked by the warden to give us the opportunity to handle a
* problem. A problem is signaled once per request for a problem
* Document and is handled at most once, so there's no issue with us
* "losing" a problem due to multiple concurrently loading Documents
* with the same URL.
*
* @param warden Reference to the warden signalling the problem. We'll
* need him to instantiate one of his warning displayers
*
* @param request The nsIRequest that is problematic
*
* @returns Boolean indicating whether we handled problem
*/
PROT_BrowserView.prototype.tryToHandleProblemRequest = function(warden,
request) {
var doc = this.getFirstUnhandledDocWithURL_(request.name);
if (doc) {
var browser = this.tabWatcher_.getBrowserFromDocument(doc);
G_Assert(this, !!browser, "Couldn't get browser from problem doc???");
G_Assert(this, !this.getProblem_(doc, browser),
"Doc is supposedly unhandled, but has state?");
this.isProblemDocument_(browser, doc, warden);
return true;
}
return false;
}
/**
* We're sure a particular Document is problematic, so let's instantiate
* a dispalyer for it and add it to the problem queue for the browser.
*
* @param browser Reference to the browser in which the problem doc resides
*
* @param doc Reference to the problematic document
*
* @param warden Reference to the warden signalling the problem.
*/
PROT_BrowserView.prototype.isProblemDocument_ = function(browser,
doc,
warden) {
G_Debug(this, "Document is problem: " + doc.location.href);
var url = doc.location.href;
// We only have one type of displayer right now
var displayer = new warden.displayers_["afterload"]("Phishing afterload",
browser,
this.doc_,
url);
// We listen for the problematic document being navigated away from
// so we can remove it from the problem queue
var hideHandler = BindToObject(this.onNavAwayFromProblem_,
this,
doc,
browser);
doc.defaultView.addEventListener("pagehide", hideHandler, true);
// More info than we technically need, but it comes in handy for debugging
var problem = {
"browser_": browser,
"doc_": doc,
"displayer_": displayer,
"url_": url,
"hideHandler_": hideHandler,
};
var numInQueue = this.queueProblem_(browser, problem);
// If the queue was empty, schedule us to take something out
if (numInQueue == 1)
new G_Alarm(BindToObject(this.unqueueNextProblem_, this, browser), 0);
}
/**
* Invoked when a problematic document is navigated away from.
*
* @param doc Reference to the problematic Document navigated away from
* @param browser Reference to the browser in which the problem document
* unloaded
*/
PROT_BrowserView.prototype.onNavAwayFromProblem_ = function(doc, browser) {
G_Debug(this, "User nav'd away from problem.");
var problem = this.getProblem_(doc, browser);
(new PROT_Reporter).report("phishnavaway", problem.url_);
G_Assert(this, doc === problem.doc_, "State doc not equal to nav away doc?");
G_Assert(this, browser === problem.browser_,
"State browser not equal to nav away browser?");
this.problemResolved(browser, doc);
}
/**
* @param browser Reference to a browser we'd like to know about
*
* @returns Boolean indicating if the browser in question has
* problematic content
*/
PROT_BrowserView.prototype.hasProblem = function(browser) {
return this.hasNonemptyProblemQueue_(browser);
}
/**
* @param browser Reference to a browser we'd like to know about
*
* @returns Boolean indicating if the browser in question has a
* problem (i.e., it has a non-empty problem queue)
*/
PROT_BrowserView.prototype.hasNonemptyProblemQueue_ = function(browser) {
try {
return !!browser.PROT_problemState__ &&
!!browser.PROT_problemState__.length;
} catch(e) {
// We could be checking a browser that has just been closed, in
// which case its properties will not be valid, causing the above
// statement to throw an error. Since this case handled elsewhere,
// just return false.
return false;
}
}
/**
* Invoked to indicate that the problem for a particular problematic
* document in a browser has been resolved (e.g., by being navigated
* away from).
*
* @param browser Reference to the browser in which resolution is happening
*
* @param opt_doc Reference to the problematic doc whose problem was resolved
* (if absent, assumes the doc assocaited with the currently
* active displayer)
*/
PROT_BrowserView.prototype.problemResolved = function(browser, opt_doc) {
var problem;
var doc;
if (!!opt_doc) {
doc = opt_doc;
problem = this.getProblem_(doc, browser);
} else {
problem = this.getCurrentProblem_(browser);
doc = problem.doc_;
}
problem.displayer_.done();
var wasHead = this.deleteProblemFromQueue_(doc, browser);
// Peek at the next problem (if any) in the queue for this browser
var queueNotEmpty = this.getCurrentProblem_(browser);
if (wasHead && queueNotEmpty) {
G_Debug(this, "More problems pending. Scheduling unqueue.");
new G_Alarm(BindToObject(this.unqueueNextProblem_, this, browser), 0);
}
}
/**
* Peek at the top of the problem queue and if there's something there,
* make it active.
*
* @param browser Reference to the browser we should activate a problem
* displayer in if one is available
*/
PROT_BrowserView.prototype.unqueueNextProblem_ = function(browser) {
var problem = this.getCurrentProblem_(browser);
if (!problem) {
G_Debug(this, "No problem in queue; doc nav'd away from? (shrug)");
return;
}
// Two problem docs that load in rapid succession could both schedule
// themselves to be unqueued before this method is called. So ensure
// that the problem at the head of the queue is not, in fact, active.
if (!problem.displayer_.isActive()) {
// It could be the case that the server is really slow to respond,
// so there might not yet be anything in the problem Document. If
// we show the warning when that's the case, the user will see a
// blank document greyed out, and if they cancel the dialog
// they'll see the page they're navigating away from because it
// hasn't been painted over yet (b/c there's no content for the
// problem page). So here we ensure that we have content for the
// problem page before showing the dialog.
var haveContent = false;
try {
// This will throw if there's no content yet
var h = problem.doc_.defaultView.getComputedStyle(problem.doc_.body, "")
.getPropertyValue("height");
G_Debug(this, "body height: " + h);
if (Number(h.substring(0, h.length - 2)))
haveContent = true;
} catch (e) {
G_Debug(this, "Masked in unqueuenextproblem: " + e);
}
if (!haveContent) {
G_Debug(this, "Didn't get computed style. Re-queueing.");
// One stuck problem document in a page shouldn't prevent us
// warning on other problem frames that might be loading or
// loaded. So stick the Document that doesn't have content
// back at the end of the queue.
var p = this.removeProblemFromQueue_(problem.doc_, browser);
G_Assert(this, p === problem, "Unqueued wrong problem?");
this.queueProblem_(browser, problem);
// Try again in a bit. This opens us up to a potential
// vulnerability (put tons of hanging frames in a page
// ahead of your real phishy frame), but the risk at the
// moment is really low (plus it is outside our threat
// model).
new G_Alarm(BindToObject(this.unqueueNextProblem_,
this,
browser),
200 /*ms*/);
return;
}
problem.displayer_.start();
// OK, we have content, but there there is an additional
// issue. Due to a bfcache bug, if we show the warning during
// paint suppression, the collapsing of the content pane affects
// the doc we're naving from :( The symptom is a page with grey
// screen on navigation to or from a phishing page (the
// contentDocument will have width zero).
//
// Paint supression lasts at most 250ms from when the parser sees
// the body, and the parser sees the body well before it has a
// height. We err on the side of caution.
//
// Thanks to bryner for helping to track the bfcache bug down.
// https://bugzilla.mozilla.org/show_bug.cgi?id=319646
if (this.tabWatcher_.getCurrentBrowser() === browser)
new G_Alarm(BindToObject(this.problemBrowserMaybeSelected,
this,
browser),
350 /*ms*/);
}
}
/**
* Helper function that adds a new problem to the queue of problems pending
* on this browser.
*
* @param browser Browser to which we should add state
*
* @param problem Object (structure, really) encapsulating the problem
*
* @returns Number indicating the number of items in the queue (and from
* which you can infer whether the recently added item was
* placed at the head, and hence should be active.
*/
PROT_BrowserView.prototype.queueProblem_ = function(browser, problem) {
G_Debug(this, "Adding problem state for " + problem.url_);
if (this.hasNonemptyProblemQueue_(browser))
G_Debug(this, "Already has problem state. Queueing this problem...");
// First problem ever signaled on this browser? Make a new queue!
if (browser.PROT_problemState__ == undefined)
browser.PROT_problemState__ = [];
browser.PROT_problemState__.push(problem);
return browser.PROT_problemState__.length;
}
/**
* Helper function that removes a problem from the queue and deactivates
* it.
*
* @param doc Reference to the doc for which we should remove state
*
* @param browser Reference to the browser from which we should remove
* state
*
* @returns Boolean indicating if the remove problem was currently active
* (that is, if it was at the head of the queue)
*/
PROT_BrowserView.prototype.deleteProblemFromQueue_ = function(doc, browser) {
G_Debug(this, "Deleting problem state for " + browser);
G_Assert(this, !!this.hasNonemptyProblemQueue_(browser),
"Browser has no problem state");
var problem = this.getProblem_(doc, browser);
G_Assert(this, !!problem, "Couldnt find state in removeproblemstate???");
var wasHead = browser.PROT_problemState__[0] === problem;
this.removeProblemFromQueue_(doc, browser);
var hideHandler = problem.hideHandler_;
G_Assert(this, !!hideHandler, "No hidehandler in state?");
problem.doc_.defaultView.removeEventListener("pagehide",
hideHandler,
true);
return wasHead;
}
/**
* Helper function that removes a problem from the queue but does
* NOT deactivate it.
*
* @param doc Reference to the doc for which we should remove state
*
* @param browser Reference to the browser from which we should remove
* state
*
* @returns Boolean indicating if the remove problem was currently active
* (that is, if it was at the head of the queue)
*/
PROT_BrowserView.prototype.removeProblemFromQueue_ = function(doc, browser) {
G_Debug(this, "Removing problem state for " + browser);
G_Assert(this, !!this.hasNonemptyProblemQueue_(browser),
"Browser has no problem state");
var problem = null;
// TODO Blech. Let's please have an abstraction here instead.
for (var i = 0; i < browser.PROT_problemState__.length; i++)
if (browser.PROT_problemState__[i].doc_ === doc) {
problem = browser.PROT_problemState__.splice(i, 1)[0];
break;
}
return problem;
}
/**
* Retrieve (but do not remove) the problem state for a particular
* problematic Document in this browser
*
* @param doc Reference to the problematic doc to get state for
*
* @param browser Reference to the browser from which to get state
*
* @returns Object encapsulating the state we stored, or null if none
*/
PROT_BrowserView.prototype.getProblem_ = function(doc, browser) {
if (!this.hasNonemptyProblemQueue_(browser))
return null;
// TODO Blech. Let's please have an abstraction here instead.
for (var i = 0; i < browser.PROT_problemState__.length; i++)
if (browser.PROT_problemState__[i].doc_ === doc)
return browser.PROT_problemState__[i];
return null;
}
/**
* Retrieve the problem state for the currently active problem Document
* in this browser
*
* @param browser Reference to the browser from which to get state
*
* @returns Object encapsulating the state we stored, or null if none
*/
PROT_BrowserView.prototype.getCurrentProblem_ = function(browser) {
return browser.PROT_problemState__[0];
}
/**
* Invoked by the controller when the user switches tabs away from a problem
* tab.
*
* @param browser Reference to the tab that was switched from
*/
PROT_BrowserView.prototype.problemBrowserUnselected = function(browser) {
var problem = this.getCurrentProblem_(browser);
G_Assert(this, !!problem, "Couldn't get state from browser");
problem.displayer_.browserUnselected();
}
/**
* Checks to see if the problem browser is selected, and if so,
* tell it it to show its warning.
*
* @param browser Reference to the browser we wish to check
*/
PROT_BrowserView.prototype.problemBrowserMaybeSelected = function(browser) {
var problem = this.getCurrentProblem_(browser);
if (this.tabWatcher_.getCurrentBrowser() === browser &&
problem &&
problem.displayer_.isActive())
this.problemBrowserSelected(browser);
}
/**
* Invoked by the controller when the user switches tabs to a problem tab
*
* @param browser Reference to the tab that was switched to
*/
PROT_BrowserView.prototype.problemBrowserSelected = function(browser) {
G_Debug(this, "Problem browser selected");
var problem = this.getCurrentProblem_(browser);
G_Assert(this, !!problem, "No state? But we're selected!");
problem.displayer_.browserSelected();
}
/**
* Invoked by the controller when the user accepts our warning. Passes
* the accept through to the message displayer, which knows what to do
* (it will be displayer-specific).
*
* @param browser Reference to the browser for which the user accepted
* our warning
*/
PROT_BrowserView.prototype.acceptAction = function(browser) {
var problem = this.getCurrentProblem_(browser);
// We run the action only after we're completely through processing
// this event. We do this because the action could cause state to be
// cleared (e.g., by navigating the problem document) that we need
// to finish processing the event.
new G_Alarm(BindToObject(problem.displayer_.acceptAction,
problem.displayer_),
0);
}
/**
* Invoked by the controller when the user declines our
* warning. Passes the decline through to the message displayer, which
* knows what to do (it will be displayer-specific).
*
* @param browser Reference to the browser for which the user declined
* our warning
*/
PROT_BrowserView.prototype.declineAction = function(browser) {
var problem = this.getCurrentProblem_(browser);
G_Assert(this, !!problem, "User declined but no state???");
// We run the action only after we're completely through processing
// this event. We do this because the action could cause state to be
// cleared (e.g., by navigating the problem document) that we need
// to finish processing the event.
new G_Alarm(BindToObject(problem.displayer_.declineAction,
problem.displayer_),
0);
}
/**
* The user wants to see the warning message. So let em! At some point when
* we have multiple types of warnings, we'll have to mediate them here.
*
* @param browser Reference to the browser that has the warning the user
* wants to see.
*/
PROT_BrowserView.prototype.explicitShow = function(browser) {
var problem = this.getCurrentProblem_(browser);
G_Assert(this, !!problem, "Explicit show on browser w/o problem state???");
problem.displayer_.explicitShow();
}

View File

@@ -0,0 +1,26 @@
<!-- Commands included by the browser overlay
browser/base/content/browser-sets.inc -->
<command id="safebrowsing-show-warning"
oncommand="SB_executeCommandLocally('safebrowsing-show-warning')" />
<command id="safebrowsing-accept-warning"
oncommand="SB_executeCommandLocally('safebrowsing-accept-warning')" />
<command id="safebrowsing-decline-warning"
oncommand="SB_executeCommandLocally('safebrowsing-decline-warning')" />
<command id="safebrowsing-palm-showmore"
oncommand="SB_executeCommandLocally('safebrowsing-palm-showmore')" />
<command id="safebrowsing-palm-phishingorg"
oncommand="SB_executeCommandLocally('safebrowsing-palm-phishingorg')" />
<command id="safebrowsing-palm-phishingfaq"
oncommand="SB_executeCommandLocally('safebrowsing-palm-phishingfaq')" />
<command id="safebrowsing-palm-fraudpage"
oncommand="SB_executeCommandLocally('safebrowsing-palm-fraudpage')" />
<command id="safebrowsing-palm-falsepositive"
oncommand="SB_executeCommandLocally('safebrowsing-palm-falsepositive')" />
<command id="safebrowsing-submit-blacklist"
oncommand="SB_executeCommandLocally('safebrowsing-submit-blacklist')" />
<command id="safebrowsing-submit-generic-phishing"
oncommand="SB_executeCommandLocally('safebrowsing-submit-generic-phishing')"/>
<command id="safebrowsing-preferences"
oncommand="SB_executeCommandLocally('safebrowsing-preferences')" />
<command id="safebrowsing-test-link"
oncommand="SB_executeCommandLocally('safebrowsing-test-link')" />

View File

@@ -0,0 +1,390 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// This is our controller -- the thingy that listens to what the user
// is doing. There is one controller per browser window, and each has
// a BrowserView that manages information about problems within the
// window. The controller figures out when the browser might want to
// know about something, but the browser view figures out what exactly
// to do (and the BrowserView's displayer figures out how to do it).
//
// For example, the controller might notice that the user has switched
// to a tab that has something problematic in it. It would tell its
// BrowserView this, and the BrowserView would figure out whether it
// is appropriate to show a warning (e.g., perhaps the user previously
// dismissed the warning for that problem). If so, the BrowserView tells
// the displayer to show the warning. Anyhoo...
//
// TODO Could move all browser-related hide/show logic into the browser
// view. Need to think about this more.
/**
* Handles user actions, translating them into messages to the view
*
* @constructor
* @param win Reference to the Window (browser window context) we should
* attach to
* @param tabWatcher Reference to the TabbedBrowserWatcher object
* the controller should use to receive events about tabs.
* @param phishingWarden Reference to the PhishingWarden we should register
* our browserview with
*/
function PROT_Controller(win, tabWatcher, phishingWarden) {
this.debugZone = "controller";
this.win_ = win;
this.phishingWarden_ = phishingWarden;
// Use this to query preferences
this.prefs_ = new G_Preferences();
// Read state: are we in advanced mode?
this.checkRemotePrefName_ = PROT_GlobalStore.getServerCheckEnabledPrefName();
this.checkRemote_ = this.prefs_.getPref(this.checkRemotePrefName_, null);
// Get notifications when the advanced mode preference changes
this.checkRemotePrefObserver = BindToObject(this.onCheckRemotePrefChanged,
this);
this.prefs_.addObserver(this.checkRemotePrefName_,
this.checkRemotePrefObserver);
// Global preference to enable the phishing warden
this.phishWardenPrefName_ = PROT_GlobalStore.getPhishWardenEnabledPrefName();
this.phishWardenEnabled_ = this.prefs_.getPref(this.phishWardenPrefName_,
null);
// Get notifications when the phishing warden enabled pref changes
this.phishWardenPrefObserver =
BindToObject(this.onPhishWardenEnabledPrefChanged, this);
this.prefs_.addObserver(this.phishWardenPrefName_,
this.phishWardenPrefObserver);
// Set us up to receive the events we want.
this.tabWatcher_ = tabWatcher;
this.onShutdown_ = BindToObject(this.shutdown, this);
this.win_.addEventListener("unload", this.onShutdown_, false);
this.onTabSwitchCallback_ = BindToObject(this.onTabSwitch, this);
this.tabWatcher_.registerListener("tabswitch",
this.onTabSwitchCallback_);
// Install our command controllers. These commands are issued from
// various places in our UI, including our preferences dialog, the
// warning dialog, etc.
var commandHandlers = {
"safebrowsing-show-warning" :
BindToObject(this.onUserShowWarning, this),
"safebrowsing-accept-warning" :
BindToObject(this.onUserAcceptWarning, this),
"safebrowsing-decline-warning" :
BindToObject(this.onUserDeclineWarning, this),
"safebrowsing-submit-blacklist" :
BindToObject(this.onUserSubmitToBlacklist, this),
"safebrowsing-submit-generic-phishing" :
BindToObject(this.onUserSubmitToGenericPhish, this),
"safebrowsing-preferences" :
BindToObject(this.onUserPreferences, this),
"safebrowsing-test-link" :
BindToObject(this.showURL_, this, PROT_GlobalStore.getTestURLs()[0]),
"safebrowsing-preferences-home-link":
BindToObject(this.showURL_, this, PROT_GlobalStore.getHomePageURL()),
"safebrowsing-preferences-policy-link":
BindToObject(this.showURL_, this, PROT_GlobalStore.getPolicyURL()),
"safebrowsing-preferences-home-link-nochrome":
BindToObject(this.showURL_, this, PROT_GlobalStore.getHomePageURL(),
true /* chromeless */),
"safebrowsing-preferences-policy-link-nochrome":
BindToObject(this.showURL_, this, PROT_GlobalStore.getPolicyURL(),
true /* chromeless */),
};
this.commandController_ = new PROT_CommandController(commandHandlers);
this.win_.controllers.appendController(this.commandController_);
// This guy embodies the logic of when to display warnings
// (displayers embody the how).
this.browserView_ = new PROT_BrowserView(this.tabWatcher_,
this.win_.document);
// We need to let the phishing warden know about this browser view so it
// can be given the opportunity to handle problem documents. We also need
// to let the warden know when this window and hence this browser view
// is going away.
this.phishingWarden_.addBrowserView(this.browserView_);
this.windowWatcher_ =
Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher);
G_Debug(this, "Controller initialized.");
}
/**
* Invoked when the browser window is closing. Do some cleanup.
*/
PROT_Controller.prototype.shutdown = function(e) {
G_Debug(this, "Browser window closing. Shutting controller down.");
if (this.browserView_) {
this.phishingWarden_.removeBrowserView(this.browserView_);
}
if (this.commandController_) {
this.win_.controllers.removeController(this.commandController_);
this.commandController_ = null;
}
// No need to drain the browser view's problem queue explicitly; it will
// receive pagehides for all the browsers in its queues as they're torn
// down, and it will remove them.
this.browserView_ = null;
this.prefs_.removeObserver(this.checkRemotePrefName_,
this.checkRemotePrefObserver);
this.prefs_.removeObserver(this.phishWardenPrefName_,
this.phishWardenPrefObserver);
if (this.tabWatcher_) {
this.tabWatcher_.removeListener("tabswitch",
this.onTabSwitchCallback_);
this.tabWatcher_.shutdown();
}
this.win_.removeEventListener("unload", this.onShutdown_, false);
this.prefs_ = null;
this.windowWatcher_ = null;
G_Debug(this, "Controller shut down.");
}
/**
* Deal with a user changing the pref that says whether we're in advanced
* mode (and thus should check the remote server)
*
* @param prefName Name of the pref holding the value indicating whether
* we should check remote server
*/
PROT_Controller.prototype.onCheckRemotePrefChanged = function(prefName) {
this.checkRemote_ = this.prefs_.getBoolPrefOrDefault(prefName,
this.checkRemote_);
}
/**
* Deal with a user changing the pref that says whether we should
* enable the phishing warden
*
* @param prefName Name of the pref holding the value indicating whether
* we should enable the phishing warden
*/
PROT_Controller.prototype.onPhishWardenEnabledPrefChanged = function(
prefName) {
this.phishWardenEnabled_ =
this.prefs_.getBoolPrefOrDefault(prefName, this.phishWardenEnabled_);
}
/**
* The user clicked the urlbar icon; they want to see the warning message
* again.
*/
PROT_Controller.prototype.onUserShowWarning = function() {
var browser = this.tabWatcher_.getCurrentBrowser();
this.browserView_.explicitShow(browser);
}
/**
* Deal with a user wanting preferences
*/
PROT_Controller.prototype.onUserPreferences = function() {
G_Debug(this, "User wants preferences.");
var instantApply = this.prefs_.getPref("browser.preferences.instantApply",
false);
var features = "chrome,titlebar,toolbar,centerscreen" +
(instantApply ? ",dialog=no" : ",modal");
var target = this.windowWatcher_.openWindow(
this.win_,
"chrome://safe-browsing/content/safebrowsing-preferences.xul",
"safebrowsingprefsdialog",
features,
null /* args */);
return true;
}
/**
* The user clicked on one of the links in the preferences text.
* Display the corresponding page in a new window with all the chrome
* enabled.
*
* @param url The URL to display in a new window
* @param opt_chromeless Boolean indicating whether to open chromeless
*/
PROT_Controller.prototype.showURL_ = function(url, opt_chromeless) {
var features = opt_chromeless ? "status,scrollbars=yes,resizable=yes" : null;
this.windowWatcher_.openWindow(this.win_,
url,
"_blank",
features,
null);
}
/**
* User wants to report a phishing page.
*
* TODO: pass url as query param. This is ugly.
*/
PROT_Controller.prototype.onUserSubmitToBlacklist = function() {
var current_window = this.tabWatcher_.getCurrentWindow();
G_Debug(this, "User wants to submit to blacklist: " +
current_window.location.href);
var target = this.windowWatcher_.openWindow(
this.windowWatcher_.activeWindow /* parent */,
PROT_GlobalStore.getSubmitUrl(),
"_blank",
"height=400em,width=800,scrollbars=yes,resizable=yes," +
"menubar,toolbar,location,directories,personalbar,status",
null /* args */);
this.maybeFillInURL_(current_window, target);
return true;
}
/**
* User wants to report something phishy, but we don't know if it's a
* false positive or negative.
*
* TODO: pass url as query param. This is ugly.
*/
PROT_Controller.prototype.onUserSubmitToGenericPhish = function() {
var current_window = this.tabWatcher_.getCurrentWindow();
G_Debug(this, "User wants to submit something about: " +
current_window.location.href);
var target = this.windowWatcher_.openWindow(
this.windowWatcher_.activeWindow /* parent */,
PROT_GlobalStore.getGenericPhishSubmitURL(),
"_blank",
"height=400em,width=800,scrollbars=yes,resizable=yes," +
"menubar,toolbar,location,directories,personalbar,status",
null /* args */);
this.maybeFillInURL_(current_window, target);
return true;
}
/**
* A really lame method used by the submission report commands to fill
* the current URL into the appropriate form field of submission page.
*
* TODO: this really needs an overhaul.
*/
PROT_Controller.prototype.maybeFillInURL_ = function(current_window, target) {
// TODO: merge in patch from perforce
return true;
}
/**
* Deal with a user accepting our warning.
*
* TODO the warning hide/display instructions here can probably be moved
* into the browserview in the future, given its knowledge of when the
* problem doc hides/shows.
*/
PROT_Controller.prototype.onUserAcceptWarning = function() {
G_Debug(this, "User accepted warning.");
var browser = this.tabWatcher_.getCurrentBrowser();
G_Assert(this, !!browser, "Couldn't get current browser?!?");
G_Assert(this, this.browserView_.hasProblem(browser),
"User accept fired, but browser doesn't have warning showing?!?");
this.browserView_.acceptAction(browser);
this.browserView_.problemResolved(browser);
}
/**
* Deal with a user declining our warning.
*
* TODO the warning hide/display instructions here can probably be moved
* into the browserview in the future, given its knowledge of when the
* problem doc hides/shows.
*/
PROT_Controller.prototype.onUserDeclineWarning = function() {
G_Debug(this, "User declined warning.");
var browser = this.tabWatcher_.getCurrentBrowser();
G_Assert(this, this.browserView_.hasProblem(browser),
"User decline fired, but browser doesn't have warning showing?!?");
this.browserView_.declineAction(browser);
// We don't call problemResolved() here because all declining does it
// hide the message; we still have the urlbar icon showing, giving
// the user the ability to bring the warning message back up if they
// so desire.
}
/**
* Notice tab switches, and display or hide warnings as appropriate.
*
* TODO this logic can probably move into the browser view at some
* point. But one thing at a time.
*/
PROT_Controller.prototype.onTabSwitch = function(e) {
if (this.browserView_.hasProblem(e.fromBrowser))
this.browserView_.problemBrowserUnselected(e.fromBrowser);
if (this.browserView_.hasProblem(e.toBrowser))
this.browserView_.problemBrowserSelected(e.toBrowser);
}
/**
* Load a URI in the browser
*
* @param browser Browser in which to load the URI
* @param url URL to load
*/
PROT_Controller.prototype.loadURI = function(browser, url) {
browser.loadURI(url, null, null);
}
/**
* Reload the current page in the given browser
*
* @param browser Browser which to reload
*/
PROT_Controller.prototype.reloadPage = function(browser) {
var normalReload = browser.webNavigation.LOAD_FLAGS_NORMAL;
browser.reload(normalReload);
}

View File

@@ -0,0 +1,83 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// Some misc command-related plumbing used by the controller.
/**
* A tiny wrapper class for super-simple command handlers.
*
* @param commandHandlerMap An object containing name/value pairs where
* the name is command name (string) and value
* is the function to execute for that command
*/
function PROT_CommandController(commandHandlerMap) {
this.debugZone = "commandhandler";
this.cmds_ = commandHandlerMap;
}
/**
* @param cmd Command to query support for
* @returns Boolean indicating if this controller supports cmd
*/
PROT_CommandController.prototype.supportsCommand = function(cmd) {
return (cmd in this.cmds_);
}
/**
* Trivial implementation
*
* @param cmd Command to query status of
*/
PROT_CommandController.prototype.isCommandEnabled = function(cmd) {
return true;
}
/**
* Execute a command
*
* @param cmd Command to execute
*/
PROT_CommandController.prototype.doCommand = function(cmd) {
return this.cmds_[cmd]();
}
/**
* Trivial implementation
*/
PROT_CommandController.prototype.onEvent = function(cmd) { }

View File

@@ -0,0 +1,216 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// A class that encapsulates globals such as the names of things. We
// centralize everything here mainly so as to ease their modification,
// but also in case we want to version.
//
// This class does _not_ embody semantics, defaults, or the like. If we
// need something that does, we'll add our own preference registry.
//
// TODO: These values are actually specific to SafeBrowsing, not url
// classifier, so this file should be moved into
// browser/components/safebrowsing
// TODO: many of these values should just be moved directly into code.
// TODO: The code needs to fail more gracefully if these values aren't set
// E.g., createInstance should fail for listmanager without these.
/**
* A clearinghouse for globals. All interfaces are read-only.
*/
function PROT_GlobalStore() {
}
/**
* Read a pref value
*/
PROT_GlobalStore.getPref_ = function(prefname) {
var pref = new G_Preferences();
return pref.getPref(prefname);
}
/**
* @returns The name of the pref determining whether phishing protection
* is enabled (i.e., whether SafeBrowsing is enabled)
*/
PROT_GlobalStore.getPhishWardenEnabledPrefName = function() {
return "safebrowsing.enabled";
}
/**
* @returns The name of the pref determining whether we enable remote
* checking (advanced protection)
*/
PROT_GlobalStore.getServerCheckEnabledPrefName = function() {
return "safebrowsing.remoteLookups";
}
/**
* @returns The name of the pref determining whether we send reports
* about user actions
*/
PROT_GlobalStore.getSendUserReportsPrefName = function() {
// We send reports iff advanced protection mode is on
return PROT_GlobalStore.getServerCheckEnabledPrefName();
}
/**
* @returns The name of the directory in which we should store data (like
* blacklists and whitelists). This is relative to the user's
* profile.
*/
PROT_GlobalStore.getAppDirectoryName = function() {
return "safebrowsing_data";
}
/**
* @returns String containing the URL to nav to when the user clicks
* "get me out of here"
*/
PROT_GlobalStore.getGetMeOutOfHereURL = function() {
// Try to get their homepage from prefs.
var prefs = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefService).getBranch(null);
var url = "about:blank";
try {
url = prefs.getComplexValue("browser.startup.homepage",
Ci.nsIPrefLocalizedString).data;
} catch(e) {
G_Debug(this, "Couldn't get homepage pref: " + e);
}
return url;
}
/**
* TODO: maybe deprecate because antiphishing.org isn't localized
* @returns String containing the URL to nav to when the user clicks
* the link to antiphishing.org in the bubble.
*/
PROT_GlobalStore.getAntiPhishingURL = function() {
return "http://antiphishing.org/";
}
/**
* @returns String containing the URL to nav to when the user clicks
* on the policy link in the preferences.
*/
PROT_GlobalStore.getPolicyURL = function() {
return "TODO";
}
/**
* @returns String containing the URL to nav to when the user wants to
* submit a generic phishing report (we're not sure if they
* want to report a false positive or negative).
*/
PROT_GlobalStore.getGenericPhishSubmitURL = function() {
return PROT_GlobalStore.getPref_("safebrowsing.provider.0.genericReportURL");
}
/**
* @returns String containing the URL to nav to when the user wants to
* report a false positive (i.e. a non-phishy page)
*/
PROT_GlobalStore.getFalsePositiveURL = function() {
return PROT_GlobalStore.getPref_("safebrowsing.provider.0.reportErrorURL");
}
/**
* @returns String containing the URL to nav to when the user wants to
* report a false negative (i.e. a phishy page)
*/
PROT_GlobalStore.getSubmitUrl = function() {
return PROT_GlobalStore.getPref_("safebrowsing.provider.0.reportPhishURL");
}
/**
* TODO: maybe deprecated because no UI location for it?
* @returns String containing the URL to nav to when the user clicks
* "more info" in the bubble or the product link in the preferences.
*/
PROT_GlobalStore.getHomePageURL = function() {
return PROT_GlobalStore.getPref_("safebrowsing.provider.0.homeURL");
}
/**
* TODO: maybe deprecated because no UI location for it?
* @returns String containing the URL to nav to when the user clicks
* "phishing FAQ" in the bubble.
*/
PROT_GlobalStore.getPhishingFaqURL = function() {
return PROT_GlobalStore.getPref_("safebrowsing.provider.0.faqURL");
}
/**
* @returns String containing the URL to nav to when the user wants to
* see the test page
*/
PROT_GlobalStore.getTestURLs = function() {
// TODO: return all test urls
return [PROT_GlobalStore.getPref_("safebrowsing.provider.0.testURL")];
}
/**
* @returns String giving url to use for lookups (used in advanced mode)
*/
PROT_GlobalStore.getLookupserverURL = function() {
return PROT_GlobalStore.getPref_("safebrowsing.provider.0.lookupURL");
}
/**
* @returns String giving url to use for updates (diff of lists)
*/
PROT_GlobalStore.getUpdateserverURL = function() {
return PROT_GlobalStore.getPref_("safebrowsing.provider.0.updateURL");
}
/**
* TODO: maybe deprecate?
* @returns String giving url to use to report actions (advanced mode only
*/
PROT_GlobalStore.getActionReportURL = function() {
return PROT_GlobalStore.getPref_("safebrowsing.provider.0.reportURL");
}
/**
* @returns String giving url to use for re-keying
*/
PROT_GlobalStore.getGetKeyURL = function() {
return PROT_GlobalStore.getPref_("safebrowsing.provider.0.keyURL");
}

View File

@@ -0,0 +1,172 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// This file implements an event registrar, an object with which you
// can register handlers for arbitrary programmer-defined
// events. Events are arbitrary strings and listeners are functions
// taking an object (stuffed with arguments) as a parameter. When you
// fire an event through the registrar, all listeners are invoked in
// an unspecified order. The firing function takes an object to be
// passed into each handler (it is _not_ copied, so be careful). We
// chose this calling convention so we don't have to change handler
// signatures when adding new information.
//
// Why not just use notifier/observers? Because passing data around
// with them requires either serialization or a new xpcom interface,
// both of which are a pain in the ass.
//
// Example:
//
// // Set up a listener
// this.handleTabload = function(e) {
// foo(e.url);
// bar(e.browser);
// };
//
// // Set up the registrar
// var eventTypes = ["tabload", "tabunload", "tabswitch"];
// var registrar = new EventRegistrar(eventTypes);
// var handler = BindToObject(this.handleTabload, this);
//
// // Register a listener
// registrar.registerListener("tabload", handler);
//
// // Fire an event and remove the listener
// var event = { "url": "http://www", "browser": browser };
// registrar.fire("tabload", event);
// registrar.removeListener("tabload", handler);
//
// TODO: could add ability to cancel further handlers by having listeners
// return a boolean
/**
* EventRegistrars are used to manage user-defined events.
*
* @constructor
* @param eventTypes {Array or Object} Array holding names of events or
* Object holding properties the values of which are
* names (strings) for which listeners can register
*/
function EventRegistrar(eventTypes) {
this.eventTypes = [];
this.listeners_ = {}; // Listener sets, index by event type
if (eventTypes instanceof Array) {
var events = eventTypes;
} else if (typeof eventTypes == "object") {
var events = [];
for (var e in eventTypes)
events.push(eventTypes[e]);
} else {
throw new Error("Unrecognized init parameter to EventRegistrar");
}
for (var i = 0; i < events.length; i++) {
this.eventTypes.push(events[i]); // Copy in case caller mutates
this.listeners_[events[i]] =
new ListDictionary(events[i] + "Listeners");
}
}
/**
* Indicates whether the given event is one the registrar can handle.
*
* @param eventType {String} The name of the event to look up
* @returns {Boolean} false if the event type is not known or the
* event type string itself if it is
*/
EventRegistrar.prototype.isKnownEventType = function(eventType) {
for (var i=0; i < this.eventTypes.length; i++)
if (eventType == this.eventTypes[i])
return eventType;
return false;
}
/**
* Add an event type to listen for.
* @param eventType {String} The name of the event to add
*/
EventRegistrar.prototype.addEventType = function(eventType) {
if (this.isKnownEventType(eventType))
throw new Error("Event type already known: " + eventType);
this.eventTypes.push(eventType);
this.listeners_[eventType] = new ListDictionary(eventType + "Listeners");
}
/**
* Register to receive events of the type passed in.
*
* @param eventType {String} indicating the event type (one of this.eventTypes)
* @param listener {Function} to invoke when the event occurs.
*/
EventRegistrar.prototype.registerListener = function(eventType, listener) {
if (this.isKnownEventType(eventType) === false)
throw new Error("Unknown event type: " + eventType);
this.listeners_[eventType].addMember(listener);
}
/**
* Unregister a listener.
*
* @param eventType {String} One of EventRegistrar.eventTypes' members
* @param listener {Function} Function to remove as listener
*/
EventRegistrar.prototype.removeListener = function(eventType, listener) {
if (this.isKnownEventType(eventType) === false)
throw new Error("Unknown event type: " + eventType);
this.listeners_[eventType].removeMember(listener);
}
/**
* Invoke the handlers for the given eventType.
*
* @param eventType {String} The event to fire
* @param e {Object} Object containing the parameters of the event
*/
EventRegistrar.prototype.fire = function(eventType, e) {
if (this.isKnownEventType(eventType) === false)
throw new Error("Unknown event type: " + eventType);
var invoke = function(listener) {
listener(e);
};
this.listeners_[eventType].forEach(invoke);
}

View File

@@ -0,0 +1,117 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// This file implements a Dictionary data structure using a list
// (array). We could instead use an object, but using a list enables
// us to have ordering guarantees for iterators. The interface it exposes
// is:
//
// addMember(item)
// removeMember(item)
// isMember(item)
// forEach(func)
//
// TODO: this class isn't really a Dictionary, it's more like a
// membership set (i.e., a set without union and whatnot). We
// should probably change the name to avoid confusion.
/**
* Create a new Dictionary data structure.
*
* @constructor
* @param name A string used to name the dictionary
*/
function ListDictionary(name) {
this.name_ = name;
this.members_ = [];
}
/**
* Look an item up.
*
* @param item An item to look up in the dictionary
* @returns Boolean indicating if the parameter is a member of the dictionary
*/
ListDictionary.prototype.isMember = function(item) {
for (var i=0; i < this.members_.length; i++)
if (this.members_[i] == item)
return true;
return false;
}
/**
* Add an item
*
* @param item An item to add (does not check for dups)
*/
ListDictionary.prototype.addMember = function(item) {
this.members_.push(item);
}
/**
* Remove an item
*
* @param item The item to remove (doesn't check for dups)
* @returns Boolean indicating if the item was removed
*/
ListDictionary.prototype.removeMember = function(item) {
for (var i=0; i < this.members_.length; i++) {
if (this.members_[i] == item) {
for (var j=i; j < this.members_.length; j++)
this.members_[j] = this.members_[j+1];
this.members_.length--;
return true;
}
}
return false;
}
/**
* Apply a function to each of the members. Does NOT replace the members
* in the dictionary with results -- it just calls the function on each one.
*
* @param func Function to apply to the dictionary's members
*/
ListDictionary.prototype.forEach = function(func) {
if (typeof func != "function")
throw new Error("argument to forEach is not a function, it's a(n) " +
typeof func);
for (var i=0; i < this.members_.length; i++)
func(this.members_[i]);
}

View File

@@ -0,0 +1,239 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Niels Provos <niels@google.com> (original author)d
* Fritz Schneider <fritz@google.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// A warden that knows how to register lists with a listmanager and keep them
// updated if necessary. The ListWarden also provides a simple interface to
// check if a URL is evil or not. Specialized wardens like the PhishingWarden
// inherit from it.
//
// Classes that inherit from ListWarden are responsible for calling
// enableTableUpdates or disableTableUpdates. This usually entails
// registering prefObservers and calling enable or disable in the base
// class as appropriate.
//
/**
* Abtracts the checking of user/browser actions for signs of
* phishing.
*
* @constructor
*/
function PROT_ListWarden() {
this.debugZone = "listwarden";
var listManager = Cc["@mozilla.org/url-classifier/listmanager;1"]
.getService(Ci.nsIUrlListManager);
this.listManager_ = listManager;
// Once we register tables, their respective names will be listed here.
this.blackTables_ = [];
this.whiteTables_ = [];
}
/**
* Tell the ListManger to keep all of our tables updated
*/
PROT_ListWarden.prototype.enableBlacklistTableUpdates = function() {
for (var i = 0; i < this.blackTables_.length; ++i) {
this.listManager_.enableUpdate(this.blackTables_[i]);
}
}
/**
* Tell the ListManager to stop updating our tables
*/
PROT_ListWarden.prototype.disableBlacklistTableUpdates = function() {
for (var i = 0; i < this.blackTables_.length; ++i) {
this.listManager_.disableUpdate(this.blackTables_[i]);
}
}
/**
* Tell the ListManager to update whitelist tables. They may be enabled even
* when other updates aren't, for performance reasons.
*/
PROT_ListWarden.prototype.enableWhitelistTableUpdates = function() {
for (var i = 0; i < this.whiteTables_.length; ++i) {
this.listManager_.enableUpdate(this.whiteTables_[i]);
}
}
/**
* Tell the ListManager to stop updating whitelist tables.
*/
PROT_ListWarden.prototype.disableWhitelistTableUpdates = function() {
for (var i = 0; i < this.whiteTables_.length; ++i) {
this.listManager_.disableUpdate(this.whiteTables_[i]);
}
}
/**
* Register a new black list table with the list manager
* @param tableName - name of the table to register
* @returns true if the table could be registered, false otherwise
*/
PROT_ListWarden.prototype.registerBlackTable = function(tableName) {
var result = this.listManager_.registerTable(tableName, false);
if (result) {
this.blackTables_.push(tableName);
}
return result;
}
/**
* Register a new white list table with the list manager
* @param tableName - name of the table to register
* @returns true if the table could be registered, false otherwise
*/
PROT_ListWarden.prototype.registerWhiteTable = function(tableName) {
var result = this.listManager_.registerTable(tableName, false);
if (result) {
this.whiteTables_.push(tableName);
}
return result;
}
/**
* Internal method that looks up a url in a set of tables
*
* @param tables array of table names
* @param url URL to look up
* @returns Boolean indicating if the url is in one of the tables
*/
PROT_ListWarden.prototype.isURLInTables_ = function(tables, url) {
for (var i = 0; i < tables.length; ++i) {
if (this.listManager_.safeExists(tables[i], url))
return true;
}
return false;
}
/**
* Internal method that looks up a url in the white list.
*
* @param url URL to look up
* @returns Boolean indicating if the url is on our whitelist
*/
PROT_ListWarden.prototype.isWhiteURL_ = function(url) {
if (!this.listManager_)
return false;
var whitelisted = this.isURLInTables_(this.whiteTables_, url);
if (whitelisted)
G_Debug(this, "Whitelist hit: " + url);
return whitelisted;
}
/**
* External method that looks up a url on the whitelist, used by the
* content-analyzer.
*
* @param url The URL to check
* @returns Boolean indicating if the url is on the whitelist
*/
PROT_ListWarden.prototype.isWhiteURL = function(url) {
return this.isWhiteURL_(url);
}
/**
* Internal method that looks up a url in the black lists. Skips the lookup if
* the URL is on our whitelist(s).
*
* @param url URL to look up
* @returns Boolean indicating if the url is on our blacklist(s)
*/
PROT_ListWarden.prototype.isBlackURL_ = function(url) {
if (!this.listManager_)
return false;
var blacklisted = this.isURLInTables_(this.blackTables_, url);
if (blacklisted)
G_Debug(this, "Blacklist hit: " + url);
return blacklisted;
}
/**
* Internal method that looks up a url in both the white and black lists.
*
* If there is conflict, the white list has precedence over the black list.
*
* @param url URL to look up
* @returns Boolean indicating if the url is phishy.
*/
PROT_ListWarden.prototype.isEvilURL_ = function(url) {
return !this.isWhiteURL_(url) && this.isBlackURL_(url);
}
// Some unittests
// TODO something more appropriate
function TEST_PROT_ListWarden() {
if (G_GDEBUG) {
var z = "listwarden UNITTEST";
G_debugService.enableZone(z);
G_Debug(z, "Starting");
var threadQueue = new TH_ThreadQueue();
var listManager = new PROT_ListManager(threadQueue, true /* testing */);
var warden = new PROT_ListWarden(listManager);
// Just some really simple test
G_Assert(z, warden.registerWhiteTable("test-white-domain"),
"Failed to register test-white-domain table");
G_Assert(z, warden.registerWhiteTable("test-black-url"),
"Failed to register test-black-url table");
listManager.safeInsert("test-white-domain", "http://foo.com/good", "1");
listManager.safeInsert("test-black-url", "http://foo.com/good/1", "1");
listManager.safeInsert("test-black-url", "http://foo.com/bad/1", "1");
G_Assert(z, !warden.isEvilURL_("http://foo.com/good/1"),
"White listing is not working.");
G_Assert(z, warden.isEvilURL_("http://foo.com/bad/1"),
"Black listing is not working.");
G_Debug(z, "PASSED");
}
}

View File

@@ -0,0 +1,272 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// A NavWatcher abstracts away the mechanics of listening for progresslistener-
// based notifications. You register on it to hear these notifications instead
// of on a tabbedbrowser or the docloader service.
//
// Since it hooks the docloader service, you only need one NavWatcher
// per application (as opposed to a TabbedBrowserWatcher, of which you need
// one per tabbedbrowser, meaning one per browser window).
//
// The notifications you can register to hear are:
//
// EVENT DESCRIPTION
// ----- -----------
//
// docnavstart Fires when the request for a Document has begun. This is
// as early a notification as you can get (roughly
// equivalent to STATE_START). As a result, there is no
// guarantee that when the notification fires the request
// has been associated with a Document accessible in a
// browser somewhere; the only guarantee is that there
// _will_ be, baring some major error. You can handle
// such error cases gracefully by examining the nsIRequest
// isPending flag. See phishing-warden.js for an example.
//
// For docnavstart the event object you'll receive will have the following
// properties:
//
// request -- reference to the nsIRequest of the new request
// url -- String containing the URL the request is for (from
// reuest.name, currently, but might be something better
// in the future)
//
// Example:
//
// function handler(e /*event object*/) {
// foo(e.request);
// };
// var watcher = new G_NavWatcher();
// watcher.registerListener("docnavstart", handler);
// watcher.removeListener("docnavstart", handler);
//
// TODO: should probably make both NavWatcher and TabbedBrowserWatcher
// subclasses of EventRegistrar
//
// TODO: might make a stateful NavWatcher, one that takes care of mapping
// requests and urls to Documents.
/**
* The NavWatcher abstracts listening for progresslistener-based
* notifications.
*
* @param opt_filterSpurious Boolean indicating whether to filter events
* for navigations to docs about which you
* probably don't care, such as about:blank,
* chrome://, and file:// URLs.
*
* @constructor
*/
function G_NavWatcher(opt_filterSpurious) {
this.debugZone = "navwatcher";
this.filterSpurious_ = !!opt_filterSpurious;
this.events = G_NavWatcher.events; // Convenience pointer
this.registrar_ = new EventRegistrar(this.events);
var wp = Ci.nsIWebProgress;
var wpService = Cc["@mozilla.org/docloaderservice;1"].getService(wp);
wpService.addProgressListener(this, wp.NOTIFY_STATE_ALL);
}
// Events for which listeners can register. Future additions could include
// things such as docnavstop or navstart (for any navigation).
G_NavWatcher.events = {
DOCNAVSTART: "docnavstart",
};
/**
* We implement nsIWebProgressListener
*/
G_NavWatcher.prototype.QueryInterface = function(iid) {
if (iid.equals(Ci.nsISupports) ||
iid.equals(Ci.nsIWebProgressListener) ||
iid.equals(Ci.nsISupportsWeakReference))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
}
/**
* Register to receive events of a particular type
*
* @param eventType String indicating the event (see
* G_NavWatcher.events)
*
* @param listener Function to invoke when the event occurs. See top-
* level comments for parameters.
*/
G_NavWatcher.prototype.registerListener = function(eventType,
listener) {
this.registrar_.registerListener(eventType, listener);
}
/**
* Unregister a listener.
*
* @param eventType String one of G_NavWatcher.events' members
*
* @param listener Function to remove as listener
*/
G_NavWatcher.prototype.removeListener = function(eventType,
listener) {
this.registrar_.removeListener(eventType, listener);
}
/**
* Send an event to all listeners for that type.
*
* @param eventType String indicating the event to trigger
*
* @param e Object to pass to each listener (NOT copied -- be careful)
*/
G_NavWatcher.prototype.fire = function(eventType, e) {
this.registrar_.fire(eventType, e);
}
/**
* Helper function to determine whether a given URL is "spurious" for some
* definition of "spurious".
*
* @param url String containing the URL to check
*
* @returns Boolean indicating whether Fritz thinks it's too boring to notice
*/
G_NavWatcher.prototype.isSpurious_ = function(url) {
return (url == "about:blank" ||
url == "about:config" ||
url.indexOf("chrome://") == 0 ||
url.indexOf("file://") == 0 ||
url.indexOf("jar:") == 0);
}
/**
* We do our dirtywork on state changes.
*/
G_NavWatcher.prototype.onStateChange = function(webProgress,
request,
stateFlags,
status) {
var wp = Ci.nsIWebProgressListener;
// Debugging stuff
// function D(msg) {
// dump(msg + "\n");
// };
//
// function w(s) {
// if (stateFlags & wp[s])
// D(s);
// };
//
// D("\nState change:");
// try {
// D("URL: " + request.name);
// } catch (e) {};
// w("STATE_IS_REQUEST");
// w("STATE_IS_DOCUMENT");
// w("STATE_IS_NETWORK");
// w("STATE_IS_WINDOW");
// w("STATE_STOP");
// w("STATE_START");
// w("STATE_REDIRECTING");
// w("STATE_TRANSFERRING");
// w("STATE_NEGOTIATING");
// Thanks Darin for helping with this
if (stateFlags & wp.STATE_START &&
stateFlags & wp.STATE_IS_REQUEST &&
request.loadFlags & Ci.nsIChannel.LOAD_DOCUMENT_URI) {
var url;
try {
url = request.name;
} catch(e) { return; }
if (!this.filterSpurious_ || !this.isSpurious_(url)) {
G_Debug(this, "firing docnavstart for " + url);
var eventObj = {
"request": request,
"url": url,
};
this.fire(this.events.DOCNAVSTART, eventObj);
}
}
}
// We don't care about the other kinds of updates, but we need to
// implement the interface anyway.
/**
* NOP
*/
G_NavWatcher.prototype.onLocationChange = function(webProgress,
request,
location) { }
/**
* NOP
*/
G_NavWatcher.prototype.onProgressChange = function(webProgress,
request,
curSelfProgress,
maxSelfProgress,
curTotalProgress,
maxTotalProgress) { }
/**
* NOP
*/
G_NavWatcher.prototype.onSecurityChange = function(webProgress,
request,
state) { }
/**
* NOP
*/
G_NavWatcher.prototype.onStatusChange = function(webProgress,
request,
status,
message) { }
/**
* NOP
*/
G_NavWatcher.prototype.onLinkIconAvailable = function(browser, aHref) { }

View File

@@ -0,0 +1,810 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// This file implements a G_TabbedBrowserWatcher, an object
// encapsulating and abstracting the mechanics of working with tabs
// and the documents within them. The watcher provides notification of
// various DOM-related events (a document loaded, a document unloaded,
// tab was created/destroyed, user switched tabs, etc.) as well as
// commonly required methods (get me the current tab, find the tab to
// which this document belongs, etc.).
//
// This class does not do progresslistener-based notifications; for that,
// use the NavWatcher.
//
// Note: I use "browser" and "tab" interchangeably.
//
// This class adds a level of indirection to event registration. You
// initialize it with a tabbedbrowser, and then register to hear
// events from it instead of from the tabbedbrowser or browser itself.
// Your handlers are invoked with a custom object as an argument (see
// below). This object contains useful information such as a reference
// to the browser in which the event is happening and whether the
// event is occurring on the top-level document in that browser.
//
// The events you can register to hear are:
//
// EVENT DESCRIPTION
// ----- -----------
// load Fires when a page is shown in the browser window and
// this page wasn't in the bfcache
//
// unload Fires when a page is nav'd away from in the browser window
// and the page isn't going into the bfcache
//
// pageshow Fires when a page is shown in the browser window, whether
// it came from bfcache or not. (There is a "persisted"
// property we can get from the event object if we'd like.
// It indicates whether the page was loaded from bfcache.
// If false then we known load recently fired).
//
// pagehide Fires when a page is nav'd away from in the browser,
// whether its going into the bfcache or not. (There is
// a persisted property here as well that we're not
// propagating -- when its true the page is going into
// the bfcache, else it's not, and unload will shortly
// fire).
//
// domcontentloaded BROKEN BROKEN BROKEN BROKEN BROKEN BROKEN BROKEN BROKEN
// Fires when a doc's DOM is ready, but its externally linked
// content hasn't necessarily loaded. This event is
// currently broken: it doesn't fire when using the
// forward/back buttons in conjunction with the bfcache.
// Bryner is working on a fix.
//
// tabload Fires when a new tab has been created (but doesn't
// necessarily have content loaded in it)
//
// tabunload Fires when a tab is being destroyed (and might have had
// the content it contains destroyed)
//
// tabswitch Fires when the user switches tabs
//
// tabmove Fires when a user drags a tab to a different position
//
//
// For pageshow, pagehide, load, unload, and domcontentloaded, the event
// object you'll receive has the following properties:
//
// doc -- reference to Document on which the event fired
// browser -- the browser in which the document resides
// isTop -- boolean indicating if it is the top-level document
// inSelected -- boolean indicating if it is in the currently selected tab
//
// For tabload and unload it has:
//
// browser -- reference to the browser that is loading or closing
//
// For tabswitch it has:
//
// fromBrowser -- reference to the browser user is switching from
// toBrowser -- reference to the browser user is switching to
//
// For tabmove it has:
//
// tab -- the tab that was moved (Note that this is the actual
// tab widget that holds the document title, not the
// browser object. We use this because it's the target
// of the DOMNodeInserted event.)
// fromIndex -- the tab index before the move
// toIndex -- the tab index after the move
//
//
// The order of events is: tabload
// domcontentloaded
// load
// pageshow
// -- --
// pagehide
// unload
// tabunload
//
// Example:
//
// function handler(e /*event object*/) {
// foo(e.browser);
// };
// var watcher = new G_TabbedBrowserWatcher(document.getElementById(gBrowser));
// watcher.registerListener("load", handler); // register for events
// watcher.removeListener("load", handler); // unregister
//
//
// TODO, BUGS, ISSUES, COMPLICATIONS:
//
// The only major problem is in closing tabs:
//
// + When you close a tab, the reference to the Document you get in the
// unload event is undefined. We pass this along. This could easily
// be fixed by not listening for unload at all, but instead inferring
// it from the information in pagehide, and then firing our own "fake"
// unload after firing pagehide.
//
// + There's no docshell during the pagehide event, so we can't determine
// if the document is top-level. We pass undefined in this case.
//
// + Though the browser reference during tabunload will be valid, its
// members most likely have already been torn down. Use it in an
// objectsafemap to keep state if you need its members.
//
// + The event listener DOMNodeInserted has the potential to cause
// performance problems if there are too many events fired. It
// should be ok, since we inserted it as far as possible into
// the xul tree.
//
//
// TODO: need better enforcement of unique names. Two tabbedbrowserwatchers
// with the same name will clobber each other because they use that
// name to mark browsers they've seen.
//
// TODO: the functions that iterate of windows and documents badly need
// to be made cleaner. Right now we have multiple implementations
// that essentially do the same thing :(
//
// But good enough for government work.
/**
* Encapsulates tab-related information. You can use the
* G_TabbedBrowserWatcher to watch for events on tabs as well as to
* retrieve tab-related data (such as what tab is currently showing).
* It receives many event notifications from G_BrowserWatchers it
* attaches to newly opening tabs.
*
* @param tabBrowser A reference to the tabbed browser you wish to watch.
*
* @param name String containing a probabilistically unique name. Used to
* ensure that each tabbedbrowserwatcher can uniquely mark
* browser it has "seen."
*
* @param opt_filterAboutBlank Boolean indicating whether to filter events
* for about:blank. These events are often
* spurious since about:blank is the default
* page for an empty browser.
*
* @constructor
*/
function G_TabbedBrowserWatcher(tabBrowser, name, opt_filterAboutBlank) {
this.debugZone = "tabbedbrowserwatcher";
this.registrar_ = new EventRegistrar(G_TabbedBrowserWatcher.events);
this.tabBrowser_ = tabBrowser;
this.filterAboutBlank_ = !!opt_filterAboutBlank;
this.events = G_TabbedBrowserWatcher.events; // Convenience pointer
// We need some way to tell if we've seen a browser before, so we
// set a property on it with a probabilistically unique string. The
// string is a combination of a static string and one passed in by
// the caller.
G_Assert(this, typeof name == "string" && !!name,
"Need a probabilistically unique name");
this.name_ = name;
this.mark_ = G_TabbedBrowserWatcher.mark_ + "-" + this.name_;
this.tabbox_ = this.getTabBrowser().mTabBox;
// We watch for events occuring in previously unseen browsers at
// this (the tabbedbrowser) level, and attach listeners to the new
// browsers when we see them. In order to do this properly, we need
// to watch for the earliest event we're interested in
// (DOMContentLoaded), because otherwise we'd miss this event in newly
// opening browsers. For example, if instead we hooked load here,
// DOMContentLoaded would already have passed by the time we noticed
// there was a new browser.
this.onDOMContentLoadedClosure_ = BindToObject(this.onDOMContentLoaded, this)
this.tabbox_.addEventListener("DOMContentLoaded",
this.onDOMContentLoadedClosure_, true);
// We watch for DOM nodes inserted under the tabbox so we can detect when
// a user drags a tab to a new location.
this.onDOMNodeInsertedClosure_ = BindToObject(this.onDOMNodeInserted, this);
this.tabbox_.addEventListener("DOMNodeInserted",
this.onDOMNodeInsertedClosure_, true);
// There's no tabswitch event in Firefox, so we fake it by watching
// for selects on the tabbox.
this.onTabSwitchClosure_ = BindToObject(this.onTabSwitch, this);
this.tabbox_.addEventListener("select",
this.onTabSwitchClosure_, true);
// Used to determine when the user has switched tabs
this.lastTab_ = this.getCurrentBrowser();
// Ensure we hook a G_BrowserWatcher to all tabs that are open at startup
this.detectNewTabs_();
}
// Events for which listeners can register
G_TabbedBrowserWatcher.events = {
DOMCONTENTLOADED: "domcontentloaded",
PAGESHOW: "pageshow",
PAGEHIDE: "pagehide",
LOAD: "load",
UNLOAD: "unload",
TABLOAD: "tabload",
TABUNLOAD: "tabunload",
TABSWITCH: "tabswitch",
TABMOVE: "tabmove",
};
// We mark new tabs as we see them
G_TabbedBrowserWatcher.mark_ = "watcher-marked";
/**
* Remove all the event handlers and clean up circular refs.
*/
G_TabbedBrowserWatcher.prototype.shutdown = function() {
G_Debug(this, "Removing event listeners");
if (this.tabbox_) {
this.tabbox_.removeEventListener("DOMContentLoaded",
this.onDOMContentLoadedClosure_, true);
this.tabbox_.removeEventListener("DOMNodeInserted",
this.onDOMNodeInsertedClosure_, true);
this.tabbox_.removeEventListener("select",
this.onTabSwitchClosure_, true);
// Break circular ref so we can be gc'ed.
this.tabbox_ = null;
}
// Break circular ref so we can be gc'ed.
if (this.lastTab_) {
this.lastTab_ = null;
}
if (this.tabBrowser_) {
this.tabBrowser_ = null;
}
}
/**
* Check to see if we've seen a browser before
*
* @param browser Browser to check
* @returns Boolean indicating if we've attached a BrowserWatcher to this
* browser
*/
G_TabbedBrowserWatcher.prototype.isInstrumented_ = function(browser) {
return !!browser[this.mark_];
}
/**
* Attaches a BrowserWatcher to a browser and marks it as seen
*
* @param browser Browser to which to attach a G_BrowserWatcher
*/
G_TabbedBrowserWatcher.prototype.instrumentBrowser_ = function(browser) {
G_Assert(this, !this.isInstrumented_(browser),
"Browser already instrumented!");
// The browserwatcher will hook itself into the browser and its parent (us)
new G_BrowserWatcher(this, browser);
browser[this.mark_] = true;
}
/**
* Attach BrowserWatchers to all open, unseen tabs
*/
G_TabbedBrowserWatcher.prototype.detectNewTabs_ = function() {
var tb = this.getTabBrowser();
for (var i = 0; i < tb.browsers.length; ++i)
this.maybeFireTabLoad(tb.browsers[i]);
}
/**
* Register to receive events of a particular type
*
* @param eventType String indicating the event (see
* G_TabbedBrowserWatcher.events)
* @param listener Function to invoke when the event occurs. See top-
* level comments for parameters.
*/
G_TabbedBrowserWatcher.prototype.registerListener = function(eventType,
listener) {
this.registrar_.registerListener(eventType, listener);
}
/**
* Unregister a listener.
*
* @param eventType String one of G_TabbedBrowserWatcher.events' members
* @param listener Function to remove as listener
*/
G_TabbedBrowserWatcher.prototype.removeListener = function(eventType,
listener) {
this.registrar_.removeListener(eventType, listener);
}
/**
* Send an event to all listeners for that type.
*
* @param eventType String indicating the event to trigger
* @param e Object to pass to each listener (NOT copied -- be careful)
*/
G_TabbedBrowserWatcher.prototype.fire = function(eventType, e) {
this.registrar_.fire(eventType, e);
}
/**
* Convenience function to send a document-related event. We use this
* convenience function because the event constructing logic and
* parameters are the same for all these events. (Document-related
* events are load, unload, pagehide, pageshow, and domcontentloaded).
*
* @param eventType String indicating the type of event to fire (one of
* the document-related events)
*
* @param doc Reference to the HTMLDocument the event is occuring to
*
* @param browser Reference to the browser in which the document is contained
*/
G_TabbedBrowserWatcher.prototype.fireDocEvent_ = function(eventType,
doc,
browser) {
// If we've already shutdown, don't bother firing any events.
if (!this.tabBrowser_) {
G_Debug(this, "Firing event after shutdown: " + eventType);
return;
}
try {
// Could be that the browser's contentDocument has already been torn
// down. If so, this throws, and we can't tell without keeping more
// state whether doc was the top frame.
var isTop = (doc == browser.contentDocument);
} catch(e) {
var isTop = undefined;
}
var inSelected = (browser == this.getCurrentBrowser());
var location = doc ? doc.location.href : undefined;
// Only send notifications for about:config's if we're supposed to
if (!this.filterAboutBlank_ || location != "about:blank") {
G_Debug(this, "firing " + eventType + " for " + location +
(isTop ? " (isTop)" : "") + (inSelected ? " (inSelected)" : ""));
this.fire(eventType, { "doc": doc,
"isTop": isTop,
"inSelected": inSelected,
"browser": browser});
}
}
/**
* Invoked on a browser to ensure we've seen it before. If we haven't,
* the browser is instrumented and the tabload event is fired.
*
* @param browser Reference to the browser to check
*/
G_TabbedBrowserWatcher.prototype.maybeFireTabLoad = function(browser) {
if (!this.isInstrumented_(browser)) { // Is it a new browser?
this.instrumentBrowser_(browser); // Add a G_BrowserWatcher
G_Debug(this, "firing tabload");
// And shoot notification
this.fire(this.events.TABLOAD, { "browser": browser });
}
}
/**
* Invoked when the document content has loaded for a document. Externally
* linked in content might not yet have loaded.
*
* @param e Event object
*/
G_TabbedBrowserWatcher.prototype.onDOMContentLoaded = function(e) {
G_Debug(this, "onDOMContentLoaded for a " + e.target);
var doc = e.target;
var browser = this.getBrowserFromDocument(doc);
if (!browser) {
G_Debug(this, "domcontentloaded: no browser for " + doc.location.href);
return;
}
this.maybeFireTabLoad(browser);
G_Debug(this, "DOMContentLoaded broken for forward/back buttons.");
this.fireDocEvent_(this.events.DOMCONTENTLOADED, doc, browser);
}
/**
* Invoked when a new xul node is inserted under the tabbox. We use this
* to detect tab moves.
*
* @param e Event object
*/
G_TabbedBrowserWatcher.prototype.onDOMNodeInserted = function(e) {
G_Debug(this, "onDOMNodeInserted for a " + e.target +
" related: " + e.relatedNode);
// Ignore the node insertion if it isn't a tab
if (e.target.localName != "tab") {
return;
}
// If the tab was just inserted (it's a new tab, not a moved tab), the
// pos value will be undefined.
if (!isDef(e.target._tPos)) {
return;
}
// Get the target tab's old position
var fromPos = e.target._tPos;
// Get the target tab's new position.
// Would like to avoid a linear search through the tabs but I'm not sure
// how to get around this.
var toPos;
for (var i = 0; i < e.relatedNode.childNodes.length; i++) {
var child = e.relatedNode.childNodes[i];
if (child == e.target) {
toPos = i;
break;
}
}
G_Debug(this, "firing tabmove");
this.fire(this.events.TABMOVE, { "tab": e.target,
"fromIndex": fromPos,
"toIndex": toPos } );
}
/**
* Invoked when the user might have switched tabs
*
* @param e Event object
*/
G_TabbedBrowserWatcher.prototype.onTabSwitch = function(e) {
// Filter spurious events
// The event target is usually tabs but can be tabpanels when tabs were opened
// programatically via tabbrowser.addTab().
if (e.target == null ||
(e.target.localName != "tabs" && e.target.localName != "tabpanels"))
return;
var fromBrowser = this.lastTab_;
var toBrowser = this.getCurrentBrowser();
if (fromBrowser != toBrowser) {
this.lastTab_ = toBrowser;
G_Debug(this, "firing tabswitch");
this.fire(this.events.TABSWITCH, { "fromBrowser": fromBrowser,
"toBrowser": toBrowser });
}
}
// Utility functions
/**
* Returns a reference to the tabbed browser this G_TabbedBrowserWatcher
* was initialized with.
*/
G_TabbedBrowserWatcher.prototype.getTabBrowser = function() {
return this.tabBrowser_;
}
/**
* Returns a reference to the currently selected tab.
*/
G_TabbedBrowserWatcher.prototype.getCurrentBrowser = function() {
return this.getTabBrowser().selectedBrowser;
}
/**
* Returns a reference to the top window in the currently selected tab.
*/
G_TabbedBrowserWatcher.prototype.getCurrentWindow = function() {
return this.getCurrentBrowser().contentWindow;
}
/**
* Find the browser corresponding to a Document
*
* @param doc Document we want the browser for
* @returns Reference to the browser in which the given document is found
* or null if not found
*/
G_TabbedBrowserWatcher.prototype.getBrowserFromDocument = function(doc) {
// Could instead get the top window of the browser in which the doc
// is found via doc.defaultView.top, but sometimes the document
// isn't in a browser at all (it's being unloaded, for example), so
// defaultView won't be valid.
// Helper: return true if doc is a sub-document of win
function docInWindow(doc, win) {
if (win.document == doc)
return true;
if (win.frames)
for (var i = 0; i < win.frames.length; i++)
if (docInWindow(doc, win.frames[i]))
return true;
return false;
}
var browsers = this.getTabBrowser().browsers;
for (var i = 0; i < browsers.length; i++)
if (docInWindow(doc, browsers[i].contentWindow))
return browsers[i];
return null;
}
/**
* Find the Document that has the given URL loaded. Returns on the
* _first_ such document found, so be careful.
*
* TODO make doc/window searches more elegant, and don't use inner functions
*
* @param url String indicating the URL we're searching for
* @param opt_browser Optional reference to a browser. If given, the
* search will be confined to only this browser.
* @returns Reference to the Document with that URL or null if not found
*/
G_TabbedBrowserWatcher.prototype.getDocumentFromURL = function(url,
opt_browser) {
// Helper function: return the Document in win that has location of url
function docWithURL(win, url) {
if (win.document.location.href == url)
return win.document;
if (win.frames)
for (var i = 0; i < win.frames.length; i++) {
var rv = docWithURL(win.frames[i], url);
if (rv)
return rv;
}
return null;
}
if (opt_browser)
return docWithURL(opt_browser.contentWindow, url);
var browsers = this.getTabBrowser().browsers;
for (var i=0; i < browsers.length; i++) {
var rv = docWithURL(browsers[i].contentWindow, url);
if (rv)
return rv;
}
return null;
}
/**
* Find the all Documents that have the given URL loaded.
*
* TODO make doc/window searches more elegant, and don't use inner functions
*
* @param url String indicating the URL we're searching for
*
* @param opt_browser Optional reference to a browser. If given, the
* search will be confined to only this browser.
*
* @returns Array of Documents with the given URL (zero length if none found)
*/
G_TabbedBrowserWatcher.prototype.getDocumentsFromURL = function(url,
opt_browser) {
var docs = [];
// Helper function: add Docs in win with the location of url
function getDocsWithURL(win, url) {
if (win.document.location.href == url)
docs.push(win.document);
if (win.frames)
for (var i = 0; i < win.frames.length; i++)
getDocsWithURL(win.frames[i], url);
}
if (opt_browser)
return getDocsWithURL(opt_browser.contentWindow, url);
var browsers = this.getTabBrowser().browsers;
for (var i=0; i < browsers.length; i++)
getDocsWithURL(browsers[i].contentWindow, url);
return docs;
}
/**
* Finds the browser in which a Window resides.
*
* @param sub Window to find
* @returns Reference to the browser in which sub resides, else null
* if not found
*/
G_TabbedBrowserWatcher.prototype.getBrowserFromWindow = function(sub) {
// Helpfer function: return true if sub is a sub-window of win
function containsSubWindow(sub, win) {
if (win == sub)
return true;
if (win.frames)
for (var i=0; i < win.frames.length; i++)
if (containsSubWindow(sub, win.frames[i]))
return true;
return false;
}
var browsers = this.getTabBrowser().browsers;
for (var i=0; i < browsers.length; i++)
if (containsSubWindow(sub, browsers[i].contentWindow))
return browsers[i];
return null;
}
/**
* Finds the XUL <tab> tag corresponding to a given browser.
*
* @param tabBrowser Reference to the tabbed browser in which browser lives
* @param browser Reference to the browser we wish to find the tab of
* @returns Reference to the browser's tab element, or null
* @static
*/
G_TabbedBrowserWatcher.getTabElementFromBrowser = function(tabBrowser,
browser) {
for (var i=0; i < tabBrowser.browsers.length; i++)
if (tabBrowser.browsers[i] == browser)
return tabBrowser.mTabContainer.childNodes[i];
return null;
}
if (G_GDEBUG) {
G_debugService.loggifier.loggify(G_TabbedBrowserWatcher.prototype);
}
/**
* The G_TabbedBrowserWatcher delegates watching most events in browsers
* to this object. It calls into its parent (the G_TabbedBrowserWatcher)
* to signal events and is garbage collected when the browser goes away
* because we don't hold a reference to it.
*
* @constructor
* @param tabbedBrowserWatcher The high-level watcher through which we
* should send notifications
* @param browser The browser to which we should attach
*/
function G_BrowserWatcher(tabbedBrowserWatcher, browser) {
this.debugZone = "browserwatcher";
this.parent_ = tabbedBrowserWatcher;
this.browser_ = browser;
G_Debug(this, "new G_BrowserWatcher");
// Now register to hear most of the doc-related events
this.onPageShowClosure_ = BindToObject(this.onPageShow, this);
this.browser_.addEventListener("pageshow", this.onPageShowClosure_, true);
this.onPageHideClosure_ = BindToObject(this.onPageHide, this);
this.browser_.addEventListener("pagehide", this.onPageHideClosure_, true);
this.onLoadClosure_ = BindToObject(this.onLoad, this);
this.browser_.addEventListener("load", this.onLoadClosure_, true);
this.onUnloadClosure_ = BindToObject(this.onUnload, this);
this.browser_.addEventListener("unload", this.onUnloadClosure_, true);
}
/**
* Invoked when pageshow fires
*
* @param e Event object passed in by event system
*/
G_BrowserWatcher.prototype.onPageShow = function(e) {
G_Debug(this, "onPageShow for " + ((e.target) ? (e.target) : ("undefined")));
if (e.target && e.target.nodeName == "#document") {
var doc = e.target;
this.parent_.fireDocEvent_(this.parent_.events.PAGESHOW,
doc,
this.browser_);
}
}
/**
* Invoked when load fires
*
* @param e Event object passed in by event system
*/
G_BrowserWatcher.prototype.onLoad = function(e) {
G_Debug(this, "onLoad for a " + e.target);
if (e.target.nodeName != "#document")
return;
var doc = e.target;
this.parent_.fireDocEvent_(this.parent_.events.LOAD, doc, this.browser_);
}
/**
* Invoked when unload fires
*
* @param e Event object passed in by event system
*/
G_BrowserWatcher.prototype.onUnload = function(e) {
G_Debug(this, "onUnload for " + ((e.target) ? (e.target) : ("undefined")));
var doc = e.target;
// We get spurious unloads for non-docs :(
if (doc && doc.nodeName == "#document")
this.parent_.fireDocEvent_("unload", doc, this.browser_);
if (!doc) { // This is a closing tab
G_Debug(this, "firing tabunload for a " + this.browser_ + "(" +
this.browser_.nodename + ")");
// fire tabunload event
this.parent_.fire(this.parent_.events.TABUNLOAD,
{ "browser": this.browser_ });
// unregister event listeners
this.browser_.removeEventListener("pageshow", this.onPageShowClosure_, true);
this.browser_.removeEventListener("pagehide", this.onPageHideClosure_, true);
this.browser_.removeEventListener("load", this.onLoadClosure_, true);
this.browser_.removeEventListener("unload", this.onUnloadClosure_, true);
this.parent_ = null;
this.browser_ = null;
G_Debug(this, "Removing event listeners");
}
}
/**
* Invoked when pagehide fires
*
* @param e Event object passed in by event system
*/
G_BrowserWatcher.prototype.onPageHide = function(e) {
G_Debug(this, "onPageHide for a " + e.target + "(" +
e.target.nodeName + ")");
if (e.target.nodeName != "#document") // Ignore non-documents
return;
var doc = e.target;
this.parent_.fireDocEvent_(this.parent_.events.PAGEHIDE, doc, this.browser_);
}

View File

@@ -0,0 +1,643 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// Implementation of the warning message we show users when we
// notice navigation to a phishing page after it has loaded. The
// browser view encapsulates all the hide/show logic, so the displayer
// doesn't need to know when to display itself, only how.
//
// Displayers implement the following interface:
//
// start() -- fired to initialize the displayer (to make it active). When
// called, this displayer starts listening for and responding to
// events. At most one displayer per tab should be active at a
// time, and start() should be called at most once.
// declineAction() -- fired when the user declines the warning.
// acceptAction() -- fired when the user declines the warning
// explicitShow() -- fired when the user wants to see the warning again
// browserSelected() -- the browser is the top tab
// browserUnselected() -- the browser is no long the top tab
// done() -- clean up. May be called once (even if the displayer wasn't
// activated).
//
// At the moment, all displayers share access to the same xul in
// safebrowsing-overlay.xul. Hence the need for at most one displayer
// to be active per tab at a time.
/**
* Factory that knows how to create a displayer appropriate to the
* user's platform. We use a clunky canvas-based displayer for all
* platforms until such time as we can overlay semi-transparent
* areas over browser content.
*
* See the base object for a description of the constructor args
*
* @constructor
*/
function PROT_PhishMsgDisplayer(msgDesc, browser, doc, url) {
// TODO: Change this to return a PhishMsgDisplayerTransp on windows
// (and maybe other platforms) when Firefox 2.0 hits.
return new PROT_PhishMsgDisplayerCanvas(msgDesc, browser, doc, url);
}
/**
* Base class that implements most of the plumbing required to hide
* and show a phishing warning. Subclasses implement the actual
* showMessage and hideMessage methods.
*
* This class is not meant to be instantiated directly.
*
* @param msgDesc String describing the kind of warning this is
* @param browser Reference to the browser over which we display the msg
* @param doc Reference to the document in which browser is found
* @param url String containing url of the problem document
* @constructor
*/
function PROT_PhishMsgDisplayerBase(msgDesc, browser, doc, url) {
this.debugZone = "phisdisplayer";
this.msgDesc_ = msgDesc; // currently unused
this.browser_ = browser;
this.doc_ = doc;
this.url_ = url;
// We'll need to manipulate the XUL in safebrowsing-overlay.xul
this.messageId_ = "safebrowsing-palm-message";
this.messageTailId_ = "safebrowsing-palm-message-tail-container";
this.messageContentId_ = "safebrowsing-palm-message-content";
this.extendedMessageId_ = "safebrowsing-palm-extended-message";
this.showmoreLinkId_ = "safebrowsing-palm-showmore-link";
this.urlbarIconId_ = "safebrowsing-urlbar-icon";
this.refElementId_ = this.urlbarIconId_;
// We use this to report user actions to the server
this.reporter_ = new PROT_Reporter();
// The active UI elements in our warning send these commands; bind them
// to their handlers but don't register the commands until we start
// (because another displayer might be active)
this.commandHandlers_ = {
"safebrowsing-palm-showmore":
BindToObject(this.showMore_, this),
"safebrowsing-palm-phishingorg":
BindToObject(this.showURL_, this, PROT_GlobalStore.getAntiPhishingURL()),
"safebrowsing-palm-phishingfaq":
BindToObject(this.showURL_, this, PROT_GlobalStore.getPhishingFaqURL()),
"safebrowsing-palm-fraudpage" :
BindToObject(this.showURL_, this, PROT_GlobalStore.getHomePageURL()),
"safebrowsing-palm-falsepositive":
BindToObject(this.showURL_, this, PROT_GlobalStore.getFalsePositiveURL()),
};
this.windowWatcher_ =
Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
.getService(Components.interfaces.nsIWindowWatcher);
}
/**
* @returns The default background color of the browser
*/
PROT_PhishMsgDisplayerBase.prototype.getBackgroundColor_ = function() {
var pref = Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefBranch);
return pref.getCharPref("browser.display.background_color");
}
/**
* Fired when the user declines our warning. Report it!
*/
PROT_PhishMsgDisplayerBase.prototype.declineAction = function() {
G_Debug(this, "User declined warning.");
G_Assert(this, this.started_, "Decline on a non-active displayer?");
this.reporter_.report("phishdecline", this.url_);
this.messageShouldShow_ = false;
if (this.messageShowing_)
this.hideMessage_();
}
/**
* Fired when the user accepts our warning
*/
PROT_PhishMsgDisplayerBase.prototype.acceptAction = function() {
G_Assert(this, this.started_, "Accept on an unstarted displayer?");
G_Assert(this, this.done_, "Accept on a finished displayer?");
G_Debug(this, "User accepted warning.");
this.reporter_.report("phishaccept", this.url_);
var url = PROT_GlobalStore.getGetMeOutOfHereURL();
this.browser_.loadURI(url);
}
/**
* Invoked when the browser is resized
*/
PROT_PhishMsgDisplayerBase.prototype.onBrowserResized_ = function(event) {
G_Debug(this, "Got resize for " + event.target.nodeName);
if (event.target == this.doc_) {
G_Debug(this, "User resized browser.");
if (this.messageShowing_) {
this.hideMessage_();
this.showMessage_();
}
}
}
/**
* Invoked by the browser view when our browser is switched to
*/
PROT_PhishMsgDisplayerBase.prototype.browserSelected = function() {
G_Assert(this, this.started_, "Displayer selected before being started???");
// If messageshowing hasn't been set, then this is the first time this
// problematic browser tab has been on top, so do our setup and show
// the warning.
if (this.messageShowing_ === undefined) {
this.messageShouldShow_ = true;
this.ensureNavBarShowing_();
}
this.hideLockIcon_(); // Comes back when we are unselected or unloaded
this.addWarningInUrlbar_(); // Goes away when we are unselected or unloaded
// messageShouldShow might be false if the user dismissed the warning,
// switched tabs, and then switched back. We're still active, but don't
// want to show the warning again. The user can cause it to show by
// clicking our icon in the urlbar.
if (this.messageShouldShow_)
this.showMessage_();
}
/**
* Invoked to display the warning message explicitly, for example if the user
* clicked the url warning icon.
*/
PROT_PhishMsgDisplayerBase.prototype.explicitShow = function() {
this.messageShouldShow_ = true;
if (!this.messageShowing_)
this.showMessage_();
}
/**
* Invoked by the browser view when our browser is switched away from
*/
PROT_PhishMsgDisplayerBase.prototype.browserUnselected = function() {
this.removeWarningInUrlbar_();
this.unhideLockIcon_();
if (this.messageShowing_)
this.hideMessage_();
}
/**
* Invoked to make this displayer active. The displayer will now start
* responding to notifications such as commands and resize events. We
* can't do this in the constructor because there might be many
* displayers instantiated waiting in the problem queue for a particular
* browser (e.g., a page has multiple framed problem pages), and we
* don't want them all responding to commands!
*
* Invoked zero (the page we're a warning for was nav'd away from
* before it reaches the head of the problem queue) or one (we're
* displaying this warning) times by the browser view.
*/
PROT_PhishMsgDisplayerBase.prototype.start = function() {
G_Assert(this, this.started_ == undefined, "Displayer started twice?");
this.started_ = true;
this.commandController_ = new PROT_CommandController(this.commandHandlers_);
this.doc_.defaultView.controllers.appendController(this.commandController_);
this.resizeHandler_ = BindToObject(this.onBrowserResized_, this);
this.doc_.defaultView.addEventListener("resize",
this.resizeHandler_,
true);
}
/**
* @returns Boolean indicating whether this displayer is currently
* active
*/
PROT_PhishMsgDisplayerBase.prototype.isActive = function() {
return !!this.started_;
}
/**
* Invoked by the browser view to clean up after the user is done
* interacting with the message. Should be called once by the browser
* view.
*/
PROT_PhishMsgDisplayerBase.prototype.done = function() {
G_Assert(this, !this.done_, "Called done more than once?");
this.done_ = true;
// If the Document we're showing the warning for was nav'd away from
// before we had a chance to get started, we have nothing to do.
if (this.started_) {
// If we were started, we must be the current problem, so these things
// must be showing
this.removeWarningInUrlbar_();
this.unhideLockIcon_();
// Could be though that they've closed the warning dialog
if (this.messageShowing_)
this.hideMessage_();
if (this.resizeHandler_) {
this.doc_.defaultView.removeEventListener("resize",
this.resizeHandler_,
true);
this.resizeHandler_ = null;
}
var win = this.doc_.defaultView;
win.controllers.removeController(this.commandController_);
this.commandController_ = null;
}
}
/**
* Helper function to remove a substring from inside a string.
*
* @param orig String to remove substring from
*
* @param toRemove String to remove (if it is present)
*
* @returns String with the substring removed
*/
PROT_PhishMsgDisplayerBase.prototype.removeIfExists_ = function(orig,
toRemove) {
var pos = orig.indexOf(toRemove);
if (pos != -1)
orig = orig.substring(0, pos) + orig.substring(pos + toRemove.length);
return orig;
}
/**
* Our message is displayed relative to an icon in the urlbar. However
* it could be the case that the urlbar isn't showing, in which case
* we would show the warning message some place crazy, such as
* offscreen. So here we put it back if it's not there. This is a
* good thing anyway: the user should be able to see the URL of the
* site if we think it is suspicious.
*/
PROT_PhishMsgDisplayerBase.prototype.ensureNavBarShowing_ = function() {
// Could be that the navbar was unselected in the Toolbars menu; fix it
var navbar = this.doc_.getElementById("nav-bar");
navbar.setAttribute("collapsed", false);
// Now for the more complicated case: a popup window with toolbar=no. This
// adds toolbar and location (among other things) to the chromhidden
// attribute on the window. We need to undo this, and manually fill in the
// location.
var win = this.doc_.getElementById("main-window");
var hidden = win.getAttribute("chromehidden");
G_Debug(this, "Old chromehidden string: " + hidden);
var newHidden = this.removeIfExists_(hidden, "toolbar");
newHidden = this.removeIfExists_(newHidden, "location");
if (newHidden != hidden) {
G_Debug(this, "New chromehidden string: " + newHidden);
win.setAttribute("chromehidden", newHidden);
// Now manually reflect the location
var urlbar = this.doc_.getElementById("urlbar");
urlbar.value = this.doc_.getElementById("content")
.contentDocument.location.href;
}
}
/**
* We don't want to confuse users if they land on a phishy page that uses
* SSL, so ensure that the lock icon never shows when we're showing our
* warning.
*/
PROT_PhishMsgDisplayerBase.prototype.hideLockIcon_ = function() {
var lockIcon = this.doc_.getElementById("lock-icon");
lockIcon.hidden = true;
}
/**
* Ensure they can see it after our warning is finished.
*/
PROT_PhishMsgDisplayerBase.prototype.unhideLockIcon_ = function() {
var lockIcon = this.doc_.getElementById("lock-icon");
lockIcon.hidden = false;
}
/**
* This method makes our warning icon visible in the location bar. It will
* be removed only when the problematic document is navigated awy from
* (i.e., when done() is called), and not when the warning is dismissed.
*/
PROT_PhishMsgDisplayerBase.prototype.addWarningInUrlbar_ = function() {
var urlbarIcon = this.doc_.getElementById(this.urlbarIconId_);
urlbarIcon.style.display = "";
}
/**
* Hides our urlbar icon
*/
PROT_PhishMsgDisplayerBase.prototype.removeWarningInUrlbar_ = function() {
var urlbarIcon = this.doc_.getElementById(this.urlbarIconId_);
urlbarIcon.style.display = "none";
}
/**
* VIRTUAL -- Displays the warning message
*/
PROT_PhishMsgDisplayerBase.prototype.showMessage_ = function() { };
/**
* VIRTUAL -- Hide the warning message from the user.
*/
PROT_PhishMsgDisplayerBase.prototype.hideMessage_ = function() { };
/**
* Reposition the message relative to refElement in the parent window
*
* @param message Reference to the element to position
* @param tail Reference to the message tail
* @param refElement Reference to element relative to which we position
* ourselves
*/
PROT_PhishMsgDisplayerBase.prototype.adjustLocation_ = function(message,
tail,
refElement) {
var refX = refElement.boxObject.x;
var refY = refElement.boxObject.y;
var refHeight = refElement.boxObject.height;
var refWidth = refElement.boxObject.width;
G_Debug(this, "Ref element is at [window-relative] (" + refX + ", " +
refY + ")");
var pixelsIntoRefY = -2;
var tailY = refY + refHeight - pixelsIntoRefY;
var tailPixelsLeftOfRefX = tail.boxObject.width;
var tailPixelsIntoRefX = Math.round(refWidth / 2);
var tailX = refX - tailPixelsLeftOfRefX + tailPixelsIntoRefX;
var messageY = tailY + tail.boxObject.height + 3;
var messagePixelsLeftOfRefX = 375;
var messageX = refX - messagePixelsLeftOfRefX;
G_Debug(this, "Message is at [window-relative] (" + messageX + ", " +
messageY + ")");
G_Debug(this, "Tail is at [window-relative] (" + tailX + ", " +
tailY + ")");
tail.style.top = tailY + "px";
tail.style.left = tailX + "px";
message.style.top = messageY + "px";
message.style.left = messageX + "px";
}
/**
* Show the extended warning message
*/
PROT_PhishMsgDisplayerBase.prototype.showMore_ = function() {
this.doc_.getElementById(this.extendedMessageId_).hidden = false;
this.doc_.getElementById(this.showmoreLinkId_).style.display = "none";
}
/**
* The user clicked on one of the links in the buble. Display the
* corresponding page in a new window with all the chrome enabled.
*
* @param url The URL to display in a new window
*/
PROT_PhishMsgDisplayerBase.prototype.showURL_ = function(url) {
this.windowWatcher_.openWindow(this.windowWatcher_.activeWindow,
url,
"_blank",
null,
null);
}
/**
* A specific implementation of the dislpayer using a canvas. This
* class is meant for use on platforms that don't support transparent
* elements over browser content (currently: all platforms).
*
* The main ugliness is the fact that we're hiding the content area and
* painting the page to canvas. As a result, we must periodically
* re-paint the canvas to reflect updates to the page. Otherwise if
* the page was half-loaded when we showed our warning, it would
* stay that way even though the page actually finished loading.
*
* See base constructor for full details of constructor args.
*
* @constructor
*/
function PROT_PhishMsgDisplayerCanvas(msgDesc, browser, doc, url) {
PROT_PhishMsgDisplayerBase.call(this, msgDesc, browser, doc, url);
this.dimAreaId_ = "safebrowsing-dim-area-canvas";
this.contentStackId_ = "safebrowsing-content-stack";
this.pageCanvasId_ = "safebrowsing-page-canvas";
this.xhtmlNS_ = "http://www.w3.org/1999/xhtml"; // we create html:canvas
}
PROT_PhishMsgDisplayerCanvas.inherits(PROT_PhishMsgDisplayerBase);
/**
* Displays the warning message.
*/
PROT_PhishMsgDisplayerCanvas.prototype.showMessage_ = function() {
G_Debug(this, "Showing message.");
this.messageShowing_ = true;
// Unhide our stack. Order here is significant, but don't ask me why
// for some of these. You need to:
// 1. add canvas to the stack in a hidden state
// 2. unhide the stack
// 3. get browser dimensions
// 4. unhide stack contents
// 5. display to the canvas
// 6. unhide the warning message
// (1)
// We add the canvas dynamically and remove it when we're done because
// leaving it hanging around consumes a lot of memory.
var pageCanvas = this.doc_.createElementNS(this.xhtmlNS_, "html:canvas");
pageCanvas.id = this.pageCanvasId_;
pageCanvas.hidden = "true";
var contentStack = this.doc_.getElementById(this.contentStackId_);
contentStack.insertBefore(pageCanvas, contentStack.firstChild);
// (2)
contentStack.hidden = false;
// (3)
var w = this.browser_.boxObject.width;
G_Debug(this, "browser w=" + w);
var h = this.browser_.boxObject.height;
G_Debug(this, "browser h=" + h);
var win = this.browser_.contentWindow;
var scrollX = win.scrollX;
G_Debug(this, "win scrollx=" + scrollX);
var scrollY = win.scrollY;
G_Debug(this, "win scrolly=" + scrollY);
// (4)
var dimarea = this.doc_.getElementById(this.dimAreaId_);
dimarea.hidden = false;
this.browser_.parentNode.collapsed = true; // And now hide the browser
// (5)
pageCanvas.setAttribute("width", w);
pageCanvas.setAttribute("height", h);
var bgcolor = this.getBackgroundColor_();
var cx = pageCanvas.getContext("2d");
cx.drawWindow(win, scrollX, scrollY, w, h, bgcolor);
// Now repaint the window every so often in case the content hasn't fully
// loaded at this point.
var debZone = this.debugZone;
function repaint() {
G_Debug(debZone, "Repainting canvas...");
cx.drawWindow(win, scrollX, scrollY, w, h, bgcolor);
};
this.repainter_ = new PROT_PhishMsgCanvasRepainter(repaint);
// (6)
var refElement = this.doc_.getElementById(this.refElementId_);
var message = this.doc_.getElementById(this.messageId_);
var tail = this.doc_.getElementById(this.messageTailId_);
message.hidden = false;
message.style.display = "block";
tail.hidden = false;
tail.style.display = "block";
this.adjustLocation_(message, tail, refElement);
}
/**
* Hide the warning message from the user.
*/
PROT_PhishMsgDisplayerCanvas.prototype.hideMessage_ = function() {
G_Debug(this, "Hiding phishing warning.");
G_Assert(this, this.messageShowing_, "Hide message called but not showing?");
this.messageShowing_ = false;
this.repainter_.cancel();
this.repainter_ = null;
var message = this.doc_.getElementById(this.messageId_);
message.hidden = true;
message.style.display = "none";
var tail = this.doc_.getElementById(this.messageTailId_);
tail.hidden = true;
tail.style.display = "none";
this.browser_.parentNode.collapsed = false;
var contentStack = this.doc_.getElementById(this.contentStackId_);
contentStack.hidden = true;
var dimarea = this.doc_.getElementById(this.dimAreaId_);
dimarea.hidden = true;
var pageCanvas = this.doc_.getElementById(this.pageCanvasId_);
contentStack.removeChild(pageCanvas);
}
/**
* Helper class that periodically repaints the canvas. We repaint
* frequently at first, and then back off to a less frequent schedule
* at "steady state," and finally just stop altogether. We have to do
* this because we're not sure if the page has finished loading when
* we first paint the canvas, and because we want to reflect any
* dynamically written content into the canvas as it appears in the
* page after load.
*
* @param repaintFunc Function to call to repaint browser.
*
* @constructor
*/
function PROT_PhishMsgCanvasRepainter(repaintFunc) {
this.count_ = 0;
this.repaintFunc_ = repaintFunc;
this.initPeriodMS_ = 500; // Initially repaint every 500ms
this.steadyStateAtMS_ = 10 * 1000; // Go slowly after 10 seconds,
this.steadyStatePeriodMS_ = 3 * 1000; // repainting every 3 seconds, and
this.quitAtMS_ = 20 * 1000; // stop after 20 seconds
this.startMS_ = (new Date).getTime();
this.alarm_ = new G_Alarm(BindToObject(this.repaint, this),
this.initPeriodMS_);
}
/**
* Called periodically to repaint the canvas
*/
PROT_PhishMsgCanvasRepainter.prototype.repaint = function() {
this.repaintFunc_();
var nextRepaint;
// If we're in "steady state", use the slow repaint rate, else fast
if ((new Date).getTime() - this.startMS_ > this.steadyStateAtMS_)
nextRepaint = this.steadyStatePeriodMS_;
else
nextRepaint = this.initPeriodMS_;
if (!((new Date).getTime() - this.startMS_ > this.quitAtMS_))
this.alarm_ = new G_Alarm(BindToObject(this.repaint, this), nextRepaint);
}
/**
* Called to stop repainting the canvas
*/
PROT_PhishMsgCanvasRepainter.prototype.cancel = function() {
if (this.alarm_) {
this.alarm_.cancel();
this.alarm_ = null;
}
this.repaintFunc_ = null;
}

View File

@@ -0,0 +1,476 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// The warden checks request to see if they are for phishy pages. It
// does so by either querying a remote server with the URL (advanced
// protectoin mode) or querying our locally stored blacklists (privacy
// mode).
//
// When the warden notices a problem, it queries all browser views
// (each of which corresopnds to an open browser window) to see
// whether one of them can handle it. A browser view can handle a
// problem if its browser window has an HTMLDocument loaded with the
// given URL and that Document hasn't already been flagged as a
// problem. For every problematic URL we notice loading, at most one
// Document is flagged as problematic. Otherwise you can get into
// trouble if multiple concurrent phishy pages load with the same URL.
//
// Since we check URLs very early in the request cycle (in a progress
// listener), the URL might not yet be associated with a Document when
// we determine that it is phishy. So the the warden retries finding
// a browser view to handle the problem until one can, or until it
// determines it should give up (see complicated logic below).
//
// The warden has displayers that the browser view uses to render
// different kinds of warnings (e.g., one that's shown before a page
// loads as opposed to one that's shown after the page has already
// loaded).
//
// Note: There is a single warden for the whole application.
//
// TODO better way to expose displayers/views to browser view
/**
* Abtracts the checking of user/browser actions for signs of
* phishing.
*
* @constructor
*/
function PROT_PhishingWarden() {
PROT_ListWarden.call(this);
this.debugZone = "phishwarden";
this.testing_ = false;
this.browserViews_ = [];
// Use this to query preferences
this.prefs_ = new G_Preferences();
// Only one displayer so far; perhaps we'll have others in the future
this.displayers_ = {
"afterload": PROT_PhishMsgDisplayer,
};
// We use this dude to do lookups on our remote server
this.fetcher_ = new PROT_TRFetcher();
// Register w/NavWatcher to hear notifications about requests for docs
if (!this.testing_) {
this.navWatcher_ = new G_NavWatcher(true /* filter spurious navs */);
this.navWatcher_.registerListener("docnavstart",
BindToObject(this.onDocNavStart,
this));
}
// We need to know whether we're enabled and whether we're in advanced
// mode, so reflect the appropriate preferences into our state.
// Read state: should we be checking remote preferences?
var checkRemotePrefName = PROT_GlobalStore.getServerCheckEnabledPrefName();
this.checkRemote_ = this.prefs_.getPref(checkRemotePrefName, null);
// Get notifications when the remote check preference changes
var checkRemotePrefObserver = BindToObject(this.onCheckRemotePrefChanged,
this);
this.prefs_.addObserver(checkRemotePrefName, checkRemotePrefObserver);
// Global preference to enable the phishing warden
var phishWardenPrefName = PROT_GlobalStore.getPhishWardenEnabledPrefName();
this.phishWardenEnabled_ = this.prefs_.getPref(phishWardenPrefName, null);
// Get notifications when the phishing warden enabled pref changes
var phishWardenPrefObserver =
BindToObject(this.onPhishWardenEnabledPrefChanged, this);
this.prefs_.addObserver(phishWardenPrefName, phishWardenPrefObserver);
// We have a hardcoded URLs we let people navigate to in order to
// check out the warning.
this.testURLs_ = PROT_GlobalStore.getTestURLs();
G_Debug(this, "phishWarden initialized");
}
PROT_PhishingWarden.inherits(PROT_ListWarden);
/**
* When a preference (either advanced features or the phishwarden
* enabled) changes, we might have to start or stop asking for updates.
*
* This is a little tricky; we start or stop management only when we
* have complete information we can use to determine whether we
* should. It could be the case that one pref or the other isn't set
* yet (e.g., they haven't opted in/out of advanced features). So do
* nothing unless we have both pref values -- we get notifications for
* both, so eventually we will start correctly.
*/
PROT_PhishingWarden.prototype.maybeToggleUpdateChecking = function() {
if (this.testing_)
return;
var phishWardenPrefName = PROT_GlobalStore.getPhishWardenEnabledPrefName();
var phishWardenEnabled = this.prefs_.getPref(phishWardenPrefName, null);
var checkRemotePrefName = PROT_GlobalStore.getServerCheckEnabledPrefName();
this.checkRemote_ = this.prefs_.getPref(checkRemotePrefName, null);
G_Debug(this, "Maybe toggling update checking. " +
"Warden enabled? " + phishWardenEnabled + " || " +
"Check remote? " + this.checkRemote_);
// Do nothing unless both prefs are set. They can be null (unset), true, or
// false.
if (phishWardenEnabled === null || this.checkRemote_ === null)
return;
// We update and save to disk all tables if we don't have remote checking
// enabled. However, as long as the warden is enabled we always update the
// whitelist, even if remote checking is disabled. This gives a performance
// benefit since we use the WL to suppress BL lookups.
//
// phishEnabled remote WLupdates BLupdates
// T T T F
// T F T T
// F T F F
// F F F F
if (phishWardenEnabled === true) {
this.enableWhitelistTableUpdates();
if (this.checkRemote_ === true) {
this.disableBlacklistTableUpdates();
} else if (this.checkRemote_ === false) {
this.enableBlacklistTableUpdates();
}
} else if (phishWardenEnabled === false) {
this.disableBlacklistTableUpdates();
this.disableWhitelistTableUpdates();
}
}
/**
* Controllers register their browser views with us
*
* @param view Reference to a browser view
*/
PROT_PhishingWarden.prototype.addBrowserView = function(view) {
G_Debug(this, "New browser view registered.");
this.browserViews_.push(view);
}
/**
* Controllers unregister their views when their window closes
*
* @param view Reference to a browser view
*/
PROT_PhishingWarden.prototype.removeBrowserView = function(view) {
for (var i = 0; i < this.browserViews_.length; i++)
if (this.browserViews_[i] === view) {
G_Debug(this, "Browser view unregistered.");
this.browserViews_.splice(i, 1);
return;
}
G_Assert(this, false, "Tried to unregister non-existent browser view!");
}
/**
* Deal with a user changing the pref that says whether we should check
* the remote server (i.e., whether we're in advanced mode)
*
* @param prefName Name of the pref holding the value indicating whether
* we should check remote server
*/
PROT_PhishingWarden.prototype.onCheckRemotePrefChanged = function(prefName) {
this.checkRemote_ = this.prefs_.getBoolPrefOrDefault(prefName,
this.checkRemote_);
this.maybeToggleUpdateChecking();
}
/**
* Deal with a user changing the pref that says whether we should
* enable the phishing warden (i.e., that SafeBrowsing is active)
*
* @param prefName Name of the pref holding the value indicating whether
* we should enable the phishing warden
*/
PROT_PhishingWarden.prototype.onPhishWardenEnabledPrefChanged = function(
prefName) {
this.phishWardenEnabled_ =
this.prefs_.getBoolPrefOrDefault(prefName, this.phishWardenEnabled_);
this.maybeToggleUpdateChecking();
}
/**
* A request for a Document has been initiated somewhere. Check it!
*
* @param e Event object passed in by the NavWatcher
*/
PROT_PhishingWarden.prototype.onDocNavStart = function(e) {
var url = e.url;
var request = e.request;
G_Debug(this, "phishWarden: " +
(this.phishWardenEnabled_ ? "enabled" : "disabled"));
G_Debug(this, "checkRemote: " +
(this.checkRemote_ ? "yes" : "no"));
G_Debug(this, "isTestURL: " +
(this.isBlacklistTestURL(url) ? "yes" : "no"));
// This logic is a bit involved. In some instances of SafeBrowsing
// (the stand-alone extension, for example), the user might yet have
// opted into or out of advanced protection mode. In this case we
// would like to show them a modal dialog requiring them
// to. However, there are links from that dialog to the test page,
// and the warden starts out as disabled. So we want to show the
// warning on the test page so long as the extension hasn't been
// explicitly disabled.
// If we're on the test page and we're not explicitly disabled
if (this.isBlacklistTestURL(url) &&
(this.phishWardenEnabled_ === true ||
this.phishWardenEnabled_ === null)) {
this.houstonWeHaveAProblem_(request);
} else if (this.phishWardenEnabled_ === true) {
// We're enabled. Either send a request off or check locally
// TODO: move this logic to checkUrl, formalize the error callback
if (this.checkRemote_) {
// Use local whitelists to suppress remote BL lookups.
if (!this.isWhiteURL_(url)) {
G_Debug(this, "Local whitelist lookup failed");
this.fetcher_.get(url,
BindToObject(this.onTRFetchComplete,
this,
request));
} else {
G_Debug(this, "WL suppressing BL lookup for " + url);
}
} else {
if (this.checkUrl(url)) {
this.houstonWeHaveAProblem_(request);
}
}
}
}
/**
* Invoked with the result of a lookupserver request.
*
* @param request The nsIRequest in which we're interested
*
* @param trValues Object holding name/value pairs parsed from the
* lookupserver's response
*/
PROT_PhishingWarden.prototype.onTRFetchComplete = function(request,
trValues) {
var callback = BindToObject(this.houstonWeHaveAProblem_, this, request);
this.checkRemoteData(callback, trValues);
}
/**
* One of our Check* methods found a problem with a request. Why do we
* need to keep the nsIRequest (instead of just passing in the URL)?
* Because we need to know when to stop looking for the URL its
* fetching, and to know this we need the nsIRequest.isPending flag.
*
* @param request nsIRequest that is problematic
*/
PROT_PhishingWarden.prototype.houstonWeHaveAProblem_ = function(request) {
// We have a problem request that might or might not be associated
// with a Document that's currently in a browser. If it is, we
// want that Document. If it's not, we want to give it a chance to
// be loaded. See below for complete details.
if (this.maybeLocateProblem_(request)) // Cases 1 and 2 (see below)
return;
// OK, so the request isn't associated with any currently accessible
// Document, and we want to give it the chance to be. We don't want
// to retry forever (e.g., what if the Document was already displayed
// and navigated away from?), so we'll use nsIRequest.isPending to help
// us decide what to do.
//
// A complication arises because there is a lag between when a
// request transitions from pending to not-pending and when it's
// associated with a Document in a browser. The transition from
// pending to not occurs just before the notification corresponding
// to NavWatcher.DOCNAVSTART (see NavWatcher), but the association
// occurs afterwards. Unfortunately, we're probably in DOCNAVSTART.
//
// Diagnosis by Darin:
// ---------------------------------------------------------------------------
// Here's a summary of what happens:
//
// RestorePresentation() {
// Dispatch_OnStateChange(dummy_request, STATE_START)
// PostCompletionEvent()
// }
//
// CompletionEvent() {
// ReallyRestorePresentation()
// Dispatch_OnStateChange(dummy_request, STATE_STOP)
// }
//
// So, now your code receives that initial OnStateChange event and sees
// that the dummy_request is not pending and not loaded in any window.
// So, you put a timeout(0) event in the queue. Then, the CompletionEvent
// is added to the queue. The stack unwinds....
//
// Your timeout runs, and you find that the dummy_request is still not
// pending and not loaded in any window. Then the CompletionEvent
// runs, and it hooks up the cached presentation.
//
// https://bugzilla.mozilla.org/show_bug.cgi?id=319527
// ---------------------------------------------------------------------------
//
// So the logic is:
//
// request found an unhandled
// case pending? doc with the url? action
// ----------------------------------------------------------------
// 1 yes yes Use that doc (handled above)
// 2 no yes Use that doc (handled above)
// 3 yes no Retry
// 4 no no Retry twice (case described above)
//
// We don't get into trouble with Docs with the same URL "stealing" the
// warning because there is exactly one warning signaled per nav to
// a problem URL, and each Doc can be marked as problematic at most once.
if (request.isPending()) { // Case 3
G_Debug(this, "Can't find problem Doc; Req pending. Retrying.");
new G_Alarm(BindToObject(this.houstonWeHaveAProblem_,
this,
request),
200 /*ms*/);
} else { // Case 4
G_Debug(this,
"Can't find problem Doc; Req completed. Retrying at most twice.");
new G_ConditionalAlarm(BindToObject(this.maybeLocateProblem_,
this,
request),
0 /* next event loop */,
true /* repeat */,
2 /* at most twice */);
}
}
/**
* Query all browser views we know about and offer them the chance to
* handle the problematic request.
*
* @param request nsIRequest that is problematic
*
* @returns Boolean indicating if someone decided to handle it
*/
PROT_PhishingWarden.prototype.maybeLocateProblem_ = function(request) {
G_Debug(this, "Trying to find the problem.");
G_Debug(this, this.browserViews_.length + " browser views to check.");
for (var i = 0; i < this.browserViews_.length; i++) {
if (this.browserViews_[i].tryToHandleProblemRequest(this, request)) {
G_Debug(this, "Found browser view willing to handle problem!");
return true;
}
G_Debug(this, "wrong browser view");
}
return false;
}
/**
* Indicates if this URL is one of the possible blacklist test URLs.
* These test URLs should always be considered as phishy.
*
* @param url URL to check
* @return A boolean indicating whether this is one of our blacklist
* test URLs
*/
PROT_PhishingWarden.prototype.isBlacklistTestURL = function(url) {
for (var i = 0, testURL = null; testURL = this.testURLs_[i]; ++i) {
if (testURL === url) {
return true;
}
}
return false;
}
/**
* Look the URL up in our local blacklists
*
* @param callback Function to invoke if there is a problem.
*
* @param url URL to check
*/
PROT_PhishingWarden.prototype.checkUrl = function(url) {
G_Debug(this, "Checking URL for " + url);
if (this.isEvilURL_(url) || this.isBlacklistTestURL(url)) {
G_Debug(this, "Local blacklist hit");
// maybe send a report
(new PROT_Reporter).report("phishblhit", url);
return true;
}
G_Debug(this, "Local blacklist miss");
return false;
}
/**
* Examine data fetched from a lookup server for evidence of a
* phishing problem.
*
* @param callback Function to invoke if there is a problem.
* @param trValues Object containing name/value pairs the server returned
*/
PROT_PhishingWarden.prototype.checkRemoteData = function(callback,
trValues) {
if (!trValues) {
G_Debug(this, "Didn't get TR values from the server.");
return;
}
G_Debug(this, "Page has phishiness " + trValues["phishy"]);
if (trValues["phishy"] == 1) { // It's on our blacklist
G_Debug(this, "Remote blacklist hit");
callback(this);
} else {
G_Debug(this, "Remote blacklist miss");
}
}

View File

@@ -0,0 +1,91 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// A tiny class to do reporting for us. We report interesting user actions
// such as the user hitting a blacklisted page, and the user accepting
// or declining the warning.
//
// Each report has a subject and data. Current reports are:
//
// subject data meaning
// --------------------------------
// phishnavaway url the user navigated away from a phishy page
// phishdecline url the user declined our warning
// phishaccept url the user accepted our warning
// phishblhit url the user loaded a phishing page
//
// We only send reports in advanced protection mode, and even then we
// strip cookies from the request before sending it.
/**
* A very complicated class to send pings to the provider. The class does
* nothing if we're not in advanced protection mode.
*
* @constructor
*/
function PROT_Reporter() {
this.debugZone = "reporter";
this.prefs_ = new G_Preferences();
}
/**
* Send a report!
*
* @param subject String indicating what this report is about (will be
* urlencoded)
* @param data String giving extra information about this report (will be
* urlencoded)
*/
PROT_Reporter.prototype.report = function(subject, data) {
// Send a report iff we're in advanced protection mode
if (!this.prefs_.getPref(PROT_GlobalStore.getSendUserReportsPrefName(),
false))
return;
// Make sure a report url is defined
var url = null;
try {
url = PROT_GlobalStore.getActionReportURL();
} catch (e) {
}
if (!url)
return;
url += "evts=" + encodeURIComponent(subject)
+ "&evtd=" + encodeURIComponent(data);
G_Debug(this, "Sending report: " + url);
(new PROT_XMLFetcher(true /* strip cookies */)).get(url, null /* no cb */);
}

View File

@@ -0,0 +1,168 @@
<!-- browser.xul overlay
included from browser/base/content/browser.xul -->
<!-- This dims out the browser content -->
<vbox id="safebrowsing-dim-area-transp" hidden="true">
<spacer flex="1" />
</vbox>
<!-- This is the spike aka tail on top of the warning -->
<hbox id="safebrowsing-palm-message-tail-container" hidden="true"
pack="end">
<image id="safebrowsing-palm-message-tail"
src="chrome://browser/skin/safebrowsing/tail.png" />
</hbox>
<!-- This is the phishing afterload warning message -->
<vbox id="safebrowsing-palm-message" hidden="true" pack="center"
class="safebrowsing-palm-fixed-width">
<!-- This is the main warning area -->
<vbox id="safebrowsing-palm-message-content"
class="safebrowsing-palm-fixed-width safebrowsing-palm-message-bubble">
<!-- Top line -->
<hbox id="safebrowsing-palm-message-titlebox"
class="safebrowsing-palm-fixed-width">
<image
src="chrome://browser/skin/safebrowsing/warning24x24.png"
id="safebrowsing-palm-title-icon" />
<description id="safebrowsing-palm-message-title"
class="safebrowsing-palm-title"
value="&safeb.palm.warning.heading;" />
<spacer flex="1" />
<vbox pack="start" align="start">
<box onclick="goDoCommand('safebrowsing-decline-warning')"><image
src="chrome://browser/skin/safebrowsing/close16x16.png"
id="safebrowsing-palm-close" /></box>
<spacer flex="1" />
</vbox>
</hbox>
<!-- Content area: short warning -->
<description id="safebrowsing-palm-content"
class="safebrowsing-paragraph">
&safeb.palm.message.part1;
<html:a class="safebrowsing-palm-link" tabindex="1"
statustext="&safeb.palm.showmore.statustext;"
onmouseover="SB_setStatus(this.getAttribute('statustext'))"
onmouseout="SB_clearStatus()"
id="safebrowsing-palm-showmore-link" href="#"
onclick="goDoCommand('safebrowsing-palm-showmore')">
&safeb.palm.message.link1.more;
</html:a>
</description>
<!-- Content area: rest of the warning, revealed if the user
hits the more link -->
<vbox id="safebrowsing-palm-extended-message" hidden="true"
class="safebrowsing-palm-fixed-width">
<description class="safebrowsing-palm-paragraph">
&safeb.palm.message.part2;
<html:a class="safebrowsing-palm-link" tabindex="2"
id="safebrowsing-palm-faq-link"
onmouseover="SB_setStatusFor(this.id)"
onmouseout="SB_clearStatus()"
onclick="goDoCommand('safebrowsing-palm-phishingfaq')">
&safeb.palm.message.link2.phishing.faq;</html:a>
&safeb.palm.message.part3;
<html:a class="safebrowsing-palm-link" tabindex="3"
id="safebrowsing-palm-phishingorg-link"
onmouseover="SB_setStatusFor(this.id)"
onmouseout="SB_clearStatus()"
onclick="goDoCommand('safebrowsing-palm-phishingorg')">
&safeb.palm.message.link3.antiphishing.org;</html:a>&safeb.palm.message.part3.dot;
</description>
<description class="safebrowsing-palm-paragraph">
&safeb.palm.message.part4;
<html:a class="safebrowsing-palm-link" tabindex="4"
id="safebrowsing-palm-fraudpage-link"
onmouseover="SB_setStatusFor(this.id)"
onmouseout="SB_clearStatus()"
onclick="goDoCommand('safebrowsing-palm-fraudpage')">
&safeb.palm.message.link4.gwghome;</html:a>&safeb.palm.message.part4.dot;</description>
<description class="safebrowsing-palm-paragraph">
&safeb.palm.message.part5;<html:a
class="safebrowsing-palm-link" tabindex="6"
id="safebrowsing-palm-falsepositive-link"
onmouseover="SB_setStatusFor(this.id)"
onmouseout="SB_clearStatus()"
onclick="goDoCommand('safebrowsing-palm-falsepositive')">
&safeb.palm.message.link5.report;</html:a>&safeb.palm.message.part5.dot;
</description>
</vbox>
<!-- Main action links -->
<hbox id="safebrowsing-palm-message-actionbox" pack="center"
align="center"
class="safebrowsing-palm-fixed-width">
<description>
<html:a
class="safebrowsing-palm-link safebrowsing-palm-bigtext safebrowsing-padded"
statustext="&safeb.palm.accept.statustext;"
id="safebrowsing-palm-accept-link"
onmouseover="SB_setStatus(this.getAttribute('statustext'))"
onmouseout="SB_clearStatus()"
tabindex="2"
onclick="goDoCommand('safebrowsing-accept-warning')">
&safeb.palm.accept.label;</html:a></description>
<spacer />
<spacer />
<description>
<html:a
class="safebrowsing-palm-link safebrowsing-palm-bigtext safebrowsing-padded"
statustext="&safeb.palm.decline.statustext;"
id="safebrowsing-palm-decline-link"
onmouseover="SB_setStatus(this.getAttribute('statustext'))"
onmouseout="SB_clearStatus()"
tabindex="3"
onclick="goDoCommand('safebrowsing-decline-warning')">
&safeb.palm.decline.label;</html:a></description>
</hbox>
<!-- Footer -->
<hbox align="center" class="safebrowsing-palm-fixed-width">
<!-- branding logo
<image id="safebrowsing-palm-logo"
tooltiptext="&safeb.palm.provider.logo.tooltip;"
src="chrome://browser/skin/safebrowsing/logo.png" />
-->
<Spacer flex="1" />
<hbox>
[<description><html:a id="safebrowsing-palm-report-link"
onmouseover="SB_setStatusFor(this.id)"
onmouseout="SB_clearStatus()"
class="safebrowsing-palm-smalltext safebrowsing-palm-link"
onclick="goDoCommand('safebrowsing-submit-generic-phishing')">
&safeb.palm.sendreport.label;</html:a></description>]
</hbox>
</hbox>
</vbox>
</vbox>
<!-- We position things over a canvas; to do so, we need a stack -->
<stack id="safebrowsing-content-stack" insertbefore="status-bar"
hidden="true">
<!-- The canvas goes here as the first child, but since it takes
up so much memory and is rarely used, we add and remove it
dynamically -->
<!-- This dims out the browser content -->
<vbox id="safebrowsing-dim-area-canvas" hidden="true" flex="1">
</vbox>
</stack>

View File

@@ -0,0 +1,153 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* This file is included into the main browser chrome from
* browser/base/content/global-scripts.inc
*/
window.addEventListener("load", SB_startup, false);
var SB_controller; // Exported so it's accessible by child windows
var SB_appContext; // The context in which our Application lives
// TODO: appContext does not need to be global
function SB_startup() {
var Cc = Components.classes;
SB_appContext = Cc["@mozilla.org/safebrowsing/application;1"]
.getService();
SB_appContext = SB_appContext.wrappedJSObject;
// Each new browser window needs its own controller.
var contentArea = document.getElementById("content");
var tabWatcher = new SB_appContext.G_TabbedBrowserWatcher(
contentArea,
"safebrowsing-watcher",
true /*ignore about:blank*/);
var phishWarden = new SB_appContext.PROT_PhishingWarden();
// Register tables
// TODO: move table names to a pref
phishWarden.registerWhiteTable("goog-white-domain");
phishWarden.registerWhiteTable("goog-white-url");
phishWarden.registerBlackTable("goog-black-url");
phishWarden.registerBlackTable("goog-black-enchash");
// Download/update lists if we're in non-enhanced mode
phishWarden.maybeToggleUpdateChecking();
SB_controller = new SB_appContext.PROT_Controller(
window,
tabWatcher,
phishWarden);
// clean up
window.removeEventListener("load", SB_startup, false);
}
// Some utils for our UI.
/**
* Execute a command on a window
*
* @param cmd String containing command to execute
* @param win Reference to Window on which to execute it
*/
function SB_executeCommand(cmd, win) {
try {
var disp = win.document.commandDispatcher;
var ctrl = disp.getControllerForCommand(cmd);
ctrl.doCommand(cmd);
} catch (e) {
dump("Exception on command: " + cmd + "\n");
dump(e);
}
}
/**
* Execute a command on this window
*
* @param cmd String containing command to execute
*/
function SB_executeCommandLocally(cmd) {
SB_executeCommand(cmd, window);
}
/**
* Set status text for a particular link. We look the URLs up in our
* globalstore.
*
* @param link ID of a link for which we should show status text
*/
function SB_setStatusFor(link) {
var gs = SB_appContext.PROT_GlobalStore;
var msg;
if (link == "safebrowsing-palm-faq-link")
msg = gs.getPhishingFaqURL();
else if (link == "safebrowsing-palm-phishingorg-link")
msg = gs.getAntiPhishingURL();
else if (link == "safebrowsing-palm-fraudpage-link")
msg = gs.getHomePageURL();
else if (link == "safebrowsing-palm-falsepositive-link")
msg = gs.getFalsePositiveURL();
else if (link == "safebrowsing-palm-report-link")
msg = gs.getSubmitUrl();
else
msg = "";
SB_setStatus(msg);
}
/**
* Actually display the status text
*
* @param msg String that we should show in the statusbar
*/
function SB_setStatus(msg) {
document.getElementById("statusbar-display").label = msg;
}
/**
* Clear the status text
*/
function SB_clearStatus() {
document.getElementById("statusbar-display").label = "";
}

View File

@@ -0,0 +1,205 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Google Safe Browsing.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Fritz Schneider <fritz@google.com> (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// A helper class that does "trustrank" lookups on a remote
// server. Right now this lookup just indicates if a page is
// phishing. The response format is protocol4 name/value pairs.
//
// Since we're sending full URLs to the server, we try to encrypt
// them before transmission. Else HTTPS query params could leak.
/**
* A helper class that fetches trustrank values, parses them, and
* passes them via an object to a callback.
*
* @constructor
*/
function PROT_TRFetcher(opt_noCrypto) {
this.debugZone = "trfetcher";
this.useCrypto_ = !opt_noCrypto;
this.lookupserverURL_ = PROT_GlobalStore.getLookupserverURL();
this.protocol4Parser_ = new G_Protocol4Parser();
// We lazily instantiate the UrlCrypto object due to:
// https://bugzilla.mozilla.org/show_bug.cgi?id=321024
//
// Otherwise here we would use:
// this.urlCrypto_ = new PROT_UrlCrypto();
}
PROT_TRFetcher.TRY_REKEYING_RESPONSE = "pleaserekey";
/**
* Query params we'll send. Don't touch unless you know what you're
* doing and are prepared to carefully test.
*/
PROT_TRFetcher.prototype.extraQueryParams = {
sourceid: "firefox-antiphish",
features: "TrustRank",
client: "navclient-auto-ffox"
};
/**
* Get the URL of the request that will fetch us TR for the argument URL
*
* @param url String containing the URL we'd like to fetch info about
*
* @returns String containing the url we should use to fetch tr info
*/
PROT_TRFetcher.prototype.getRequestURL_ = function(url) {
if (!this.urlCrypto_)
this.urlCrypto_ = new PROT_UrlCrypto();
G_Debug(this, "Fetching for " + url);
var requestURL = this.lookupserverURL_;
for (var param in this.extraQueryParams)
requestURL += param + "=" + this.extraQueryParams[param] + "&";
if (this.useCrypto_) {
var maybeCryptedParams = this.urlCrypto_.maybeCryptParams({ "q": url});
for (var param in maybeCryptedParams)
requestURL += param + "=" +
encodeURIComponent(maybeCryptedParams[param]) + "&";
} else {
requestURL += "q=" + encodeURIComponent(url);
}
G_Debug(this, "Request URL: " + requestURL);
return requestURL;
};
/**
* Fetches information about a page.
*
* @param forPage URL for which to fetch info
*
* @param callback Function to call back when complete.
*/
PROT_TRFetcher.prototype.get = function(forPage, callback) {
var url = this.getRequestURL_(forPage);
var closure = BindToObject(this.onFetchComplete_, this, callback);
(new PROT_XMLFetcher()).get(url, closure);
// Make this true if you want to dump URLs in a format we can use
// for testing
if (false)
dump("\ntests[\"" + url + "\"] = \"" + forPage + "\";\n");
};
/**
* Invoked when a fetch has completed.
*
* @param callback Function to invoke with parsed response object
*
* @param responseText Text of the protocol4 message
*/
PROT_TRFetcher.prototype.onFetchComplete_ = function(callback, responseText) {
var responseObj = this.extractResponse_(responseText);
// The server might tell us to rekey, for example if it sees that
// our request was unencrypted (meaning that we might not yet have
// a key). If so, pass this hint along to the crypto key manager.
if (responseObj[PROT_TRFetcher.TRY_REKEYING_RESPONSE] == "1" &&
this.urlCrypto_) {
G_Debug(this, "We're supposed to re-key. Trying.");
var manager = this.urlCrypto_.getManager();
if (manager)
manager.maybeReKey();
}
G_Debug(this, "TR Response:");
for (var field in responseObj)
G_Debug(this, field + "=" + responseObj[field]);
callback(responseObj);
};
/**
* Parse a protocol4 message (lookup server response)
*
* @param responseText String containing the server's response
*
* @returns Object containing the returned values or null if no
* response was received
*/
PROT_TRFetcher.prototype.extractResponse_ = function(responseText) {
return this.protocol4Parser_.parse(responseText);
};
// Unittests
function TEST_PROT_TRFetcher() {
if (G_GDEBUG) {
var z = "trfetcher UNITTEST";
G_debugService.enableZone(z);
G_Debug(z, "Starting");
// Old test from before protocol4parser was broken out on its own.
// But they do no harm, so we leave 'em.
var fetcher = new PROT_TRFetcher();
var fakeResponse = "foo:3:foo\nbar:3:bar\nbaz:1:b";
var fields = fetcher.extractResponse_(fakeResponse);
G_Assert(z, fields["foo"] == "foo", "Bad parse: foo");
G_Assert(z, fields["bar"] == "bar", "Bad parse: bar");
G_Assert(z, fields["baz"] == "b", "Bad parse: baz");
G_Assert(z, !fields["yourmom"], "Bad parse: yourmom");
// Test re-keying by stubbing the fetcher's urlCrypto_ object.
var calledRekey = false;
var fakeManager = {
maybeReKey: function() {
calledRekey = true;
}
};
fetcher.urlCrypto_ = {};
fetcher.urlCrypto_.getManager = function() { return fakeManager; };
var rekeyText = "foo:1:bar\n" +
PROT_TRFetcher.TRY_REKEYING_RESPONSE + ":1:1";
fetcher.onFetchComplete_(function() {}, rekeyText);
G_Assert(z, calledRekey, "rekey didn't trigger call to maybeReKey()");
G_Debug(z, "PASSED");
}
}

View File

@@ -0,0 +1,22 @@
browser.jar:
# http://developer.mozilla.org/en/docs/jar.mn
# script file included into main browser.js
+ content/browser/safebrowsing/sb-loader.js (content/sb-loader.js)
#
# main src files
+ content/browser/safebrowsing/application.js (content/application.js)
+ content/browser/safebrowsing/browser-view.js (content/browser-view.js)
+ content/browser/safebrowsing/controller.js (content/controller.js)
+ content/browser/safebrowsing/firefox-commands.js (content/firefox-commands.js)
+ content/browser/safebrowsing/globalstore.js (content/globalstore.js)
+ content/browser/safebrowsing/list-warden.js (content/list-warden.js)
+ content/browser/safebrowsing/phishing-afterload-displayer.js (content/phishing-afterload-displayer.js)
+ content/browser/safebrowsing/phishing-warden.js (content/phishing-warden.js)
+ content/browser/safebrowsing/reporter.js (content/reporter.js)
+ content/browser/safebrowsing/tr-fetcher.js (content/tr-fetcher.js)
#
# general library files
+ content/browser/safebrowsing/js/eventregistrar.js (content/js/eventregistrar.js)
+ content/browser/safebrowsing/js/listdictionary.js (content/js/listdictionary.js)
+ content/browser/safebrowsing/moz/navwatcher.js (content/moz/navwatcher.js)
+ content/browser/safebrowsing/moz/tabbedbrowserwatcher.js (content/moz/tabbedbrowserwatcher.js)

View File

@@ -0,0 +1,49 @@
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (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.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is mozilla.org code.
#
# The Initial Developer of the Original Code is
# Netscape Communications Corporation.
# Portions created by the Initial Developer are Copyright (C) 1998
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
DEPTH = ../../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
# EXTRA_COMPONENTS installs components written in JS to dist/bin/components
EXTRA_COMPONENTS = safebrowsingApplication.js
include $(topsrcdir)/config/rules.mk

View File

@@ -0,0 +1,99 @@
const Cc = Components.classes;
const Ci = Components.interfaces;
const G_GDEBUG = true;
// Use subscript loader to load files. The files in ../content get mapped
// to chrome://browser/content/safebrowsing/. Order matters if one file depends
// on another file during initialization.
const LIB_FILES = [
// some files are from toolkit
"chrome://global/content/url-classifier/js/lang.js",
"chrome://global/content/url-classifier/moz/preferences.js",
"chrome://global/content/url-classifier/moz/filesystem.js",
"chrome://global/content/url-classifier/moz/debug.js", // req js/lang.js moz/prefs.js moz/filesystem.js
"chrome://global/content/url-classifier/js/arc4.js",
"chrome://global/content/url-classifier/moz/alarm.js",
"chrome://global/content/url-classifier/moz/base64.js",
"chrome://global/content/url-classifier/moz/cryptohasher.js",
"chrome://global/content/url-classifier/moz/lang.js",
"chrome://global/content/url-classifier/moz/objectsafemap.js",
"chrome://global/content/url-classifier/moz/observer.js",
"chrome://global/content/url-classifier/moz/protocol4.js",
"chrome://global/content/url-classifier/application.js",
"chrome://global/content/url-classifier/url-crypto.js",
"chrome://global/content/url-classifier/url-crypto-key-manager.js",
"chrome://global/content/url-classifier/xml-fetcher.js",
// some are in browser
"chrome://browser/content/safebrowsing/js/eventregistrar.js",
"chrome://browser/content/safebrowsing/js/listdictionary.js",
"chrome://browser/content/safebrowsing/moz/navwatcher.js",
"chrome://browser/content/safebrowsing/moz/tabbedbrowserwatcher.js",
"chrome://browser/content/safebrowsing/application.js",
"chrome://browser/content/safebrowsing/browser-view.js",
"chrome://browser/content/safebrowsing/controller.js",
"chrome://browser/content/safebrowsing/firefox-commands.js",
"chrome://browser/content/safebrowsing/globalstore.js",
"chrome://browser/content/safebrowsing/list-warden.js",
"chrome://browser/content/safebrowsing/phishing-afterload-displayer.js",
"chrome://browser/content/safebrowsing/phishing-warden.js",
"chrome://browser/content/safebrowsing/reporter.js",
"chrome://browser/content/safebrowsing/tr-fetcher.js",
];
for (var i = 0, libFile; libFile = LIB_FILES[i]; ++i) {
//dump('*** loading subscript ' + libFile + '\n');
Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader)
.loadSubScript(libFile);
}
// Module object
function SafebrowsingApplicationMod() {
this.firstTime = true;
this.cid = Components.ID("{c64d0bcb-8270-4ca7-a0b3-3380c8ffecb5}");
this.progid = "@mozilla.org/safebrowsing/application;1";
}
SafebrowsingApplicationMod.prototype.registerSelf = function(compMgr, fileSpec, loc, type) {
if (this.firstTime) {
this.firstTime = false;
throw Components.results.NS_ERROR_FACTORY_REGISTER_AGAIN;
}
compMgr = compMgr.QueryInterface(Ci.nsIComponentRegistrar);
compMgr.registerFactoryLocation(this.cid,
"Safebrowsing Application Module",
this.progid,
fileSpec,
loc,
type);
};
SafebrowsingApplicationMod.prototype.getClassObject = function(compMgr, cid, iid) {
if (!cid.equals(this.cid))
throw Components.results.NS_ERROR_NO_INTERFACE;
if (!iid.equals(Ci.nsIFactory))
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
return this.factory;
}
SafebrowsingApplicationMod.prototype.canUnload = function(compMgr) {
return true;
}
SafebrowsingApplicationMod.prototype.factory = {
createInstance: function(outer, iid) {
if (outer != null)
throw Components.results.NS_ERROR_NO_AGGREGATION;
return new PROT_Application();
}
};
var ApplicationModInst = new SafebrowsingApplicationMod();
function NSGetModule(compMgr, fileSpec) {
return ApplicationModInst;
}

View File

@@ -0,0 +1,47 @@
#
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (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.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is mozilla.org code.
#
# The Initial Developer of the Original Code is
# Netscape Communications Corporation.
# Portions created by the Initial Developer are Copyright (C) 1998
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
DEPTH = ../../../..
topsrcdir = @top_srcdir@
srcdir = @srcdir@
VPATH = @srcdir@
include $(DEPTH)/config/autoconf.mk
include $(topsrcdir)/config/rules.mk

View File

@@ -0,0 +1,2 @@
browser.jar:
+ content/browser/protection/unittests.xul (unittests.xul)

View File

@@ -0,0 +1,70 @@
<?xml version="1.0"?>
<window id="PROT_unittest"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload="onProtUnittestLoad();"
title="prot unittests">
<script><![CDATA[
const Cc = Components.classes;
const Ci = Components.interfaces;
function G_Debug(zone, s) {
var label = document.createElement('label');
var txt = "[" + zone + "] " + s;
label.appendChild(document.createTextNode(txt));
document.documentElement.appendChild(label);
}
function G_Assert(zone, cond, msg) {
if (!cond) {
G_Debug(zone, msg);
throw msg;
}
}
function ProtectionPhishWardenTests() {
var z = "phishwarden UNITTEST";
G_Debug(z, "Starting");
var listManager = Cc["@mozilla.org/protection/protectionlistmanager;1"]
.getService(Ci.nsIProtectionListManager);
var warden = Cc['@mozilla.org/protection/phishwarden;1']
.createInstance(Ci.nsIProtectionListWarden);
// Register tables that we are interested in.
warden.registerBlackTable("test-black-url");
var blacklistedCount = 0;
var blackURLs = [
"http://foo.com/1",
"http://foo.com/2",
"http://foo.com/3",
"http://foo.com/4",
"http://www.goodsite.com/test",
];
for (var i = 0; i < blackURLs.length; i++)
listManager.safeInsert("test-black-url", blackURLs[i], "1");
G_Assert(z, !warden.checkUrl("http://bar.com/"), 'should not have found');
G_Assert(z, warden.checkUrl("http://foo.com/1"), 'should have found (1)');
G_Assert(z, warden.checkUrl("http://foo.com/2"), 'should have found (2)');
G_Assert(z, warden.checkUrl("http://foo.com/3"), 'should have found (3)');
G_Assert(z, warden.checkUrl("http://foo.com/4"), 'should have found (4)');
warden.registerWhiteTable('test-white-domain');
listManager.safeInsert("test-white-domain", "http://www.goodsite.com/", "1");
G_Assert(z, !warden.checkUrl("http://www.goodsite.com/"),
'whitelist failed?')
G_Debug(z, "PASSED");
}
function onProtUnittestLoad() {
ProtectionPhishWardenTests();
}
]]></script>
</window>