bug 515433, bug 515437: Content Security Policy (CSP) core
☠☠ backed out by d283ea341118 ☠ ☠
authorSid Stamm <sstamm@mozilla.com>, Brandon Sterne <bsterne@mozilla.com>
Wed, 13 Jan 2010 14:18:24 -0800
changeset 37160 a6ce37b09cf51cce00745096ee5606706e239443
parent 37159 745af1f3dbf5bdb93a95e38d0a534ba489754cc8
child 37162 2c86dc6d6ec4412b534cafa2624f0bdc448ead9a
child 37212 d283ea341118d2d349147d900f23672939bfd4e9
push idunknown
push userunknown
push dateunknown
bugs515433, 515437
milestone1.9.3a1pre
bug 515433, bug 515437: Content Security Policy (CSP) core
browser/installer/package-manifest.in
caps/idl/nsIPrincipal.idl
caps/include/nsPrincipal.h
caps/src/nsNullPrincipal.cpp
caps/src/nsPrincipal.cpp
caps/src/nsSystemPrincipal.cpp
content/base/public/IContentSecurityPolicy.idl
content/base/public/Makefile.in
content/base/src/CSPUtils.jsm
content/base/src/Makefile.in
content/base/src/contentSecurityPolicy.js
content/base/src/nsCSPService.cpp
content/base/src/nsCSPService.h
content/base/src/nsDocument.cpp
content/base/src/nsDocument.h
content/base/src/nsGkAtomList.h
content/base/test/Makefile.in
content/base/test/file_CSP.sjs
content/base/test/file_CSP_main.html
content/base/test/file_CSP_main.js
content/base/test/test_CSP.html
content/base/test/unit/test_csputils.js
layout/build/nsLayoutModule.cpp
modules/libpref/src/init/all.js
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -321,16 +321,17 @@
 @BINPATH@/components/PlacesProtocolHandler.js
 @BINPATH@/components/nsDefaultCLH.js
 @BINPATH@/components/nsContentPrefService.js
 @BINPATH@/components/nsContentDispatchChooser.js
 @BINPATH@/components/nsHandlerService.js
 @BINPATH@/components/nsWebHandlerApp.js
 @BINPATH@/components/nsBadCertHandler.js
 @BINPATH@/components/nsFormAutoComplete.js
+@BINPATH@/components/contentSecurityPolicy.js
 #ifdef XP_MACOSX
 @BINPATH@/components/libalerts_s.dylib
 #endif
 #ifdef MOZ_ENABLE_DBUS
 @BINPATH@/components/@DLL_PREFIX@dbusservice@DLL_SUFFIX@
 #endif
 
 ; Modules
--- a/caps/idl/nsIPrincipal.idl
+++ b/caps/idl/nsIPrincipal.idl
@@ -42,21 +42,22 @@
 #include "nsISerializable.idl"
 
 %{C++
 struct JSContext;
 struct JSPrincipals;
 %}
 
 interface nsIURI;
+interface IContentSecurityPolicy;
 
 [ptr] native JSContext(JSContext);
 [ptr] native JSPrincipals(JSPrincipals);
 
-[scriptable, uuid(b8268b9a-2403-44ed-81e3-614075c92034)]
+[scriptable, uuid(799ab95c-0038-4e0f-b705-74c21f185bb5)]
 interface nsIPrincipal : nsISerializable
 {
     /**
      * Values of capabilities for each principal. Order is
      * significant: if an operation is performed on a set
      * of capabilities, the minimum is computed.
      */
     const short ENABLE_DENIED                = 1;
@@ -236,9 +237,14 @@ interface nsIPrincipal : nsISerializable
      */
     readonly attribute AUTF8String subjectName;
 
     /**
      * The certificate associated with this principal, if any.  If there isn't
      * one, this will return null.  Getting this attribute never throws.
      */
     readonly attribute nsISupports certificate;
+
+    /**
+     * A Content Security Policy associated with this principal.
+     */
+    [noscript] attribute IContentSecurityPolicy csp;
 };
--- a/caps/include/nsPrincipal.h
+++ b/caps/include/nsPrincipal.h
@@ -133,16 +133,17 @@ protected:
 
   // Keep this is a pointer, even though it may slightly increase the
   // cost of keeping a certificate, this is a good tradeoff though since
   // it is very rare that we actually have a certificate.
   nsAutoPtr<Certificate> mCert;
 
   DomainPolicy* mSecurityPolicy;
 
+  nsCOMPtr<IContentSecurityPolicy> mCSP;
   nsCOMPtr<nsIURI> mCodebase;
   nsCOMPtr<nsIURI> mDomain;
   PRPackedBool mTrusted;
   PRPackedBool mInitialized;
   // If mCodebaseImmutable is true, mCodebase is non-null and immutable
   PRPackedBool mCodebaseImmutable;
   PRPackedBool mDomainImmutable;
 };
--- a/caps/src/nsNullPrincipal.cpp
+++ b/caps/src/nsNullPrincipal.cpp
@@ -247,16 +247,31 @@ nsNullPrincipal::DisableCapability(const
 
 NS_IMETHODIMP 
 nsNullPrincipal::GetURI(nsIURI** aURI)
 {
   return NS_EnsureSafeToReturn(mURI, aURI);
 }
 
 NS_IMETHODIMP
+nsNullPrincipal::GetCsp(IContentSecurityPolicy** aCsp)
+{
+  // CSP on a null principal makes no sense
+  *aCsp = nsnull;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNullPrincipal::SetCsp(IContentSecurityPolicy* aCsp)
+{
+  // CSP on a null principal makes no sense
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
 nsNullPrincipal::GetDomain(nsIURI** aDomain)
 {
   return NS_EnsureSafeToReturn(mURI, aDomain);
 }
 
 NS_IMETHODIMP
 nsNullPrincipal::SetDomain(nsIURI* aDomain)
 {
--- a/caps/src/nsPrincipal.cpp
+++ b/caps/src/nsPrincipal.cpp
@@ -52,16 +52,17 @@
 #include "nsVoidArray.h"
 #include "nsHashtable.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "nsIPrefBranch2.h"
 #include "nsIPrefService.h"
 #include "nsIClassInfoImpl.h"
 #include "nsDOMError.h"
+#include "IContentSecurityPolicy.h"
 
 #include "nsPrincipal.h"
 
 class nsCodeBasePrefObserver : nsIObserver
 {
 public:
   nsCodeBasePrefObserver()
   {
@@ -770,16 +771,35 @@ nsPrincipal::GetCertificate(nsISupports*
   }
   else {
     *aCertificate = nsnull;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsPrincipal::GetCsp(IContentSecurityPolicy** aCsp)
+{
+  NS_IF_ADDREF(*aCsp = mCSP);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrincipal::SetCsp(IContentSecurityPolicy* aCsp)
+{
+  // If CSP was already set, it should not be destroyed!  Instead, it should
+  // get set anew when a new principal is created.
+  if (mCSP)
+    return NS_ERROR_ALREADY_INITIALIZED;
+
+  mCSP = aCsp;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsPrincipal::GetHashValue(PRUint32* aValue)
 {
   NS_PRECONDITION(mCert || mCodebase, "Need a cert or codebase");
 
   // If there is a certificate, it takes precendence over the codebase.
   if (mCert) {
     *aValue = nsCRT::HashCode(mCert->fingerprint.get());
   }
--- a/caps/src/nsSystemPrincipal.cpp
+++ b/caps/src/nsSystemPrincipal.cpp
@@ -219,16 +219,30 @@ nsSystemPrincipal::GetCertificate(nsISup
 NS_IMETHODIMP 
 nsSystemPrincipal::GetHasCertificate(PRBool* aResult)
 {
     *aResult = PR_FALSE;
     return NS_OK;
 }
 
 NS_IMETHODIMP
+nsSystemPrincipal::GetCsp(IContentSecurityPolicy** aCsp)
+{
+  *aCsp = nsnull;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSystemPrincipal::SetCsp(IContentSecurityPolicy* aCsp)
+{
+  // CSP on a null principal makes no sense
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsSystemPrincipal::GetDomain(nsIURI** aDomain)
 {
     *aDomain = nsnull;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSystemPrincipal::SetDomain(nsIURI* aDomain)
new file mode 100644
--- /dev/null
+++ b/content/base/public/IContentSecurityPolicy.idl
@@ -0,0 +1,152 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Content Security Policy IDL definition.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation
+ *
+ * Contributor(s):
+ *   Sid Stamm <sid@mozilla.com>
+ *   Brandon Sterne <bsterne@mozilla.com>
+ *   Daniel Veditz <dveditz@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsIHttpChannel;
+interface nsIDocShell;
+
+/**
+ * IContentSecurityPolicy  
+ * Describes an XPCOM component used to model an enforce CSPs.
+ */
+
+[scriptable, uuid(AB36A2BF-CB32-4AA6-AB41-6B4E4444A221)]
+interface IContentSecurityPolicy : nsISupports
+{
+
+  /**
+   * Set to true when the CSP has been read in and parsed and is ready to
+   * enforce.  This is a barrier for the nsDocument so it doesn't load any
+   * sub-content until either it knows that a CSP is ready or will not be used.
+   */
+  attribute boolean isInitialized;
+
+  /**
+   * When set to true, content load-blocking and fail-closed are disabled: CSP
+   * will ONLY send reports, and not modify behavior.
+   */
+  attribute boolean reportOnlyMode;
+
+  /**
+   * A read-only string version of the policy for debugging.
+   */
+  readonly attribute AString policy;
+
+  /**
+   * Whether this policy allows in-page script.
+   *
+   * Calls to this may trigger violation reports when queried, so
+   * this value should not be cached.
+   */
+  readonly attribute boolean allowsInlineScript;
+
+  /**
+   * whether this policy allows eval and eval-like functions
+   * such as setTimeout("code string", time).
+   *
+   * Calls to this may trigger violation reports when queried, so
+   * this value should not be cached.
+   */
+  readonly attribute boolean allowsEval;
+
+  /**
+   * Manually triggers violation report sending given a URI and reason.
+   * The URI may be null, in which case "self" is sent.
+   * @param blockedURI
+   *     the URI that violated the policy
+   * @param violatedDirective
+   *     the directive that was violated.
+   * @return 
+   *     nothing.
+   */
+  void sendReports(in AString blockedURI, in AString violatedDirective);
+
+  /**
+   * Called after the CSP object is created to fill in the appropriate request
+   * and request header information needed in case a report needs to be sent.
+   */
+  void scanRequestData(in nsIHttpChannel aChannel);
+
+  /**
+   * Updates the policy currently stored in the CSP to be "refined" or
+   * tightened by the one specified in the string policyString.
+   */
+  void refinePolicy(in AString policyString, in nsIURI selfURI);
+
+  /**
+   * Verifies ancestry as permitted by the policy.
+   *
+   * Calls to this may trigger violation reports when queried, so
+   * this value should not be cached.
+   *
+   * @param docShell
+   *    containing the protected resource
+   * @return
+   *    true if the frame's ancestors are all permitted by policy
+   */
+  boolean permitsAncestry(in nsIDocShell docShell);
+
+  /**
+   * Delegate method called by the service when sub-elements of the protected
+   * document are being loaded.  Given a bit of information about the request,
+   * decides whether or not the policy is satisfied.
+   *
+   * Calls to this may trigger violation reports when queried, so
+   * this value should not be cached.
+   */
+  short shouldLoad(in unsigned long   aContentType, 
+                   in nsIURI          aContentLocation, 
+                   in nsIURI          aRequestOrigin, 
+                   in nsISupports     aContext, 
+                   in ACString        aMimeTypeGuess, 
+                   in nsISupports     aExtra);
+
+  /**
+   * Delegate method called by the service when sub-elements of the protected
+   * document are being processed.  Given a bit of information about the request,
+   * decides whether or not the policy is satisfied.
+   */
+  short shouldProcess(in unsigned long   aContentType, 
+                      in nsIURI          aContentLocation, 
+                      in nsIURI          aRequestOrigin, 
+                      in nsISupports     aContext, 
+                      in ACString        aMimeType,
+                      in nsISupports     aExtra);
+
+};
--- a/content/base/public/Makefile.in
+++ b/content/base/public/Makefile.in
@@ -108,12 +108,13 @@ XPIDLSRCS	= \
 		nsISyncLoadDOMService.idl  \
 		nsIDragDropHandler.idl \
 		nsIScriptEventHandler.idl \
 		nsIScriptEventManager.idl \
 		nsIImageLoadingContent.idl \
 		nsIObjectLoadingContent.idl \
 		nsIFrameLoader.idl \
 		nsIXMLHttpRequest.idl \
+		IContentSecurityPolicy.idl \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
new file mode 100644
--- /dev/null
+++ b/content/base/src/CSPUtils.jsm
@@ -0,0 +1,1295 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Content Security Policy data structures.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation
+ *
+ * Contributor(s):
+ *   Sid Stamm <sid@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/**
+ * Content Security Policy Utilities
+ * 
+ * Overview
+ * This contains a set of classes and utilities for CSP.  It is in this
+ * separate file for testing purposes.
+ */
+
+// Module stuff
+var EXPORTED_SYMBOLS = ["CSPRep", "CSPSourceList", "CSPSource", 
+                        "CSPHost", "CSPWarning", "CSPError", "CSPdebug"];
+
+
+// these are not exported
+var gIoService = Components.classes["@mozilla.org/network/io-service;1"]
+                 .getService(Components.interfaces.nsIIOService);
+
+var gETLDService = Components.classes["@mozilla.org/network/effective-tld-service;1"]
+                   .getService(Components.interfaces.nsIEffectiveTLDService);
+
+
+function CSPWarning(aMsg) {
+  // customize this to redirect output.
+  aMsg = 'CSP WARN:  ' + aMsg + "\n";
+  dump(aMsg);
+  Components.classes["@mozilla.org/consoleservice;1"]
+                    .getService(Components.interfaces.nsIConsoleService)
+                    .logStringMessage(aMsg);
+}
+function CSPError(aMsg) {
+  aMsg = 'CSP ERROR: ' + aMsg + "\n";
+  dump(aMsg);
+  Components.classes["@mozilla.org/consoleservice;1"]
+                    .getService(Components.interfaces.nsIConsoleService)
+                    .logStringMessage(aMsg);
+}
+function CSPdebug(aMsg) {
+  aMsg = 'CSP debug: ' + aMsg + "\n";
+  dump(aMsg);
+  Components.classes["@mozilla.org/consoleservice;1"]
+                    .getService(Components.interfaces.nsIConsoleService)
+                    .logStringMessage(aMsg);
+}
+
+//:::::::::::::::::::::::: CLASSES ::::::::::::::::::::::::::// 
+
+/**
+ * Class that represents a parsed policy structure.
+ */
+function CSPRep() {
+  // this gets set to true when the policy is done parsing, or when a
+  // URI-borne policy has finished loading.
+  this._isInitialized = false;
+
+  this._allowEval = false;
+  this._allowInlineScripts = false;
+
+  // don't auto-populate _directives, so it is easier to find bugs
+  this._directives = {};
+}
+
+CSPRep.SRC_DIRECTIVES = {
+  ALLOW:            "allow",
+  SCRIPT_SRC:       "script-src",
+  STYLE_SRC:        "style-src",
+  MEDIA_SRC:        "media-src",
+  IMG_SRC:          "img-src",
+  OBJECT_SRC:       "object-src",
+  FRAME_SRC:        "frame-src",
+  FRAME_ANCESTORS:  "frame-ancestors",
+  FONT_SRC:         "font-src",
+  XHR_SRC:          "xhr-src"
+};
+
+CSPRep.URI_DIRECTIVES = {
+  REPORT_URI:       "report-uri", /* list of URIs */
+  POLICY_URI:       "policy-uri"  /* single URI */
+};
+
+CSPRep.OPTIONS_DIRECTIVE = "options";
+
+/**
+  * Factory to create a new CSPRep, parsed from a string.
+  *
+  * @param aStr
+  *        string rep of a CSP
+  * @param self (optional)
+  *        string or CSPSource representing the "self" source
+  * @returns
+  *        an instance of CSPRep
+  */
+CSPRep.fromString = function(aStr, self) {
+  var SD = CSPRep.SRC_DIRECTIVES;
+  var UD = CSPRep.URI_DIRECTIVES;
+  var aCSPR = new CSPRep();
+  aCSPR._originalText = aStr;
+
+  var dirs = aStr.split(";");
+
+  directive:
+  for each(var dir in dirs) {
+    dir = dir.trim();
+    var dirname = dir.split(/\s+/)[0];
+    var dirvalue = dir.substring(dirname.length).trim();
+
+    // OPTIONS DIRECTIVE ////////////////////////////////////////////////
+    if (dirname === CSPRep.OPTIONS_DIRECTIVE) {
+      // grab value tokens and interpret them
+      var options = dirvalue.split(/\s+/);
+      for each (var opt in options) {
+        if (opt === "inline-script")
+          aCSPR._allowInlineScripts = true;
+        else if (opt === "eval-script")
+          aCSPR._allowEval = true;
+        else
+          CSPWarning("don't understand option '" + opt + "'.  Ignoring it.");
+      }
+      continue directive;
+    }
+
+    // SOURCE DIRECTIVES ////////////////////////////////////////////////
+    for each(var sdi in SD) {
+      if (dirname === sdi) {
+        // process dirs, and enforce that 'self' is defined.
+        var dv = CSPSourceList.fromString(dirvalue, self, true);
+        if (dv) {
+          aCSPR._directives[sdi] = dv;
+          continue directive;
+        }
+      }
+    }
+    
+    // REPORT URI ///////////////////////////////////////////////////////
+    if (dirname === UD.REPORT_URI) {
+      // might be space-separated list of URIs
+      var uriStrings = dirvalue.split(/\s+/);
+      var okUriStrings = [];
+      var selfUri = self ? gIoService.newURI(self.toString(),null,null) : null;
+
+      // Verify that each report URI is in the same etld + 1
+      // if "self" is defined, and just that it's valid otherwise.
+      for (let i in uriStrings) {
+        try {
+          var uri = gIoService.newURI(uriStrings[i],null,null);
+          if (self) {
+            if (gETLDService.getBaseDomain(uri) === 
+                gETLDService.getBaseDomain(selfUri)) {
+              okUriStrings.push(uriStrings[i]);
+            } else {
+              CSPWarning("can't use report URI from non-matching eTLD+1: "
+                         + gETLDService.getBaseDomain(uri));
+            }
+          }
+        } catch(e) {
+          CSPWarning("couldn't parse report URI: " + dirvalue);
+        }
+      }
+      aCSPR._directives[UD.REPORT_URI] = okUriStrings.join(' ');
+      continue directive;
+    }
+
+    // POLICY URI //////////////////////////////////////////////////////////
+    if (dirname === UD.POLICY_URI) {
+      // POLICY_URI can only be alone
+      if (aCSPR._directives.length > 0 || dirs.length > 1) {
+        CSPError("policy-uri directive can only appear alone");
+        return CSPRep.fromString("allow 'none'");
+      }
+
+      var uri = '';
+      try {
+        uri = gIoService.newURI(dirvalue, null, null);
+      } catch(e) {
+        CSPError("could not parse URI in policy URI: " + dirvalue);
+        return CSPRep.fromString("allow 'none'");
+      }
+      
+      // Verify that policy URI comes from the same origin
+      if (self) {
+        var selfUri = gIoService.newURI(self.toString(), null, null);
+        if (selfUri.host !== uri.host){
+          CSPError("can't fetch policy uri from non-matching hostname: " + uri.host);
+          return CSPRep.fromString("allow 'none'");
+        }
+        if (selfUri.port !== uri.port){
+          CSPError("can't fetch policy uri from non-matching port: " + uri.port);
+          return CSPRep.fromString("allow 'none'");
+        }
+        if (selfUri.scheme !== uri.scheme){
+          CSPError("can't fetch policy uri from non-matching scheme: " + uri.scheme);
+          return CSPRep.fromString("allow 'none'");
+        }
+      }
+
+      var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]  
+                  .createInstance(Components.interfaces.nsIXMLHttpRequest);  
+
+      // insert error hook
+      req.onerror = CSPError;
+
+      // synchronous -- otherwise we need to architect a callback into the
+      // xpcom component so that whomever creates the policy object gets
+      // notified when it's loaded and ready to go.
+      req.open("GET", dirvalue, false);
+
+      // make request anonymous
+      // This prevents sending cookies with the request, in case the policy URI
+      // is injected, it can't be abused for CSRF.
+      req.channel.loadFlags |= Components.interfaces.nsIChannel.LOAD_ANONYMOUS;
+
+      req.send(null);  
+      if (req.status == 200) {
+        aCSPR = CSPRep.fromString(req.responseText, self);
+        // remember where we got the policy
+        aCSPR._directives[UD.POLICY_URI] = dirvalue;
+        return aCSPR;
+      }
+      CSPError("Error fetching policy URI: server response was " + req.status);
+      return CSPRep.fromString("allow 'none'");
+    }
+
+    // UNIDENTIFIED DIRECTIVE /////////////////////////////////////////////
+    CSPWarning("Couldn't process unknown directive '" + dirname + "'");
+
+  } // end directive: loop
+
+  aCSPR.makeExplicit();
+  return aCSPR;
+};
+
+CSPRep.prototype = {
+  /**
+   * Returns a space-separated list of all report uris defined, or 'none' if there are none.
+   */
+  getReportURIs:
+  function() {
+    if (!this._directives[CSPRep.URI_DIRECTIVES.REPORT_URI])
+      return "";
+    return this._directives[CSPRep.URI_DIRECTIVES.REPORT_URI];
+  },
+
+  /**
+   * Compares this CSPRep instance to another.
+   */
+  equals:
+  function(that) {
+    if (this._directives.length != that._directives.length) {
+      return false;
+    }
+    for (var i in this._directives) {
+      if (!that._directives[i] || !this._directives[i].equals(that._directives[i])) {
+        return false;
+      }
+    }
+    return (this.allowsInlineScripts === that.allowsInlineScripts)
+        && (this.allowsEvalInScripts === that.allowsEvalInScripts);
+  },
+
+  /**
+   * Generates string representation of the policy.  Should be fairly similar
+   * to the original.
+   */
+  toString:
+  function csp_toString() {
+    var dirs = [];
+
+    if (this._allowEval || this._allowInlineScripts) {
+      dirs.push("options " + (this._allowEval ? "eval-script" : "")
+                           + (this._allowInlineScripts ? "inline-script" : ""));
+    }
+    for (var i in this._directives) {
+      if (this._directives[i]) {
+        dirs.push(i + " " + this._directives[i].toString());
+      }
+    }
+    return dirs.join("; ");
+  },
+
+  /**
+   * Determines if this policy accepts a URI.
+   * @param aContext
+   *        one of the SRC_DIRECTIVES defined above
+   * @returns 
+   *        true if the policy permits the URI in given context.
+   */
+  permits:
+  function csp_permits(aURI, aContext) {
+    if (!aURI) return false;
+
+    // GLOBALLY ALLOW "about:" SCHEME
+    if (aURI instanceof String && aURI.substring(0,6) === "about:")
+      return true;
+    if (aURI instanceof Components.interfaces.nsIURI && aURI.scheme === "about")
+      return true;
+
+    // make sure the context is valid
+    for (var i in CSPRep.SRC_DIRECTIVES) {
+      if (CSPRep.SRC_DIRECTIVES[i] === aContext) {
+        return this._directives[aContext].permits(aURI);
+      }
+    }
+    return false;
+  },
+
+  /**
+   * Intersects with another CSPRep, deciding the subset policy 
+   * that should be enforced, and returning a new instance.
+   * @param aCSPRep
+   *        a CSPRep instance to use as "other" CSP
+   * @returns
+   *        a new CSPRep instance of the intersection
+   */
+  intersectWith:
+  function cspsd_intersectWith(aCSPRep) {
+    var newRep = new CSPRep();
+
+    for (var dir in CSPRep.SRC_DIRECTIVES) {
+      var dirv = CSPRep.SRC_DIRECTIVES[dir];
+      newRep._directives[dirv] = this._directives[dirv]
+               .intersectWith(aCSPRep._directives[dirv]);
+    }
+
+    // REPORT_URI
+    var reportURIDir = CSPRep.URI_DIRECTIVES.REPORT_URI;
+    if (this._directives[reportURIDir] && aCSPRep._directives[reportURIDir]) {
+      newRep._directives[reportURIDir] =
+        this._directives[reportURIDir].concat(aCSPRep._directives[reportURIDir]);
+    }
+    else if (this._directives[reportURIDir]) {
+      // blank concat makes a copy of the string.
+      newRep._directives[reportURIDir] = this._directives[reportURIDir].concat();
+    }
+    else if (aCSPRep._directives[reportURIDir]) {
+      // blank concat makes a copy of the string.
+      newRep._directives[reportURIDir] = aCSPRep._directives[reportURIDir].concat();
+    }
+
+    for (var dir in CSPRep.SRC_DIRECTIVES) {
+      var dirv = CSPRep.SRC_DIRECTIVES[dir];
+      newRep._directives[dirv] = this._directives[dirv]
+               .intersectWith(aCSPRep._directives[dirv]);
+    }
+
+    newRep._allowEval =          this.allowsEvalInScripts
+                           && aCSPRep.allowsEvalInScripts;
+
+    newRep._allowInlineScripts = this.allowsInlineScripts 
+                           && aCSPRep.allowsInlineScripts;
+
+    return newRep;
+  },
+
+  /**
+   * Copies default source list to each unspecified directive.
+   * @returns
+   *      true  if the makeExplicit succeeds
+   *      false if it fails (for some weird reason)
+   */
+  makeExplicit:
+  function cspsd_makeExplicit() {
+    var SD = CSPRep.SRC_DIRECTIVES;
+    var allowDir = this._directives[SD.ALLOW];
+    if (!allowDir) {
+      return false;
+    }
+
+    for (var dir in SD) {
+      var dirv = SD[dir];
+      if (dirv === SD.ALLOW) continue;
+      if (!this._directives[dirv]) {
+        // implicit directive, make explicit
+        this._directives[dirv] = allowDir.clone();
+        this._directives[dirv]._isImplicit = true;
+      }
+    }
+    this._isInitialized = true;
+    return true;
+  },
+
+  /**
+   * Returns true if "eval" is enabled through the "eval" keyword.
+   */
+  get allowsEvalInScripts () {
+    return this._allowEval;
+  },
+
+  /**
+   * Returns true if inline scripts are enabled through the "inline"
+   * keyword.
+   */
+  get allowsInlineScripts () {
+    return this._allowInlineScripts;
+  },
+};
+
+//////////////////////////////////////////////////////////////////////
+/**
+ * Class to represent a list of sources 
+ */
+function CSPSourceList() {
+  this._sources = [];
+  this._permitAllSources = false;
+
+  // Set to true when this list is created using "makeExplicit()"
+  // It's useful to know this when reporting the directive that was violated.
+  this._isImplicit = false;
+}
+
+/**
+ * Factory to create a new CSPSourceList, parsed from a string.
+ *
+ * @param aStr
+ *        string rep of a CSP Source List
+ * @param self (optional)
+ *        string or CSPSource representing the "self" source
+ * @param enforceSelfChecks (optional)
+ *        if present, and "true", will check to be sure "self" has the
+ *        appropriate values to inherit when they are omitted from the source.
+ * @returns
+ *        an instance of CSPSourceList 
+ */
+CSPSourceList.fromString = function(aStr, self, enforceSelfChecks) {
+  // Source list is:
+  //    <host-dir-value> ::= <source-list>
+  //                       | "'none'"
+  //    <source-list>    ::= <source>
+  //                       | <source-list>" "<source>
+
+  var slObj = new CSPSourceList();
+  if (aStr === "'none'")
+    return slObj;
+
+  if (aStr === "*") {
+    slObj._permitAllSources = true;
+    return slObj;
+  }
+
+  var tokens = aStr.split(/\s+/);
+  for (var i in tokens) {
+    if (tokens[i] === "") continue;
+    var src = CSPSource.create(tokens[i], self, enforceSelfChecks);
+    if (!src) {
+      CSPWarning("Failed to parse unrecoginzied source " + tokens[i]);
+      continue;
+    }
+    slObj._sources.push(src);
+  }
+
+  return slObj;
+};
+
+CSPSourceList.prototype = {
+  /**
+   * Compares one CSPSourceList to another.
+   *
+   * @param that
+   *        another CSPSourceList
+   * @returns 
+   *        true if they have the same data
+   */
+  equals:
+  function(that) {
+    if (that._sources.length != this._sources.length) {
+      return false;
+    }
+    // sort both arrays and compare like a zipper
+    // XXX (sid): I think we can make this more efficient
+    var sortfn = function(a,b) {
+      return a.toString() > b.toString();
+    };
+    var a_sorted = this._sources.sort(sortfn);
+    var b_sorted = that._sources.sort(sortfn);
+    for (var i in a_sorted) {
+      if (!a_sorted[i].equals(b_sorted[i])) {
+        return false;
+      }
+    }
+    return true;
+  },
+
+  /**
+   * Generates string representation of the Source List.
+   * Should be fairly similar to the original.
+   */
+  toString:
+  function() {
+    if (this.isNone()) {
+      return "'none'";
+    }
+    if (this._permitAllSources) {
+      return "*";
+    }
+    return this._sources.map(function(x) { return x.toString(); }).join(" ");
+  },
+
+  /**
+   * Returns whether or not this source list represents the "'none'" special
+   * case.
+   */
+  isNone:
+  function() {
+    return (!this._permitAllSources) && (this._sources.length < 1);
+  },
+
+  /**
+   * Returns whether or not this source list permits all sources (*).
+   */
+  isAll:
+  function() {
+    return this._permitAllSources;
+  },
+
+  /**
+   * Makes a new instance that resembles this object.
+   * @returns
+   *      a new CSPSourceList
+   */
+  clone:
+  function() {
+    var aSL = new CSPSourceList();
+    aSL._permitAllSources = this._permitAllSources;
+    for (var i in this._sources) {
+      aSL._sources[i] = this._sources[i].clone();
+    }
+    return aSL;
+  },
+
+  /**
+   * Determines if this directive accepts a URI.
+   * @param aURI
+   *        the URI in question
+   * @returns
+   *        true if the URI matches a source in this source list.
+   */
+  permits:
+  function cspsd_permits(aURI) {
+    if (this.isNone())    return false;
+    if (this.isAll())     return true;
+
+    for (var i in this._sources) {
+      if (this._sources[i].permits(aURI)) {
+        return true;
+      }
+    }
+    return false;
+  },
+
+  /**
+   * Intersects with another CSPSourceList, deciding the subset directive
+   * that should be enforced, and returning a new instance.
+   * @param that 
+   *        the other CSPSourceList to intersect "this" with
+   * @returns
+   *        a new instance of a CSPSourceList representing the intersection
+   */
+  intersectWith:
+  function cspsd_intersectWith(that) {
+
+    var newCSPSrcList = null;
+
+    if (this.isNone() || that.isNone())
+      newCSPSrcList = CSPSourceList.fromString("'none'");
+
+    if (this.isAll()) newCSPSrcList = that.clone();
+    if (that.isAll()) newCSPSrcList = this.clone();
+
+    if (!newCSPSrcList) {
+      // the shortcuts didn't apply, must do intersection the hard way.
+      // --  find only common sources
+
+      // XXX (sid): we should figure out a better algorithm for this.
+      // This is horribly inefficient.
+      var isrcs = [];
+      for (var i in this._sources) {
+        for (var j in that._sources) {
+          var s = that._sources[j].intersectWith(this._sources[i]);
+          if (s) {
+            isrcs.push(s);
+          }
+        }
+      }
+      // Next, remove duplicates
+      dup: for (var i = 0; i < isrcs.length; i++) {
+        for (var j = 0; j < i; j++) {
+          if (isrcs[i].equals(isrcs[j])) {
+            isrcs.splice(i, 1);
+            i--;
+            continue dup;
+          }
+        }
+      }
+      newCSPSrcList = new CSPSourceList();
+      newCSPSrcList._sources = isrcs;
+    }
+
+    // if either was explicit, so is this.
+    newCSPSrcList._isImplicit = this._isImplicit && that._isImplicit;
+
+    return newCSPSrcList;
+  }
+}
+
+//////////////////////////////////////////////////////////////////////
+/**
+ * Class to model a source (scheme, host, port)
+ */
+function CSPSource() {
+  this._scheme = undefined;
+  this._port = undefined;
+  this._host = undefined;
+
+  // when set to true, this source represents 'self'
+  this._isSelf = false;
+}
+
+/**
+ * General factory method to create a new source from one of the following
+ * types:
+ *  - nsURI
+ *  - string
+ *  - CSPSource (clone)
+ */
+CSPSource.create = function(aData, self, enforceSelfChecks) {
+  if (typeof aData === 'string')
+    return CSPSource.fromString(aData, self, enforceSelfChecks);
+
+  if (aData instanceof Components.interfaces.nsIURI)
+    return CSPSource.fromURI(aData, self, enforceSelfChecks);
+
+  if (aData instanceof CSPSource) {
+    var ns = aData.clone();
+    ns._self = CSPSource.create(self);
+    return ns;
+  }
+
+  return null;
+}
+
+/**
+ * Factory to create a new CSPSource, from a nsIURI.
+ *
+ * Don't use this if you want to wildcard ports!
+ *
+ * @param aURI
+ *        nsIURI rep of a URI
+ * @param self (optional)
+ *        string or CSPSource representing the "self" source
+ * @param enforceSelfChecks (optional)
+ *        if present, and "true", will check to be sure "self" has the
+ *        appropriate values to inherit when they are omitted from aURI.
+ * @returns
+ *        an instance of CSPSource 
+ */
+CSPSource.fromURI = function(aURI, self, enforceSelfChecks) {
+  if (!(aURI instanceof Components.interfaces.nsIURI)){
+    CSPError("Provided argument is not an nsIURI");
+    return null;
+  }
+
+  if (!self && enforceSelfChecks) {
+    CSPError("Can't use 'self' if self data is not provided");
+    return null;
+  }
+
+  if (self && !(self instanceof CSPSource)) {
+    self = CSPSource.create(self, undefined, false);
+  }
+
+  var sObj = new CSPSource();
+  sObj._self = self;
+
+  // PARSE
+  // If 'self' is undefined, then use default port for scheme if there is one.
+
+  // grab scheme (if there is one)
+  try {
+    sObj._scheme = aURI.scheme;
+  } catch(e) {
+    sObj._scheme = undefined;
+    CSPError("can't parse a URI without a scheme: " + aURI.asciiSpec);
+    return null;
+  }
+
+  // grab host (if there is one)
+  try {
+    // if there's no host, an exception will get thrown
+    // (NS_ERROR_FAILURE)
+    sObj._host = CSPHost.fromString(aURI.host);
+  } catch(e) {
+    sObj._host = undefined;
+  }
+
+  // grab port (if there is one)
+  // creating a source from an nsURI is limited in that one cannot specify "*"
+  // for port.  In fact, there's no way to represent "*" differently than 
+  // a blank port in an nsURI, since "*" turns into -1, and so does an 
+  // absence of port declaration.
+  try {
+    // if there's no port, an exception will get thrown
+    // (NS_ERROR_FAILURE)
+    if (aURI.port > 0) {
+      sObj._port = aURI.port;
+    } else {
+      // port is never inherited from self -- this gets too confusing.
+      // Instead, whatever scheme is used (an explicit one or the inherited
+      // one) dictates the port if no port is explicitly stated.
+      if (sObj._scheme) {
+        sObj._port = gIoService.getProtocolHandler(sObj._scheme).defaultPort;
+        if (sObj._port < 1) 
+          sObj._port = undefined;
+      }
+    }
+  } catch(e) {
+    sObj._port = undefined;
+  }
+
+  return sObj;
+};
+
+/**
+ * Factory to create a new CSPSource, parsed from a string.
+ *
+ * @param aStr
+ *        string rep of a CSP Source
+ * @param self (optional)
+ *        string or CSPSource representing the "self" source
+ * @param enforceSelfChecks (optional)
+ *        if present, and "true", will check to be sure "self" has the
+ *        appropriate values to inherit when they are omitted from aURI.
+ * @returns
+ *        an instance of CSPSource 
+ */
+CSPSource.fromString = function(aStr, self, enforceSelfChecks) {
+  if (!aStr)
+    return null;
+
+  if (!(typeof aStr === 'string')) {
+    CSPError("Provided argument is not a string");
+    return null;
+  }
+
+  if (!self && enforceSelfChecks) {
+    CSPError("Can't use 'self' if self data is not provided");
+    return null;
+  }
+
+  if (self && !(self instanceof CSPSource)) {
+    self = CSPSource.create(self, undefined, false);
+  }
+
+  var sObj = new CSPSource();
+  sObj._self = self;
+
+  // take care of 'self' keyword
+  if (aStr === "'self'") {
+    if (!self) {
+      CSPError("self keyword used, but no self data specified");
+      return null;
+    }
+    sObj._isSelf = true;
+    sObj._self = self.clone();
+    return sObj;
+  }
+
+  // We could just create a URI and then send this off to fromURI, but
+  // there's no way to leave out the scheme or wildcard the port in an nsURI.
+  // That has to be supported here.
+
+  // split it up
+  var chunks = aStr.split(":");
+
+  // If there is only one chunk, it's gotta be a host.
+  if (chunks.length == 1) {
+    sObj._host = CSPHost.fromString(chunks[0]);
+    if (!sObj._host) {
+      CSPError("Couldn't parse invalid source " + aStr);
+      return null;
+    }
+
+    // enforce 'self' inheritance
+    if (enforceSelfChecks) {
+      // note: the non _scheme accessor checks sObj._self
+      if (!sObj.scheme || !sObj.port) {
+        CSPError("Can't create host-only source " + aStr + " without 'self' data");
+        return null;
+      }
+    }
+    return sObj;
+  }
+
+  // If there are two chunks, it's either scheme://host or host:port
+  //   ... but scheme://host can have an empty host.
+  //   ... and host:port can have an empty host
+  if (chunks.length == 2) {
+
+    // is the last bit a port?
+    if (chunks[1] === "*" || chunks[1].match(/^\d+$/)) {
+      sObj._port = chunks[1];
+      // then the previous chunk *must* be a host or empty.
+      if (chunks[0] !== "") {
+        sObj._host = CSPHost.fromString(chunks[0]);
+        if (!sObj._host) {
+          CSPError("Couldn't parse invalid source " + aStr);
+          return null;
+        }
+      }
+      // enforce 'self' inheritance 
+      // (scheme:host requires port, host:port does too.  Wildcard support is
+      // only available if the scheme and host are wildcarded)
+      if (enforceSelfChecks) {
+        // note: the non _scheme accessor checks sObj._self
+        if (!sObj.scheme || !sObj.host || !sObj.port) {
+          CSPError("Can't create source " + aStr + " without 'self' data");
+          return null;
+        }
+      }
+    }
+    // is the first bit a scheme?
+    else if (CSPSource.validSchemeName(chunks[0])) {
+      sObj._scheme = chunks[0];
+      // then the second bit *must* be a host or empty
+      if (chunks[1] === "") {
+        // Allow scheme-only sources!  These default to wildcard host/port,
+        // especially since host and port don't always matter.
+        // Example: "javascript:" and "data:" 
+        if (!sObj._host) sObj._host = "*";
+        if (!sObj._port) sObj._port = "*";
+      } else {
+        // some host was defined.
+        // ... remove <= 3 leading slashes (from the scheme) and parse
+        var cleanHost = chunks[1].replace(/^\/{0,3}/,"");
+        // ... and parse
+        sObj._host = CSPHost.fromString(cleanHost);
+        if (!sObj._host) {
+          CSPError("Couldn't parse invalid host " + cleanHost);
+          return null;
+        }
+      }
+
+      // enforce 'self' inheritance (scheme-only should be scheme:*:* now, and
+      // if there was a host provided it should be scheme:host:selfport
+      if (enforceSelfChecks) {
+        // note: the non _scheme accessor checks sObj._self
+        if (!sObj.scheme || !sObj.host || !sObj.port) {
+          CSPError("Can't create source " + aStr + " without 'self' data");
+          return null;
+        }
+      }
+    }
+    else  {
+      // AAAH!  Don't know what to do!  No valid scheme or port!
+      CSPError("Couldn't parse invalid source " + aStr);
+      return null;
+    }
+
+    return sObj;
+  }
+
+  // If there are three chunks, we got 'em all!
+  if (!CSPSource.validSchemeName(chunks[0])) {
+    CSPError("Couldn't parse scheme in " + aStr);
+    return null;
+  }
+  sObj._scheme = chunks[0];
+  if (!(chunks[2] === "*" || chunks[2].match(/^\d+$/))) {
+    CSPError("Couldn't parse port in " + aStr);
+    return null;
+  }
+
+  sObj._port = chunks[2];
+
+  // ... remove <= 3 leading slashes (from the scheme) and parse
+  var cleanHost = chunks[1].replace(/^\/{0,3}/,"");
+  sObj._host = CSPHost.fromString(cleanHost);
+
+  return sObj._host ? sObj : null;
+};
+
+CSPSource.validSchemeName = function(aStr) {
+  // <scheme-name>       ::= <alpha><scheme-suffix>
+  // <scheme-suffix>     ::= <scheme-chr> 
+  //                      | <scheme-suffix><scheme-chr> 
+  // <scheme-chr>        ::= <letter> | <digit> | "+" | "." | "-"
+  
+  return aStr.match(/^[a-zA-Z][a-zA-Z0-9+.-]*$/);
+};
+
+CSPSource.prototype = {
+
+  get scheme () {
+    if (!this._scheme && this._self)
+      return this._self.scheme;
+    return this._scheme;
+  },
+
+  get host () {
+    if (!this._host && this._self)
+      return this._self.host;
+    return this._host;
+  },
+
+  /** 
+   * If 'self' has port hard-defined, and this doesn't have a port
+   * hard-defined, use the self's port.  Otherwise, if both are implicit,
+   * resolve default port for this scheme.
+   */
+  get port () {
+    if (this._port) return this._port;
+    // if no port, get the default port for the scheme.
+    if (this._scheme) {
+      try {
+        var port = gIoService.getProtocolHandler(this._scheme).defaultPort;
+        if (port > 0) return port;
+      } catch(e) {
+        // if any errors happen, fail gracefully.
+      }
+    }
+    // if there was no scheme (and thus no default scheme), return self.port
+    if (this._self && this._self.port) return this._self.port;
+
+    return undefined;
+  },
+
+  /**
+   * Generates string representation of the Source.
+   * Should be fairly similar to the original.
+   */
+  toString:
+  function() {
+    if (this._isSelf) 
+      return this._self.toString();
+
+    var s = "";
+    if (this._scheme)
+      s = s + this._scheme + "://";
+    if (this._host)
+      s = s + this._host;
+    if (this._port)
+      s = s + ":" + this._port;
+    return s;
+  },
+
+  /**
+   * Makes a new instance that resembles this object.
+   * @returns
+   *      a new CSPSource
+   */
+  clone:
+  function() {
+    var aClone = new CSPSource();
+    aClone._self = this._self ? this._self.clone() : undefined;
+    aClone._scheme = this._scheme;
+    aClone._port = this._port;
+    aClone._host = this._host ? this._host.clone() : undefined;
+    aClone._isSelf = this._isSelf;
+    return aClone;
+  },
+
+  /**
+   * Determines if this Source accepts a URI.
+   * @param aSource
+   *        the URI, or CSPSource in question
+   * @returns
+   *        true if the URI matches a source in this source list.
+   */
+  permits:
+  function(aSource) {
+    if (!aSource) return false;
+
+    if (!(aSource instanceof CSPSource))
+      return this.permits(CSPSource.create(aSource));
+
+    // verify scheme
+    if (this.scheme != aSource.scheme)
+      return false;
+
+    // port is defined in 'this' (undefined means it may not be relevant
+    // to the scheme) AND this port (implicit or explicit) matches 
+    // aSource's port
+    if (this.port && this.port !== "*" && this.port != aSource.port)
+      return false;
+
+    // host is defined in 'this' (undefined means it may not be relevant
+    // to the scheme) AND this host (implicit or explicit) permits 
+    // aSource's host.
+    if (this.host && !this.host.permits(aSource.host))
+      return false;
+
+    // all scheme, host and port matched!
+    return true;
+  },
+
+  /**
+   * Determines the intersection of two sources.
+   * Returns a null object if intersection generates no
+   * hosts that satisfy it.
+   * @param that 
+   *        the other CSPSource to intersect "this" with
+   * @returns
+   *        a new instance of a CSPSource representing the intersection
+   */
+  intersectWith:
+  function(that) {
+    var newSource = new CSPSource();
+
+    // 'self' is not part of the intersection.  Intersect the raw values from
+    // the source, self must be set by someone creating this source.
+    // When intersecting, we take the more specific of the two: if one scheme,
+    // host or port is undefined, the other is taken.  (This is contrary to
+    // when "permits" is called -- there, the value of 'self' is looked at 
+    // when a scheme, host or port is undefined.)
+
+    // port
+    if (!this._port)
+      newSource._port = that._port;
+    else if (!that._port)
+      newSource._port = this._port;
+    else if (this._port === "*") 
+      newSource._port = that._port;
+    else if (that._port === "*")
+      newSource._port = this._port;
+    else if (that._port === this._port)
+      newSource._port = this._port;
+    else {
+      CSPError("Could not intersect " + this + " with " + that
+               + " due to port problems.");
+      return null;
+    }
+
+    // scheme
+    if (!this._scheme)
+      newSource._scheme = that._scheme;
+    else if (!that._scheme)
+      newSource._scheme = this._scheme;
+    if (this._scheme === "*")
+      newSource._scheme = that._scheme;
+    else if (that._scheme === "*")
+      newSource._scheme = this._scheme;
+    else if (that._scheme === this._scheme)
+      newSource._scheme = this._scheme;
+    else {
+      CSPError("Could not intersect " + this + " with " + that
+               + " due to scheme problems.");
+      return null;
+    }
+
+    // host
+    if (!this._host)
+      newSource._host = that._host;
+    else if (!that._host)
+      newSource._host = this._host;
+    else // both this and that have hosts
+      newSource._host = this._host.intersectWith(that._host);
+
+    return newSource;
+  },
+
+  /**
+   * Compares one CSPSource to another.
+   *
+   * @param that
+   *        another CSPSource
+   * @param resolveSelf (optional) 
+   *        if present, and 'true', implied values are obtained from 'self'
+   *        instead of assumed to be "anything"
+   * @returns 
+   *        true if they have the same data
+   */
+  equals:
+  function(that, resolveSelf) {
+    // 1. schemes match
+    // 2. ports match
+    // 3. either both hosts are undefined, or one equals the other.
+    if (resolveSelf)
+      return this.scheme === that.scheme
+          && this.port   === that.port
+          && (!(this.host || that.host) ||
+               (this.host && this.host.equals(that.host)));
+
+    // otherwise, compare raw (non-self-resolved values)
+    return this._scheme === that._scheme
+        && this._port   === that._port
+        && (!(this._host || that._host) ||
+              (this._host && this._host.equals(that._host)));
+  },
+
+};
+
+//////////////////////////////////////////////////////////////////////
+/**
+ * Class to model a host *.x.y.
+ */
+function CSPHost() {
+  this._segments = [];
+}
+
+/**
+ * Factory to create a new CSPHost, parsed from a string.
+ *
+ * @param aStr
+ *        string rep of a CSP Host 
+ * @returns
+ *        an instance of CSPHost
+ */
+CSPHost.fromString = function(aStr) {
+  if (!aStr) return null;
+
+  // host string must be LDH with dots and stars.
+  var invalidChar = aStr.match(/[^a-zA-Z0-9\-\.\*]/);
+  if (invalidChar) {
+    CSPdebug("Invalid character '" + invalidChar + "' in host " + aStr);
+    return null;
+  }
+
+  var hObj = new CSPHost();
+  hObj._segments = aStr.split(/\./);
+  if (hObj._segments.length < 1 ||
+      hObj._segments.length == 1 && hObj._segments[0] != "*"
+                                 && hObj._segments[0] != "localhost") {
+    // only short hosts allowed are "*" and "localhost"
+    return null;
+  }
+
+  // validate data in segments
+  for (var i in hObj._segments) {
+    var seg = hObj._segments[i];
+    if (seg == "*") {
+      if (i > 0) {
+        // Wildcard must be FIRST
+        CSPdebug("Wildcard char located at invalid position in '" + aStr + "'");
+        return null;
+      }
+    } 
+    else if (seg.match(/[^a-zA-Z0-9\-]/)) {
+      // Non-wildcard segment must be LDH string
+      CSPdebug("Invalid segment '" + seg + "' in host value");
+      return null;
+    }
+  }
+  return hObj;
+};
+
+CSPHost.prototype = {
+  /**
+   * Generates string representation of the Source.
+   * Should be fairly similar to the original.
+   */
+  toString:
+  function() {
+    return this._segments.join(".");
+  },
+
+  /**
+   * Makes a new instance that resembles this object.
+   * @returns
+   *      a new CSPHost
+   */
+  clone:
+  function() {
+    var aHost = new CSPHost();
+    for (var i in this._segments) {
+      aHost._segments[i] = this._segments[i];
+    }
+    return aHost;
+  },
+
+  /**
+   * Returns true if this host accepts the provided host (or the other way
+   * around).
+   * @param aHost
+   *        the FQDN in question (CSPHost or String)
+   * @returns
+   */
+  permits:
+  function(aHost) {
+    if (!aHost) return false;
+
+    if (!(aHost instanceof CSPHost)) {
+      // -- compare CSPHost to String
+      return this.permits(CSPHost.fromString(aHost));
+    }
+    var thislen = this._segments.length;
+    var thatlen = aHost._segments.length;
+
+    // don't accept a less specific host: 
+    //   \--> *.b.a doesn't accept b.a.
+    if (thatlen < thislen) { return false; }
+
+    // check for more specific host (and wildcard):
+    //   \--> *.b.a accepts d.c.b.a.
+    //   \--> c.b.a doesn't accept d.c.b.a.
+    if ((thatlen > thislen) && this._segments[0] != "*") {
+      return false;
+    }
+
+    // Given the wildcard condition (from above), 
+    // only necessary to compare elements that are present
+    // in this host.  Extra tokens in aHost are ok. 
+    // * Compare from right to left.
+    for (var i=1; i <= thislen; i++) {
+      if (this._segments[thislen-i] != "*" && 
+          (this._segments[thislen-i] != aHost._segments[thatlen-i])) {
+        return false;
+      }
+    }
+
+    // at this point, all conditions are met, so the host is allowed
+    return true;
+  },
+
+  /**
+   * Determines the intersection of two Hosts.
+   * Basically, they must be the same, or one must have a wildcard.
+   * @param that 
+   *        the other CSPHost to intersect "this" with
+   * @returns
+   *        a new instance of a CSPHost representing the intersection
+   *        (or null, if they can't be intersected)
+   */
+  intersectWith:
+  function(that) {
+    if (!(this.permits(that) || that.permits(this))) {
+      // host definitions cannot co-exist without a more general host
+      // ... one must be a subset of the other, or intersection makes no sense.
+      return null;
+    } 
+
+    // pick the more specific one, if both are same length.
+    if (this._segments.length == that._segments.length) {
+      // *.a vs b.a : b.a
+      return (this._segments[0] === "*") ? that.clone() : this.clone();
+    }
+
+    // different lengths...
+    // *.b.a vs *.a : *.b.a
+    // *.b.a vs d.c.b.a : d.c.b.a
+    return (this._segments.length > that._segments.length) ?
+            this.clone() : that.clone();
+  },
+
+  /**
+   * Compares one CSPHost to another.
+   *
+   * @param that
+   *        another CSPHost
+   * @returns 
+   *        true if they have the same data
+   */
+  equals:
+  function(that) {
+    if (this._segments.length != that._segments.length)
+      return false;
+
+    for (var i=0; i<this._segments.length; i++) {
+      if (this._segments[i] != that._segments[i])
+        return false;
+    }
+    return true;
+  }
+};
--- a/content/base/src/Makefile.in
+++ b/content/base/src/Makefile.in
@@ -74,16 +74,17 @@ CPPSRCS		= \
 		nsContentAreaDragDrop.cpp \
 		nsContentIterator.cpp \
 		nsContentList.cpp \
 		nsContentPolicy.cpp \
 		nsContentSink.cpp \
 		nsContentUtils.cpp \
 		nsCopySupport.cpp \
 		nsCrossSiteListenerProxy.cpp \
+		nsCSPService.cpp \
 		nsDataDocumentContentPolicy.cpp \
 		nsDOMAttribute.cpp \
 		nsDOMAttributeMap.cpp \
 		nsDOMDocumentType.cpp \
 		nsDOMFile.cpp \
 		nsDOMFileReader.cpp \
 		nsDOMLists.cpp \
 		nsDOMParser.cpp \
@@ -136,17 +137,24 @@ CPPSRCS		= \
 		$(NULL)
 
 GQI_SRCS = contentbase.gqi
 
 # we don't want the shared lib, but we want to force the creation of a
 # static lib.
 FORCE_STATIC_LIB = 1
 
-EXTRA_COMPONENTS = $(srcdir)/nsBadCertHandler.js
+EXTRA_COMPONENTS = \
+		$(srcdir)/nsBadCertHandler.js \
+		contentSecurityPolicy.js \
+		$(NULL)
+
+EXTRA_JS_MODULES = \
+		CSPUtils.jsm \
+		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
 INCLUDES	+= \
 		-I$(srcdir)/../../events/src \
 		-I$(srcdir)/../../xml/content/src \
 		-I$(srcdir)/../../../layout/xul/base/src \
 		-I$(srcdir)/../../xul/content/src \
new file mode 100644
--- /dev/null
+++ b/content/base/src/contentSecurityPolicy.js
@@ -0,0 +1,445 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the ContentSecurityPolicy module.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation
+ *
+ * Contributor(s):
+ *   Sid Stamm <sid@mozilla.com>
+ *   Brandon Sterne <bsterne@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+/**
+ * Content Security Policy
+ * 
+ * Overview
+ * This is a stub component that will be fleshed out to do all the fancy stuff
+ * that ContentSecurityPolicy has to do.
+ */
+
+/* :::::::: Constants and Helpers ::::::::::::::: */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+const CSP_VIOLATION_TOPIC = "csp-on-violate-policy";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/CSPUtils.jsm");
+
+/* ::::: Policy Parsing & Data structures :::::: */
+
+function ContentSecurityPolicy() {
+  CSPdebug("CSP CREATED");
+  this._isInitialized = false;
+  this._reportOnlyMode = false;
+  this._policy = CSPRep.fromString("allow *");
+
+  // default options "wide open" since this policy will be intersected soon
+  this._policy._allowInlineScripts = true;
+  this._policy._allowEval = true;
+
+  this._requestHeaders = []; 
+  this._request = "";
+  CSPdebug("CSP POLICY INITED TO 'allow *'");
+
+  this._observerService = Cc['@mozilla.org/observer-service;1']
+                            .getService(Ci.nsIObserverService);
+}
+
+/*
+ * Set up mappings from nsIContentPolicy content types to CSP directives.
+ */
+{
+  let cp = Ci.nsIContentPolicy;
+  let csp = ContentSecurityPolicy;
+  let cspr_sd = CSPRep.SRC_DIRECTIVES;
+
+  csp._MAPPINGS=[];
+
+  /* default, catch-all case */
+  csp._MAPPINGS[cp.TYPE_OTHER]             =  cspr_sd.ALLOW;
+
+  /* self */
+  csp._MAPPINGS[cp.TYPE_DOCUMENT]          =  null;
+
+  /* shouldn't see this one */
+  csp._MAPPINGS[cp.TYPE_REFRESH]           =  null;
+
+  /* categorized content types */
+  csp._MAPPINGS[cp.TYPE_SCRIPT]            = cspr_sd.SCRIPT_SRC;
+  csp._MAPPINGS[cp.TYPE_IMAGE]             = cspr_sd.IMG_SRC;
+  csp._MAPPINGS[cp.TYPE_STYLESHEET]        = cspr_sd.STYLE_SRC;
+  csp._MAPPINGS[cp.TYPE_OBJECT]            = cspr_sd.OBJECT_SRC;
+  csp._MAPPINGS[cp.TYPE_SUBDOCUMENT]       = cspr_sd.FRAME_SRC;
+  csp._MAPPINGS[cp.TYPE_MEDIA]             = cspr_sd.MEDIA_SRC;
+  csp._MAPPINGS[cp.TYPE_FONT]              = cspr_sd.FONT_SRC;
+  csp._MAPPINGS[cp.TYPE_XMLHTTPREQUEST]    = cspr_sd.XHR_SRC;
+
+
+  /* These must go through the catch-all */
+  csp._MAPPINGS[cp.TYPE_XBL]               = cspr_sd.ALLOW;
+  csp._MAPPINGS[cp.TYPE_PING]              = cspr_sd.ALLOW;
+  csp._MAPPINGS[cp.TYPE_OBJECT_SUBREQUEST] = cspr_sd.ALLOW;
+  csp._MAPPINGS[cp.TYPE_DTD]               = cspr_sd.ALLOW;
+}
+
+ContentSecurityPolicy.prototype = {
+  classDescription: "Content Security Policy Component",
+  contractID:       "@mozilla.org/contentsecuritypolicy;1",
+  classID:          Components.ID("{AB36A2BF-CB32-4AA6-AB41-6B4E4444A221}"),
+  QueryInterface:   XPCOMUtils.generateQI([Ci.IContentSecurityPolicy]),
+
+  // get this contractID registered for certain categories via XPCOMUtils
+  _xpcom_categories: [ ],
+
+  get isInitialized() {
+    return this._isInitialized;
+  },
+
+  set isInitialized (foo) {
+    this._isInitialized = foo;
+  },
+
+  get policy () {
+    return this._policy.toString();
+  },
+
+  get allowsInlineScript() {
+    // trigger automatic report to go out when inline scripts are disabled.
+    if (!this._policy.allowsInlineScripts) {
+      var violation = 'violated base restriction: Inline Scripts will not execute';
+      // gotta wrap the violation string, since it's sent out to observers as
+      // an nsISupports.
+      let wrapper = Cc["@mozilla.org/supports-cstring;1"]
+                      .createInstance(Ci.nsISupportsCString);
+      wrapper.data = violation;
+      this._observerService.notifyObservers(
+                              wrapper,
+                              CSP_VIOLATION_TOPIC,
+                              'inline script base restriction');
+      this.sendReports('self', violation);
+    }
+    return this._reportOnlyMode || this._policy.allowsInlineScripts;
+  },
+
+  get allowsEval() {
+    // trigger automatic report to go out when eval and friends are disabled.
+    if (!this._policy.allowsEvalInScripts) {
+      var violation = 'violated base restriction: Code will not be created from strings';
+      // gotta wrap the violation string, since it's sent out to observers as
+      // an nsISupports.
+      let wrapper = Cc["@mozilla.org/supports-cstring;1"]
+                      .createInstance(Ci.nsISupportsCString);
+      wrapper.data = violation;
+      this._observerService.notifyObservers(
+                              wrapper,
+                              CSP_VIOLATION_TOPIC,
+                              'eval script base restriction');
+      this.sendReports('self', violation);
+    }
+    return this._reportOnlyMode || this._policy.allowsEvalInScripts;
+  },
+
+  set reportOnlyMode(val) {
+    this._reportOnlyMode = val;
+  },
+
+  get reportOnlyMode () {
+    return this._reportOnlyMode;
+  },
+
+  /*
+  // Having a setter is a bad idea... opens up the policy to "loosening"
+  // Instead, use "refinePolicy."
+  set policy (aStr) {
+    this._policy = CSPRep.fromString(aStr);
+  },
+  */
+
+  /**
+   * Given an nsIHttpChannel, fill out the appropriate data.
+   */
+  scanRequestData:
+  function(aChannel) {
+    // grab the request line
+    var internalChannel = aChannel.QueryInterface(Ci.nsIHttpChannelInternal);
+    var reqMaj = {};
+    var reqMin = {};
+    var reqVersion = internalChannel.getRequestVersion(reqMaj, reqMin);
+    this._request = aChannel.requestMethod + " " 
+                  + aChannel.URI.asciiSpec
+                  + " HTTP/" + reqMaj.value + "." + reqMin.value;
+
+    // grab the request headers
+    var self = this;
+    aChannel.visitRequestHeaders({
+      visitHeader: function(aHeader, aValue) {
+        self._requestHeaders.push(aHeader + ": " + aValue);
+      }});
+  },
+
+/* ........ Methods .............. */
+
+  /**
+   * Given a new policy, intersects the currently enforced policy with the new
+   * one and stores the result.  The effect is a "tightening" or refinement of
+   * an old policy.  This is called any time a new policy is encountered and
+   * the effective policy has to be refined.
+   */
+  refinePolicy:
+  function csp_refinePolicy(aPolicy, selfURI) {
+    CSPdebug("REFINE POLICY: " + aPolicy);
+    CSPdebug("         SELF: " + selfURI.asciiSpec);
+
+    // stay uninitialized until policy merging is done
+    this._isInitialized = false;
+
+    // If there is a policy-uri, fetch the policy, then re-call this function.
+    // (1) parse and create a CSPRep object
+    var newpolicy = CSPRep.fromString(aPolicy,
+                                      selfURI.scheme + "://" + selfURI.hostPort);
+
+    // (2) Intersect the currently installed CSPRep object with the new one
+    var intersect = this._policy.intersectWith(newpolicy);
+ 
+    // (3) Save the result
+    this._policy = intersect;
+    this._isInitialized = true;
+  },
+
+  /**
+   * Generates and sends a violation report to the specified report URIs.
+   */
+  sendReports:
+  function(blockedUri, violatedDirective) {
+    var uriString = this._policy.getReportURIs();
+    var uris = uriString.split(/\s+/);
+    if (uris.length > 0) {
+      // Generate report to send composed of:
+      // <csp-report>
+      //   <request>GET /index.html HTTP/1.1</request>
+      //   <request-headers>Host: example.com
+      //            User-Agent: ...
+      //            ...
+      //   </request-headers>
+      //   <blocked-uri>...</blocked-uri>
+      //   <violated-directive>...</violated-directive>
+      // </csp-report>
+      //   
+      var strHeaders = "";
+      for (let i in this._requestHeaders) {
+        strHeaders += this._requestHeaders[i] + "\n";
+      }
+
+      var report = "<csp-report>\n" +
+        " <request>" + this._request + "</request>\n" +
+        "   <request-headers><![CDATA[\n" +
+        strHeaders +
+        "   ]]></request-headers>\n" +
+        "   <blocked-uri>" + 
+        (typeof blockedUri === "nsIURI" ? blockedUri.asciiSpec : blockedUri) + 
+        "</blocked-uri>\n" +
+        "   <violated-directive>" + violatedDirective + "</violated-directive>\n" +
+        "</csp-report>\n";
+
+      CSPdebug("Constructed violation report:\n" + report);
+
+      // For each URI in the report list, send out a report.
+      for (let i in uris) {
+        if (uris[i] === "")
+          continue;
+
+        var failure = function(aEvt) {  
+          if (req.readyState == 4 && req.status != 200) {
+            CSPError("Failed to send report to " + reportURI);
+          }  
+        };  
+        var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]  
+                    .createInstance(Ci.nsIXMLHttpRequest);  
+
+        try {
+          req.open("POST", uris[i], true);
+          req.setRequestHeader('Content-Type', 'application/xml');
+          req.upload.addEventListener("error", failure, false);
+          req.upload.addEventListener("abort", failure, false);
+          //req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+ 
+          // make request anonymous
+          // This prevents sending cookies with the request,
+          // in case the policy URI is injected, it can't be
+          // abused for CSRF.
+          req.channel.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS;
+
+          req.send(report);
+          CSPdebug("Sent violation report to " + uris[i]);
+        } catch(e) {
+          // it's possible that the URI was invalid, just log a
+          // warning and skip over that.
+          CSPWarning("Tried to send report to invalid URI: \"" + uris[i] + "\"");
+        }
+      }
+    }
+  },
+
+  /**
+   * Exposed Method to analyze docShell for approved frame ancestry.
+   * Also sends violation reports if necessary.
+   * @param docShell
+   *    the docShell for this policy's resource.
+   * @return
+   *    true if the frame ancestry is allowed by this policy.
+   */
+  permitsAncestry:
+  function(docShell) {
+    if (!docShell) { return false; }
+    CSPdebug(" in permitsAncestry(), docShell = " + docShell);
+
+    // walk up this docShell tree until we hit chrome
+    var dst = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                      .getInterface(Ci.nsIDocShellTreeItem);
+
+    // collect ancestors and make sure they're allowed.
+    var ancestors = [];
+    while (dst.parent) {
+      dst = dst.parent;
+      let it = dst.QueryInterface(Ci.nsIInterfaceRequestor)
+                  .getInterface(Ci.nsIWebNavigation);
+      if (it.currentURI) {
+        if (it.currentURI.scheme === "chrome") {
+          break;
+        }
+        let ancestor = it.currentURI;
+        CSPdebug(" found frame ancestor " + ancestor.asciiSpec);
+        ancestors.push(ancestor);
+      }
+    } 
+
+    // scan the discovered ancestors
+    let cspContext = CSPRep.SRC_DIRECTIVES.FRAME_ANCESTORS;
+    for (let i in ancestors) {
+      let ancestor = ancestors[i].prePath;
+      if (!this._policy.permits(ancestor, cspContext)) {
+        // report the frame-ancestor violation
+        let directive = this._policy._directives[cspContext];
+        let violatedPolicy = (directive._isImplicit
+                                ? 'allow' : 'frame-ancestors ')
+                                + directive.toString();
+        // send an nsIURI object to the observers (more interesting than a string)
+        this._observerService.notifyObservers(
+                                ancestors[i],
+                                CSP_VIOLATION_TOPIC, 
+                                violatedPolicy);
+        this.sendReports(ancestors[i].asciiSpec, violatedPolicy);
+        // need to lie if we are testing in report-only mode
+        return this._reportOnlyMode;
+      }
+    }
+    return true;
+  },
+
+  /**
+   * Delegate method called by the service when sub-elements of the protected
+   * document are being loaded.  Given a bit of information about the request,
+   * decides whether or not the policy is satisfied.
+   */
+  shouldLoad:
+  function csp_shouldLoad(aContentType, 
+                          aContentLocation, 
+                          aRequestOrigin, 
+                          aContext, 
+                          aMimeTypeGuess, 
+                          aExtra) {
+
+    // don't filter chrome stuff
+    if (aContentLocation.scheme === 'chrome') {
+      return Ci.nsIContentPolicy.ACCEPT;
+    }
+
+    // interpret the context, and then pass off to the decision structure
+    CSPdebug("shouldLoad location = " + aContentLocation.asciiSpec);
+    CSPdebug("shouldLoad content type = " + aContentType);
+    var cspContext = ContentSecurityPolicy._MAPPINGS[aContentType];
+    // CSPdebug("shouldLoad CSP directive =" + cspContext);
+
+    // if the mapping is null, there's no policy, let it through.
+    if (!cspContext) {
+      return Ci.nsIContentPolicy.ACCEPT;
+    }
+
+    // otherwise, honor the translation
+    // var source = aContentLocation.scheme + "://" + aContentLocation.hostPort; 
+    var res = this._policy.permits(aContentLocation, cspContext)
+              ? Ci.nsIContentPolicy.ACCEPT 
+              : Ci.nsIContentPolicy.REJECT_SERVER;
+
+    // frame-ancestors is taken care of early on (as this document is loaded)
+
+    // If the result is *NOT* ACCEPT, then send report
+    if (res != Ci.nsIContentPolicy.ACCEPT) { 
+      CSPdebug("blocking request for " + aContentLocation.asciiSpec);
+      try {
+        let directive = this._policy._directives[cspContext];
+        let violatedPolicy = (directive._isImplicit
+                                ? 'allow' : cspContext)
+                                + ' ' + directive.toString();
+        this._observerService.notifyObservers(
+                                aContentLocation,
+                                CSP_VIOLATION_TOPIC, 
+                                violatedPolicy);
+        this.sendReports(aContentLocation, violatedPolicy);
+      } catch(e) {
+        CSPdebug('---------------- ERROR: ' + e);
+      }
+    }
+
+    return (this._reportOnlyMode ? Ci.nsIContentPolicy.ACCEPT : res);
+  },
+  
+  shouldProcess:
+  function csp_shouldProcess(aContentType,
+                             aContentLocation,
+                             aRequestOrigin,
+                             aContext,
+                             aMimeType,
+                             aExtra) {
+    // frame-ancestors check is done outside the ContentPolicy
+    var res = Ci.nsIContentPolicy.ACCEPT;
+    CSPdebug("shouldProcess aContext=" + aContext);
+    return res;
+  },
+
+};
+
+
+
+function NSGetModule(aComMgr, aFileSpec)
+  XPCOMUtils.generateModule([ContentSecurityPolicy]);
new file mode 100644
--- /dev/null
+++ b/content/base/src/nsCSPService.cpp
@@ -0,0 +1,214 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Brandon Sterne <bsterne@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "prlog.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+#include "nsIPrincipal.h"
+#include "nsIObserver.h"
+#include "nsIDocument.h"
+#include "nsIContent.h"
+#include "nsContentUtils.h"
+#include "nsCSPService.h"
+#include "IContentSecurityPolicy.h"
+
+/* Keeps track of whether or not CSP is enabled */
+static PRBool gCSPEnabled = PR_TRUE;
+
+#ifdef PR_LOGGING
+static PRLogModuleInfo* gCspPRLog;
+#endif
+
+CSPService::CSPService()
+{
+  nsContentUtils::AddBoolPrefVarCache("security.csp.enable", &gCSPEnabled);
+
+#ifdef PR_LOGGING
+  if (!gCspPRLog)
+    gCspPRLog = PR_NewLogModule("CSP");
+#endif
+}
+
+CSPService::~CSPService()
+{
+}
+
+NS_IMPL_ISUPPORTS1(CSPService, nsIContentPolicy)
+
+/* nsIContentPolicy implementation */
+NS_IMETHODIMP
+CSPService::ShouldLoad(PRUint32 aContentType,
+                       nsIURI *aContentLocation,
+                       nsIURI *aRequestOrigin,
+                       nsISupports *aRequestContext,
+                       const nsACString &aMimeTypeGuess,
+                       nsISupports *aExtra,
+                       PRInt16 *aDecision)
+{
+    if (!aContentLocation)
+        return NS_ERROR_FAILURE;
+
+#ifdef PR_LOGGING 
+    {
+        nsCAutoString location;
+        aContentLocation->GetSpec(location);
+        PR_LOG(gCspPRLog, PR_LOG_DEBUG, 
+            ("CSPService::ShouldLoad called for %s", location.get()));
+    }
+#endif
+    // default decision, CSP can revise it if there's a policy to enforce
+    *aDecision = nsIContentPolicy::ACCEPT;
+
+    // No need to continue processing if CSP is disabled
+    if (!gCSPEnabled)
+        return NS_OK;
+
+    // find the nsDocument that initiated this request and see if it has a
+    // CSP policy object
+    nsresult rv;
+    nsCOMPtr<nsIDocument> doc;
+    nsCOMPtr<nsIPrincipal> principal;
+    nsCOMPtr<IContentSecurityPolicy> csp;
+    nsCOMPtr<nsIContent> node(do_QueryInterface(aRequestContext));
+    if (node) {
+        doc = node->GetOwnerDoc();
+    }
+    if (!doc) {
+        doc = do_QueryInterface(aRequestContext);
+    }
+  
+    if (doc) {
+        principal = doc->NodePrincipal();
+        principal->GetCsp(getter_AddRefs(csp));
+
+        if (csp) {
+#ifdef PR_LOGGING 
+            nsAutoString policy;
+            csp->GetPolicy(policy);
+            PR_LOG(gCspPRLog, PR_LOG_DEBUG, 
+                    ("Document has CSP: %s", 
+                     NS_ConvertUTF16toUTF8(policy).get()));
+#endif
+            // obtain the enforcement decision
+            csp->ShouldLoad(aContentType,
+                            aContentLocation,
+                            aRequestOrigin,
+                            aRequestContext,
+                            aMimeTypeGuess,
+                            aExtra,
+                            aDecision);
+        }
+    }
+#ifdef PR_LOGGING
+    else {
+        nsCAutoString uriSpec;
+        aContentLocation->GetSpec(uriSpec);
+        PR_LOG(gCspPRLog, PR_LOG_DEBUG, 
+            ("COULD NOT get nsIDocument for location: %s", uriSpec.get()));
+    }
+#endif
+	
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+CSPService::ShouldProcess(PRUint32         aContentType,
+                          nsIURI           *aContentLocation,
+                          nsIURI           *aRequestOrigin,
+                          nsISupports      *aRequestContext,
+                          const nsACString &aMimeTypeGuess,
+                          nsISupports      *aExtra,
+                          PRInt16          *aDecision)
+{
+    if (!aContentLocation)
+        return NS_ERROR_FAILURE;
+
+    // default decision is to accept the item
+    *aDecision = nsIContentPolicy::ACCEPT;
+
+    // No need to continue processing if CSP is disabled
+    if (!gCSPEnabled)
+        return NS_OK;
+
+    // find the nsDocument that initiated this request and see if it has a
+    // CSP policy object
+    nsresult rv;
+    nsCOMPtr<nsIDocument> doc;
+    nsCOMPtr<nsIPrincipal> principal;
+    nsCOMPtr<IContentSecurityPolicy> csp;
+    nsCOMPtr<nsIContent> node(do_QueryInterface(aRequestContext));
+    if (node) {
+        doc = node->GetOwnerDoc();
+    }
+    if (!doc) {
+        doc = do_QueryInterface(aRequestContext);
+    }
+    
+    if (doc) {
+        principal = doc->NodePrincipal();
+        principal->GetCsp(getter_AddRefs(csp));
+
+        if (csp) {
+#ifdef PR_LOGGING
+            nsAutoString policy;
+            csp->GetPolicy(policy);
+            PR_LOG(gCspPRLog, PR_LOG_DEBUG, 
+                  ("shouldProcess - document has policy: %s",
+                    NS_ConvertUTF16toUTF8(policy).get()));
+#endif
+            // obtain the enforcement decision
+            csp->ShouldProcess(aContentType,
+                               aContentLocation,
+                               aRequestOrigin,
+                               aRequestContext,
+                               aMimeTypeGuess,
+                               aExtra,
+                               aDecision);
+        }
+    }
+#ifdef PR_LOGGING
+    else {
+        nsCAutoString uriSpec;
+        aContentLocation->GetSpec(uriSpec);
+        PR_LOG(gCspPRLog, PR_LOG_DEBUG, 
+            ("COULD NOT get nsIDocument for location: %s", uriSpec.get()));
+    }
+#endif
+    return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/content/base/src/nsCSPService.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Brandon Sterne <bsterne@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsXPCOM.h"
+#include "nsIContentPolicy.h"
+
+#define CSPSERVICE_CONTRACTID "@mozilla.org/cspservice;1"
+#define CSPSERVICE_CID \
+  { 0x8d2f40b2, 0x4875, 0x4c95, \
+    { 0x97, 0xd9, 0x3f, 0x7d, 0xca, 0x2c, 0xb4, 0x60 } }
+class CSPService : public nsIContentPolicy
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSICONTENTPOLICY
+  
+  CSPService();
+  virtual ~CSPService();
+
+private:
+  PRBool mEnabled;
+};
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -176,25 +176,32 @@ static NS_DEFINE_CID(kDOMEventGroupCID, 
 #include "mozAutoDocUpdate.h"
 
 #ifdef MOZ_SMIL
 #include "nsSMILAnimationController.h"
 #include "imgIContainer.h"
 #include "nsSVGUtils.h"
 #endif // MOZ_SMIL
 
+// FOR CSP (autogenerated by xpidl)
+#include "IContentSecurityPolicy.h"
+
 
 #ifdef MOZ_LOGGING
 // so we can get logging even in release builds
 #define FORCE_PR_LOG 1
 #endif
 #include "prlog.h"
 
+/* Keeps track of whether or not CSP is enabled */
+static PRBool gCSPEnabled = PR_TRUE;
+
 #ifdef PR_LOGGING
 static PRLogModuleInfo* gDocumentLeakPRLog;
+static PRLogModuleInfo* gCspPRLog;
 #endif
 
 void
 nsUint32ToContentHashEntry::Destroy()
 {
   HashSet* set = GetHashSet();
   if (set) {
     delete set;
@@ -1490,18 +1497,23 @@ nsDocument::nsDocument(const char* aCont
   
 #ifdef PR_LOGGING
   if (!gDocumentLeakPRLog)
     gDocumentLeakPRLog = PR_NewLogModule("DocumentLeak");
 
   if (gDocumentLeakPRLog)
     PR_LOG(gDocumentLeakPRLog, PR_LOG_DEBUG,
            ("DOCUMENT %p created", this));
+
+  if (!gCspPRLog)
+    gCspPRLog = PR_NewLogModule("CSP");
 #endif
 
+  nsContentUtils::AddBoolPrefVarCache("security.csp.enable", &gCSPEnabled);
+
   // Start out mLastStyleSheetSet as null, per spec
   SetDOMStringToNull(mLastStyleSheetSet);
 }
 
 static PLDHashOperator
 ClearAllBoxObjects(const void* aKey, nsPIBoxObject* aBoxObject, void* aUserArg)
 {
   if (aBoxObject) {
@@ -2246,17 +2258,121 @@ nsDocument::StartDocumentLoad(const char
     semicolon = start;
     FindCharInReadable(';', semicolon, end);
     mContentType = Substring(start, semicolon);
   }
 
   RetrieveRelevantHeaders(aChannel);
 
   mChannel = aChannel;
-
+  
+  nsresult rv = InitCSP();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+nsresult
+nsDocument::InitCSP()
+{
+  if (gCSPEnabled) {
+    nsAutoString cspHeaderValue;
+    nsAutoString cspROHeaderValue;
+
+    this->GetHeaderData(nsGkAtoms::headerCSP, cspHeaderValue);
+    this->GetHeaderData(nsGkAtoms::headerCSPReportOnly, cspROHeaderValue);
+
+    PRBool system = PR_FALSE;
+    nsIScriptSecurityManager *ssm = nsContentUtils::GetSecurityManager();
+
+    if (NS_SUCCEEDED(ssm->IsSystemPrincipal(NodePrincipal(), &system)) && system) {
+      // only makes sense to register new CSP if this document is not priviliged
+      return NS_OK;
+    }
+
+    if (cspHeaderValue.IsEmpty() && cspROHeaderValue.IsEmpty()) {
+      // no CSP header present, stop processing
+      return NS_OK;
+    }
+
+#ifdef PR_LOGGING 
+    PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("CSP header specified for document %p", this));
+#endif
+
+    nsresult rv;
+    nsCOMPtr<IContentSecurityPolicy> mCSP;
+    mCSP = do_CreateInstance("@mozilla.org/contentsecuritypolicy;1", &rv);
+
+    if (NS_FAILED(rv)) {
+#ifdef PR_LOGGING 
+      PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("Failed to create CSP object: %x", rv));
+#endif
+      return rv;
+    }
+
+    // Store the request context for violation reports
+    nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(mChannel);
+    mCSP->ScanRequestData(httpChannel);
+
+    // Start parsing the policy
+    nsCOMPtr<nsIURI> chanURI;
+    mChannel->GetURI(getter_AddRefs(chanURI));
+
+#ifdef PR_LOGGING 
+    PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("CSP Loaded"));
+#endif
+
+    // ReportOnly mode is enabled *only* if there are no regular-strength CSP
+    // headers present.  If there are, then we ignore the ReportOnly mode and
+    // toss a warning into the error console, proceeding with enforcing the
+    // regular-strength CSP.
+    if (cspHeaderValue.IsEmpty()) {
+      mCSP->SetReportOnlyMode(true);
+      mCSP->RefinePolicy(cspROHeaderValue, chanURI);
+#ifdef PR_LOGGING 
+      {
+        PR_LOG(gCspPRLog, PR_LOG_DEBUG, 
+                ("CSP (report only) refined, policy: \"%s\"", 
+                  NS_ConvertUTF16toUTF8(cspROHeaderValue).get()));
+      }
+#endif
+    } else {
+      //XXX(sstamm): maybe we should post a warning when both read only and regular 
+      // CSP headers are present.
+      mCSP->RefinePolicy(cspHeaderValue, chanURI);
+#ifdef PR_LOGGING 
+      {
+        PR_LOG(gCspPRLog, PR_LOG_DEBUG, 
+               ("CSP refined, policy: \"%s\"",
+                NS_ConvertUTF16toUTF8(cspHeaderValue).get()));
+      }
+#endif
+    }
+
+    //Copy into principal
+    nsIPrincipal* principal = GetPrincipal();
+
+    if (principal) {
+        principal->SetCsp(mCSP);
+#ifdef PR_LOGGING
+        PR_LOG(gCspPRLog, PR_LOG_DEBUG, 
+                ("Inserted CSP into principal %p", principal));
+    }
+    else {
+      PR_LOG(gCspPRLog, PR_LOG_DEBUG, 
+              ("Couldn't copy CSP into absent principal %p", principal));
+#endif
+    }
+  }
+#ifdef PR_LOGGING
+  else { //CSP was not enabled!
+    PR_LOG(gCspPRLog, PR_LOG_DEBUG, 
+           ("CSP is disabled, skipping CSP init for document %p", this));
+  }
+#endif
   return NS_OK;
 }
 
 void
 nsDocument::StopDocumentLoad()
 {
   if (mParser) {
     mParser->Terminate();
@@ -6637,16 +6753,18 @@ nsDocument::RetrieveRelevantHeaders(nsIC
 
     static const char *const headers[] = {
       "default-style",
       "content-style-type",
       "content-language",
       "content-disposition",
       "refresh",
       "x-dns-prefetch-control",
+      "x-content-security-policy",
+      "x-content-security-policy-read-only",
       // add more http headers if you need
       // XXXbz don't add content-location support without reading bug
       // 238654 and its dependencies/dups first.
       0
     };
     
     nsCAutoString headerVal;
     const char *const *name = headers;
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -1206,16 +1206,18 @@ protected:
   nsCOMPtr<nsIContent> mFirstBaseNodeWithHref;
 
 private:
   friend class nsUnblockOnloadEvent;
 
   void PostUnblockOnloadEvent();
   void DoUnblockOnload();
 
+  nsresult InitCSP();
+
   /**
    * See if aDocument is a child of this.  If so, return the frame element in
    * this document that holds currentDoc (or an ancestor).
    */
   already_AddRefed<nsIDOMElement>
     CheckAncestryAndGetFrame(nsIDocument* aDocument) const;
 
   // Just like EnableStyleSheetsForSet, but doesn't check whether
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -1002,16 +1002,18 @@ GK_ATOM(where, "where")
 GK_ATOM(widget, "widget")
 GK_ATOM(width, "width")
 GK_ATOM(window, "window")
 GK_ATOM(headerWindowTarget, "window-target")
 GK_ATOM(withParam, "with-param")
 GK_ATOM(wizard, "wizard")
 GK_ATOM(wrap, "wrap")
 GK_ATOM(headerDNSPrefetchControl,"x-dns-prefetch-control")
+GK_ATOM(headerCSP, "x-content-security-policy")
+GK_ATOM(headerCSPReportOnly, "x-content-security-policy-report-only")
 GK_ATOM(xml, "xml")
 GK_ATOM(xmlns, "xmlns")
 GK_ATOM(xmp, "xmp")
 GK_ATOM(xulcontentsgenerated, "xulcontentsgenerated")
 GK_ATOM(yes, "yes")
 GK_ATOM(z_index, "z-index")
 GK_ATOM(zeroDigit, "zero-digit")
 
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -328,16 +328,20 @@ include $(topsrcdir)/config/rules.mk
 		test_bug475156.html \
 		bug475156.sjs \
 		test_copypaste.html \
 		test_bug503481.html \
 		file_bug503481.sjs \
 		test_bug503481b.html \
 		file_bug503481b_inner.html \
 		test_viewport_scroll.html \
+		test_CSP.html \
+		file_CSP.sjs \
+		file_CSP_main.html \
+		file_CSP_main.js \
 		$(NULL)
 
 # Disabled; see bug 492181
 #		test_plugin_freezing.html
 
 # Disabled for now. Mochitest isn't reliable enough for these.
 # test_bug444546.html \
 # bug444546.sjs \
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_CSP.sjs
@@ -0,0 +1,44 @@
+// SJS file for CSP mochitests
+
+function handleRequest(request, response)
+{
+  var query = {};
+  request.queryString.split('&').forEach(function (val) {
+    var [name, value] = val.split('=');
+    query[name] = unescape(value);
+  });
+
+  var isPreflight = request.method == "OPTIONS";
+
+
+  //avoid confusing cache behaviors
+  response.setHeader("Cache-Control", "no-cache", false);
+
+  if ("main" in query) {
+    var xhr = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]  
+                   .createInstance(Components.interfaces.nsIXMLHttpRequest);
+    //serve the main page with a CSP header!
+    // -- anything served from 'self' (localhost:8888) will be allowed,
+    // -- anything served from other hosts (example.com:80) will be blocked.
+    // -- XHR tests are set up in the file_CSP_main.js file which is sourced.
+    response.setHeader("X-Content-Security-Policy", 
+                       "allow 'self'",
+                       false);
+    xhr.open("GET", "http://localhost:8888/tests/content/base/test/file_CSP_main.html", false);
+    xhr.send(null);
+    if(xhr.status == 200) {
+      response.write(xhr.responseText);
+    }
+  } else {
+    if ("type" in query) {
+      response.setHeader("Content-Type", unescape(query['type']), false);
+    } else {
+      response.setHeader("Content-Type", "text/html", false);
+    }
+
+    if ("content" in query) {
+      response.setHeader("Content-Type", "text/html", false);
+      response.write(unescape(query['content']));
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_CSP_main.html
@@ -0,0 +1,55 @@
+<html>
+  <head>
+    <link rel='stylesheet' type='text/css'
+          href='http://example.org/tests/content/base/test/file_CSP.sjs?testid=style_bad&type=text/css' />
+    <link rel='stylesheet' type='text/css'
+          href='file_CSP.sjs?testid=style_good&type=text/css' />
+
+
+    <style>
+      /* CSS font embedding tests */
+      @font-face {
+        font-family: "arbitrary_good";
+        src: url('file_CSP.sjs?testid=font_good&type=application/octet-stream');
+      }
+      @font-face {
+        font-family: "arbitrary_bad";
+        src: url('http://example.org/tests/content/base/test/file_CSP.sjs?testid=font_bad&type=application/octet-stream');
+      }
+
+      .div_arbitrary_good { font-family: "arbitrary_good"; }
+      .div_arbitrary_bad { font-family: "arbitrary_bad"; }
+    </style>
+  </head>
+  <body>
+    <!-- these should be stopped by CSP.  :) -->
+    <img src="http://example.org/tests/content/base/test/file_CSP.sjs?testid=img_bad&type=img/png" />
+    <audio src="http://example.org/tests/content/base/test/file_CSP.sjs?testid=media_bad&type=audio/vorbis"></audio>
+    <script src='http://example.org/tests/content/base/test/file_CSP.sjs?testid=script_bad&type=text/javascript'></script>
+    <iframe src='http://example.org/tests/content/base/test/file_CSP.sjs?testid=frame_bad&content=FAIL'></iframe>
+    <object width="10" height="10">
+      <param name="movie" value="http://example.org/tests/content/base/test/file_CSP.sjs?testid=object_bad&type=application/x-shockwave-flash">
+      <embed src="http://example.org/tests/content/base/test/file_CSP.sjs?testid=object_bad&type=application/x-shockwave-flash"></embed>
+    </object>
+
+    <!-- these should load ok.  :) -->
+    <img src="file_CSP.sjs?testid=img_good&type=img/png" />
+    <audio src="file_CSP.sjs?testid=media_good&type=audio/vorbis"></audio>
+    <script src='file_CSP.sjs?testid=script_good&type=text/javascript'></script>
+    <iframe src='file_CSP.sjs?testid=frame_good&content=PASS'></iframe>
+
+    <object width="10" height="10">
+      <param name="movie" value="file_CSP.sjs?testid=object_good&type=application/x-shockwave-flash">
+      <embed src="file_CSP.sjs?testid=object_good&type=application/x-shockwave-flash"></embed>
+    </object>
+
+    <!-- XHR tests... they're taken care of in this script, 
+         and since the URI doesn't have any 'testid' values, 
+         it will just be ignored by the test framework.  -->
+    <script src='file_CSP_main.js'></script>
+
+    <!-- Support elements for the @font-face test -->
+    <div class="div_arbitrary_good">arbitrary good</div>
+    <div class="div_arbitrary_bad">arbitrary_bad</div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/file_CSP_main.js
@@ -0,0 +1,16 @@
+// some javascript for the CSP XHR tests
+//
+
+try {
+  var xhr_good = new XMLHttpRequest();
+  var xhr_good_uri ="http://localhost:8888/tests/content/base/test/file_CSP.sjs?testid=xhr_good";
+  xhr_good.open("GET", xhr_good_uri, true);
+  xhr_good.send(null);
+} catch(e) {}
+
+try {
+  var xhr_bad = new XMLHttpRequest();
+  var xhr_bad_uri ="http://example.com/tests/content/base/test/file_CSP.sjs?testid=xhr_bad";
+  xhr_bad.open("GET", xhr_bad_uri, true);
+  xhr_bad.send(null);
+} catch(e) {}
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_CSP.html
@@ -0,0 +1,127 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Content Security Policy Connections</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>        
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+  
+</div>
+
+<iframe style="width:200px;height:200px;" id='cspframe'></iframe>
+<script class="testbody" type="text/javascript">
+
+var path = "/tests/content/base/test/";
+
+// These are test results: -1 means it hasn't run, 
+// true/false is the pass/fail result.
+window.tests = {
+  img_good: -1,
+  img_bad: -1,
+  style_good: -1,
+  style_bad: -1,
+  frame_good: -1,
+  frame_bad: -1,
+  script_good: -1,
+  script_bad: -1,
+  xhr_good: -1,
+  xhr_bad: -1,
+  media_good: -1,
+  media_bad: -1,
+  font_good: -1,
+  font_bad: -1,
+  object_good: -1,
+  object_bad: -1,
+};
+
+
+// This is used to watch the blocked data bounce off CSP and allowed data 
+// get sent out to the wire.
+function examiner() {
+  netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+  var obsvc = Components.classes['@mozilla.org/observer-service;1']
+                        .getService(Components.interfaces.nsIObserverService);
+  obsvc.addObserver(this, "csp-on-violate-policy", false);
+  obsvc.addObserver(this, "http-on-modify-request", false);
+}
+examiner.prototype  = {
+  observe: function(subject, topic, data) {
+    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+    // subject should be an nsURI, and should be either allowed or blocked.
+    if(!subject.QueryInterface)
+      return;
+      
+    var testpat = new RegExp("testid=([a-z0-9_]+)");
+
+    //_good things better be allowed!
+    //_bad things better be stopped!
+
+    if (topic === "http-on-modify-request") {
+      //these things were allowed by CSP
+      var uri = subject.QueryInterface(Components.interfaces.nsIHttpChannel).URI;
+      if (!testpat.test(uri.asciiSpec)) return;
+      var testid = testpat.exec(uri.asciiSpec)[1];
+      window.testResult(testid,
+                        /_good/.test(testid),
+                        uri.asciiSpec + " allowed by csp");
+
+    }
+
+    if(topic === "csp-on-violate-policy") {
+      //these were blocked... record that they were blocked
+      var uri = subject.QueryInterface(Components.interfaces.nsIURI);
+      if (!testpat.test(uri.asciiSpec)) return;
+      var testid = testpat.exec(uri.asciiSpec)[1];
+      window.testResult(testid,
+                        /_bad/.test(testid),
+                        uri.asciiSpec + " blocked by \"" + data + "\"");
+    }
+  },
+
+  // must eventually call this to remove the listener, 
+  // or mochitests might get borked.
+  remove: function() {
+    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+    var obsvc = Components.classes['@mozilla.org/observer-service;1']
+                          .getService(Components.interfaces.nsIObserverService);
+    obsvc.removeObserver(this, "csp-on-violate-policy");
+    obsvc.removeObserver(this, "http-on-modify-request");
+  }
+}
+
+window.examiner = new examiner();
+
+window.testResult = function(testname, result, msg) {
+
+  //test already complete.... forget it... remember the first result.
+  if (window.tests[testname] != -1)
+    return;
+
+  window.tests[testname] = result;
+  is(result, true, testname + ' test: ' + msg);
+
+  // if any test is incomplete, keep waiting
+  for (var v in window.tests)
+    if(tests[v] == -1)
+      return;
+
+  // ... otherwise, finish
+  window.examiner.remove();
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+// save this for last so that our listeners are registered.
+// ... this loads the testbed of good and bad requests.
+document.getElementById('cspframe').src = 'file_CSP.sjs?main=1';
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/unit/test_csputils.js
@@ -0,0 +1,497 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Content Security Policy Data Structures testing code.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation
+ *
+ * Contributor(s):
+ *   Sid Stamm <sid@mozilla.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+//load('CSPUtils.jsm');
+Components.utils.import('resource://gre/modules/CSPUtils.jsm');
+
+// load the HTTP server
+do_load_httpd_js();
+
+var httpServer = new nsHttpServer();
+
+const POLICY_FROM_URI = "allow 'self'; img-src *";
+const POLICY_PORT = 9000;
+const POLICY_URI = "http://localhost:" + POLICY_PORT + "/policy";
+
+// helper to assert that an object or array must have a given key
+function do_check_has_key(foo, key, stack) {
+  if (!stack) 
+    stack = Components.stack.caller;
+
+  var keys = [];
+  for(let k in keys) { keys.push(k); }
+  var text = key + " in [" + keys.join(",") + "]";
+
+  for(var x in foo) {
+    if(x == key) {
+      //succeed
+      ++_passedChecks;
+      dump("TEST-PASS | " + stack.filename + " | [" + stack.name + " : " +
+           stack.lineNumber + "] " + text + "\n");
+      return;
+    }
+  }
+  do_throw(text, stack);
+}
+
+// helper to use .equals on stuff
+function do_check_equivalent(foo, bar, stack) {
+  if (!stack) 
+    stack = Components.stack.caller;
+
+  var text = foo + ".equals(" + bar + ")";
+
+  if(foo.equals && foo.equals(bar)) {
+    ++_passedChecks;
+      dump("TEST-PASS | " + stack.filename + " | [" + stack.name + " : " +
+           stack.lineNumber + "] " + text + "\n");
+      return;
+  }
+  do_throw(text, stack);
+}
+
+var tests = [];
+function test(fcn) {
+  tests.push(fcn);
+}
+
+test(
+  function test_CSPHost_fromstring() {
+    var h;
+
+    h = CSPHost.fromString("*");
+    do_check_neq(null, h); // "* lone wildcard should work"
+
+    h = CSPHost.fromString("foo.bar");
+    do_check_neq(null, h); // "standard tuple failed"
+
+    h = CSPHost.fromString("*.bar");
+    do_check_neq(null, h); // "wildcard failed"
+
+    h = CSPHost.fromString("foo.*.bar");
+    do_check_eq(null, h); // "wildcard in wrong place worked"
+
+    h = CSPHost.fromString("com");
+    do_check_eq(null, h); // "lone symbol should fail"
+
+    h = CSPHost.fromString("f00b4r.com");
+    do_check_neq(null, h); // "Numbers in hosts should work"
+
+    h = CSPHost.fromString("foo-bar.com");
+    do_check_neq(null, h); // "dashes in hosts should work"
+
+    h = CSPHost.fromString("foo!bar.com");
+    do_check_eq(null, h); // "special chars in hosts should fail"
+  });
+
+test(
+  function test_CSPHost_clone() {
+    h = CSPHost.fromString("*.a.b.c");
+    h2 = h.clone();
+    for(var i in h._segments) {
+      // "cloned segments should match"
+      do_check_eq(h._segments[i], h2._segments[i]);
+    }
+  });
+
+test(
+  function test_CSPHost_permits() {
+    var h = CSPHost.fromString("*.b.c");
+    var h2 = CSPHost.fromString("a.b.c");
+    do_check_true( h.permits(h2));       //"CSPHost *.b.c should allow CSPHost a.b.c"
+    do_check_true( h.permits("a.b.c"));  //"CSPHost *.b.c should allow string a.b.c"
+    do_check_false(h.permits("b.c"));    //"CSPHost *.b.c should not allow string b.c"
+    do_check_false(h.permits("a.a.c"));  //"CSPHost *.b.c should not allow string a.a.c"
+    do_check_false(h2.permits(h));       //"CSPHost a.b.c should not allow CSPHost *.b.c"
+    do_check_false(h2.permits("b.c"));   //"CSPHost a.b.c should not allow string b.c"
+    do_check_true( h2.permits("a.b.c")); //"CSPHost a.b.c should allow string a.b.c"
+  });
+
+test(
+    function test_CSPHost_intersectWith() {
+      var h = CSPHost.fromString("*.b.c");
+      //"*.a.b.c ^ *.b.c should be *.a.b.c"
+      do_check_eq("*.a.b.c", h.intersectWith(CSPHost.fromString("*.a.b.c")).toString());
+
+      //"*.b.c ^ *.d.e should not work (null)"
+      do_check_eq(null, h.intersectWith(CSPHost.fromString("*.d.e")));
+    });
+
+///////////////////// Test the Source object //////////////////////
+
+test(
+    function test_CSPSource_fromString() {
+    // can't do these tests because "self" is not defined.
+      //"basic source should not be null.");
+      do_check_neq(null, CSPSource.fromString("a.com"));
+
+      //"ldh characters should all work for host.");
+      do_check_neq(null, CSPSource.fromString("a2-c.com"));
+
+      //"wildcard should work in first token for host.");
+      do_check_neq(null, CSPSource.fromString("*.a.com"));
+
+      //print(" --- Ignore the following two errors if they print ---");
+      //"wildcard should not work in non-first token for host.");
+      do_check_eq(null, CSPSource.fromString("x.*.a.com"));
+
+      //"funny characters (#) should not work for host.");
+      do_check_eq(null, CSPSource.fromString("a#2-c.com"));
+      //print(" --- Stop ignoring errors that print ---\n");
+
+      //"failed to parse host with port.");
+      do_check_neq(null, CSPSource.create("a.com:23"));
+      //"failed to parse host with scheme.");
+      do_check_neq(null, CSPSource.create("https://a.com"));
+      //"failed to parse host with scheme and port.");
+      do_check_neq(null, CSPSource.create("https://a.com:200"));
+    });
+
+test(
+    function test_CSPSource_fromString_withSelf() {
+      var src;
+      src = CSPSource.create("a.com", "https://foobar.com:443");
+      //"src should inherit port *
+      do_check_true(src.permits("https://a.com:443"));
+      //"src should inherit and require https scheme
+      do_check_false(src.permits("http://a.com"));
+      //"src should inherit scheme 'https'"
+      do_check_true(src.permits("https://a.com"));
+      
+      src = CSPSource.create("http://a.com", "https://foobar.com:443");
+      //"src should inherit and require http scheme"
+      do_check_false(src.permits("https://a.com"));
+      //"src should inherit scheme 'http'"
+      do_check_true(src.permits("http://a.com"));
+      //"src should inherit port and scheme from parent"
+      //"src should inherit default port for 'http'"
+      do_check_true(src.permits("http://a.com:80"));
+      
+      src = CSPSource.create("'self'", "https://foobar.com:443");
+      //"src should inherit port *
+      do_check_true(src.permits("https://foobar.com:443"));
+      //"src should inherit and require https scheme
+      do_check_false(src.permits("http://foobar.com"));
+      //"src should inherit scheme 'https'"
+      do_check_true(src.permits("https://foobar.com"));
+      //"src should reject other hosts"
+      do_check_false(src.permits("https://a.com"));
+    });
+
+///////////////////// Test the source list //////////////////////
+
+test(
+    function test_CSPSourceList_fromString() {
+      var sd = CSPSourceList.fromString("'none'");
+      //"'none' -- should parse"
+      do_check_neq(null,sd);
+      // "'none' should be a zero-length list"
+      do_check_eq(0, sd._sources.length);
+      do_check_true(sd.isNone());
+
+      sd = CSPSourceList.fromString("*");
+      //"'*' should be a zero-length list"
+      do_check_eq(0, sd._sources.length);
+
+      //print(" --- Ignore the following three errors if they print ---");
+      //"funny char in host"
+      do_check_true(CSPSourceList.fromString("f!oo.bar").isNone());
+      //"funny char in scheme"
+      do_check_true(CSPSourceList.fromString("ht!ps://f-oo.bar").isNone());
+      //"funny char in port"
+      do_check_true(CSPSourceList.fromString("https://f-oo.bar:3f").isNone());
+      //print(" --- Stop ignoring errors that print ---\n");
+    });
+
+test(
+    function test_CSPSourceList_fromString_twohost() {
+      var str = "foo.bar:21 https://ras.bar";
+      var parsed = "foo.bar:21 https://ras.bar";
+      var sd = CSPSourceList.fromString(str, "http://self.com:80");
+      //"two-host list should parse"
+      do_check_neq(null,sd);
+      //"two-host list should parse to two hosts"
+      do_check_eq(2, sd._sources.length);
+      //"two-host list should contain original data"
+      do_check_eq(parsed, sd.toString());
+    });
+
+test(
+    function test_CSPSourceList_permits() {
+      var nullSourceList = CSPSourceList.fromString("'none'");
+      var simpleSourceList = CSPSourceList.fromString("a.com", "http://self.com");
+      var doubleSourceList = CSPSourceList.fromString("https://foo.com http://bar.com:88",
+                                                      "http://self.com:88");
+      var allSourceList = CSPSourceList.fromString("*");
+
+      //'none' should permit none."
+      do_check_false( nullSourceList.permits("http://a.com"));
+      //a.com should permit a.com"
+      do_check_true( simpleSourceList.permits("http://a.com"));
+      //wrong host"
+      do_check_false( simpleSourceList.permits("http://b.com"));
+      //double list permits http://bar.com:88"
+      do_check_true( doubleSourceList.permits("http://bar.com:88"));
+      //double list permits https://bar.com:88"
+      do_check_false( doubleSourceList.permits("https://bar.com:88"));
+      //double list does not permit http://bar.com:443"
+      do_check_false( doubleSourceList.permits("http://bar.com:443"));
+      //"double list permits https://foo.com:88" (should not inherit port)
+      do_check_false( doubleSourceList.permits("https://foo.com:88"));
+      //"double list does not permit foo.com on http"
+      do_check_false( doubleSourceList.permits("http://foo.com"));
+
+      //"* does not permit specific host"
+      do_check_true( allSourceList.permits("http://x.com:23"));
+      //"* does not permit a long host with no port"
+      do_check_true( allSourceList.permits("http://a.b.c.d.e.f.g.h.i.j.k.l.x.com"));
+
+    });
+
+test(
+    function test_CSPSourceList_intersect() {
+      // for this test, 'self' values are irrelevant
+      // policy a /\ policy b intersects policies, not context (where 'self'
+      // values come into play)
+      var nullSourceList = CSPSourceList.fromString("'none'");
+      var simpleSourceList = CSPSourceList.fromString("a.com");
+      var doubleSourceList = CSPSourceList.fromString("https://foo.com http://bar.com:88");
+      var singleFooSourceList = CSPSourceList.fromString("https://foo.com");
+      var allSourceList = CSPSourceList.fromString("*");
+
+      //"Intersection of one source with 'none' source list should be none.");
+      do_check_true(nullSourceList.intersectWith(simpleSourceList).isNone());
+      //"Intersection of two sources with 'none' source list should be none.");
+      do_check_true(nullSourceList.intersectWith(doubleSourceList).isNone());
+      //"Intersection of '*' with 'none' source list should be none.");
+      do_check_true(nullSourceList.intersectWith(allSourceList).isNone());
+
+      //"Intersection of one source with '*' source list should be one source.");
+      do_check_equivalent(allSourceList.intersectWith(simpleSourceList),
+                          simpleSourceList);
+      //"Intersection of two sources with '*' source list should be two sources.");
+      do_check_equivalent(allSourceList.intersectWith(doubleSourceList),
+                          doubleSourceList);
+
+      //"Non-overlapping source lists should intersect to 'none'");
+      do_check_true(simpleSourceList.intersectWith(doubleSourceList).isNone());
+
+      //"subset and superset should intersect to subset.");
+      do_check_equivalent(singleFooSourceList,
+                          doubleSourceList.intersectWith(singleFooSourceList));
+
+      //TODO: write more tests?
+
+    });
+
+///////////////////// Test the Whole CSP rep object //////////////////////
+
+test(
+    function test_CSPRep_fromString() {
+
+      // check default init
+      //ASSERT(!(new CSPRep())._isInitialized, "Uninitialized rep thinks it is.")
+
+      var cspr;
+      var cspr_allowval;
+
+      // check default policy "allow *"
+      cspr = CSPRep.fromString("allow *", "http://self.com:80");
+      //"ALLOW directive is missing when specified in fromString"
+      do_check_has_key(cspr._directives, CSPRep.SRC_DIRECTIVES.ALLOW);
+
+      // ... and check that the other directives were auto-filled with the
+      // ALLOW one.
+      var SD = CSPRep.SRC_DIRECTIVES;
+      cspr_allowval = cspr._directives[SD.ALLOW];
+      for(var d in CSPRep.SRC_DIRECTIVES) {
+        //"Missing key " + d
+        do_check_has_key(cspr._directives, SD[d]);
+        //"Implicit directive " + d + " has non-allow value."
+        do_check_eq(cspr._directives[SD[d]].toString(), cspr_allowval.toString());
+      }
+    });
+
+
+test(
+    function test_CSPRep_fromString_oneDir() {
+
+      var cspr;
+      var SD = CSPRep.SRC_DIRECTIVES;
+      var DEFAULTS = [SD.STYLE_SRC, SD.MEDIA_SRC, SD.IMG_SRC,
+                      SD.FRAME_ANCESTORS, SD.FRAME_SRC];
+
+      // check one-directive policies
+      cspr = CSPRep.fromString("allow bar.com; script-src https://foo.com", 
+                               "http://self.com");
+
+      for(var x in DEFAULTS) {
+        //DEFAULTS[x] + " does not use default rule."
+        do_check_false(cspr.permits("http://bar.com:22", DEFAULTS[x]));
+        //DEFAULTS[x] + " does not use default rule."
+        do_check_true(cspr.permits("http://bar.com:80", DEFAULTS[x]));
+        //DEFAULTS[x] + " does not use default rule."
+        do_check_false(cspr.permits("https://foo.com:400", DEFAULTS[x]));
+        //DEFAULTS[x] + " does not use default rule."
+        do_check_false(cspr.permits("https://foo.com", DEFAULTS[x]));
+      }
+      //"script-src false positive in policy.
+      do_check_false(cspr.permits("http://bar.com:22", SD.SCRIPT_SRC));
+      //"script-src false negative in policy.
+      do_check_true(cspr.permits("https://foo.com:443", SD.SCRIPT_SRC));
+    });
+
+test(
+    function test_CSPRep_fromString_twodir() {
+      var cspr;
+      var SD = CSPRep.SRC_DIRECTIVES;
+      var DEFAULTS = [SD.STYLE_SRC, SD.MEDIA_SRC, SD.FRAME_ANCESTORS, SD.FRAME_SRC];
+
+      // check two-directive policies
+      var polstr = "allow allow.com; "
+                  + "script-src https://foo.com; "
+                  + "img-src bar.com:*";
+      cspr = CSPRep.fromString(polstr, "http://self.com");
+
+      for(var x in DEFAULTS) {
+        do_check_true(cspr.permits("http://allow.com", DEFAULTS[x]));
+        //DEFAULTS[x] + " does not use default rule.
+        do_check_false(cspr.permits("https://foo.com:400", DEFAULTS[x]));
+        //DEFAULTS[x] + " does not use default rule.
+        do_check_false(cspr.permits("http://bar.com:400", DEFAULTS[x]));
+        //DEFAULTS[x] + " does not use default rule.
+      }
+      //"img-src does not use default rule.
+      do_check_false(cspr.permits("http://allow.com:22", SD.IMG_SRC));
+      //"img-src does not use default rule.
+      do_check_false(cspr.permits("https://foo.com:400", SD.IMG_SRC));
+      //"img-src does not use default rule.
+      do_check_true(cspr.permits("http://bar.com:88", SD.IMG_SRC));
+
+      //"script-src does not use default rule.
+      do_check_false(cspr.permits("http://allow.com:22", SD.SCRIPT_SRC));
+      //"script-src does not use default rule.
+      do_check_true(cspr.permits("https://foo.com:443", SD.SCRIPT_SRC));
+      //"script-src does not use default rule.
+      do_check_false(cspr.permits("http://bar.com:400", SD.SCRIPT_SRC));
+    });
+
+test(function test_CSPRep_fromString_withself() {
+      var cspr;
+      var SD = CSPRep.SRC_DIRECTIVES;
+      var self = "https://self.com:34";
+
+      // check one-directive policies
+      cspr = CSPRep.fromString("allow 'self'; script-src 'self' https://*:*",
+                              self);
+      //"img-src does not enforce default rule, 'self'.
+      do_check_false(cspr.permits("https://foo.com:400", SD.IMG_SRC));
+      //"img-src does not allow self
+      CSPdebug(cspr);
+      do_check_true(cspr.permits(self, SD.IMG_SRC));
+      //"script-src is too relaxed
+      do_check_false(cspr.permits("http://evil.com", SD.SCRIPT_SRC));
+      //"script-src should allow self
+      do_check_true(cspr.permits(self, SD.SCRIPT_SRC));
+      //"script-src is too strict on host/port
+      do_check_true(cspr.permits("https://evil.com:100", SD.SCRIPT_SRC));
+     });
+
+///////////////////// TEST POLICY_URI //////////////////////
+test(function test_CSPRep_fromPolicyURI() {
+        var cspr;
+        var SD = CSPRep.SRC_DIRECTIVES;
+        var self = "http://localhost:" + POLICY_PORT;
+
+        cspr = CSPRep.fromString("policy-uri " + POLICY_URI, self);
+        cspr_static = CSPRep.fromString(POLICY_FROM_URI, self);
+
+        //"policy-uri failed to load"
+        do_check_neq(null,cspr);
+
+        // other directives inherit self
+        for(var i in SD) {
+          //SD[i] + " parsed wrong from policy uri"
+          do_check_equivalent(cspr._directives[SD[i]],
+                              cspr_static._directives[SD[i]]);
+        }
+    });
+/*
+
+test(function test_CSPRep_fromPolicyURI_failswhenmixed() {
+        var cspr;
+        var self = "http://localhost:" + POLICY_PORT;
+        var closed_policy = CSPRep.fromString("allow 'none'");
+        var my_uri_policy = "policy-uri " + POLICY_URI;
+
+        //print(" --- Ignore the following two errors if they print ---");
+        cspr = CSPRep.fromString("allow *; " + my_uri_policy, self);
+
+        //"Parsing should fail when 'policy-uri' is mixed with allow directive"
+        do_check_equivalent(cspr, closed_policy);
+        cspr = CSPRep.fromString("img-src 'self'; " + my_uri_policy, self);
+
+        //"Parsing should fail when 'policy-uri' is mixed with other directives"
+        do_check_equivalent(cspr, closed_policy);
+        //print(" --- Stop ignoring errors that print ---\n");
+
+    });
+*/
+
+// TODO: test reporting
+// TODO: test refinements (?)
+// TODO: test 'eval' and 'inline' keywords
+
+function run_test() {
+  function policyresponder(request,response) {
+    response.setStatusLine(request.httpVersion, 200, "OK");
+    response.setHeader("Content-Type", "text/csp", false);
+    response.bodyOutputStream.write(POLICY_FROM_URI, POLICY_FROM_URI.length);
+  }
+  //server.registerDirectory("/", nsILocalFileForBasePath);
+  httpServer.registerPathHandler("/policy", policyresponder);
+  httpServer.start(POLICY_PORT);
+
+  for(let i in tests) {
+    tests[i]();
+  }
+
+  //teardown
+  httpServer.stop(function() { });
+  do_test_finished();
+}
+
+
+
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -263,16 +263,17 @@ static nsresult Initialize(nsIModule* aS
 static void Shutdown();
 
 #ifdef MOZ_XTF
 #include "nsIXTFService.h"
 #include "nsIXMLContentBuilder.h"
 #endif
 
 #include "nsGeolocation.h"
+#include "nsCSPService.h"
 
 // Transformiix
 /* {0C351177-0159-4500-86B0-A219DFDE4258} */
 #define TRANSFORMIIX_XPATH1_SCHEME_CID \
 { 0xc351177, 0x159, 0x4500, { 0x86, 0xb0, 0xa2, 0x19, 0xdf, 0xde, 0x42, 0x58 } }
 
 /* 5d5d92cd-6bf8-11d9-bf4a-000a95dc234c */
 #define TRANSFORMIIX_NODESET_CID \
@@ -843,16 +844,70 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsBaseDOM
 
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGeolocation, Init)
 
 #define NS_GEOLOCATION_SERVICE_CID \
   { 0x404d02a, 0x1CA, 0xAAAB, { 0x47, 0x62, 0x94, 0x4b, 0x1b, 0xf2, 0xf7, 0xb5 } }
 
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsGeolocationService, nsGeolocationService::GetGeolocationService)
 
+static NS_METHOD
+CSPServiceRegistration(nsIComponentManager *aCompMgr,
+                       nsIFile *aPath,
+                       const char *registryLocation,
+                       const char *componentType,
+                       const nsModuleComponentInfo *info)
+{
+  nsresult rv;
+  nsCOMPtr<nsIServiceManager> servman = do_QueryInterface((nsISupports*)aCompMgr, &rv);
+  if (NS_FAILED(rv))
+    return rv;
+
+  nsCOMPtr<nsICategoryManager> catman;
+  rv = servman->GetServiceByContractID(NS_CATEGORYMANAGER_CONTRACTID,
+                                       NS_GET_IID(nsICategoryManager),
+                                       getter_AddRefs(catman));
+  if (NS_FAILED(rv))
+    return rv;
+  
+  nsXPIDLCString previous;
+  rv = catman->AddCategoryEntry("content-policy",
+                                "CSPService",
+                                CSPSERVICE_CONTRACTID,
+                                PR_TRUE,
+                                PR_TRUE,
+                                getter_Copies(previous));
+  return rv;
+}
+
+static NS_METHOD
+CSPServiceUnregistration(nsIComponentManager *aCompMgr,
+                         nsIFile *aPath,
+                         const char *registryLocation,
+                         const nsModuleComponentInfo *info){
+  nsresult rv;
+
+  nsCOMPtr<nsIServiceManager> servman = do_QueryInterface((nsISupports*)aCompMgr, &rv);
+  if (NS_FAILED(rv)) return rv;
+
+  nsCOMPtr<nsICategoryManager> catman;
+  rv = servman->GetServiceByContractID(NS_CATEGORYMANAGER_CONTRACTID,
+                                       NS_GET_IID(nsICategoryManager),
+                                       getter_AddRefs(catman));
+  if (NS_FAILED(rv)) return rv;
+
+  rv = catman->DeleteCategoryEntry("content-policy",
+                                   "CSPService",
+                                   PR_TRUE);
+
+  return rv;
+}
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(CSPService)
+
 // The list of components we register
 static const nsModuleComponentInfo gComponents[] = {
 #ifdef DEBUG
   { "Frame utility",
     NS_FRAME_UTIL_CID,
     nsnull,
     CreateNewFrameUtil },
   { "Layout debugger",
@@ -1448,16 +1503,22 @@ static const nsModuleComponentInfo gComp
       "@mozilla.org/geolocation;1",
       nsGeolocationConstructor },
 
     { "Focus Manager",
       NS_FOCUSMANAGER_CID,
       "@mozilla.org/focus-manager;1",
       CreateFocusManager },
 
+    { "Content Security Policy Service",
+      CSPSERVICE_CID,
+      CSPSERVICE_CONTRACTID,
+      CSPServiceConstructor,
+      CSPServiceRegistration,
+      CSPServiceUnregistration },
 
     { "Event Listener Service",
       NS_EVENTLISTENERSERVICE_CID,
       NS_EVENTLISTENERSERVICE_CONTRACTID,
       CreateEventListenerService }
 };
 
 NS_IMPL_NSGETMODULE_WITH_CTOR(nsLayoutModule, gComponents, Initialize)
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -945,16 +945,18 @@ pref("images.dither", "auto");
 pref("security.directory",              "");
 
 pref("signed.applets.codebase_principal_support", false);
 pref("security.checkloaduri", true);
 pref("security.xpconnect.plugin.unrestricted", true);
 // security-sensitive dialogs should delay button enabling. In milliseconds.
 pref("security.dialog_enable_delay", 2000);
 
+pref("security.csp.enable", true);
+
 // Modifier key prefs: default to Windows settings,
 // menu access key = alt, accelerator key = control.
 // Use 17 for Ctrl, 18 for Alt, 224 for Meta, 0 for none. Mac settings in macprefs.js
 pref("ui.key.accelKey", 17);
 pref("ui.key.menuAccessKey", 18);
 pref("ui.key.generalAccessKey", -1);
 
 // If generalAccessKey is -1, use the following two prefs instead.