Bug 515437 CSP connection code, r=jst,dveditz sr=jst
authorSid Stamm <sstamm@mozilla.com>
Fri, 22 Jan 2010 13:38:21 -0800
changeset 37418 7229621a1886329d61150f1668878a4dde0e58e9
parent 37417 04d2879af6993575d7f1ef255dfee983741095b3
child 37419 840d02509d39153e0b2cf68f5c2e83ab033ad344
push idunknown
push userunknown
push dateunknown
reviewersjst, dveditz, jst
bugs515437
milestone1.9.3a1pre
Bug 515437 CSP connection code, r=jst,dveditz sr=jst
caps/idl/nsIPrincipal.idl
caps/include/nsPrincipal.h
caps/src/nsNullPrincipal.cpp
caps/src/nsPrincipal.cpp
caps/src/nsSystemPrincipal.cpp
content/base/src/Makefile.in
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
layout/build/nsLayoutModule.cpp
--- 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 nsIContentSecurityPolicy;
 
 [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 nsIContentSecurityPolicy 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<nsIContentSecurityPolicy> 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(nsIContentSecurityPolicy** aCsp)
+{
+  // CSP on a null principal makes no sense
+  *aCsp = nsnull;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNullPrincipal::SetCsp(nsIContentSecurityPolicy* 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 "nsIContentSecurityPolicy.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(nsIContentSecurityPolicy** aCsp)
+{
+  NS_IF_ADDREF(*aCsp = mCSP);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrincipal::SetCsp(nsIContentSecurityPolicy* 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(nsIContentSecurityPolicy** aCsp)
+{
+  *aCsp = nsnull;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSystemPrincipal::SetCsp(nsIContentSecurityPolicy* 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)
--- 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 \
new file mode 100644
--- /dev/null
+++ b/content/base/src/nsCSPService.cpp
@@ -0,0 +1,198 @@
+/* -*- 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 "nsIContentSecurityPolicy.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<nsINode> node(do_QueryInterface(aRequestContext));
+    nsCOMPtr<nsIPrincipal> principal;
+    nsCOMPtr<nsIContentSecurityPolicy> csp;
+    if (node) {
+        principal = node->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 nsINode 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<nsINode> node(do_QueryInterface(aRequestContext));
+    nsCOMPtr<nsIPrincipal> principal;
+    nsCOMPtr<nsIContentSecurityPolicy> csp;
+    if (node) {
+        principal = node->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 nsINode 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 "nsIContentSecurityPolicy.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<nsIContentSecurityPolicy> 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();
@@ -6643,16 +6759,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
@@ -1208,16 +1208,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"> </img>
+    <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>
--- 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)