[Pkg-mozext-commits] [requestpolicy] 26/100: [refactoring] move shouldLoad() functionality to RequestProcessor

David Prévot taffit at moszumanska.debian.org
Fri Dec 12 22:56:51 UTC 2014


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository requestpolicy.

commit 331b1bf3d8e0e24cb7b02c57004d65f30713706f
Author: myrdd <myrdd at users.noreply.github.com>
Date:   Tue Sep 16 15:16:34 2014 +0200

    [refactoring] move shouldLoad() functionality to RequestProcessor
    
    besides that, the Request class has been created, which use will be extended
    in future commits
---
 src/components/requestpolicyService.js | 1302 ++------------------------------
 src/content/overlay.js                 |   17 +-
 src/modules/PolicyManager.jsm          |    2 +
 src/modules/Request.jsm                |  121 +++
 src/modules/RequestProcessor.jsm       | 1257 ++++++++++++++++++++++++++++++
 src/modules/RequestUtil.jsm            |    5 +-
 6 files changed, 1445 insertions(+), 1259 deletions(-)

diff --git a/src/components/requestpolicyService.js b/src/components/requestpolicyService.js
index 1fdc037..ff63b90 100644
--- a/src/components/requestpolicyService.js
+++ b/src/components/requestpolicyService.js
@@ -31,10 +31,6 @@ const CP_REJECT = CI.nsIContentPolicy.REJECT_SERVER;
 
 const EXTENSION_ID = "requestpolicy at requestpolicy.com";
 
-// A value intended to not conflict with aExtra passed to shouldLoad() by any
-// other callers. Was chosen randomly.
-const CP_MAPPEDDESTINATION = 0x178c40bf;
-
 const HTTPS_EVERYWHERE_REWRITE_TOPIC = "https-everywhere-uri-rewrite";
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
@@ -45,8 +41,8 @@ if (!requestpolicy) {
 }
 
 function RequestPolicyService() {
-  // If you only need to access your component from Javascript, uncomment the following line:
-  // https://developer.mozilla.org/en-US/docs/wrappedJSObject
+  // If you only need to access your component from Javascript, uncomment the
+  // following line: (https://developer.mozilla.org/en-US/docs/wrappedJSObject)
   this.wrappedJSObject = this;
 }
 
@@ -88,58 +84,13 @@ RequestPolicyService.prototype = {
 
   _conflictingExtensions : [],
 
-  _rejectedRequests : null,
-  _allowedRequests : null,
-
-  /**
-   * These are redirects that the user allowed when presented with a redirect
-   * notification.
-   */
-  _userAllowedRedirects : {},
-
-  _blockedRedirects : {},
-  _allowedRedirectsReverse : {},
-
   _prefService : null,
   _rootPrefs : null,
 
-  _historyRequests : {},
-
-  _submittedForms : {},
-  _submittedFormsReverse : {},
-
-  _clickedLinks : {},
-  _clickedLinksReverse : {},
-
-  _faviconRequests : {},
-
-  _mappedDestinations : {},
-
-  _requestObservers : [],
-
-  /**
-   * Number of elapsed milliseconds from the time of the last shouldLoad() call
-   * at which the cached results of the last shouldLoad() call are discarded.
-   *
-   * @type Number
-   */
-  _lastShouldLoadCheckTimeout : 200,
-
-  // Calls to shouldLoad appear to be repeated, so successive repeated calls and
-  // their result (accept or reject) are tracked to avoid duplicate processing
-  // and duplicate logging.
-  /**
-   * Object that caches the last shouldLoad
-   */
-  _lastShouldLoadCheck : {
-    "origin" : null,
-    "destination" : null,
-    "time" : 0,
-    "result" : null
-  },
 
   _subscriptions : null,
   _policyMgr : null,
+  _requestProcessor : null,
 
   _prefNameToObjectMap : null,
 
@@ -163,9 +114,6 @@ RequestPolicyService.prototype = {
       this._loadLibraries();
 
       try {
-        this._rejectedRequests = new requestpolicy.mod.RequestSet();
-        this._allowedRequests = new requestpolicy.mod.RequestSet();
-
         this._initContentPolicy();
         this._register();
         this._initializePrefSystem();
@@ -207,7 +155,8 @@ RequestPolicyService.prototype = {
 
     try {
       // For Firefox <= 3.6.
-      var em = CC["@mozilla.org/extensions/manager;1"].getService(CI.nsIExtensionManager);
+      var em = CC["@mozilla.org/extensions/manager;1"].
+          getService(CI.nsIExtensionManager);
       var ext;
       for (var i = 0; i < idArray.length; i++) {
         requestpolicy.mod.Logger.info(requestpolicy.mod.Logger.TYPE_INTERNAL,
@@ -325,7 +274,8 @@ RequestPolicyService.prototype = {
   },
 
   _initializeApplicationCompatibility : function() {
-    var appInfo = CC["@mozilla.org/xre/app-info;1"].getService(CI.nsIXULAppInfo);
+    var appInfo = CC["@mozilla.org/xre/app-info;1"].
+        getService(CI.nsIXULAppInfo);
 
     // Mozilla updates (doing this for all applications, not just individual
     // applications from the Mozilla community that I'm aware of).
@@ -432,7 +382,8 @@ RequestPolicyService.prototype = {
     this._updateLoggingSettings();
 
     this._defaultAllow = this.prefs.getBoolPref("defaultPolicy.allow");
-    this._defaultAllowSameDomain = this.prefs.getBoolPref("defaultPolicy.allowSameDomain");
+    this._defaultAllowSameDomain = this.prefs.
+        getBoolPref("defaultPolicy.allowSameDomain");
     this._blockingDisabled = this.prefs.getBoolPref("startWithAllowAllEnabled");
 
     // Disable link prefetch.
@@ -555,7 +506,8 @@ RequestPolicyService.prototype = {
   },
 
   _register : function() {
-    var os = CC['@mozilla.org/observer-service;1'].getService(CI.nsIObserverService);
+    var os = CC['@mozilla.org/observer-service;1'].
+        getService(CI.nsIObserverService);
     os.addObserver(this, "http-on-examine-response", false);
     os.addObserver(this, "http-on-modify-request", false);
     os.addObserver(this, "xpcom-shutdown", false);
@@ -578,7 +530,8 @@ RequestPolicyService.prototype = {
 
   _unregister : function() {
     try {
-      var os = CC['@mozilla.org/observer-service;1'].getService(CI.nsIObserverService);
+      var os = CC['@mozilla.org/observer-service;1'].
+          getService(CI.nsIObserverService);
       os.removeObserver(this, "http-on-examine-response");
       os.removeObserver(this, "http-on-modify-request");
       os.removeObserver(this, "xpcom-shutdown");
@@ -601,7 +554,8 @@ RequestPolicyService.prototype = {
 
   _initializePrefSystem : function() {
     // Get the preferences branch and setup the preferences observer.
-    this._prefService = CC["@mozilla.org/preferences-service;1"].getService(CI.nsIPrefService);
+    this._prefService = CC["@mozilla.org/preferences-service;1"].
+        getService(CI.nsIPrefService);
 
     this.prefs = this._prefService.getBranch("extensions.requestpolicy.")
         .QueryInterface(CI.nsIPrefBranch2);
@@ -638,7 +592,8 @@ RequestPolicyService.prototype = {
             }
           });
       } else {
-        var em = CC["@mozilla.org/extensions/manager;1"].getService(CI.nsIExtensionManager);
+        var em = CC["@mozilla.org/extensions/manager;1"].
+            getService(CI.nsIExtensionManager);
         var addon = em.getItemForID(EXTENSION_ID);
         this.prefs.setCharPref("lastVersion", addon.version);
         util.curVersion = addon.version;
@@ -690,6 +645,8 @@ RequestPolicyService.prototype = {
       "DomainUtil.jsm",
       "Policy.jsm",
       "PolicyManager.jsm",
+      "Request.jsm",
+      "RequestProcessor.jsm",
       "RequestUtil.jsm",
       "Subscription.jsm",
       "Util.jsm"
@@ -722,342 +679,14 @@ RequestPolicyService.prototype = {
 
   _initializePrivateBrowsing : function() {
     try {
-      var pbs = CC["@mozilla.org/privatebrowsing;1"].getService(CI.nsIPrivateBrowsingService);
+      var pbs = CC["@mozilla.org/privatebrowsing;1"].
+          getService(CI.nsIPrivateBrowsingService);
       this._privateBrowsingEnabled = pbs.privateBrowsingEnabled;
     } catch (e) {
       // Ignore exceptions from browsers that do not support private browsing.
     }
   },
 
-  /**
-   * Checks whether a request is initiated by a content window. If it's from a
-   * content window, then it's from unprivileged code.
-   */
-  _isContentRequest : function(channel) {
-    var callbacks = [];
-    if (channel.notificationCallbacks) {
-      callbacks.push(channel.notificationCallbacks);
-    }
-    if (channel.loadGroup && channel.loadGroup.notificationCallbacks) {
-      callbacks.push(channel.loadGroup.notificationCallbacks);
-    }
-
-    for (var i = 0; i < callbacks.length; i++) {
-      var callback = callbacks[i];
-      try {
-        // For Gecko 1.9.1
-        return callback.getInterface(CI.nsILoadContext).isContent;
-      } catch (e) {
-      }
-      try {
-        // For Gecko 1.9.0
-        var itemType = callback.getInterface(CI.nsIWebNavigation)
-            .QueryInterface(CI.nsIDocShellTreeItem).itemType;
-        return itemType == CI.nsIDocShellTreeItem.typeContent;
-      } catch (e) {
-      }
-    }
-
-    return false;
-  },
-
-  _examineHttpResponse : function(observedSubject) {
-    // Currently, if a user clicks a link to download a file and that link
-    // redirects and is subsequently blocked, the user will see the blocked
-    // destination in the menu. However, after they have allowed it from
-    // the menu and attempted the download again, they won't see the allowed
-    // request in the menu. Fixing that might be a pain and also runs the
-    // risk of making the menu cluttered and confusing with destinations of
-    // followed links from the current page.
-
-    // TODO: Make user aware of blocked headers so they can allow them if
-    // desired.
-
-    var httpChannel = observedSubject.QueryInterface(CI.nsIHttpChannel);
-
-    var headerType;
-    var dest;
-
-    try {
-      // If there is no such header, getResponseHeader() will throw
-      // NS_ERROR_NOT_AVAILABLE. If there is more than header, the last one is
-      // the one that will be used.
-      headerType = "Location";
-      dest = httpChannel.getResponseHeader(headerType);
-    } catch (e) {
-      // No location header. Look for a Refresh header.
-      try {
-        headerType = "Refresh";
-        var refreshString = httpChannel.getResponseHeader(headerType);
-      } catch (e) {
-        // No Location header or Refresh header.
-        return;
-      }
-      try {
-        var parts = requestpolicy.mod.DomainUtil.parseRefresh(refreshString);
-      } catch (e) {
-        requestpolicy.mod.Logger.warning(
-            requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT,
-            "Invalid refresh header: <" + refreshString + ">");
-        if (!this._blockingDisabled) {
-          httpChannel.setResponseHeader(headerType, "", false);
-        }
-        return;
-      }
-      // We can ignore the delay (parts[0]) because we aren't manually doing
-      // the refreshes. Allowed refreshes we still leave to the browser.
-      // The dest may be empty if the origin is what should be refreshed. This
-      // will be handled by DomainUtil.determineRedirectUri().
-      dest = parts[1];
-    }
-
-    // For origins that are IDNs, this will always be in ACE format. We want
-    // it in UTF8 format if it's a TLD that Mozilla allows to be in UTF8.
-    var origin = requestpolicy.mod.DomainUtil.formatIDNUri(httpChannel.name);
-
-    // Allow redirects of requests from privileged code.
-    if (!this._isContentRequest(httpChannel)) {
-      // However, favicon requests that are redirected appear as non-content
-      // requests. So, check if the original request was for a favicon.
-      var originPath = requestpolicy.mod.DomainUtil.getPath(httpChannel.name);
-      // We always have to check "/favicon.ico" because Firefox will use this
-      // as a default path and that request won't pass through shouldLoad().
-      if (originPath == "/favicon.ico" || this._faviconRequests[origin]) {
-        // If the redirected request is allowed, we need to know that was a
-        // favicon request in case it is further redirected.
-        this._faviconRequests[dest] = true;
-        requestpolicy.mod.Logger.info(
-            requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT, "'" + headerType
-                + "' header to <" + dest + "> " + "from <" + origin
-                + "> appears to be a redirected favicon request. "
-                + "This will be treated as a content request.");
-      } else {
-        requestpolicy.mod.Logger.warning(
-            requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT, "** ALLOWED ** '"
-                + headerType + "' header to <" + dest + "> " + "from <"
-                + origin + ">. Original request is from privileged code.");
-        return;
-      }
-    }
-
-    // If it's not a valid uri, the redirect is relative to the origin host.
-    // The way we have things written currently, without this check the full
-    // dest string will get treated as the destination and displayed in the
-    // menu because DomainUtil.getIdentifier() doesn't raise exceptions.
-    // We add this to fix https://www.requestpolicy.com/dev/ticket/39.
-    if (!requestpolicy.mod.DomainUtil.isValidUri(dest)) {
-      var destAsUri = requestpolicy.mod.DomainUtil.determineRedirectUri(origin,
-          dest);
-      requestpolicy.mod.Logger.warning(
-          requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT,
-          "Redirect destination is not a valid uri, assuming dest <" + dest
-              + "> from origin <" + origin + "> is actually dest <" + destAsUri
-              + ">.");
-      dest = destAsUri;
-    }
-
-    // Ignore redirects to javascript. The browser will ignore them, as well.
-    if (requestpolicy.mod.DomainUtil.getUriObject(dest)
-          .schemeIs("javascript")) {
-      requestpolicy.mod.Logger.warning(
-          requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT,
-          "Ignoring redirect to javascript URI <" + dest + ">");
-      return;
-    }
-
-    var result = this.checkRedirect(origin, dest);
-    if (true === result.isAllowed) {
-      requestpolicy.mod.Logger.warning(
-          requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT, "** ALLOWED ** '"
-              + headerType + "' header to <" + dest + "> " + "from <" + origin
-              + ">. Same hosts or allowed origin/destination.");
-      this._recordAllowedRequest(origin, dest, false, result);
-      this._allowedRedirectsReverse[dest] = origin;
-
-      // If this was a link click or a form submission, we register an
-      // additional click/submit with the original source but with a new
-      // destination of the target of the redirect. This is because future
-      // requests (such as using back/forward) might show up as directly from
-      // the initial origin to the ultimate redirected destination.
-      if (httpChannel.referrer) {
-        var realOrigin = httpChannel.referrer.spec;
-
-        if (this._clickedLinks[realOrigin]
-            && this._clickedLinks[realOrigin][origin]) {
-          requestpolicy.mod.Logger.warning(
-              requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT,
-              "This redirect was from a link click."
-                  + " Registering an additional click to <" + dest + "> "
-                  + "from <" + realOrigin + ">");
-          this.registerLinkClicked(realOrigin, dest);
-
-        } else if (this._submittedForms[realOrigin]
-            && this._submittedForms[realOrigin][origin.split("?")[0]]) {
-          requestpolicy.mod.Logger.warning(
-              requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT,
-              "This redirect was from a form submission."
-                  + " Registering an additional form submission to <" + dest
-                  + "> " + "from <" + realOrigin + ">");
-          this.registerFormSubmitted(realOrigin, dest);
-        }
-      }
-
-      return;
-    }
-
-    // The header isn't allowed, so remove it.
-    try {
-      if (!this._blockingDisabled) {
-        httpChannel.setResponseHeader(headerType, "", false);
-        this._blockedRedirects[origin] = dest;
-
-        try {
-          contentDisp = httpChannel.getResponseHeader("Content-Disposition");
-          if (contentDisp.indexOf("attachment") != -1) {
-            try {
-              httpChannel.setResponseHeader("Content-Disposition", "", false);
-              requestpolicy.mod.Logger.warning(
-                  requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT,
-                  "Removed 'Content-Disposition: attachment' header to "
-                      + "prevent display of about:neterror.");
-            } catch (e) {
-              requestpolicy.mod.Logger.warning(
-                  requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT,
-                  "Unable to remove 'Content-Disposition: attachment' header "
-                      + "to prevent display of about:neterror. " + e);
-            }
-          }
-        } catch (e) {
-          // No Content-Disposition header.
-        }
-
-        // We try to trace the blocked redirect back to a link click or form
-        // submission if we can. It may indicate, for example, a link that
-        // was to download a file but a redirect got blocked at some point.
-        var initialOrigin = origin;
-        var initialDest = dest;
-        // To prevent infinite loops, bound the number of iterations.
-        // Note that an apparent redirect loop doesn't mean a problem with a
-        // website as the site may be using other information, such as cookies
-        // that get set in the redirection process, to stop the redirection.
-        var iterations = 0;
-        const ASSUME_REDIRECT_LOOP = 100; // Chosen arbitrarily.
-        while (this._allowedRedirectsReverse[initialOrigin]) {
-          if (iterations++ >= ASSUME_REDIRECT_LOOP) {
-            break;
-          }
-          initialDest = initialOrigin;
-          initialOrigin = this._allowedRedirectsReverse[initialOrigin];
-        }
-
-        if (this._clickedLinksReverse[initialOrigin]) {
-          for (var i in this._clickedLinksReverse[initialOrigin]) {
-            // We hope there's only one possibility of a source page (that is,
-            // ideally there will be one iteration of this loop).
-            var sourcePage = i;
-          }
-
-          this._notifyRequestObserversOfBlockedLinkClickRedirect(sourcePage,
-              origin, dest);
-
-          // Maybe we just record the clicked link and each step in between as
-          // an allowed request, and the final blocked one as a blocked request.
-          // That is, make it show up in the requestpolicy menu like anything
-          // else.
-          // We set the "isInsert" parameter so we don't clobber the existing
-          // info about allowed and deleted requests.
-          this._recordAllowedRequest(sourcePage, initialOrigin, true, result);
-        }
-
-        // if (this._submittedFormsReverse[initialOrigin]) {
-        // // TODO: implement for form submissions whose redirects are blocked
-        // }
-
-        this._recordRejectedRequest(origin, dest, result);
-      }
-      requestpolicy.mod.Logger.warning(
-          requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT, "** BLOCKED ** '"
-              + headerType + "' header to <" + dest + ">"
-              + " found in response from <" + origin + ">");
-    } catch (e) {
-      requestpolicy.mod.Logger.severe(
-          requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT, "Failed removing "
-              + "'" + headerType + "' header to <" + dest + ">"
-              + "  in response from <" + origin + ">." + e);
-    }
-  },
-
-  /**
-   * Currently this just looks for prefetch requests that are getting through
-   * which we currently can't stop.
-   */
-  _examineHttpRequest : function(observedSubject) {
-    var httpChannel = observedSubject.QueryInterface(CI.nsIHttpChannel);
-    try {
-      // Determine if prefetch requests are slipping through.
-      if (httpChannel.getRequestHeader("X-moz") == "prefetch") {
-        // Seems to be too late to block it at this point. Calling the
-        // cancel(status) method didn't stop it.
-        requestpolicy.mod.Logger.warning(requestpolicy.mod.Logger.TYPE_CONTENT,
-            "Discovered prefetch request being sent to: " + httpChannel.name);
-      }
-    } catch (e) {
-      // No X-moz header.
-    }
-  },
-
-  _printAllowedRequests : function() {
-    this._allowedRequests.print();
-  },
-
-  _printRejectedRequests : function() {
-    this._rejectedRequests.print();
-  },
-
-  _notifyRequestObserversOfBlockedRequest : function(originUri, destUri,
-        requestResult) {
-    for (var i = 0; i < this._requestObservers.length; i++) {
-      if (!this._requestObservers[i]) {
-        continue;
-      }
-      this._requestObservers[i].observeBlockedRequest(originUri, destUri,
-            requestResult);
-    }
-  },
-
-  _notifyRequestObserversOfAllowedRequest : function(originUri, destUri,
-        requestResult) {
-    for (var i = 0; i < this._requestObservers.length; i++) {
-      if (!this._requestObservers[i]) {
-        continue;
-      }
-      this._requestObservers[i].observeAllowedRequest(originUri, destUri,
-            requestResult);
-    }
-  },
-
-  _notifyRequestObserversOfBlockedLinkClickRedirect : function(sourcePageUri,
-      linkDestUri, blockedRedirectUri) {
-    for (var i = 0; i < this._requestObservers.length; i++) {
-      if (!this._requestObservers[i]) {
-        continue;
-      }
-      this._requestObservers[i].observeBlockedLinkClickRedirect(sourcePageUri,
-          linkDestUri, blockedRedirectUri);
-    }
-  },
-
-  _notifyBlockedTopLevelDocRequest : function(originUri, destUri) {
-    // TODO: this probably could be done async.
-    for (var i = 0; i < this._requestObservers.length; i++) {
-      if (!this._requestObservers[i]) {
-        continue;
-      }
-      this._requestObservers[i].observeBlockedTopLevelDocRequest(originUri,
-          destUri);
-    }
-  },
-
   // /////////////////////////////////////////////////////////////////////////
   // nsIRequestPolicy interface
   // /////////////////////////////////////////////////////////////////////////
@@ -1071,100 +700,19 @@ RequestPolicyService.prototype = {
   },
 
   registerHistoryRequest : function(destinationUrl) {
-    var destinationUrl = requestpolicy.mod.DomainUtil
-        .ensureUriHasPath(requestpolicy.mod.DomainUtil
-            .stripFragment(destinationUrl));
-    this._historyRequests[destinationUrl] = true;
-    requestpolicy.mod.Logger.info(requestpolicy.mod.Logger.TYPE_INTERNAL,
-        "History item requested: <" + destinationUrl + ">.");
+    this._requestProcessor.registerHistoryRequest;
   },
 
   registerFormSubmitted : function(originUrl, destinationUrl) {
-    var originUrl = requestpolicy.mod.DomainUtil
-        .ensureUriHasPath(requestpolicy.mod.DomainUtil.stripFragment(originUrl));
-    var destinationUrl = requestpolicy.mod.DomainUtil
-        .ensureUriHasPath(requestpolicy.mod.DomainUtil
-            .stripFragment(destinationUrl));
-
-    requestpolicy.mod.Logger.info(requestpolicy.mod.Logger.TYPE_INTERNAL,
-        "Form submitted from <" + originUrl + "> to <" + destinationUrl + ">.");
-
-    // Drop the query string from the destination url because form GET requests
-    // will end up with a query string on them when shouldLoad is called, so
-    // we'll need to be dropping the query string there.
-    destinationUrl = destinationUrl.split("?")[0];
-
-    if (this._submittedForms[originUrl] == undefined) {
-      this._submittedForms[originUrl] = {};
-    }
-    if (this._submittedForms[originUrl][destinationUrl] == undefined) {
-      // TODO: See timestamp note for registerLinkClicked.
-      this._submittedForms[originUrl][destinationUrl] = true;
-    }
-
-    // Keep track of a destination-indexed map, as well.
-    if (this._submittedFormsReverse[destinationUrl] == undefined) {
-      this._submittedFormsReverse[destinationUrl] = {};
-    }
-    if (this._submittedFormsReverse[destinationUrl][originUrl] == undefined) {
-      // TODO: See timestamp note for registerLinkClicked.
-      this._submittedFormsReverse[destinationUrl][originUrl] = true;
-    }
+    this._requestProcessor.registerFormSubmitted;
   },
 
   registerLinkClicked : function(originUrl, destinationUrl) {
-    var originUrl = requestpolicy.mod.DomainUtil
-        .ensureUriHasPath(requestpolicy.mod.DomainUtil.stripFragment(originUrl));
-    var destinationUrl = requestpolicy.mod.DomainUtil
-        .ensureUriHasPath(requestpolicy.mod.DomainUtil
-            .stripFragment(destinationUrl));
-
-    requestpolicy.mod.Logger.info(requestpolicy.mod.Logger.TYPE_INTERNAL,
-        "Link clicked from <" + originUrl + "> to <" + destinationUrl + ">.");
-
-    if (this._clickedLinks[originUrl] == undefined) {
-      this._clickedLinks[originUrl] = {};
-    }
-    if (this._clickedLinks[originUrl][destinationUrl] == undefined) {
-      // TODO: Possibly set the value to a timestamp that can be used elsewhere
-      // to determine if this is a recent click. This is probably necessary as
-      // multiple calls to shouldLoad get made and we need a way to allow
-      // multiple in a short window of time. Alternately, as it seems to always
-      // be in order (repeats are always the same as the last), the last one
-      // could be tracked and always allowed (or allowed within a small period
-      // of time). This would have the advantage that we could delete items from
-      // the _clickedLinks object. One of these approaches would also reduce log
-      // clutter, which would be good.
-      this._clickedLinks[originUrl][destinationUrl] = true;
-    }
-
-    // Keep track of a destination-indexed map, as well.
-    if (this._clickedLinksReverse[destinationUrl] == undefined) {
-      this._clickedLinksReverse[destinationUrl] = {};
-    }
-    if (this._clickedLinksReverse[destinationUrl][originUrl] == undefined) {
-      // TODO: Possibly set the value to a timestamp, as described above.
-      this._clickedLinksReverse[destinationUrl][originUrl] = true;
-    }
+    this._requestProcessor.registerLinkClicked;
   },
 
   registerAllowedRedirect : function(originUrl, destinationUrl) {
-    var originUrl = requestpolicy.mod.DomainUtil
-        .ensureUriHasPath(requestpolicy.mod.DomainUtil.stripFragment(originUrl));
-    var destinationUrl = requestpolicy.mod.DomainUtil
-        .ensureUriHasPath(requestpolicy.mod.DomainUtil
-            .stripFragment(destinationUrl));
-
-    requestpolicy.mod.Logger.info(requestpolicy.mod.Logger.TYPE_INTERNAL,
-        "User-allowed redirect from <" + originUrl + "> to <" + destinationUrl
-            + ">.");
-
-    if (this._userAllowedRedirects[originUrl] == undefined) {
-      this._userAllowedRedirects[originUrl] = {};
-    }
-    if (this._userAllowedRedirects[originUrl][destinationUrl] == undefined) {
-      this._userAllowedRedirects[originUrl][destinationUrl] = true;
-    }
+    this._requestProcessor.registerAllowedRedirect;
   },
 
   setBlockingDisabled : function(disabled) {
@@ -1178,11 +726,13 @@ RequestPolicyService.prototype = {
   },
 
   addAllowRule : function(rawRule, noStore) {
-    this._policyMgr.addRule(requestpolicy.mod.RULE_TYPE_ALLOW, rawRule, noStore);
+    this._policyMgr.addRule(requestpolicy.mod.RULE_TYPE_ALLOW, rawRule,
+        noStore);
   },
 
   addTemporaryAllowRule : function(rawRule) {
-    this._policyMgr.addTemporaryRule(requestpolicy.mod.RULE_TYPE_ALLOW, rawRule);
+    this._policyMgr.addTemporaryRule(requestpolicy.mod.RULE_TYPE_ALLOW,
+        rawRule);
   },
 
   removeAllowRule : function(rawRule) {
@@ -1237,7 +787,8 @@ RequestPolicyService.prototype = {
 
   temporarilyAllowDestination : function(host) {
     var ruleData = {"d": {"h" : host}};
-    this._policyMgr.addTemporaryRule(requestpolicy.mod.RULE_TYPE_ALLOW, ruleData);
+    this._policyMgr.addTemporaryRule(requestpolicy.mod.RULE_TYPE_ALLOW,
+        ruleData);
   },
 
   _allowOriginToDestination : function(originIdentifier, destIdentifier,
@@ -1265,7 +816,8 @@ RequestPolicyService.prototype = {
       "o": {"h" : originIdentifier},
       "d": {"h" : destIdentifier}
     };
-    this._policyMgr.addTemporaryRule(requestpolicy.mod.RULE_TYPE_ALLOW, ruleData);
+    this._policyMgr.addTemporaryRule(requestpolicy.mod.RULE_TYPE_ALLOW,
+        ruleData);
   },
 
   temporaryRulesExist : function() {
@@ -1276,87 +828,8 @@ RequestPolicyService.prototype = {
     this._policyMgr.resetTemporaryPolicy();
   },
 
-  mapDestinations : function(origDestUri, newDestUri) {
-    origDestUri = requestpolicy.mod.DomainUtil.stripFragment(origDestUri);
-    newDestUri = requestpolicy.mod.DomainUtil.stripFragment(newDestUri);
-    requestpolicy.mod.Logger.info(requestpolicy.mod.Logger.TYPE_INTERNAL,
-        "Mapping destination <" + origDestUri + "> to <" + newDestUri + ">.");
-    if (!this._mappedDestinations[newDestUri]) {
-      this._mappedDestinations[newDestUri] = {};
-    }
-    this._mappedDestinations[newDestUri][origDestUri] =
-        requestpolicy.mod.DomainUtil.getUriObject(origDestUri);
-  },
-
   isAllowedRedirect : function(originUri, destinationUri) {
-    return (true === this.checkRedirect(originUri, destinationUri).isAllowed);
-  },
-
-  checkRedirect : function(originUri, destinationUri) {
-    // TODO: Find a way to get rid of repitition of code between this and
-    // shouldLoad().
-
-    // Note: If changing the logic here, also make necessary changes to
-    // shouldLoad().
-
-    // This is not including link clicks, form submissions, and user-allowed
-    // redirects.
-
-    var originUriObj = requestpolicy.mod.DomainUtil.getUriObject(originUri);
-    var destUriObj = requestpolicy.mod.DomainUtil.getUriObject(destinationUri);
-
-    var result = this._policyMgr.checkRequestAgainstUserPolicies(originUriObj,
-          destUriObj);
-    result.requestType = requestpolicy.mod.REQUEST_TYPE_REDIRECT;
-    // For now, we always give priority to deny rules.
-    if (result.denyRulesExist()) {
-      result.isAllowed = false;
-      return result;
-    }
-    if (result.allowRulesExist()) {
-      result.isAllowed = true;
-      return result;
-    }
-
-    var result = this._policyMgr.checkRequestAgainstSubscriptionPolicies(
-          originUriObj, destUriObj);
-    result.requestType = requestpolicy.mod.REQUEST_TYPE_REDIRECT;
-    // For now, we always give priority to deny rules.
-    if (result.denyRulesExist()) {
-      result.isAllowed = false;
-      return result;
-    }
-    if (result.allowRulesExist()) {
-      result.isAllowed = true;
-      return result;
-    }
-
-    if (destinationUri[0] && destinationUri[0] == '/'
-        || destinationUri.indexOf(":") == -1) {
-      // Redirect is to a relative url.
-      // ==> allow.
-      return new requestpolicy.mod.RequestResult(
-          true,
-          requestpolicy.mod.REQUEST_TYPE_REDIRECT,
-          requestpolicy.mod.REQUEST_REASON_RELATIVE_URL);
-    }
-
-    for (var i = 0; i < this._compatibilityRules.length; i++) {
-      var rule = this._compatibilityRules[i];
-      var allowOrigin = rule[0] ? originUri.indexOf(rule[0]) == 0 : true;
-      var allowDest = rule[1] ? destinationUri.indexOf(rule[1]) == 0 : true;
-      if (allowOrigin && allowDest) {
-        return new requestpolicy.mod.RequestResult(
-          true,
-          requestpolicy.mod.REQUEST_TYPE_REDIRECT,
-          requestpolicy.mod.REQUEST_REASON_COMPATIBILITY
-        );
-      }
-    }
-
-    var result = this._checkByDefaultPolicy(originUri, destinationUri);
-    result.requestType = requestpolicy.mod.REQUEST_TYPE_REDIRECT;
-    return result;
+    return this._requestProcessor.isAllowedRedirect(originUri, destinationUri);
   },
 
   /**
@@ -1420,42 +893,6 @@ RequestPolicyService.prototype = {
   },
 
   /**
-   * Add an observer to be notified of all blocked and allowed requests. TODO:
-   * This should be made to accept instances of a defined interface.
-   *
-   * @param {}
-   *          observer
-   */
-  addRequestObserver : function(observer) {
-    if (!("observeBlockedRequest" in observer)) {
-      throw "Observer passed to addRequestObserver does "
-          + "not have an observeBlockedRequest() method.";
-    }
-    requestpolicy.mod.Logger.debug(requestpolicy.mod.Logger.TYPE_INTERNAL,
-        "Adding request observer: " + observer.toString());
-    this._requestObservers.push(observer);
-  },
-
-  /**
-   * Remove an observer added through addRequestObserver().
-   *
-   * @param {}
-   *          observer
-   */
-  removeRequestObserver : function(observer) {
-    for (var i = 0; i < this._requestObservers.length; i++) {
-      if (this._requestObservers[i] == observer) {
-        requestpolicy.mod.Logger.debug(requestpolicy.mod.Logger.TYPE_INTERNAL,
-            "Removing request observer: " + observer.toString());
-        delete this._requestObservers[i];
-        return;
-      }
-    }
-    requestpolicy.mod.Logger.warning(requestpolicy.mod.Logger.TYPE_INTERNAL,
-        "Could not find observer to remove " + "in removeRequestObserver()");
-  },
-
-  /**
    * Handles observer notifications sent by the HTTPS Everywhere extension
    * that inform us of URIs that extension has rewritten.
    *
@@ -1464,7 +901,7 @@ RequestPolicyService.prototype = {
    */
   _handleHttpsEverywhereUriRewrite : function(oldURI, newSpec) {
     oldURI = oldURI.QueryInterface(CI.nsIURI);
-    this.mapDestinations(oldURI.spec, newSpec);
+    this._requestProcessor.mapDestinations(oldURI.spec, newSpec);
   },
 
   _handleUninstallOrDisable : function() {
@@ -1495,10 +932,10 @@ RequestPolicyService.prototype = {
   observe : function(subject, topic, data) {
     switch (topic) {
       case "http-on-examine-response" :
-        this._examineHttpResponse(subject);
+        this._requestProcessor._examineHttpResponse(subject);
         break;
       case "http-on-modify-request" :
-        this._examineHttpRequest(subject);
+        this._requestProcessor._examineHttpRequest(subject);
         break;
       case requestpolicy.mod.SUBSCRIPTION_UPDATED_TOPIC:
         requestpolicy.mod.Logger.debug(
@@ -1530,8 +967,9 @@ RequestPolicyService.prototype = {
             }
           }
           function updateCompleted(result) {
-            requestpolicy.mod.Logger.info(requestpolicy.mod.Logger.TYPE_INTERNAL,
-                                          'Subscription update completed: ' + result);
+            requestpolicy.mod.Logger.info(
+                requestpolicy.mod.Logger.TYPE_INTERNAL,
+                'Subscription update completed: ' + result);
           }
           this._subscriptions.update(updateCompleted, serials);
         }
@@ -1626,667 +1064,31 @@ RequestPolicyService.prototype = {
 
   // enable our actual shouldLoad function
   _initContentPolicy : function() {
+    this._requestProcessor = new requestpolicy.mod.RequestProcessor(this);
     this.shouldLoad = this.mainContentPolicy.shouldLoad;
     if (!this.mimeService) {
       // this.rejectCode = typeof(/ /) == "object" ? -4 : -3;
       this.rejectCode = CI.nsIContentPolicy.REJECT_SERVER;
-      this.mimeService = CC['@mozilla.org/uriloader/external-helper-app-service;1']
+      this.mimeService =
+          CC['@mozilla.org/uriloader/external-helper-app-service;1']
           .getService(CI.nsIMIMEService);
     }
   },
 
-  _argumentsToString : function(args) {
-    // Note: try not to cause side effects of toString() during load, so "<HTML
-    // Element>" is hard-coded.
-    return "type: "
-        + args.aContentType
-        + ", destination: "
-        + args.dest
-        + ", origin: "
-        + args.origin
-        + ", context: "
-        + ((args.aContext) instanceof (CI.nsIDOMHTMLElement)
-            ? "<HTML Element>"
-            : args.aContext)
-        + ", mime: " + args.aMimeTypeGuess
-        + ", " + args.aExtra;
-  },
-
-  // We always call this from shouldLoad to reject a request.
-  reject : function(reason, args, requestResult) {
-    requestpolicy.mod.Logger.warning(requestpolicy.mod.Logger.TYPE_CONTENT,
-        "** BLOCKED ** reason: "
-        + reason + ". " + this._argumentsToString(args));
-
-    if (this._blockingDisabled) {
-      return CP_OK;
-    }
-
-    args.aContext.requestpolicyBlocked = true;
-
-    this._cacheShouldLoadResult(CP_REJECT, args.origin, args.dest);
-    this._recordRejectedRequest(args.origin, args.dest, requestResult);
-
-    if (CI.nsIContentPolicy.TYPE_DOCUMENT == args.aContentType) {
-      // This was a blocked top-level document request. This may be due to
-      // a blocked attempt by javascript to set the document location.
-      // TODO: pass requestResult?
-      this._notifyBlockedTopLevelDocRequest(args.origin, args.dest);
-    }
-
-    return CP_REJECT;
-  },
-
-  _recordRejectedRequest : function(originUri, destUri, requestResult) {
-    this._rejectedRequests.addRequest(originUri, destUri, requestResult);
-    this._allowedRequests.removeRequest(originUri, destUri);
-    this._notifyRequestObserversOfBlockedRequest(originUri, destUri);
-  },
-
-  // We only call this from shouldLoad when the request was a remote request
-  // initiated by the content of a page. this is partly for efficiency. in other
-  // cases we just return CP_OK rather than return this function which
-  // ultimately returns CP_OK. Fourth param, "unforbidable", is set to true if
-  // this request shouldn't be recorded as an allowed request.
-  /**
-   * @param reason {String}
-   * @param args
-   * @param unforbidable {Boolean}
-   * @param requestResult {RequestResult}
-   */
-  accept : function(reason, args, requestResult, unforbidable) {
-    requestpolicy.mod.Logger.warning(requestpolicy.mod.Logger.TYPE_CONTENT,
-        "** ALLOWED ** reason: "
-        + reason + ". " + this._argumentsToString(args));
-
-    this._cacheShouldLoadResult(CP_OK, args.origin, args.dest);
-    // We aren't recording the request so it doesn't show up in the menu, but we
-    // want it to still show up in the request log.
-    if (unforbidable) {
-      this._notifyRequestObserversOfAllowedRequest(args.origin, args.dest,
-          requestResult);
-    } else {
-      this._recordAllowedRequest(args.origin, args.dest, false, requestResult);
-    }
-
-    return CP_OK;
-  },
-
-  _recordAllowedRequest : function(originUri, destUri, isInsert,
-                                   requestResult) {
-    var destIdentifier = this.getUriIdentifier(destUri);
-
-    if (isInsert == undefined) {
-      isInsert = false;
-    }
-
-    // Reset the accepted and rejected requests originating from this
-    // destination. That is, if this accepts a request to a uri that may itself
-    // originate further requests, reset the information about what that page is
-    // accepting and rejecting.
-    // If "isInsert" is set, then we don't want to clear the destUri info.
-    if (!isInsert) {
-      this._allowedRequests.removeOriginUri(destUri);
-      this._rejectedRequests.removeOriginUri(destUri);
-    }
-    this._rejectedRequests.removeRequest(originUri, destUri);
-    this._allowedRequests.addRequest(originUri, destUri, requestResult);
-    this._notifyRequestObserversOfAllowedRequest(originUri, destUri,
-          requestResult);
-  },
-
-  _cacheShouldLoadResult : function(result, originUri, destUri) {
-    var date = new Date();
-    this._lastShouldLoadCheck.time = date.getTime();
-    this._lastShouldLoadCheck.destination = destUri;
-    this._lastShouldLoadCheck.origin = originUri;
-    this._lastShouldLoadCheck.result = result;
-  },
-
-  /**
-   * Determines if a request is only related to internal resources.
-   *
-   * @param {}
-   *          aContentLocation
-   * @param {}
-   *          aRequestOrigin
-   * @return {Boolean} true if the request is only related to internal
-   *         resources.
-   */
-  _isInternalRequest : function(aContentLocation, aRequestOrigin) {
-    // Note: Don't OK the origin scheme "moz-nullprincipal" without further
-    // understanding. It appears to be the source when test8.html is used. That
-    // is, javascript redirect to a "javascript:" url that creates the entire
-    // page's content which includes a form that it submits. Maybe
-    // "moz-nullprincipal" always shows up when using "document.location"?
-
-    // Not cross-site requests.
-    if (aContentLocation.scheme == "resource"
-        || aContentLocation.scheme == "about"
-        || aContentLocation.scheme == "data"
-        || aContentLocation.scheme == "chrome"
-        || aContentLocation.scheme == "moz-icon"
-        || aContentLocation.scheme == "moz-filedata"
-        || aContentLocation.scheme == "blob"
-        || aContentLocation.scheme == "wyciwyg"
-        || aContentLocation.scheme == "javascript") {
-      return true;
-    }
-
-    if (aRequestOrigin == undefined || aRequestOrigin == null) {
-      return true;
-    }
-
-    try {
-      // The asciiHost values will exist but be empty strings for the "file"
-      // scheme, so we don't want to allow just because they are empty strings,
-      // only if not set at all.
-      aRequestOrigin.asciiHost;
-      aContentLocation.asciiHost;
-      // The spec can be empty if odd things are going on, like the Refcontrol
-      // extension causing back/forward button-initiated requests to have
-      // aRequestOrigin be a virtually empty nsIURL object.
-      var missingSpecOrHost = aRequestOrigin.spec === "";
-    } catch (e) {
-      missingSpecOrHost = true;
-    }
-
-    if (missingSpecOrHost) {
-      requestpolicy.mod.Logger.info(requestpolicy.mod.Logger.TYPE_CONTENT,
-          "No asciiHost or empty spec on either aRequestOrigin <"
-              + aRequestOrigin.spec + "> or aContentLocation <"
-              + aContentLocation.spec + ">");
-      return true;
-    }
-
-    var destHost = aContentLocation.asciiHost;
-
-    // "global" dest are [some sort of interal requests]
-    // "browser" dest are [???]
-    if (destHost == "global" || destHost == "browser") {
-      return true;
-    }
-
-    if (aRequestOrigin.scheme == 'about'
-        && aRequestOrigin.spec.indexOf("about:neterror?") == 0) {
-      return true;
-    }
-
-    // If there are entities in the document, they may trigger a local file
-    // request. We'll only allow requests to .dtd files, though, so we don't
-    // open up all file:// destinations.
-    if (aContentLocation.scheme == "file"
-        && /.\.dtd$/.test(aContentLocation.path)) {
-      return true;
-    }
-
-    return false;
-  },
-
-  _checkByDefaultPolicy : function(origin, dest) {
-    if (this._defaultAllow) {
-      var result = new requestpolicy.mod.RequestResult(
-          true,
-          undefined,
-          requestpolicy.mod.REQUEST_REASON_DEFAULT_POLICY);
-      return result;
-    }
-    if (this._defaultAllowSameDomain) {
-      var originDomain = requestpolicy.mod.DomainUtil.getDomain(origin);
-      var destDomain = requestpolicy.mod.DomainUtil.getDomain(dest);
-      return new requestpolicy.mod.RequestResult(
-          originDomain == destDomain,
-          undefined,
-          requestpolicy.mod.REQUEST_REASON_DEFAULT_SAME_DOMAIN);
-    }
-    // We probably want to allow requests from http:80 to https:443 of the same
-    // domain. However, maybe this is so uncommon it's not worth any extra
-    // complexity.
-    var originIdent = requestpolicy.mod.DomainUtil.getIdentifier(origin,
-          requestpolicy.mod.DomainUtil.LEVEL_SOP);
-    var destIdent = requestpolicy.mod.DomainUtil.getIdentifier(dest,
-          requestpolicy.mod.DomainUtil.LEVEL_SOP);
-    return new requestpolicy.mod.RequestResult(
-        (originIdent == destIdent),
-        undefined,
-        requestpolicy.mod.REQUEST_REASON_DEFAULT_SAME_DOMAIN);
-  },
-
-  /**
-   * Determines if a request is a duplicate of the last call to shouldLoad(). If
-   * it is, the cached result in _lastShouldLoadCheck.result can be used. Not
-   * sure why, it seems that there are duplicates so using this simple cache of
-   * the last call to shouldLoad() keeps duplicates out of log data.
-   *
-   * @param {}
-   *          aContentLocation
-   * @param {}
-   *          aRequestOrigin
-   * @return {Boolean} true if the request a duplicate.
-   */
-  _isDuplicateRequest : function(dest, origin) {
-
-    if (this._lastShouldLoadCheck.origin == origin
-        && this._lastShouldLoadCheck.destination == dest) {
-      var date = new Date();
-      if (date.getTime() - this._lastShouldLoadCheck.time < this._lastShouldLoadCheckTimeout) {
-        requestpolicy.mod.Logger.debug(requestpolicy.mod.Logger.TYPE_CONTENT,
-            "Using cached shouldLoad() result of "
-                + this._lastShouldLoadCheck.result + " for request to <" + dest
-                + "> from <" + origin + ">.");
-        return true;
-      } else {
-        requestpolicy.mod.Logger.debug(requestpolicy.mod.Logger.TYPE_CONTENT,
-            "shouldLoad() cache expired for result of "
-                + this._lastShouldLoadCheck.result + " for request to <" + dest
-                + "> from <" + origin + ">.");
-      }
-    }
-    return false;
-  },
-
-  // the content policy that does something useful
   mainContentPolicy : {
-
     // https://developer.mozilla.org/en/nsIContentPolicy
     shouldLoad : function(aContentType, aContentLocation, aRequestOrigin,
         aContext, aMimeTypeGuess, aExtra, aRequestPrincipal) {
-      //requestpolicy.mod.Logger.vardump(aRequestOrigin);
-      //requestpolicy.mod.Logger.vardump(aContentLocation);
-      try {
-
-        if (this._isInternalRequest(aContentLocation, aRequestOrigin)) {
-          return CP_OK;
-        }
-
-        // We don't need to worry about ACE formatted IDNs because it seems
-        // that they'll automatically be converted to UTF8 format before we
-        // even get here, as long as they're valid and Mozilla allows the TLD
-        // to have UTF8 formatted IDNs.
-        var origin = aRequestOrigin.specIgnoringRef;
-        var dest = aContentLocation.specIgnoringRef;
-
-        // Fx 16 changed the following: 1) we should be able to count on the
-        // referrer (aRequestOrigin) being set to something besides
-        // moz-nullprincipal when there is a referrer, and 2) the new argument
-        // aRequestPrincipal is provided. This means our hackery to set the
-        // referrer based on aContext when aRequestOrigin is moz-nullprincipal
-        // is now causing requests that don't have a referrer (namely, URLs
-        // entered in the address bar) to be blocked and trigger a top-level
-        // document redirect notification.
-        if (aRequestOrigin.scheme == "moz-nullprincipal" && aRequestPrincipal) {
-          requestpolicy.mod.Logger.warning(
-              requestpolicy.mod.Logger.TYPE_CONTENT,
-              "Allowing request that appears to be a URL entered in the "
-                  + "location bar or some other good explanation: " + dest);
-          return CP_OK;
-        }
-
-        // Note: Assuming the Fx 16 moz-nullprincipal+aRequestPrincipal check
-        // above is correct, this should be able to be removed when Fx < 16 is
-        // no longer supported.
-        if (aRequestOrigin.scheme == "moz-nullprincipal" && aContext) {
-          var newOrigin = requestpolicy.mod.DomainUtil
-                .stripFragment(aContext.contentDocument.documentURI);
-          requestpolicy.mod.Logger.info(
-              requestpolicy.mod.Logger.TYPE_CONTENT,
-              "Considering moz-nullprincipal origin <"
-                  + origin + "> to be origin <" + newOrigin + ">");
-          origin = newOrigin;
-          aRequestOrigin = requestpolicy.mod.DomainUtil.getUriObject(origin);
-        }
-
-        if (aRequestOrigin.scheme == "view-source") {
-          var newOrigin = origin.split(":").slice(1).join(":");
-          requestpolicy.mod.Logger.info(
-            requestpolicy.mod.Logger.TYPE_CONTENT,
-            "Considering view-source origin <"
-              + origin + "> to be origin <" + newOrigin + ">");
-          origin = newOrigin;
-          aRequestOrigin = requestpolicy.mod.DomainUtil.getUriObject(origin);
-        }
-
-        if (aContentLocation.scheme == "view-source") {
-          var newDest = dest.split(":").slice(1).join(":");
-          if (newDest.indexOf("data:text/html") == 0) {
-            // "View Selection Source" has been clicked
-            requestpolicy.mod.Logger.info(
-                requestpolicy.mod.Logger.TYPE_CONTENT,
-                "Allowing \"data:text/html\" view-source destination"
-                    + " (Selection Source)");
-            return CP_OK;
-          } else {
-            requestpolicy.mod.Logger.info(
-                requestpolicy.mod.Logger.TYPE_CONTENT,
-                "Considering view-source destination <"
-                    + dest + "> to be destination <" + newDest + ">");
-            dest = newDest;
-            aContentLocation = requestpolicy.mod.DomainUtil.getUriObject(dest);
-          }
-        }
-
-        if (origin == "about:blank" && aContext) {
-          var newOrigin;
-          if (aContext.documentURI && aContext.documentURI != "about:blank") {
-            newOrigin = aContext.documentURI;
-          } else if (aContext.ownerDocument &&
-                     aContext.ownerDocument.documentURI &&
-                     aContext.ownerDocument.documentURI != "about:blank") {
-            newOrigin = aContext.ownerDocument.documentURI;
-          }
-          if (newOrigin) {
-            newOrigin = requestpolicy.mod.DomainUtil.stripFragment(newOrigin);
-            requestpolicy.mod.Logger.info(
-                requestpolicy.mod.Logger.TYPE_CONTENT, "Considering origin <"
-                    + origin + "> to be origin <" + newOrigin + ">");
-            origin = newOrigin;
-            aRequestOrigin = requestpolicy.mod.DomainUtil.getUriObject(origin);
-          }
-        }
-
-        if (this._isDuplicateRequest(dest, origin)) {
-          return this._lastShouldLoadCheck.result;
-        }
-
-        // Sometimes, clicking a link to a fragment will result in a request
-        // where the origin is the same as the destination, but none of the
-        // additional content of the page is again requested. The result is that
-        // nothing ends up showing for blocked or allowed destinations because
-        // all of that data was cleared due to the new request.
-        // Example to test with: Click on "expand all" at
-        // http://code.google.com/p/SOME_PROJECT/source/detail?r=SOME_REVISION
-        if (origin == dest) {
-          requestpolicy.mod.Logger.warning(
-              requestpolicy.mod.Logger.TYPE_CONTENT,
-              "Allowing (but not recording) request "
-                  + "where origin is the same as the destination: " + origin);
-          return CP_OK;
-        }
-
-        var args = {
-          aContentType: aContentType,
-          dest: dest,
-          origin: origin,
-          aContext: aContext,
-          aMimeTypeGuess: aMimeTypeGuess,
-          aExtra: aExtra
-        };
-
-        if (aContext && aContext.nodeName == "LINK" &&
-            (aContext.rel == "icon" || aContext.rel == "shortcut icon")) {
-          this._faviconRequests[dest] = true;
-        }
-
-        // Note: If changing the logic here, also make necessary changes to
-        // isAllowedRedirect).
-
-        // Checking for link clicks, form submissions, and history requests
-        // should be done before other checks. Specifically, when link clicks
-        // were done after allowed-origin and other checks, then links that
-        // were allowed due to other checks would end up recorded in the origin
-        // url's allowed requests, and woud then show up on one tab if link
-        // was opened in a new tab but that link would have been allowed
-        // regardless of the link click. The original tab would then show it
-        // in its menu.
-        if (this._clickedLinks[origin] && this._clickedLinks[origin][dest]) {
-          // Don't delete the _clickedLinks item. We need it for if the user
-          // goes back/forward through their history.
-          // delete this._clickedLinks[origin][dest];
-
-          // We used to have this not be recorded so that it wouldn't cause us
-          // to forget blocked/allowed requests. However, when a policy change
-          // causes a page refresh after a link click, it looks like a link
-          // click again and so if we don't forget the previous blocked/allowed
-          // requests, the menu becomes inaccurate. Now the question is: what
-          // are we breaking by clearing the blocked/allowed requests here?
-          var result = new requestpolicy.mod.RequestResult(
-              true,
-              undefined,
-              requestpolicy.mod.REQUEST_REASON_LINK_CLICK);
-          return this.accept("User-initiated request by link click", args, result);
-             //null, true);
-
-        } else if (this._submittedForms[origin]
-            && this._submittedForms[origin][dest.split("?")[0]]) {
-          // Note: we dropped the query string from the dest because form GET
-          // requests will have that added on here but the original action of
-          // the form may not have had it.
-          // Don't delete the _clickedLinks item. We need it for if the user
-          // goes back/forward through their history.
-          // delete this._submittedForms[origin][dest.split("?")[0]];
-
-          // See the note above for link clicks and forgetting blocked/allowed
-          // requests on refresh. I haven't tested if it's the same for forms
-          // but it should be so we're making the same change here.
-          var result = new requestpolicy.mod.RequestResult(
-              true,
-              undefined,
-              requestpolicy.mod.REQUEST_REASON_FORM_SUBMISSION);
-          return this.accept("User-initiated request by form submission", args, result);
-              //null, true);
-
-        } else if (this._historyRequests[dest]) {
-          // When the user goes back and forward in their history, a request for
-          // the url comes through but is not followed by requests for any of
-          // the page's content. Therefore, we make sure that our cache of
-          // blocked requests isn't removed in this case.
-          delete this._historyRequests[dest];
-          var result = new requestpolicy.mod.RequestResult(
-              true,
-              undefined,
-              requestpolicy.mod.REQUEST_REASON_HISTORY_REQUEST);
-          return this.accept("History request", args, result, true);
-        } else if (this._userAllowedRedirects[origin]
-            && this._userAllowedRedirects[origin][dest]) {
-          // shouldLoad is called by location.href in overlay.js as of Fx
-          // 3.7a5pre and SeaMonkey 2.1a.
-          var result = new requestpolicy.mod.RequestResult(
-              true,
-              undefined,
-              requestpolicy.mod.REQUEST_REASON_USER_ALLOWED_REDIRECT);
-          return this.accept("User-allowed redirect", args, result, true);
-        }
-
-        if (aRequestOrigin.scheme == "chrome") {
-          if (aRequestOrigin.asciiHost == "browser") {
-            // "browser" origin shows up for favicon.ico and an address entered
-            // in address bar.
-            var result = new requestpolicy.mod.RequestResult(
-                true,
-                undefined,
-                requestpolicy.mod.REQUEST_REASON_USER_ACTION);
-            return this.accept(
-                "User action (e.g. address entered in address bar) or other good "
-                    + "explanation (e.g. new window/tab opened)", args, result);
-          } else {
-            // TODO: It seems sketchy to allow all requests from chrome. If I
-            // had to put my money on a possible bug (in terms of not blocking
-            // requests that should be), I'd put it here. Doing this, however,
-            // saves a lot of blocking of legitimate requests from extensions
-            // that originate from their xul files. If you're reading this and
-            // you know of a way to use this to evade RequestPolicy, please let
-            // me know, I will be very grateful.
-            var result = new requestpolicy.mod.RequestResult(
-                true,
-                undefined,
-                requestpolicy.mod.REQUEST_REASON_USER_ACTION);
-            return this.accept(
-                "User action (e.g. address entered in address bar) or other good "
-                    + "explanation (e.g. new window/tab opened)", args, result);
-          }
-        }
-
-        // This is mostly here for the case of popup windows where the user has
-        // allowed popups for the domain. In that case, the window.open() call
-        // that made the popup isn't calling the wrapped version of
-        // window.open() and we can't find a better way to register the source
-        // and destination before the request is made. This should be able to be
-        // removed if we can find a better solution for the allowed popup case.
-        if (aContext && aContext.nodeName == "xul:browser" && aContext.currentURI
-            && aContext.currentURI.spec == "about:blank") {
-          var result = new requestpolicy.mod.RequestResult(
-              true,
-              undefined,
-              requestpolicy.mod.REQUEST_REASON_NEW_WINDOW);
-          return this
-              .accept(
-                  "New window (should probably only be an allowed popup's initial request)",
-                  args, result, true);
-        }
-
-        // XMLHttpRequests made within chrome's context have these origins.
-        // Greasemonkey uses such a method to provide their cross-site xhr.
-        if (origin == "resource://gre/res/hiddenWindow.html" ||
-            origin == "resource://gre-resources/hiddenWindow.html") {
-        }
-
-        // Now that we have blacklists, a user could prevent themselves from
-        // being able to reload a page by blocking requests from * to the
-        // destination page. As a simple hack around this, for now we'll always
-        // allow request to the same origin. It would be nice to have a a better
-        // solution but I'm not sure what that solution is.
-        var originIdent = requestpolicy.mod.DomainUtil.getIdentifier(origin);
-        var destIdent = requestpolicy.mod.DomainUtil.getIdentifier(dest);
-        if (originIdent == destIdent) {
-          var result = new requestpolicy.mod.RequestResult(
-              true,
-              undefined,
-              requestpolicy.mod.REQUEST_REASON_IDENTICAL_IDENTIFIER);
-          return this.accept(
-            "Allowing request where origin protocol, host, and port are the" +
-              " same as the destination: " + originIdent,
-            args, result);
-        }
-
-        var result = this._policyMgr.checkRequestAgainstUserPolicies(
-          aRequestOrigin, aContentLocation);
-        for (var i = 0; i < result.matchedDenyRules.length; i++) {
-          requestpolicy.mod.Logger.dump('Matched deny rules');
-          requestpolicy.mod.Logger.vardump(result.matchedDenyRules[i]);
-        }
-        for (var i = 0; i < result.matchedAllowRules.length; i++) {
-          requestpolicy.mod.Logger.dump('Matched allow rules');
-          requestpolicy.mod.Logger.vardump(result.matchedAllowRules[i]);
-        }
-        // If there are both allow and deny rules, then fall back on the default
-        // policy. I believe this is effectively the same as giving precedence
-        // to allow rules when in default allow mode and giving precedence to
-        // deny rules when in default deny mode. It's just a different way of
-        // expressing the same logic. Now, whether that's the right logic we
-        // should be using to solve the problem of rule precedence and support
-        // for fine-grained rules overriding course-grained ones is a different
-        // question.
-        if (result.allowRulesExist() && result.denyRulesExist()) {
-          result.resultReason = requestpolicy.mod.REQUEST_REASON_DEFAULT_POLICY_INCONSISTENT_RULES;
-          if (this._defaultAllow) {
-            result.isAllowed = true;
-            return this.accept("User policy indicates both allow and block. " +
-                               "Using default allow policy", args, result);
-          } else {
-            result.isAllowed = false;
-            return this.reject("User policy indicates both allow and block. " +
-                               "Using default block policy", args, result);
-          }
-        }
-        if (result.allowRulesExist()) {
-          result.resultReason = requestpolicy.mod.REQUEST_REASON_USER_POLICY;
-          result.isAllowed = true;
-          return this.accept("Allowed by user policy", args, result);
-        }
-        if (result.denyRulesExist()) {
-          result.resultReason = requestpolicy.mod.REQUEST_REASON_USER_POLICY;
-          result.isAllowed = false;
-          return this.reject("Blocked by user policy", args, result);
-        }
-
-        var result = this._policyMgr.checkRequestAgainstSubscriptionPolicies(
-          aRequestOrigin, aContentLocation);
-        for (var i = 0; i < result.matchedDenyRules.length; i++) {
-          requestpolicy.mod.Logger.dump('Matched deny rules');
-          requestpolicy.mod.Logger.vardump(result.matchedDenyRules[i]);
-        }
-        for (var i = 0; i < result.matchedAllowRules.length; i++) {
-          requestpolicy.mod.Logger.dump('Matched allow rules');
-          requestpolicy.mod.Logger.vardump(result.matchedAllowRules[i]);
-        }
-        if (result.allowRulesExist() && result.denyRulesExist()) {
-          result.resultReason = requestpolicy.mod.REQUEST_REASON_DEFAULT_POLICY_INCONSISTENT_RULES;
-          if (this._defaultAllow) {
-            result.isAllowed = true;
-            return this.accept("Subscription policies indicate both allow and block. " +
-                               "Using default allow policy", args, result);
-          } else {
-            result.isAllowed = false;
-            return this.reject("Subscription policies indicate both allow and block. " +
-                               "Using default block policy", args, result);
-          }
-        }
-        if (result.denyRulesExist()) {
-          result.resultReason = requestpolicy.mod.REQUEST_REASON_SUBSCRIPTION_POLICY;
-          result.isAllowed = false;
-          return this.reject("Blocked by subscription policy", args, result);
-        }
-        if (result.allowRulesExist()) {
-          result.resultReason = requestpolicy.mod.REQUEST_REASON_SUBSCRIPTION_POLICY;
-          result.isAllowed = true;
-          return this.accept("Allowed by subscription policy", args, result);
-        }
-
-        for (var i = 0; i < this._compatibilityRules.length; i++) {
-          var rule = this._compatibilityRules[i];
-          var allowOrigin = rule[0] ? origin.indexOf(rule[0]) == 0 : true;
-          var allowDest = rule[1] ? dest.indexOf(rule[1]) == 0 : true;
-          if (allowOrigin && allowDest) {
-            var result = new requestpolicy.mod.RequestResult(
-                true,
-                undefined,
-                requestpolicy.mod.REQUEST_REASON_COMPATIBILITY);
-            return this.accept(
-                "Extension/application compatibility rule matched [" + rule[2]
-                    + "]", args, result, true);
-          }
-        }
-
-        // If the destination has a mapping (i.e. it was originally a different
-        // destination but was changed into the current one), accept this
-        // request if the original destination would have been accepted.
-        // Check aExtra against CP_MAPPEDDESTINATION to stop further recursion.
-        if (aExtra != CP_MAPPEDDESTINATION && this._mappedDestinations[dest]) {
-          for (var mappedDest in this._mappedDestinations[dest]) {
-            var mappedDestUriObj = this._mappedDestinations[dest][mappedDest];
-            requestpolicy.mod.Logger.warning(
-                requestpolicy.mod.Logger.TYPE_CONTENT,
-                "Checking mapped destination: " + mappedDest);
-            var mappedResult = this.shouldLoad(aContentType, mappedDestUriObj,
-                aRequestOrigin, aContext, aMimeTypeGuess, CP_MAPPEDDESTINATION);
-            if (mappedResult == CP_OK) {
-              return CP_OK;
-            }
-          }
-        }
-
-        var result = this._checkByDefaultPolicy(origin, dest);
-        if (result.isAllowed) {
-          return this.accept("Allowed by default policy", args, result);
-        } else {
-          // We didn't match any of the conditions in which to allow the request,
-          // so reject it.
-          return aExtra == CP_MAPPEDDESTINATION ? CP_REJECT :
-                this.reject("Denied by default policy", args, result);
-        }
-
-
-      } catch (e) {
-        requestpolicy.mod.Logger.severe(requestpolicy.mod.Logger.TYPE_ERROR,
-            "Fatal Error, " + e + ", stack was: " + e.stack);
-        requestpolicy.mod.Logger.severe(requestpolicy.mod.Logger.TYPE_CONTENT,
-            "Rejecting request due to internal error.");
-        return this._blockingDisabled ? CP_OK : CP_REJECT;
-      }
-
-    } // end shouldLoad
+      var request = new requestpolicy.mod.Request(
+          aContentType, aContentLocation, aRequestOrigin, aContext,
+          aMimeTypeGuess, aExtra, aRequestPrincipal);
+      return this._requestProcessor.process(request);
+      // TODO: implement the following
+//       this._requestProcessor.process(request);
+//       return request.getShouldLoadResult();
+    }
 
-  } // end mainContentPolicy
+  }
 
   // /////////////////////////////////////////////////////////////////////////
   // end nsIContentPolicy interface
diff --git a/src/content/overlay.js b/src/content/overlay.js
index 379b6c5..c8ac38b 100644
--- a/src/content/overlay.js
+++ b/src/content/overlay.js
@@ -141,7 +141,7 @@ requestpolicy.overlay = {
         // Register this window with the requestpolicy service so that we can be
         // notified of blocked requests. When blocked requests happen, this
         // object's observerBlockedRequests() method will be called.
-        this._rpService.addRequestObserver(this);
+        this._rpService._requestProcessor.addRequestObserver(this);
 
         //this.setContextMenuEnabled(this._rpService.prefs
         //    .getBoolPref("contextMenu"));
@@ -229,7 +229,7 @@ requestpolicy.overlay = {
   //},
 
   onWindowClose : function(event) {
-    this._rpService.removeRequestObserver(this);
+    this._rpService._requestProcessor.removeRequestObserver(this);
     this._removeHistoryObserver();
     this._removeLocationObserver();
   },
@@ -672,8 +672,8 @@ requestpolicy.overlay = {
       return;
     }
     var images = document.getElementsByTagName("img");
-    var rejectedRequests = this._rpService
-          ._rejectedRequests.getOriginUri(document.location);
+    var rejectedRequests = this._rpService._requestProcessor.
+        _rejectedRequests.getOriginUri(document.location);
     var blockedUris = {};
     for (var destBase in rejectedRequests) {
       for (var destIdent in rejectedRequests[destBase]) {
@@ -969,14 +969,17 @@ requestpolicy.overlay = {
           }, false);
     }
 
-    if (this._rpService._blockedRedirects[document.location]) {
-      var dest = this._rpService._blockedRedirects[document.location];
+    // TODO: implement a function in RequestProcessor for this
+    if (this._rpService._requestProcessor.
+        _blockedRedirects[document.location]) {
+      var dest = this._rpService._requestProcessor.
+          _blockedRedirects[document.location];
       requestpolicy.mod.Logger.warning(
           requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT,
           "Showing notification for blocked redirect. To <" + dest + "> "
               + "from <" + document.location + ">");
       this._showRedirectNotification(document, dest);
-      delete this._rpService._blockedRedirects[document.location];
+      delete this._rpService._requestProcessor._blockedRedirects[document.location];
     }
 
     this._wrapWindowOpen(document.defaultView);
diff --git a/src/modules/PolicyManager.jsm b/src/modules/PolicyManager.jsm
index 235ac2f..8cd6ecb 100644
--- a/src/modules/PolicyManager.jsm
+++ b/src/modules/PolicyManager.jsm
@@ -119,6 +119,8 @@ const REQUEST_REASON_IDENTICAL_IDENTIFIER  = 13;
 const REQUEST_REASON_RELATIVE_URL          = 14; // TODO: give user control about relative urls on the page
 
 
+// TODO: merge this Class with the "Request" class and/or some kind of
+// "RememberedRequest" or "RequestInfo" class.
 /**
  * RequestResult objects are used to hand over the result of a check
  * whether a request is allowed or not. Sometimes only the boolean value of
diff --git a/src/modules/Request.jsm b/src/modules/Request.jsm
new file mode 100644
index 0000000..dad6ca1
--- /dev/null
+++ b/src/modules/Request.jsm
@@ -0,0 +1,121 @@
+/*
+ * ***** BEGIN LICENSE BLOCK *****
+ *
+ * RequestPolicy - A Firefox extension for control over cross-site requests.
+ * Copyright (c) 2008 Justin Samuel
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * ***** END LICENSE BLOCK *****
+ */
+
+var EXPORTED_SYMBOLS = ["Request"];
+
+Components.utils.import("resource://requestpolicy/DomainUtil.jsm");
+Components.utils.import("resource://requestpolicy/Logger.jsm");
+Components.utils.import("resource://requestpolicy/PolicyManager.jsm");
+
+
+function Request(aContentType, aContentLocation, aRequestOrigin, aContext,
+    aMimeTypeGuess, aExtra, aRequestPrincipal) {
+  this.aContentType = aContentType;
+  this.aContentLocation = aContentLocation;
+  this.aRequestOrigin = aRequestOrigin;
+  this.aContext = aContext;
+  this.aMimeTypeGuess = aMimeTypeGuess;
+  this.aExtra = aExtra;
+  this.aRequestPrincipal = aRequestPrincipal;
+
+  this.shouldLoadResult = undefined;
+
+  // TODO: Merge "RequestResult" into this class.
+  this.requestResult = undefined;
+}
+
+/**
+  * Determines if a request is only related to internal resources.
+  *
+  * @return {Boolean} true if the request is only related to internal
+  *         resources.
+  */
+Request.prototype.isInternal = function() {
+  // Note: Don't OK the origin scheme "moz-nullprincipal" without further
+  // understanding. It appears to be the source when test8.html is used. That
+  // is, javascript redirect to a "javascript:" url that creates the entire
+  // page's content which includes a form that it submits. Maybe
+  // "moz-nullprincipal" always shows up when using "document.location"?
+
+  // Not cross-site requests.
+  if (this.aContentLocation.scheme == "resource"
+      || this.aContentLocation.scheme == "about"
+      || this.aContentLocation.scheme == "data"
+      || this.aContentLocation.scheme == "chrome"
+      || this.aContentLocation.scheme == "moz-icon"
+      || this.aContentLocation.scheme == "moz-filedata"
+      || this.aContentLocation.scheme == "blob"
+      || this.aContentLocation.scheme == "wyciwyg"
+      || this.aContentLocation.scheme == "javascript") {
+    return true;
+  }
+
+  if (this.aRequestOrigin == undefined || this.aRequestOrigin == null) {
+    return true;
+  }
+
+  var missingSpecOrHost;
+  try {
+    // The asciiHost values will exist but be empty strings for the "file"
+    // scheme, so we don't want to allow just because they are empty strings,
+    // only if not set at all.
+    this.aRequestOrigin.asciiHost;
+    this.aContentLocation.asciiHost;
+    // The spec can be empty if odd things are going on, like the Refcontrol
+    // extension causing back/forward button-initiated requests to have
+    // aRequestOrigin be a virtually empty nsIURL object.
+    missingSpecOrHost = this.aRequestOrigin.spec === "";
+  } catch (e) {
+    missingSpecOrHost = true;
+  }
+
+  if (missingSpecOrHost) {
+    requestpolicy.mod.Logger.info(requestpolicy.mod.Logger.TYPE_CONTENT,
+        "No asciiHost or empty spec on either aRequestOrigin <"
+            + this.aRequestOrigin.spec + "> or aContentLocation <"
+            + this.aContentLocation.spec + ">");
+    return true;
+  }
+
+  var destHost = this.aContentLocation.asciiHost;
+
+  // "global" dest are [some sort of interal requests]
+  // "browser" dest are [???]
+  if (destHost == "global" || destHost == "browser") {
+    return true;
+  }
+
+  if (this.aRequestOrigin.scheme == 'about'
+      && this.aRequestOrigin.spec.indexOf("about:neterror?") == 0) {
+    return true;
+  }
+
+  // If there are entities in the document, they may trigger a local file
+  // request. We'll only allow requests to .dtd files, though, so we don't
+  // open up all file:// destinations.
+  if (this.aContentLocation.scheme == "file"
+      && /.\.dtd$/.test(this.aContentLocation.path)) {
+    return true;
+  }
+
+  return false;
+};
diff --git a/src/modules/RequestProcessor.jsm b/src/modules/RequestProcessor.jsm
new file mode 100644
index 0000000..877ff0f
--- /dev/null
+++ b/src/modules/RequestProcessor.jsm
@@ -0,0 +1,1257 @@
+/*
+ * ***** BEGIN LICENSE BLOCK *****
+ *
+ * RequestPolicy - A Firefox extension for control over cross-site requests.
+ * Copyright (c) 2008 Justin Samuel
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * ***** END LICENSE BLOCK *****
+ */
+
+var EXPORTED_SYMBOLS = ["RequestProcessor"];
+
+const CI = Components.interfaces;
+const CC = Components.classes;
+
+const CP_OK = CI.nsIContentPolicy.ACCEPT;
+const CP_REJECT = CI.nsIContentPolicy.REJECT_SERVER;
+
+// A value intended to not conflict with aExtra passed to shouldLoad() by any
+// other callers. Was chosen randomly.
+const CP_MAPPEDDESTINATION = 0x178c40bf;
+
+
+if (!requestpolicy) {
+  var requestpolicy = {
+    mod : {}
+  };
+}
+Components.utils.import("resource://requestpolicy/DomainUtil.jsm",
+    requestpolicy.mod);
+Components.utils.import("resource://requestpolicy/Logger.jsm",
+    requestpolicy.mod);
+Components.utils.import("resource://requestpolicy/PolicyManager.jsm",
+    requestpolicy.mod);
+Components.utils.import("resource://requestpolicy/RequestUtil.jsm",
+    requestpolicy.mod);
+
+
+
+
+
+function RequestProcessor(rpService) {
+  this._rpService = rpService;
+
+  /**
+   * Number of elapsed milliseconds from the time of the last shouldLoad() call
+   * at which the cached results of the last shouldLoad() call are discarded.
+   *
+   * @type {number}
+   */
+  this._lastShouldLoadCheckTimeout = 200;
+
+  // Calls to shouldLoad appear to be repeated, so successive repeated calls and
+  // their result (accept or reject) are tracked to avoid duplicate processing
+  // and duplicate logging.
+  /**
+   * Object that caches the last shouldLoad
+   * @type {Object}
+   */
+  this._lastShouldLoadCheck = {
+    "origin" : null,
+    "destination" : null,
+    "time" : 0,
+    "result" : null
+  };
+
+  this._rejectedRequests = new requestpolicy.mod.RequestSet();
+  this._allowedRequests = new requestpolicy.mod.RequestSet();
+
+
+  /**
+   * These are redirects that the user allowed when presented with a redirect
+   * notification.
+   */
+  this._userAllowedRedirects = {};
+
+  this._blockedRedirects = {};
+  this._allowedRedirectsReverse = {};
+
+  this._historyRequests = {};
+
+  this._submittedForms = {};
+  this._submittedFormsReverse = {};
+
+  this._clickedLinks = {};
+  this._clickedLinksReverse = {};
+
+  this._faviconRequests = {};
+
+  this._mappedDestinations = {};
+
+  this._requestObservers = [];
+}
+RequestProcessor.prototype.process = function(request) {
+  //requestpolicy.mod.Logger.vardump(request.aRequestOrigin);
+  //requestpolicy.mod.Logger.vardump(request.aContentLocation);
+  try {
+
+    if (request.isInternal()) {
+      return CP_OK;
+    }
+
+    // We don't need to worry about ACE formatted IDNs because it seems
+    // that they'll automatically be converted to UTF8 format before we
+    // even get here, as long as they're valid and Mozilla allows the TLD
+    // to have UTF8 formatted IDNs.
+    var originURI = request.aRequestOrigin.specIgnoringRef;
+    var destURI = request.aContentLocation.specIgnoringRef;
+
+    // Fx 16 changed the following: 1) we should be able to count on the
+    // referrer (aRequestOrigin) being set to something besides
+    // moz-nullprincipal when there is a referrer, and 2) the new argument
+    // aRequestPrincipal is provided. This means our hackery to set the
+    // referrer based on aContext when aRequestOrigin is moz-nullprincipal
+    // is now causing requests that don't have a referrer (namely, URLs
+    // entered in the address bar) to be blocked and trigger a top-level
+    // document redirect notification.
+    if (request.aRequestOrigin.scheme == "moz-nullprincipal" &&
+        request.aRequestPrincipal) {
+      requestpolicy.mod.Logger.warning(
+          requestpolicy.mod.Logger.TYPE_CONTENT,
+          "Allowing request that appears to be a URL entered in the "
+              + "location bar or some other good explanation: " + destURI);
+      return CP_OK;
+    }
+
+    // Note: Assuming the Fx 16 moz-nullprincipal+aRequestPrincipal check
+    // above is correct, this should be able to be removed when Fx < 16 is
+    // no longer supported.
+    if (request.aRequestOrigin.scheme == "moz-nullprincipal" &&
+        request.aContext) {
+      var newOriginURI = requestpolicy.mod.DomainUtil
+            .stripFragment(request.aContext.contentDocument.documentURI);
+      requestpolicy.mod.Logger.info(
+          requestpolicy.mod.Logger.TYPE_CONTENT,
+          "Considering moz-nullprincipal origin <"
+              + originURI + "> to be origin <" + newOriginURI + ">");
+      originURI = newOriginURI;
+      request.aRequestOrigin = requestpolicy.mod.DomainUtil.
+          getUriObject(originURI);
+    }
+
+    if (request.aRequestOrigin.scheme == "view-source") {
+      var newOriginURI = originURI.split(":").slice(1).join(":");
+      requestpolicy.mod.Logger.info(
+        requestpolicy.mod.Logger.TYPE_CONTENT,
+        "Considering view-source origin <"
+          + originURI + "> to be origin <" + newOriginURI + ">");
+      originURI = newOriginURI;
+      request.aRequestOrigin = requestpolicy.mod.DomainUtil.
+          getUriObject(originURI);
+    }
+
+    if (request.aContentLocation.scheme == "view-source") {
+      var newDestURI = destURI.split(":").slice(1).join(":");
+      if (newDestURI.indexOf("data:text/html") == 0) {
+        // "View Selection Source" has been clicked
+        requestpolicy.mod.Logger.info(
+            requestpolicy.mod.Logger.TYPE_CONTENT,
+            "Allowing \"data:text/html\" view-source destination"
+                + " (Selection Source)");
+        return CP_OK;
+      } else {
+        requestpolicy.mod.Logger.info(
+            requestpolicy.mod.Logger.TYPE_CONTENT,
+            "Considering view-source destination <"
+                + destURI + "> to be destination <" + newDestURI + ">");
+        destURI = newDestURI;
+        request.aContentLocation = requestpolicy.mod.DomainUtil.
+            getUriObject(destURI);
+      }
+    }
+
+    if (originURI == "about:blank" && request.aContext) {
+      var newOriginURI;
+      if (request.aContext.documentURI &&
+          request.aContext.documentURI != "about:blank") {
+        newOriginURI = request.aContext.documentURI;
+      } else if (request.aContext.ownerDocument &&
+                  request.aContext.ownerDocument.documentURI &&
+                  request.aContext.ownerDocument.documentURI != "about:blank") {
+        newOriginURI = request.aContext.ownerDocument.documentURI;
+      }
+      if (newOriginURI) {
+        newOriginURI = requestpolicy.mod.DomainUtil.stripFragment(newOriginURI);
+        requestpolicy.mod.Logger.info(
+            requestpolicy.mod.Logger.TYPE_CONTENT, "Considering origin <"
+                + originURI + "> to be origin <" + newOriginURI + ">");
+        originURI = newOriginURI;
+        request.aRequestOrigin = requestpolicy.mod.DomainUtil.
+            getUriObject(originURI);
+      }
+    }
+
+    // originURI and destURI are not changed anymore
+    request.destURI = destURI;
+    request.originURI = originURI;
+
+
+    if (this._isDuplicateRequest(request)) {
+      return this._lastShouldLoadCheck.result;
+    }
+
+    // Sometimes, clicking a link to a fragment will result in a request
+    // where the origin is the same as the destination, but none of the
+    // additional content of the page is again requested. The result is that
+    // nothing ends up showing for blocked or allowed destinations because
+    // all of that data was cleared due to the new request.
+    // Example to test with: Click on "expand all" at
+    // http://code.google.com/p/SOME_PROJECT/source/detail?r=SOME_REVISION
+    if (originURI == destURI) {
+      requestpolicy.mod.Logger.warning(
+          requestpolicy.mod.Logger.TYPE_CONTENT,
+          "Allowing (but not recording) request "
+              + "where origin is the same as the destination: " + originURI);
+      return CP_OK;
+    }
+
+
+
+    if (request.aContext && request.aContext.nodeName == "LINK" &&
+        (request.aContext.rel == "icon" ||
+            request.aContext.rel == "shortcut icon")) {
+      this._faviconRequests[destURI] = true;
+    }
+
+    // Note: If changing the logic here, also make necessary changes to
+    // isAllowedRedirect).
+
+    // Checking for link clicks, form submissions, and history requests
+    // should be done before other checks. Specifically, when link clicks
+    // were done after allowed-origin and other checks, then links that
+    // were allowed due to other checks would end up recorded in the origin
+    // url's allowed requests, and woud then show up on one tab if link
+    // was opened in a new tab but that link would have been allowed
+    // regardless of the link click. The original tab would then show it
+    // in its menu.
+    if (this._clickedLinks[originURI] &&
+        this._clickedLinks[originURI][destURI]) {
+      // Don't delete the _clickedLinks item. We need it for if the user
+      // goes back/forward through their history.
+      // delete this._clickedLinks[originURI][destURI];
+
+      // We used to have this not be recorded so that it wouldn't cause us
+      // to forget blocked/allowed requests. However, when a policy change
+      // causes a page refresh after a link click, it looks like a link
+      // click again and so if we don't forget the previous blocked/allowed
+      // requests, the menu becomes inaccurate. Now the question is: what
+      // are we breaking by clearing the blocked/allowed requests here?
+      request.requestResult = new requestpolicy.mod.RequestResult(
+          true,
+          undefined,
+          requestpolicy.mod.REQUEST_REASON_LINK_CLICK);
+      return this.accept("User-initiated request by link click", request);
+
+    } else if (this._submittedForms[originURI] &&
+        this._submittedForms[originURI][destURI.split("?")[0]]) {
+      // Note: we dropped the query string from the destURI because form GET
+      // requests will have that added on here but the original action of
+      // the form may not have had it.
+      // Don't delete the _clickedLinks item. We need it for if the user
+      // goes back/forward through their history.
+      // delete this._submittedForms[originURI][destURI.split("?")[0]];
+
+      // See the note above for link clicks and forgetting blocked/allowed
+      // requests on refresh. I haven't tested if it's the same for forms
+      // but it should be so we're making the same change here.
+      request.requestResult = new requestpolicy.mod.RequestResult(
+          true,
+          undefined,
+          requestpolicy.mod.REQUEST_REASON_FORM_SUBMISSION);
+      return this.accept("User-initiated request by form submission", request);
+
+    } else if (this._historyRequests[destURI]) {
+      // When the user goes back and forward in their history, a request for
+      // the url comes through but is not followed by requests for any of
+      // the page's content. Therefore, we make sure that our cache of
+      // blocked requests isn't removed in this case.
+      delete this._historyRequests[destURI];
+      request.requestResult = new requestpolicy.mod.RequestResult(
+          true,
+          undefined,
+          requestpolicy.mod.REQUEST_REASON_HISTORY_REQUEST);
+      return this.accept("History request", request, true);
+    } else if (this._userAllowedRedirects[originURI]
+        && this._userAllowedRedirects[originURI][destURI]) {
+      // shouldLoad is called by location.href in overlay.js as of Fx
+      // 3.7a5pre and SeaMonkey 2.1a.
+      request.requestResult = new requestpolicy.mod.RequestResult(
+          true,
+          undefined,
+          requestpolicy.mod.REQUEST_REASON_USER_ALLOWED_REDIRECT);
+      return this.accept("User-allowed redirect", request, true);
+    }
+
+    if (request.aRequestOrigin.scheme == "chrome") {
+      if (request.aRequestOrigin.asciiHost == "browser") {
+        // "browser" origin shows up for favicon.ico and an address entered
+        // in address bar.
+        request.requestResult = new requestpolicy.mod.RequestResult(
+            true,
+            undefined,
+            requestpolicy.mod.REQUEST_REASON_USER_ACTION);
+        return this.accept(
+            "User action (e.g. address entered in address bar) or other good "
+                + "explanation (e.g. new window/tab opened)", request);
+      } else {
+        // TODO: It seems sketchy to allow all requests from chrome. If I
+        // had to put my money on a possible bug (in terms of not blocking
+        // requests that should be), I'd put it here. Doing this, however,
+        // saves a lot of blocking of legitimate requests from extensions
+        // that originate from their xul files. If you're reading this and
+        // you know of a way to use this to evade RequestPolicy, please let
+        // me know, I will be very grateful.
+        request.requestResult = new requestpolicy.mod.RequestResult(
+            true,
+            undefined,
+            requestpolicy.mod.REQUEST_REASON_USER_ACTION);
+        return this.accept(
+            "User action (e.g. address entered in address bar) or other good "
+                + "explanation (e.g. new window/tab opened)", request);
+      }
+    }
+
+    // This is mostly here for the case of popup windows where the user has
+    // allowed popups for the domain. In that case, the window.open() call
+    // that made the popup isn't calling the wrapped version of
+    // window.open() and we can't find a better way to register the source
+    // and destination before the request is made. This should be able to be
+    // removed if we can find a better solution for the allowed popup case.
+    if (request.aContext && request.aContext.nodeName == "xul:browser" &&
+        request.aContext.currentURI &&
+        request.aContext.currentURI.spec == "about:blank") {
+      request.requestResult = new requestpolicy.mod.RequestResult(
+          true,
+          undefined,
+          requestpolicy.mod.REQUEST_REASON_NEW_WINDOW);
+      return this.accept("New window (should probably only be an allowed " +
+          "popup's initial request)", request, true);
+    }
+
+    // XMLHttpRequests made within chrome's context have these origins.
+    // Greasemonkey uses such a method to provide their cross-site xhr.
+    if (originURI == "resource://gre/res/hiddenWindow.html" ||
+        originURI == "resource://gre-resources/hiddenWindow.html") {
+    }
+
+    // Now that we have blacklists, a user could prevent themselves from
+    // being able to reload a page by blocking requests from * to the
+    // destination page. As a simple hack around this, for now we'll always
+    // allow request to the same origin. It would be nice to have a a better
+    // solution but I'm not sure what that solution is.
+    var originIdent = requestpolicy.mod.DomainUtil.getIdentifier(originURI);
+    var destIdent = requestpolicy.mod.DomainUtil.getIdentifier(destURI);
+    if (originIdent == destIdent) {
+      request.requestResult = new requestpolicy.mod.RequestResult(
+          true,
+          undefined,
+          requestpolicy.mod.REQUEST_REASON_IDENTICAL_IDENTIFIER);
+      return this.accept(
+          "Allowing request where origin protocol, host, and port are the" +
+          " same as the destination: " + originIdent, request);
+    }
+
+    request.requestResult = this._rpService._policyMgr.
+        checkRequestAgainstUserPolicies(request.aRequestOrigin,
+            request.aContentLocation);
+    for (var i = 0; i < request.requestResult.matchedDenyRules.length; i++) {
+      requestpolicy.mod.Logger.dump('Matched deny rules');
+      requestpolicy.mod.Logger.vardump(
+          request.requestResult.matchedDenyRules[i]);
+    }
+    for (var i = 0; i < request.requestResult.matchedAllowRules.length; i++) {
+      requestpolicy.mod.Logger.dump('Matched allow rules');
+      requestpolicy.mod.Logger.vardump(
+          request.requestResult.matchedAllowRules[i]);
+    }
+    // If there are both allow and deny rules, then fall back on the default
+    // policy. I believe this is effectively the same as giving precedence
+    // to allow rules when in default allow mode and giving precedence to
+    // deny rules when in default deny mode. It's just a different way of
+    // expressing the same logic. Now, whether that's the right logic we
+    // should be using to solve the problem of rule precedence and support
+    // for fine-grained rules overriding course-grained ones is a different
+    // question.
+    if (request.requestResult.allowRulesExist() &&
+        request.requestResult.denyRulesExist()) {
+      request.requestResult.resultReason = requestpolicy.mod.
+          REQUEST_REASON_DEFAULT_POLICY_INCONSISTENT_RULES;
+      if (this._rpService._defaultAllow) {
+        request.requestResult.isAllowed = true;
+        return this.accept("User policy indicates both allow and block. " +
+            "Using default allow policy", request);
+      } else {
+        request.requestResult.isAllowed = false;
+        return this.reject("User policy indicates both allow and block. " +
+            "Using default block policy", request);
+      }
+    }
+    if (request.requestResult.allowRulesExist()) {
+      request.requestResult.resultReason = requestpolicy.mod.
+          REQUEST_REASON_USER_POLICY;
+      request.requestResult.isAllowed = true;
+      return this.accept("Allowed by user policy", request);
+    }
+    if (request.requestResult.denyRulesExist()) {
+      request.requestResult.resultReason = requestpolicy.mod.
+          REQUEST_REASON_USER_POLICY;
+      request.requestResult.isAllowed = false;
+      return this.reject("Blocked by user policy", request);
+    }
+
+    request.requestResult = this._rpService._policyMgr.
+        checkRequestAgainstSubscriptionPolicies(request.aRequestOrigin,
+            request.aContentLocation);
+    for (var i = 0; i < request.requestResult.matchedDenyRules.length; i++) {
+      requestpolicy.mod.Logger.dump('Matched deny rules');
+      requestpolicy.mod.Logger.vardump(
+          request.requestResult.matchedDenyRules[i]);
+    }
+    for (var i = 0; i < request.requestResult.matchedAllowRules.length; i++) {
+      requestpolicy.mod.Logger.dump('Matched allow rules');
+      requestpolicy.mod.Logger.vardump(
+          request.requestResult.matchedAllowRules[i]);
+    }
+    if (request.requestResult.allowRulesExist() &&
+        request.requestResult.denyRulesExist()) {
+      request.requestResult.resultReason = requestpolicy.mod.
+          REQUEST_REASON_DEFAULT_POLICY_INCONSISTENT_RULES;
+      if (this._rpService._defaultAllow) {
+        request.requestResult.isAllowed = true;
+        return this.accept(
+            "Subscription policies indicate both allow and block. " +
+            "Using default allow policy", request);
+      } else {
+        request.requestResult.isAllowed = false;
+        return this.reject(
+            "Subscription policies indicate both allow and block. " +
+            "Using default block policy", request);
+      }
+    }
+    if (request.requestResult.denyRulesExist()) {
+      request.requestResult.resultReason = requestpolicy.mod.
+          REQUEST_REASON_SUBSCRIPTION_POLICY;
+      request.requestResult.isAllowed = false;
+      return this.reject("Blocked by subscription policy", request);
+    }
+    if (request.requestResult.allowRulesExist()) {
+      request.requestResult.resultReason = requestpolicy.mod.
+          REQUEST_REASON_SUBSCRIPTION_POLICY;
+      request.requestResult.isAllowed = true;
+      return this.accept("Allowed by subscription policy", request);
+    }
+
+    for (var i = 0; i < this._rpService._compatibilityRules.length; i++) {
+      var rule = this._rpService._compatibilityRules[i];
+      var allowOrigin = rule[0] ? originURI.indexOf(rule[0]) == 0 : true;
+      var allowDest = rule[1] ? destURI.indexOf(rule[1]) == 0 : true;
+      if (allowOrigin && allowDest) {
+        request.requestResult = new requestpolicy.mod.RequestResult(
+            true,
+            undefined,
+            requestpolicy.mod.REQUEST_REASON_COMPATIBILITY);
+        return this.accept(
+            "Extension/application compatibility rule matched [" + rule[2] +
+            "]", request, true);
+      }
+    }
+
+    // If the destination has a mapping (i.e. it was originally a different
+    // destination but was changed into the current one), accept this
+    // request if the original destination would have been accepted.
+    // Check aExtra against CP_MAPPEDDESTINATION to stop further recursion.
+    if (request.aExtra != CP_MAPPEDDESTINATION &&
+        this._mappedDestinations[destURI]) {
+      for (var mappedDest in this._mappedDestinations[destURI]) {
+        var mappedDestUriObj = this._mappedDestinations[destURI][mappedDest];
+        requestpolicy.mod.Logger.warning(
+            requestpolicy.mod.Logger.TYPE_CONTENT,
+            "Checking mapped destination: " + mappedDest);
+        var mappedResult = this._rpService.shouldLoad(request.aContentType,
+            mappedDestUriObj, request.aRequestOrigin, request.aContext,
+            request.aMimeTypeGuess, CP_MAPPEDDESTINATION);
+        if (mappedResult == CP_OK) {
+          return CP_OK;
+        }
+      }
+    }
+
+    request.requestResult = this._checkByDefaultPolicy(originURI, destURI);
+    if (request.requestResult.isAllowed) {
+      return this.accept("Allowed by default policy", request);
+    } else {
+      // We didn't match any of the conditions in which to allow the request,
+      // so reject it.
+      return request.aExtra == CP_MAPPEDDESTINATION ? CP_REJECT :
+            this.reject("Denied by default policy", request);
+    }
+
+
+  } catch (e) {
+    requestpolicy.mod.Logger.severe(requestpolicy.mod.Logger.TYPE_ERROR,
+        "Fatal Error, " + e + ", stack was: " + e.stack);
+    requestpolicy.mod.Logger.severe(requestpolicy.mod.Logger.TYPE_CONTENT,
+        "Rejecting request due to internal error.");
+    return this._rpService._blockingDisabled ? CP_OK : CP_REJECT;
+  }
+};
+// RequestProcessor.prototype.finishProcessing = function(request, result) {
+//   request.shouldLoadResult = result;
+// };
+
+RequestProcessor.prototype.mapDestinations = function(origDestUri, newDestUri) {
+  origDestUri = requestpolicy.mod.DomainUtil.stripFragment(origDestUri);
+  newDestUri = requestpolicy.mod.DomainUtil.stripFragment(newDestUri);
+  requestpolicy.mod.Logger.info(requestpolicy.mod.Logger.TYPE_INTERNAL,
+      "Mapping destination <" + origDestUri + "> to <" + newDestUri + ">.");
+  if (!this._mappedDestinations[newDestUri]) {
+    this._mappedDestinations[newDestUri] = {};
+  }
+  this._mappedDestinations[newDestUri][origDestUri] =
+      requestpolicy.mod.DomainUtil.getUriObject(origDestUri);
+};
+
+
+
+
+/**
+ * Checks whether a request is initiated by a content window. If it's from a
+ * content window, then it's from unprivileged code.
+ */
+RequestProcessor.prototype._isContentRequest = function(channel) {
+  var callbacks = [];
+  if (channel.notificationCallbacks) {
+    callbacks.push(channel.notificationCallbacks);
+  }
+  if (channel.loadGroup && channel.loadGroup.notificationCallbacks) {
+    callbacks.push(channel.loadGroup.notificationCallbacks);
+  }
+
+  for (var i = 0; i < callbacks.length; i++) {
+    var callback = callbacks[i];
+    try {
+      // For Gecko 1.9.1
+      return callback.getInterface(CI.nsILoadContext).isContent;
+    } catch (e) {
+    }
+    try {
+      // For Gecko 1.9.0
+      var itemType = callback.getInterface(CI.nsIWebNavigation)
+          .QueryInterface(CI.nsIDocShellTreeItem).itemType;
+      return itemType == CI.nsIDocShellTreeItem.typeContent;
+    } catch (e) {
+    }
+  }
+
+  return false;
+};
+
+/**
+ * Called after a response has been received from the web server. Headers are
+ * available on the channel. The response can be accessed and modified via
+ * nsITraceableChannel.
+ */
+RequestProcessor.prototype._examineHttpResponse = function(observedSubject) {
+  // Currently, if a user clicks a link to download a file and that link
+  // redirects and is subsequently blocked, the user will see the blocked
+  // destination in the menu. However, after they have allowed it from
+  // the menu and attempted the download again, they won't see the allowed
+  // request in the menu. Fixing that might be a pain and also runs the
+  // risk of making the menu cluttered and confusing with destinations of
+  // followed links from the current page.
+
+  // TODO: Make user aware of blocked headers so they can allow them if
+  // desired.
+
+  var httpChannel = observedSubject.QueryInterface(CI.nsIHttpChannel);
+
+  var headerType;
+  var dest;
+
+  try {
+    // If there is no such header, getResponseHeader() will throw
+    // NS_ERROR_NOT_AVAILABLE. If there is more than header, the last one is
+    // the one that will be used.
+    headerType = "Location";
+    dest = httpChannel.getResponseHeader(headerType);
+  } catch (e) {
+    // No location header. Look for a Refresh header.
+    try {
+      headerType = "Refresh";
+      var refreshString = httpChannel.getResponseHeader(headerType);
+    } catch (e) {
+      // No Location header or Refresh header.
+      return;
+    }
+    try {
+      var parts = requestpolicy.mod.DomainUtil.parseRefresh(refreshString);
+    } catch (e) {
+      requestpolicy.mod.Logger.warning(
+          requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT,
+          "Invalid refresh header: <" + refreshString + ">");
+      if (!this._rpService._blockingDisabled) {
+        httpChannel.setResponseHeader(headerType, "", false);
+      }
+      return;
+    }
+    // We can ignore the delay (parts[0]) because we aren't manually doing
+    // the refreshes. Allowed refreshes we still leave to the browser.
+    // The dest may be empty if the origin is what should be refreshed. This
+    // will be handled by DomainUtil.determineRedirectUri().
+    dest = parts[1];
+  }
+
+  // For origins that are IDNs, this will always be in ACE format. We want
+  // it in UTF8 format if it's a TLD that Mozilla allows to be in UTF8.
+  var origin = requestpolicy.mod.DomainUtil.formatIDNUri(httpChannel.name);
+
+  // Allow redirects of requests from privileged code.
+  if (!this._isContentRequest(httpChannel)) {
+    // However, favicon requests that are redirected appear as non-content
+    // requests. So, check if the original request was for a favicon.
+    var originPath = requestpolicy.mod.DomainUtil.getPath(httpChannel.name);
+    // We always have to check "/favicon.ico" because Firefox will use this
+    // as a default path and that request won't pass through shouldLoad().
+    if (originPath == "/favicon.ico" || this._faviconRequests[origin]) {
+      // If the redirected request is allowed, we need to know that was a
+      // favicon request in case it is further redirected.
+      this._faviconRequests[dest] = true;
+      requestpolicy.mod.Logger.info(
+          requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT, "'" + headerType
+              + "' header to <" + dest + "> " + "from <" + origin
+              + "> appears to be a redirected favicon request. "
+              + "This will be treated as a content request.");
+    } else {
+      requestpolicy.mod.Logger.warning(
+          requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT, "** ALLOWED ** '"
+              + headerType + "' header to <" + dest + "> " + "from <"
+              + origin + ">. Original request is from privileged code.");
+      return;
+    }
+  }
+
+  // If it's not a valid uri, the redirect is relative to the origin host.
+  // The way we have things written currently, without this check the full
+  // dest string will get treated as the destination and displayed in the
+  // menu because DomainUtil.getIdentifier() doesn't raise exceptions.
+  // We add this to fix https://www.requestpolicy.com/dev/ticket/39.
+  if (!requestpolicy.mod.DomainUtil.isValidUri(dest)) {
+    var destAsUri = requestpolicy.mod.DomainUtil.determineRedirectUri(origin,
+        dest);
+    requestpolicy.mod.Logger.warning(
+        requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT,
+        "Redirect destination is not a valid uri, assuming dest <" + dest
+            + "> from origin <" + origin + "> is actually dest <" + destAsUri
+            + ">.");
+    dest = destAsUri;
+  }
+
+  // Ignore redirects to javascript. The browser will ignore them, as well.
+  if (requestpolicy.mod.DomainUtil.getUriObject(dest)
+        .schemeIs("javascript")) {
+    requestpolicy.mod.Logger.warning(
+        requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT,
+        "Ignoring redirect to javascript URI <" + dest + ">");
+    return;
+  }
+
+  var result = this.checkRedirect(origin, dest);
+  if (true === result.isAllowed) {
+    requestpolicy.mod.Logger.warning(
+        requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT, "** ALLOWED ** '"
+            + headerType + "' header to <" + dest + "> " + "from <" + origin
+            + ">. Same hosts or allowed origin/destination.");
+    this._recordAllowedRequest(origin, dest, false, result);
+    this._allowedRedirectsReverse[dest] = origin;
+
+    // If this was a link click or a form submission, we register an
+    // additional click/submit with the original source but with a new
+    // destination of the target of the redirect. This is because future
+    // requests (such as using back/forward) might show up as directly from
+    // the initial origin to the ultimate redirected destination.
+    if (httpChannel.referrer) {
+      var realOrigin = httpChannel.referrer.spec;
+
+      if (this._clickedLinks[realOrigin]
+          && this._clickedLinks[realOrigin][origin]) {
+        requestpolicy.mod.Logger.warning(
+            requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT,
+            "This redirect was from a link click."
+                + " Registering an additional click to <" + dest + "> "
+                + "from <" + realOrigin + ">");
+        this.registerLinkClicked(realOrigin, dest);
+
+      } else if (this._submittedForms[realOrigin]
+          && this._submittedForms[realOrigin][origin.split("?")[0]]) {
+        requestpolicy.mod.Logger.warning(
+            requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT,
+            "This redirect was from a form submission."
+                + " Registering an additional form submission to <" + dest
+                + "> " + "from <" + realOrigin + ">");
+        this.registerFormSubmitted(realOrigin, dest);
+      }
+    }
+
+    return;
+  }
+
+  // The header isn't allowed, so remove it.
+  try {
+    if (!this._rpService._blockingDisabled) {
+      httpChannel.setResponseHeader(headerType, "", false);
+      this._blockedRedirects[origin] = dest;
+
+      try {
+        contentDisp = httpChannel.getResponseHeader("Content-Disposition");
+        if (contentDisp.indexOf("attachment") != -1) {
+          try {
+            httpChannel.setResponseHeader("Content-Disposition", "", false);
+            requestpolicy.mod.Logger.warning(
+                requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT,
+                "Removed 'Content-Disposition: attachment' header to "
+                    + "prevent display of about:neterror.");
+          } catch (e) {
+            requestpolicy.mod.Logger.warning(
+                requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT,
+                "Unable to remove 'Content-Disposition: attachment' header "
+                    + "to prevent display of about:neterror. " + e);
+          }
+        }
+      } catch (e) {
+        // No Content-Disposition header.
+      }
+
+      // We try to trace the blocked redirect back to a link click or form
+      // submission if we can. It may indicate, for example, a link that
+      // was to download a file but a redirect got blocked at some point.
+      var initialOrigin = origin;
+      var initialDest = dest;
+      // To prevent infinite loops, bound the number of iterations.
+      // Note that an apparent redirect loop doesn't mean a problem with a
+      // website as the site may be using other information, such as cookies
+      // that get set in the redirection process, to stop the redirection.
+      var iterations = 0;
+      const ASSUME_REDIRECT_LOOP = 100; // Chosen arbitrarily.
+      while (this._allowedRedirectsReverse[initialOrigin]) {
+        if (iterations++ >= ASSUME_REDIRECT_LOOP) {
+          break;
+        }
+        initialDest = initialOrigin;
+        initialOrigin = this._allowedRedirectsReverse[initialOrigin];
+      }
+
+      if (this._clickedLinksReverse[initialOrigin]) {
+        for (var i in this._clickedLinksReverse[initialOrigin]) {
+          // We hope there's only one possibility of a source page (that is,
+          // ideally there will be one iteration of this loop).
+          var sourcePage = i;
+        }
+
+        this._notifyRequestObserversOfBlockedLinkClickRedirect(sourcePage,
+            origin, dest);
+
+        // Maybe we just record the clicked link and each step in between as
+        // an allowed request, and the final blocked one as a blocked request.
+        // That is, make it show up in the requestpolicy menu like anything
+        // else.
+        // We set the "isInsert" parameter so we don't clobber the existing
+        // info about allowed and deleted requests.
+        this._recordAllowedRequest(sourcePage, initialOrigin, true, result);
+      }
+
+      // if (this._submittedFormsReverse[initialOrigin]) {
+      // // TODO: implement for form submissions whose redirects are blocked
+      // }
+
+      this._recordRejectedRequest(origin, dest, result);
+    }
+    requestpolicy.mod.Logger.warning(
+        requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT, "** BLOCKED ** '"
+            + headerType + "' header to <" + dest + ">"
+            + " found in response from <" + origin + ">");
+  } catch (e) {
+    requestpolicy.mod.Logger.severe(
+        requestpolicy.mod.Logger.TYPE_HEADER_REDIRECT, "Failed removing "
+            + "'" + headerType + "' header to <" + dest + ">"
+            + "  in response from <" + origin + ">." + e);
+  }
+};
+
+/**
+ * Called as a http request is made. The channel is available to allow you to
+ * modify headers and such.
+ *
+ * Currently this just looks for prefetch requests that are getting through
+ * which we currently can't stop.
+ */
+RequestProcessor.prototype._examineHttpRequest = function(observedSubject) {
+  var httpChannel = observedSubject.QueryInterface(CI.nsIHttpChannel);
+  try {
+    // Determine if prefetch requests are slipping through.
+    if (httpChannel.getRequestHeader("X-moz") == "prefetch") {
+      // Seems to be too late to block it at this point. Calling the
+      // cancel(status) method didn't stop it.
+      requestpolicy.mod.Logger.warning(requestpolicy.mod.Logger.TYPE_CONTENT,
+          "Discovered prefetch request being sent to: " + httpChannel.name);
+    }
+  } catch (e) {
+    // No X-moz header.
+  }
+};
+
+RequestProcessor.prototype._printAllowedRequests = function() {
+  this._allowedRequests.print();
+};
+
+RequestProcessor.prototype._printRejectedRequests = function() {
+  this._rejectedRequests.print();
+};
+
+RequestProcessor.prototype._notifyRequestObserversOfBlockedRequest = function(
+    request) {
+  for (var i = 0; i < this._requestObservers.length; i++) {
+    if (!this._requestObservers[i]) {
+      continue;
+    }
+    this._requestObservers[i].observeBlockedRequest(request.originURI,
+        request.destURI, request.requestResult);
+  }
+};
+
+RequestProcessor.prototype._notifyRequestObserversOfAllowedRequest = function(
+    originUri, destUri, requestResult) {
+  for (var i = 0; i < this._requestObservers.length; i++) {
+    if (!this._requestObservers[i]) {
+      continue;
+    }
+    this._requestObservers[i].observeAllowedRequest(originUri, destUri,
+        requestResult);
+  }
+};
+
+RequestProcessor.prototype._notifyRequestObserversOfBlockedLinkClickRedirect =
+    function(sourcePageUri, linkDestUri, blockedRedirectUri) {
+  for (var i = 0; i < this._requestObservers.length; i++) {
+    if (!this._requestObservers[i]) {
+      continue;
+    }
+    this._requestObservers[i].observeBlockedLinkClickRedirect(sourcePageUri,
+        linkDestUri, blockedRedirectUri);
+  }
+};
+
+RequestProcessor.prototype._notifyBlockedTopLevelDocRequest = function(
+    originUri, destUri) {
+  // TODO: this probably could be done async.
+  for (var i = 0; i < this._requestObservers.length; i++) {
+    if (!this._requestObservers[i]) {
+      continue;
+    }
+    this._requestObservers[i].observeBlockedTopLevelDocRequest(originUri,
+        destUri);
+  }
+};
+
+
+
+
+
+RequestProcessor.prototype.registerHistoryRequest = function(destinationUrl) {
+  var destinationUrl = requestpolicy.mod.DomainUtil
+      .ensureUriHasPath(requestpolicy.mod.DomainUtil
+          .stripFragment(destinationUrl));
+  this._historyRequests[destinationUrl] = true;
+  requestpolicy.mod.Logger.info(requestpolicy.mod.Logger.TYPE_INTERNAL,
+      "History item requested: <" + destinationUrl + ">.");
+};
+
+RequestProcessor.prototype.registerFormSubmitted = function(originUrl,
+    destinationUrl) {
+  var originUrl = requestpolicy.mod.DomainUtil
+      .ensureUriHasPath(requestpolicy.mod.DomainUtil.stripFragment(originUrl));
+  var destinationUrl = requestpolicy.mod.DomainUtil
+      .ensureUriHasPath(requestpolicy.mod.DomainUtil
+          .stripFragment(destinationUrl));
+
+  requestpolicy.mod.Logger.info(requestpolicy.mod.Logger.TYPE_INTERNAL,
+      "Form submitted from <" + originUrl + "> to <" + destinationUrl + ">.");
+
+  // Drop the query string from the destination url because form GET requests
+  // will end up with a query string on them when shouldLoad is called, so
+  // we'll need to be dropping the query string there.
+  destinationUrl = destinationUrl.split("?")[0];
+
+  if (this._submittedForms[originUrl] == undefined) {
+    this._submittedForms[originUrl] = {};
+  }
+  if (this._submittedForms[originUrl][destinationUrl] == undefined) {
+    // TODO: See timestamp note for registerLinkClicked.
+    this._submittedForms[originUrl][destinationUrl] = true;
+  }
+
+  // Keep track of a destination-indexed map, as well.
+  if (this._submittedFormsReverse[destinationUrl] == undefined) {
+    this._submittedFormsReverse[destinationUrl] = {};
+  }
+  if (this._submittedFormsReverse[destinationUrl][originUrl] == undefined) {
+    // TODO: See timestamp note for registerLinkClicked.
+    this._submittedFormsReverse[destinationUrl][originUrl] = true;
+  }
+};
+
+RequestProcessor.prototype.registerLinkClicked = function(originUrl,
+    destinationUrl) {
+  var originUrl = requestpolicy.mod.DomainUtil
+      .ensureUriHasPath(requestpolicy.mod.DomainUtil.stripFragment(originUrl));
+  var destinationUrl = requestpolicy.mod.DomainUtil
+      .ensureUriHasPath(requestpolicy.mod.DomainUtil
+          .stripFragment(destinationUrl));
+
+  requestpolicy.mod.Logger.info(requestpolicy.mod.Logger.TYPE_INTERNAL,
+      "Link clicked from <" + originUrl + "> to <" + destinationUrl + ">.");
+
+  if (this._clickedLinks[originUrl] == undefined) {
+    this._clickedLinks[originUrl] = {};
+  }
+  if (this._clickedLinks[originUrl][destinationUrl] == undefined) {
+    // TODO: Possibly set the value to a timestamp that can be used elsewhere
+    // to determine if this is a recent click. This is probably necessary as
+    // multiple calls to shouldLoad get made and we need a way to allow
+    // multiple in a short window of time. Alternately, as it seems to always
+    // be in order (repeats are always the same as the last), the last one
+    // could be tracked and always allowed (or allowed within a small period
+    // of time). This would have the advantage that we could delete items from
+    // the _clickedLinks object. One of these approaches would also reduce log
+    // clutter, which would be good.
+    this._clickedLinks[originUrl][destinationUrl] = true;
+  }
+
+  // Keep track of a destination-indexed map, as well.
+  if (this._clickedLinksReverse[destinationUrl] == undefined) {
+    this._clickedLinksReverse[destinationUrl] = {};
+  }
+  if (this._clickedLinksReverse[destinationUrl][originUrl] == undefined) {
+    // TODO: Possibly set the value to a timestamp, as described above.
+    this._clickedLinksReverse[destinationUrl][originUrl] = true;
+  }
+};
+
+RequestProcessor.prototype.registerAllowedRedirect = function(originUrl,
+    destinationUrl) {
+  var originUrl = requestpolicy.mod.DomainUtil
+      .ensureUriHasPath(requestpolicy.mod.DomainUtil.stripFragment(originUrl));
+  var destinationUrl = requestpolicy.mod.DomainUtil
+      .ensureUriHasPath(requestpolicy.mod.DomainUtil
+          .stripFragment(destinationUrl));
+
+  requestpolicy.mod.Logger.info(requestpolicy.mod.Logger.TYPE_INTERNAL,
+      "User-allowed redirect from <" + originUrl + "> to <" + destinationUrl
+          + ">.");
+
+  if (this._userAllowedRedirects[originUrl] == undefined) {
+    this._userAllowedRedirects[originUrl] = {};
+  }
+  if (this._userAllowedRedirects[originUrl][destinationUrl] == undefined) {
+    this._userAllowedRedirects[originUrl][destinationUrl] = true;
+  }
+};
+
+RequestProcessor.prototype.isAllowedRedirect = function(originUri,
+    destinationUri) {
+  return (true === this.checkRedirect(originUri, destinationUri).isAllowed);
+};
+
+RequestProcessor.prototype.checkRedirect = function(originUri, destinationUri) {
+  // TODO: Find a way to get rid of repitition of code between this and
+  // shouldLoad().
+
+  // Note: If changing the logic here, also make necessary changes to
+  // shouldLoad().
+
+  // This is not including link clicks, form submissions, and user-allowed
+  // redirects.
+
+  var originUriObj = requestpolicy.mod.DomainUtil.getUriObject(originUri);
+  var destUriObj = requestpolicy.mod.DomainUtil.getUriObject(destinationUri);
+
+  var result = this._rpService._policyMgr.checkRequestAgainstUserPolicies(
+      originUriObj, destUriObj);
+  result.requestType = requestpolicy.mod.REQUEST_TYPE_REDIRECT;
+  // For now, we always give priority to deny rules.
+  if (result.denyRulesExist()) {
+    result.isAllowed = false;
+    return result;
+  }
+  if (result.allowRulesExist()) {
+    result.isAllowed = true;
+    return result;
+  }
+
+  var result = this._rpService._policyMgr.
+      checkRequestAgainstSubscriptionPolicies(originUriObj, destUriObj);
+  result.requestType = requestpolicy.mod.REQUEST_TYPE_REDIRECT;
+  // For now, we always give priority to deny rules.
+  if (result.denyRulesExist()) {
+    result.isAllowed = false;
+    return result;
+  }
+  if (result.allowRulesExist()) {
+    result.isAllowed = true;
+    return result;
+  }
+
+  if (destinationUri[0] && destinationUri[0] == '/'
+      || destinationUri.indexOf(":") == -1) {
+    // Redirect is to a relative url.
+    // ==> allow.
+    return new requestpolicy.mod.RequestResult(
+        true,
+        requestpolicy.mod.REQUEST_TYPE_REDIRECT,
+        requestpolicy.mod.REQUEST_REASON_RELATIVE_URL);
+  }
+
+  for (var i = 0; i < this._rpService._compatibilityRules.length; i++) {
+    var rule = this._rpService._compatibilityRules[i];
+    var allowOrigin = rule[0] ? originUri.indexOf(rule[0]) == 0 : true;
+    var allowDest = rule[1] ? destinationUri.indexOf(rule[1]) == 0 : true;
+    if (allowOrigin && allowDest) {
+      return new requestpolicy.mod.RequestResult(
+        true,
+        requestpolicy.mod.REQUEST_TYPE_REDIRECT,
+        requestpolicy.mod.REQUEST_REASON_COMPATIBILITY
+      );
+    }
+  }
+
+  var result = this._checkByDefaultPolicy(originUri, destinationUri);
+  result.requestType = requestpolicy.mod.REQUEST_TYPE_REDIRECT;
+  return result;
+};
+
+/**
+ * Add an observer to be notified of all blocked and allowed requests. TODO:
+ * This should be made to accept instances of a defined interface.
+ *
+ * @param {Object} observer
+ */
+RequestProcessor.prototype.addRequestObserver = function(observer) {
+  if (!("observeBlockedRequest" in observer)) {
+    throw "Observer passed to addRequestObserver does "
+        + "not have an observeBlockedRequest() method.";
+  }
+  requestpolicy.mod.Logger.debug(requestpolicy.mod.Logger.TYPE_INTERNAL,
+      "Adding request observer: " + observer.toString());
+  this._requestObservers.push(observer);
+};
+
+/**
+ * Remove an observer added through addRequestObserver().
+ *
+ * @param {Object} observer
+ */
+RequestProcessor.prototype.removeRequestObserver = function(observer) {
+  for (var i = 0; i < this._requestObservers.length; i++) {
+    if (this._requestObservers[i] == observer) {
+      requestpolicy.mod.Logger.debug(requestpolicy.mod.Logger.TYPE_INTERNAL,
+          "Removing request observer: " + observer.toString());
+      delete this._requestObservers[i];
+      return;
+    }
+  }
+  requestpolicy.mod.Logger.warning(requestpolicy.mod.Logger.TYPE_INTERNAL,
+      "Could not find observer to remove " + "in removeRequestObserver()");
+};
+
+
+
+
+RequestProcessor.prototype._requestDetailsToString = function(request) {
+  // Note: try not to cause side effects of toString() during load, so "<HTML
+  // Element>" is hard-coded.
+  return "type: " + request.aContentType +
+      ", destination: " + request.dest +
+      ", origin: " + request.origin +
+      ", context: " + ((request.aContext) instanceof (CI.nsIDOMHTMLElement)
+          ? "<HTML Element>"
+          : request.aContext) +
+      ", mime: " + request.aMimeTypeGuess +
+      ", " + request.aExtra;
+};
+
+// We always call this from shouldLoad to reject a request.
+RequestProcessor.prototype.reject = function(reason, request) {
+  requestpolicy.mod.Logger.warning(requestpolicy.mod.Logger.TYPE_CONTENT,
+      "** BLOCKED ** reason: "
+      + reason + ". " + this._requestDetailsToString(request));
+
+  if (this._rpService._blockingDisabled) {
+    return CP_OK;
+  }
+
+  request.aContext.requestpolicyBlocked = true;
+
+  this._cacheShouldLoadResult(CP_REJECT, request.originURI, request.destURI);
+  this._recordRejectedRequest(request);
+
+  if (CI.nsIContentPolicy.TYPE_DOCUMENT == request.aContentType) {
+    // This was a blocked top-level document request. This may be due to
+    // a blocked attempt by javascript to set the document location.
+    // TODO: pass requestResult?
+    this._notifyBlockedTopLevelDocRequest(request.originURI, request.destURI);
+  }
+
+  return CP_REJECT;
+};
+
+RequestProcessor.prototype._recordRejectedRequest = function(request) {
+  this._rejectedRequests.addRequest(request.originURI, request.destURI,
+      request.requestResult);
+  this._allowedRequests.removeRequest(request.originURI, request.destURI);
+  this._notifyRequestObserversOfBlockedRequest(request);
+};
+
+// We only call this from shouldLoad when the request was a remote request
+// initiated by the content of a page. this is partly for efficiency. in other
+// cases we just return CP_OK rather than return this function which
+// ultimately returns CP_OK. Fourth param, "unforbidable", is set to true if
+// this request shouldn't be recorded as an allowed request.
+/**
+ * @param {string} reason
+ * @param {Request} request
+ * @param {boolean} unforbidable
+ */
+RequestProcessor.prototype.accept = function(reason, request, unforbidable) {
+  requestpolicy.mod.Logger.warning(requestpolicy.mod.Logger.TYPE_CONTENT,
+      "** ALLOWED ** reason: "
+      + reason + ". " + this._requestDetailsToString(request));
+
+  this._cacheShouldLoadResult(CP_OK, request.originURI, request.destURI);
+  // We aren't recording the request so it doesn't show up in the menu, but we
+  // want it to still show up in the request log.
+  if (unforbidable) {
+    this._notifyRequestObserversOfAllowedRequest(originUri, destUri,
+        requestResult);
+  } else {
+    this._recordAllowedRequest(request.originURI, request.destURI, false,
+        request.requestResult);
+  }
+
+  return CP_OK;
+};
+
+RequestProcessor.prototype._recordAllowedRequest = function(originUri, destUri,
+    isInsert, requestResult) {
+  var destIdentifier = this._rpService.getUriIdentifier(destUri);
+
+  if (isInsert == undefined) {
+    isInsert = false;
+  }
+
+  // Reset the accepted and rejected requests originating from this
+  // destination. That is, if this accepts a request to a uri that may itself
+  // originate further requests, reset the information about what that page is
+  // accepting and rejecting.
+  // If "isInsert" is set, then we don't want to clear the destUri info.
+  if (!isInsert) {
+    this._allowedRequests.removeOriginUri(destUri);
+    this._rejectedRequests.removeOriginUri(destUri);
+  }
+  this._rejectedRequests.removeRequest(originUri, destUri);
+  this._allowedRequests.addRequest(originUri, destUri, requestResult);
+  this._notifyRequestObserversOfAllowedRequest(originUri, destUri,
+      requestResult);
+};
+
+RequestProcessor.prototype._cacheShouldLoadResult = function(result, originUri,
+    destUri) {
+  var date = new Date();
+  this._lastShouldLoadCheck.time = date.getTime();
+  this._lastShouldLoadCheck.destination = destUri;
+  this._lastShouldLoadCheck.origin = originUri;
+  this._lastShouldLoadCheck.result = result;
+};
+
+RequestProcessor.prototype._checkByDefaultPolicy = function(originUri,
+    destUri) {
+  if (this._rpService._defaultAllow) {
+    var result = new requestpolicy.mod.RequestResult(
+        true,
+        undefined,
+        requestpolicy.mod.REQUEST_REASON_DEFAULT_POLICY);
+    return result;
+  }
+  if (this._rpService._defaultAllowSameDomain) {
+    var originDomain = requestpolicy.mod.DomainUtil.getDomain(
+        originUri);
+    var destDomain = requestpolicy.mod.DomainUtil.getDomain(destUri);
+    return new requestpolicy.mod.RequestResult(
+        originDomain == destDomain,
+        undefined,
+        requestpolicy.mod.REQUEST_REASON_DEFAULT_SAME_DOMAIN);
+  }
+  // We probably want to allow requests from http:80 to https:443 of the same
+  // domain. However, maybe this is so uncommon it's not worth any extra
+  // complexity.
+  var originIdent = requestpolicy.mod.DomainUtil.getIdentifier(
+      originUri, requestpolicy.mod.DomainUtil.LEVEL_SOP);
+  var destIdent = requestpolicy.mod.DomainUtil.getIdentifier(destUri,
+      requestpolicy.mod.DomainUtil.LEVEL_SOP);
+  return new requestpolicy.mod.RequestResult(
+      (originIdent == destIdent),
+      undefined,
+      requestpolicy.mod.REQUEST_REASON_DEFAULT_SAME_DOMAIN);
+};
+
+/**
+ * Determines if a request is a duplicate of the last call to shouldLoad(). If
+ * it is, the cached result in _lastShouldLoadCheck.result can be used. Not
+ * sure why, it seems that there are duplicates so using this simple cache of
+ * the last call to shouldLoad() keeps duplicates out of log data.
+ *
+ * @param {Request} request
+ * @return {boolean} True if the request is a duplicate of the previous one.
+ */
+RequestProcessor.prototype._isDuplicateRequest = function(request) {
+
+  if (this._lastShouldLoadCheck.origin == request.originURI &&
+      this._lastShouldLoadCheck.destination == request.destURI) {
+    var date = new Date();
+    if (date.getTime() - this._lastShouldLoadCheck.time <
+        this._lastShouldLoadCheckTimeout) {
+      requestpolicy.mod.Logger.debug(requestpolicy.mod.Logger.TYPE_CONTENT,
+          "Using cached shouldLoad() result of " +
+          this._lastShouldLoadCheck.result + " for request to <" +
+          request.destURI + "> from <" + request.originURI + ">.");
+      return true;
+    } else {
+      requestpolicy.mod.Logger.debug(requestpolicy.mod.Logger.TYPE_CONTENT,
+          "shouldLoad() cache expired for result of " +
+          this._lastShouldLoadCheck.result + " for request to <" +
+          request.destURI + "> from <" + request.originURI + ">.");
+    }
+  }
+  return false;
+};
diff --git a/src/modules/RequestUtil.jsm b/src/modules/RequestUtil.jsm
index 7d88d8c..af6be43 100644
--- a/src/modules/RequestUtil.jsm
+++ b/src/modules/RequestUtil.jsm
@@ -380,7 +380,8 @@ var RequestUtil = {
       checkedOrigins[originURI] = true;
     }
     this._addAllDeniedRequestsFromURI(originURI, reqSet);
-    var allowedRequests = this._rpService._allowedRequests.getOriginUri(originURI);
+    var allowedRequests = this._rpService._requestProcessor.
+        _allowedRequests.getOriginUri(originURI);
     if (allowedRequests) {
       for (var destBase in allowedRequests) {
         for (var destIdent in allowedRequests[destBase]) {
@@ -406,7 +407,7 @@ var RequestUtil = {
     Logger
       .dump("Looking for other origins within denied requests from "
               + originURI);
-    var requests = this._rpService._rejectedRequests.getOriginUri(originURI);
+    var requests = this._rpService._requestProcessor._rejectedRequests.getOriginUri(originURI);
     if (requests) {
       for (var destBase in requests) {
         for (var destIdent in requests[destBase]) {

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-mozext/requestpolicy.git



More information about the Pkg-mozext-commits mailing list