Bug 802905 - Create custom Content Type for CSP Reports. r=sstamm, sr=bz
authorTanvi Vyas <tvyas@mozilla.com>
Tue, 20 Nov 2012 20:28:34 -0500
changeset 113853 0f76932d28c58881dd87455b031b71e90770c7b1
parent 113852 0b37a6293086382db3ad3e95e0cb4b8d3380c471
child 113854 dfa9f6c4c60820b8f7e86b5a4a505119bef228fc
push id23891
push useremorley@mozilla.com
push dateWed, 21 Nov 2012 15:30:36 +0000
treeherdermozilla-central@905492e644e3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssstamm, bz
bugs802905
milestone20.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 802905 - Create custom Content Type for CSP Reports. r=sstamm, sr=bz
content/base/public/nsContentPolicyUtils.h
content/base/public/nsIContentPolicy.idl
content/base/src/contentSecurityPolicy.js
extensions/permissions/nsContentBlocker.cpp
--- a/content/base/public/nsContentPolicyUtils.h
+++ b/content/base/public/nsContentPolicyUtils.h
@@ -102,16 +102,17 @@ NS_CP_ContentTypeName(uint32_t contentTy
     CASE_RETURN( TYPE_XBL               );
     CASE_RETURN( TYPE_PING              );
     CASE_RETURN( TYPE_XMLHTTPREQUEST    );
     CASE_RETURN( TYPE_OBJECT_SUBREQUEST );
     CASE_RETURN( TYPE_DTD               );
     CASE_RETURN( TYPE_FONT              );
     CASE_RETURN( TYPE_MEDIA             );
     CASE_RETURN( TYPE_WEBSOCKET         );
+    CASE_RETURN( TYPE_CSP_REPORT        );
    default:
     return "<Unknown Type>";
   }
 }
 
 #endif // defined(PR_LOGGING)
 
 #undef CASE_RETURN
--- a/content/base/public/nsIContentPolicy.idl
+++ b/content/base/public/nsIContentPolicy.idl
@@ -6,116 +6,149 @@
 
 #include "nsISupports.idl"
 #include "nsIPrincipal.idl"
 
 interface nsIURI;
 interface nsIDOMNode;
 
 /**
+ * The type of nsIContentPolicy::TYPE_*
+ */
+typedef unsigned long nsContentPolicyType;
+
+/**
  * Interface for content policy mechanism.  Implementations of this
  * interface can be used to control loading of various types of out-of-line
  * content, or processing of certain types of in-line content.
  *
  * WARNING: do not block the caller from shouldLoad or shouldProcess (e.g.,
  * by launching a dialog to prompt the user for something).
  */
 
-[scriptable,uuid(e590e74f-bac7-4876-8c58-54dde92befb2)]
+[scriptable,uuid(e48e3024-f302-4a16-b8b6-2034d3a4b279)]
 interface nsIContentPolicy : nsISupports
 {
-  const unsigned long TYPE_OTHER       = 1;
+  /**
+   * Gecko/Firefox developers: Do not use TYPE_OTHER under any circumstances.
+   *
+   * Extension developers: Whenever it is reasonable, use one of the existing
+   * content types. If none of the existing content types are right for
+   * something you are doing, file a bug in the Core/DOM component that
+   * includes a patch that adds your new content type to the end of the list of
+   * TYPE_* constants here. But, don't start using your new content type until
+   * your patch has been accepted, because it will be uncertain what exact
+   * value and name your new content type will have; in that interim period,
+   * use TYPE_OTHER. In your patch, document your new content type in the style
+   * of the existing ones. In the bug you file, provide a more detailed
+   * description of the new type of content you want Gecko to support, so that
+   * the existing implementations of nsIContentPolicy can be properly modified
+   * to deal with that new type of content.
+   *
+   * Implementations of nsIContentPolicy should treat this the same way they
+   * treat unknown types, because existing users of TYPE_OTHER may be converted
+   * to use new content types.
+   */
+  const nsContentPolicyType TYPE_OTHER = 1;
 
   /**
    * Indicates an executable script (such as JavaScript).
    */
-  const unsigned long TYPE_SCRIPT      = 2;
+  const nsContentPolicyType TYPE_SCRIPT = 2;
 
   /**
    * Indicates an image (e.g., IMG elements).
    */
-  const unsigned long TYPE_IMAGE       = 3;
+  const nsContentPolicyType TYPE_IMAGE = 3;
 
   /**
    * Indicates a stylesheet (e.g., STYLE elements).
    */
-  const unsigned long TYPE_STYLESHEET  = 4;
+  const nsContentPolicyType TYPE_STYLESHEET = 4;
 
   /**
    * Indicates a generic object (plugin-handled content typically falls under
    * this category).
    */
-  const unsigned long TYPE_OBJECT      = 5;
+  const nsContentPolicyType TYPE_OBJECT = 5;
 
   /**
    * Indicates a document at the top-level (i.e., in a browser).
    */
-  const unsigned long TYPE_DOCUMENT    = 6;
+  const nsContentPolicyType TYPE_DOCUMENT = 6;
 
   /**
    * Indicates a document contained within another document (e.g., IFRAMEs,
    * FRAMES, and OBJECTs).
    */
-  const unsigned long TYPE_SUBDOCUMENT = 7;
+  const nsContentPolicyType TYPE_SUBDOCUMENT = 7;
 
   /**
    * Indicates a timed refresh.
    *
    * shouldLoad will never get this, because it does not represent content
    * to be loaded (the actual load triggered by the refresh will go through
    * shouldLoad as expected).
    *
    * shouldProcess will get this for, e.g., META Refresh elements and HTTP
    * Refresh headers.
    */
-  const unsigned long TYPE_REFRESH     = 8;
+  const nsContentPolicyType TYPE_REFRESH = 8;
 
   /**
    * Indicates an XBL binding request, triggered either by -moz-binding CSS
    * property or Document.addBinding method.
    */
-  const unsigned long TYPE_XBL         = 9;
+  const nsContentPolicyType TYPE_XBL = 9;
 
   /**
    * Indicates a ping triggered by a click on <A PING="..."> element.
    */
-  const unsigned long TYPE_PING        = 10;
+  const nsContentPolicyType TYPE_PING = 10;
 
   /**
    * Indicates an XMLHttpRequest. Also used for document.load and for EventSource.
    */
-  const unsigned long TYPE_XMLHTTPREQUEST = 11;
-  const unsigned long TYPE_DATAREQUEST    = 11; // alias
+  const nsContentPolicyType TYPE_XMLHTTPREQUEST = 11;
+  const nsContentPolicyType TYPE_DATAREQUEST    = 11; // alias
 
   /**
    * Indicates a request by a plugin.
    */
-  const unsigned long TYPE_OBJECT_SUBREQUEST = 12;
+  const nsContentPolicyType TYPE_OBJECT_SUBREQUEST = 12;
 
   /**
    * Indicates a DTD loaded by an XML document.
    */
-  const unsigned long TYPE_DTD = 13;
+  const nsContentPolicyType TYPE_DTD = 13;
 
   /**
    * Indicates a font loaded via @font-face rule.
    */
-  const unsigned long TYPE_FONT = 14;
+  const nsContentPolicyType TYPE_FONT = 14;
 
   /**
    * Indicates a video or audio load.
    */
-  const unsigned long TYPE_MEDIA = 15;  
+  const nsContentPolicyType TYPE_MEDIA = 15;
 
   /**
    * Indicates a WebSocket load.
    */
-  const unsigned long TYPE_WEBSOCKET = 16;
+  const nsContentPolicyType TYPE_WEBSOCKET = 16;
 
-  /* Please update nsContentBlocker when adding new content types. */
+  /**
+   * Indicates a Content Security Policy report.
+   */
+  const nsContentPolicyType TYPE_CSP_REPORT = 17;
+
+  /* When adding new content types, please update nsContentBlocker,
+   * NS_CP_ContentTypeName, contentScurityPolicy.js, all nsIContentPolicy
+   * implementations, and other things that are not listed here that are
+   * related to nsIContentPolicy. */
 
   //////////////////////////////////////////////////////////////////////
 
   /**
    * Returned from shouldLoad or shouldProcess if the load or process request
    * is rejected based on details of the request.
    */
   const short REJECT_REQUEST = -1;
@@ -130,17 +163,17 @@ interface nsIContentPolicy : nsISupports
   const short REJECT_TYPE = -2;
 
   /**
    * Returned from shouldLoad or shouldProcess if the load/process is rejected
    * based on the server it is hosted on or requested from (aContentLocation or
    * aRequestOrigin), e.g., if you block an IMAGE because it is served from
    * goatse.cx (even if you don't necessarily block other types from that
    * server/domain).
-   * 
+   *
    * NOTE that it is not meant to stop future requests for this server--only the
    * current request.
    */
   const short REJECT_SERVER = -3;
 
   /**
    * Returned from shouldLoad or shouldProcess if the load/process is rejected
    * based on some other criteria. Mozilla callers will handle this like
@@ -208,17 +241,17 @@ interface nsIContentPolicy : nsISupports
    * 5)  [JavaScript implementations only] Access properties of any sort on any
    *     object without using XPCNativeWrapper (either explicitly or
    *     implicitly).  Due to various DOM0 things, this leads to item 4.
    * If you do any of these things in your shouldLoad implementation, expect
    * unpredictable behavior, possibly including crashes, content not showing
    * up, content showing up doubled, etc.  If you need to do any of the things
    * above, do them off timeout or event.
    */
-  short shouldLoad(in unsigned long aContentType,
+  short shouldLoad(in nsContentPolicyType aContentType,
                    in nsIURI        aContentLocation,
                    in nsIURI        aRequestOrigin,
                    in nsISupports   aContext,
                    in ACString      aMimeTypeGuess,
                    in nsISupports   aExtra,
                    [optional] in nsIPrincipal  aRequestPrincipal);
 
   /**
@@ -251,17 +284,17 @@ interface nsIContentPolicy : nsISupports
    *                          callers to pass extra data to callees.
    *
    * @return ACCEPT or REJECT_*
    *
    * @note shouldProcess can be called while the DOM and layout of the document
    * involved is in an inconsistent state.  See the note on shouldLoad to see
    * what this means for implementors of this method.
    */
-  short shouldProcess(in unsigned long aContentType,
+  short shouldProcess(in nsContentPolicyType aContentType,
                       in nsIURI        aContentLocation,
                       in nsIURI        aRequestOrigin,
                       in nsISupports   aContext,
                       in ACString      aMimeType,
                       in nsISupports   aExtra,
                       [optional] in nsIPrincipal  aRequestPrincipal);
 
 };
--- a/content/base/src/contentSecurityPolicy.js
+++ b/content/base/src/contentSecurityPolicy.js
@@ -32,16 +32,18 @@ function ContentSecurityPolicy() {
   this._reportOnlyMode = false;
   this._policy = CSPRep.fromString("default-src *");
 
   // default options "wide open" since this policy will be intersected soon
   this._policy._allowInlineScripts = true;
   this._policy._allowEval = true;
 
   this._request = "";
+  this._requestOrigin = "";
+  this._requestPrincipal = "";
   this._referrer = "";
   this._docRequest = null;
   CSPdebug("CSP POLICY INITED TO 'default-src *'");
 }
 
 /*
  * Set up mappings from nsIContentPolicy content types to CSP directives.
  */
@@ -68,16 +70,18 @@ function ContentSecurityPolicy() {
   csp._MAPPINGS[cp.TYPE_OBJECT]            = cspr_sd.OBJECT_SRC;
   csp._MAPPINGS[cp.TYPE_OBJECT_SUBREQUEST] = 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;
   csp._MAPPINGS[cp.TYPE_WEBSOCKET]         = cspr_sd.XHR_SRC;
 
+  /* CSP cannot block CSP reports */
+  csp._MAPPINGS[cp.TYPE_CSP_REPORT]        = null;
 
   /* These must go through the catch-all */
   csp._MAPPINGS[cp.TYPE_XBL]               = cspr_sd.DEFAULT_SRC;
   csp._MAPPINGS[cp.TYPE_PING]              = cspr_sd.DEFAULT_SRC;
   csp._MAPPINGS[cp.TYPE_DTD]               = cspr_sd.DEFAULT_SRC;
 }
 
 ContentSecurityPolicy.prototype = {
@@ -164,16 +168,21 @@ ContentSecurityPolicy.prototype = {
 
     // Save the docRequest for fetching a policy-uri
     this._docRequest = aChannel;
 
     // save the document URI (minus <fragment>) and referrer for reporting
     let uri = aChannel.URI.cloneIgnoringRef();
     uri.userPass = '';
     this._request = uri.asciiSpec;
+    this._requestOrigin = uri;
+
+    //store a reference to the principal, that can later be used in shouldLoad
+    this._requestPrincipal = Components.classes["@mozilla.org/scriptsecuritymanager;1"].
+    getService(Components.interfaces.nsIScriptSecurityManager).getChannelPrincipal(aChannel);
 
     if (aChannel.referrer) {
       let referrer = aChannel.referrer.cloneIgnoringRef();
       referrer.userPass = '';
       this._referrer = referrer.asciiSpec;
     }
   },
 
@@ -197,26 +206,26 @@ ContentSecurityPolicy.prototype = {
       selfURI = selfURI.innermostURI;
     }
 
     // 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
-    // Note that we pass the full URI since when it's parsed as 'self' to construct a 
-    // CSPSource only the scheme, host, and port are kept. 
+    // Note that we pass the full URI since when it's parsed as 'self' to construct a
+    // CSPSource only the scheme, host, and port are kept.
     var newpolicy = CSPRep.fromString(aPolicy,
 				      selfURI,
                                       this._docRequest,
                                       this);
 
     // (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.
    */
@@ -320,18 +329,19 @@ ContentSecurityPolicy.prototype = {
             chan.requestMethod = "POST";
           } catch(e) {} // throws only if chan is not an nsIHttpChannel.
 
           // check with the content policy service to see if we're allowed to
           // send this request.
           try {
             var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"]
                                   .getService(Ci.nsIContentPolicy);
-            if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_OTHER,
-                                         chan.URI, null, null, null, null)
+            if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_CSP_REPORT,
+                                         chan.URI, this._requestOrigin,
+                                         null, null, null, this._requestPrincipal)
                 != Ci.nsIContentPolicy.ACCEPT) {
               continue; // skip unauthorized URIs
             }
           } catch(e) {
             continue; // refuse to load if we can't do a security check.
           }
 
           //send data (and set up error notifications)
@@ -373,17 +383,17 @@ ContentSecurityPolicy.prototype = {
       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];
@@ -401,21 +411,21 @@ ContentSecurityPolicy.prototype = {
   },
 
   /**
    * 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, 
+  function csp_shouldLoad(aContentType,
+                          aContentLocation,
+                          aRequestOrigin,
+                          aContext,
+                          aMimeTypeGuess,
                           aOriginalUri) {
 
     // don't filter chrome stuff
     if (aContentLocation.scheme === 'chrome' ||
         aContentLocation.scheme === 'resource') {
       return Ci.nsIContentPolicy.ACCEPT;
     }
 
@@ -425,40 +435,40 @@ ContentSecurityPolicy.prototype = {
     var cspContext = ContentSecurityPolicy._MAPPINGS[aContentType];
 
     // 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 source = aContentLocation.scheme + "://" + aContentLocation.hostPort;
     var res = this._policy.permits(aContentLocation, cspContext)
-              ? Ci.nsIContentPolicy.ACCEPT 
+              ? 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) { 
+    if (res != Ci.nsIContentPolicy.ACCEPT) {
       CSPdebug("blocking request for " + aContentLocation.asciiSpec);
       try {
         let directive = this._policy._directives[cspContext];
         let violatedPolicy = (directive._isImplicit
                                 ? 'default-src' : cspContext)
                                 + ' ' + directive.toString();
         this._asyncReportViolation(aContentLocation, aOriginalUri, 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
--- a/extensions/permissions/nsContentBlocker.cpp
+++ b/extensions/permissions/nsContentBlocker.cpp
@@ -31,17 +31,18 @@ static const char *kTypeString[] = {"oth
                                     "refresh",
                                     "xbl",
                                     "ping",
                                     "xmlhttprequest",
                                     "objectsubrequest",
                                     "dtd",
                                     "font",
                                     "media",
-                                    "websocket"};
+                                    "websocket"
+                                    "csp_report"};
 
 #define NUMBER_OF_TYPES NS_ARRAY_LENGTH(kTypeString)
 uint8_t nsContentBlocker::mBehaviorPref[NUMBER_OF_TYPES];
 
 NS_IMPL_ISUPPORTS3(nsContentBlocker, 
                    nsIContentPolicy,
                    nsIObserver,
                    nsSupportsWeakReference)