bug 767778 bring report struct in-line w/1.0 spec; include original URI for redirects r=sstamm, a=akeybl
☠☠ backed out by 0b4a966d3f85 ☠ ☠
authorDaniel Veditz <dveditz@cruzio.com>
Tue, 10 Jul 2012 00:44:05 -0700
changeset 98752 1cefe3f794ba37322b8657efe178e97d6642b460
parent 98751 8bba6a37776e41e0f8aa0785bcd0cc8dab7d00ae
child 98753 0b4a966d3f85e60f6c1e0e874d1df7e32bceab3f
push idunknown
push userunknown
push dateunknown
reviewerssstamm, akeybl
bugs767778
milestone16.0a1
bug 767778 bring report struct in-line w/1.0 spec; include original URI for redirects r=sstamm, a=akeybl
content/base/src/contentSecurityPolicy.js
content/base/src/nsCSPService.cpp
content/base/test/test_bug548193.html
--- a/content/base/src/contentSecurityPolicy.js
+++ b/content/base/src/contentSecurityPolicy.js
@@ -32,16 +32,17 @@ 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._referrer = "";
   this._docRequest = null;
   CSPdebug("CSP POLICY INITED TO 'default-src *'");
 }
 
 /*
  * Set up mappings from nsIContentPolicy content types to CSP directives.
  */
 {
@@ -147,23 +148,23 @@ ContentSecurityPolicy.prototype = {
   logViolationDetails:
   function(aViolationType, aSourceFile, aScriptSample, aLineNum) {
     // allowsInlineScript and allowsEval both return true when report-only mode
     // is enabled, resulting in a call to this function. Therefore we need to
     // check that the policy was in fact violated before logging any violations
     switch (aViolationType) {
     case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT:
       if (!this._policy.allowsInlineScripts)
-        this._asyncReportViolation('self','inline script base restriction',
+        this._asyncReportViolation('self',null,'inline script base restriction',
                                    'violated base restriction: Inline Scripts will not execute',
                                    aSourceFile, aScriptSample, aLineNum);
       break;
     case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL:
       if (!this._policy.allowsEvalInScripts)
-        this._asyncReportViolation('self','eval script base restriction',
+        this._asyncReportViolation('self',null,'eval script base restriction',
                                    'violated base restriction: Code will not be created from strings',
                                    aSourceFile, aScriptSample, aLineNum);
       break;
     }
   },
 
   set reportOnlyMode(val) {
     this._reportOnlyMode = val;
@@ -183,35 +184,28 @@ ContentSecurityPolicy.prototype = {
 
   /**
    * Given an nsIHttpChannel, fill out the appropriate data.
    */
   scanRequestData:
   function(aChannel) {
     if (!aChannel)
       return;
-    // grab the request line
-    var internalChannel = null;
-    try {
-      internalChannel = aChannel.QueryInterface(Ci.nsIHttpChannelInternal);
-    } catch (e) {
-      CSPdebug("No nsIHttpChannelInternal for " + aChannel.URI.asciiSpec);
-    }
 
-    this._request = aChannel.requestMethod + " " + aChannel.URI.asciiSpec;
+    // Save the docRequest for fetching a policy-uri
     this._docRequest = aChannel;
 
-    // We will only be able to provide the HTTP version information if aChannel
-    // implements nsIHttpChannelInternal
-    if (internalChannel) {
-      var reqMaj = {};
-      var reqMin = {};
-      var reqVersion = internalChannel.getRequestVersion(reqMaj, reqMin);
-      this._request += " HTTP/" + reqMaj.value + "." + reqMin.value;
-    }
+    // save the document URI (minus <fragment>) and referrer for reporting
+    let uri = aChannel.URI.cloneIgnoringRef();
+    uri.userPass = '';
+    this._request = uri.asciiSpec;
+
+    let referrer = aChannel.referrer.cloneIgnoringRef();
+    referrer.userPass = '';
+    this._referrer = referrer.asciiSpec;
   },
 
 /* ........ 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
@@ -248,37 +242,58 @@ ContentSecurityPolicy.prototype = {
     this._policy = intersect;
     this._isInitialized = true;
   },
 
   /**
    * Generates and sends a violation report to the specified report URIs.
    */
   sendReports:
-  function(blockedUri, violatedDirective, aSourceFile, aScriptSample, aLineNum) {
+  function(blockedUri, originalUri, violatedDirective,
+           aSourceFile, aScriptSample, aLineNum) {
     var uriString = this._policy.getReportURIs();
     var uris = uriString.split(/\s+/);
     if (uris.length > 0) {
+      // see if we need to sanitize the blocked-uri
+      let blocked = '';
+      if (originalUri) {
+        // We've redirected, only report the blocked origin
+        let clone = blockedUri.clone();
+        clone.path = '';
+        blocked = clone.asciiSpec;
+      }
+      else if (blockedUri instanceof Ci.nsIURI) {
+        blocked = blockedUri.cloneIgnoringRef().asciiSpec;
+      }
+      else {
+        // blockedUri is a string for eval/inline-script violations
+        blocked = blockedUri;
+      }
+
       // Generate report to send composed of
       // {
       //   csp-report: {
-      //     request: "GET /index.html HTTP/1.1",
+      //     document-uri: "http://example.com/file.html?params",
+      //     referrer: "...",
       //     blocked-uri: "...",
       //     violated-directive: "..."
       //   }
       // }
       var report = {
         'csp-report': {
-          'request': this._request,
-          'blocked-uri': (blockedUri instanceof Ci.nsIURI ?
-                          blockedUri.asciiSpec : blockedUri),
+          'document-uri': this._request,
+          'referrer': this._referrer,
+          'blocked-uri': blocked,
           'violated-directive': violatedDirective
         }
       }
+
       // extra report fields for script errors (if available)
+      if (originalUri)
+        report["csp-report"]["original-uri"] = originalUri.cloneIgnoringRef().asciiSpec;
       if (aSourceFile)
         report["csp-report"]["source-file"] = aSourceFile;
       if (aScriptSample)
         report["csp-report"]["script-sample"] = aScriptSample;
       if (aLineNum)
         report["csp-report"]["line-number"] = aLineNum;
 
       var reportString = JSON.stringify(report);
@@ -398,17 +413,17 @@ ContentSecurityPolicy.prototype = {
       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
                                 ? 'default-src' : 'frame-ancestors ')
                                 + directive.toString();
 
-        this._asyncReportViolation(ancestors[i], violatedPolicy);
+        this._asyncReportViolation(ancestors[i], null, violatedPolicy);
 
         // need to lie if we are testing in report-only mode
         return this._reportOnlyMode;
       }
     }
     return true;
   },
 
@@ -418,17 +433,17 @@ ContentSecurityPolicy.prototype = {
    * decides whether or not the policy is satisfied.
    */
   shouldLoad:
   function csp_shouldLoad(aContentType, 
                           aContentLocation, 
                           aRequestOrigin, 
                           aContext, 
                           aMimeTypeGuess, 
-                          aExtra) {
+                          aOriginalUri) {
 
     // don't filter chrome stuff
     if (aContentLocation.scheme === 'chrome' ||
         aContentLocation.scheme === 'resource') {
       return Ci.nsIContentPolicy.ACCEPT;
     }
 
     // interpret the context, and then pass off to the decision structure
@@ -452,17 +467,17 @@ ContentSecurityPolicy.prototype = {
     // 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
                                 ? 'default-src' : cspContext)
                                 + ' ' + directive.toString();
-        this._asyncReportViolation(aContentLocation, violatedPolicy);
+        this._asyncReportViolation(aContentLocation, aOriginalUri, violatedPolicy);
       } catch(e) {
         CSPdebug('---------------- ERROR: ' + e);
       }
     }
 
     return (this._reportOnlyMode ? Ci.nsIContentPolicy.ACCEPT : res);
   },
   
@@ -482,30 +497,32 @@ ContentSecurityPolicy.prototype = {
   /**
    * Asynchronously notifies any nsIObservers listening to the CSP violation
    * topic that a violation occurred.  Also triggers report sending.  All
    * asynchronous on the main thread.
    *
    * @param blockedContentSource
    *        Either a CSP Source (like 'self', as string) or nsIURI: the source
    *        of the violation.
+   * @param originalUri
+   *        The original URI if the blocked content is a redirect, else null
    * @param violatedDirective
    *        the directive that was violated (string).
    * @param observerSubject
    *        optional, subject sent to the nsIObservers listening to the CSP
    *        violation topic.
    * @param aSourceFile
    *        name of the file containing the inline script violation
    * @param aScriptSample
    *        a sample of the violating inline script
    * @param aLineNum
    *        source line number of the violation (if available)
    */
   _asyncReportViolation:
-  function(blockedContentSource, violatedDirective, observerSubject,
+  function(blockedContentSource, originalUri, violatedDirective, observerSubject,
            aSourceFile, aScriptSample, aLineNum) {
     // if optional observerSubject isn't specified, default to the source of
     // the violation.
     if (!observerSubject)
       observerSubject = blockedContentSource;
 
     // gotta wrap things that aren't nsISupports, since it's sent out to
     // observers as such.  Objects that are not nsISupports are converted to
@@ -518,17 +535,18 @@ ContentSecurityPolicy.prototype = {
     }
 
     var reportSender = this;
     Services.tm.mainThread.dispatch(
       function() {
         Services.obs.notifyObservers(observerSubject,
                                      CSP_VIOLATION_TOPIC,
                                      violatedDirective);
-        reportSender.sendReports(blockedContentSource, violatedDirective,
+        reportSender.sendReports(blockedContentSource, originalUri,
+                                 violatedDirective,
                                  aSourceFile, aScriptSample, aLineNum);
       }, Ci.nsIThread.DISPATCH_NORMAL);
   },
 };
 
 // The POST of the violation report (if it happens) should not follow
 // redirects, per the spec. hence, we implement an nsIChannelEventSink
 // with an object so we can tell XHR to abort if a redirect happens.
--- a/content/base/src/nsCSPService.cpp
+++ b/content/base/src/nsCSPService.cpp
@@ -91,22 +91,23 @@ CSPService::ShouldLoad(PRUint32 aContent
 #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
+            // (don't pass aExtra, we use that slot for redirects)
             csp->ShouldLoad(aContentType,
                             aContentLocation,
                             aRequestOrigin,
                             aRequestContext,
                             aMimeTypeGuess,
-                            aExtra,
+                            nsnull,
                             aDecision);
         }
     }
 #ifdef PR_LOGGING
     else {
         nsCAutoString uriSpec;
         aContentLocation->GetSpec(uriSpec);
         PR_LOG(gCspPRLog, PR_LOG_DEBUG,
@@ -215,23 +216,25 @@ CSPService::AsyncOnChannelRedirect(nsICh
    * the new channel's property bag. This container is propagated forward when
    * channels redirect.
    */
 
   // Does the CSP permit this host for this type of load?
   // If not, cancel the load now.
   nsCOMPtr<nsIURI> newUri;
   newChannel->GetURI(getter_AddRefs(newUri));
+  nsCOMPtr<nsIURI> originalUri;
+  oldChannel->GetOriginalURI(getter_AddRefs(originalUri));
   PRInt16 aDecision = nsIContentPolicy::ACCEPT;
   csp->ShouldLoad(loadType,        // load type per nsIContentPolicy (PRUint32)
                   newUri,          // nsIURI
                   nsnull,          // nsIURI
                   nsnull,          // nsISupports
                   EmptyCString(),  // ACString - MIME guess
-                  nsnull,          // nsISupports - extra
+                  originalUri,     // nsISupports - extra
                   &aDecision);
 
 #ifdef PR_LOGGING
   if (newUri) {
     nsCAutoString newUriSpec("None");
     newUri->GetSpec(newUriSpec);
     PR_LOG(gCspPRLog, PR_LOG_DEBUG,
            ("CSPService::AsyncOnChannelRedirect called for %s",
--- a/content/base/test/test_bug548193.html
+++ b/content/base/test/test_bug548193.html
@@ -77,18 +77,18 @@ examiner.prototype  = {
 }
 
 // content file that triggers a violation report
 var testFile = "file_bug548193.sjs";
 
 window.checkResults = function(reportObj) {
   var cspReport = reportObj["csp-report"];
   // correct violating request
-  is(cspReport["request"],
-     "GET http://mochi.test:8888/tests/content/base/test/" + testFile + " HTTP/1.1",
+  is(cspReport["document-uri"],
+     "http://mochi.test:8888/tests/content/base/test/" + testFile,
      "Incorrect violating request");
   // correct blocked-uri
   is(cspReport["blocked-uri"],
      "http://example.org/tests/content/base/test/file_CSP.sjs?testid=img_bad&type=img/png",
      "Incorrect blocked uri");
   // correct violated-directive
   is(cspReport["violated-directive"], "default-src http://mochi.test:8888",
      "Incorrect violated directive");