content/base/src/contentSecurityPolicy.js

Thu, 15 Jan 2015 21:03:48 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 21:03:48 +0100
branch
TOR_BUG_9701
changeset 11
deefc01c0e14
permissions
-rw-r--r--

Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 /**
     7  * Content Security Policy
     8  *
     9  * Overview
    10  * This is a stub component that will be fleshed out to do all the fancy stuff
    11  * that ContentSecurityPolicy has to do.
    12  */
    14 // these identifiers are also defined in contentSecurityPolicy.manifest.
    15 const CSP_CLASS_ID = Components.ID("{d1680bb4-1ac0-4772-9437-1188375e44f2}");
    16 const CSP_CONTRACT_ID = "@mozilla.org/contentsecuritypolicy;1";
    18 /* :::::::: Constants and Helpers ::::::::::::::: */
    20 const Cc = Components.classes;
    21 const Ci = Components.interfaces;
    22 const Cr = Components.results;
    23 const Cu = Components.utils;
    25 const CSP_VIOLATION_TOPIC = "csp-on-violate-policy";
    27 // Needed to support CSP 1.0 spec and our original CSP implementation - should
    28 // be removed when our original implementation is deprecated.
    29 const CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT = "csp_type_xmlhttprequest_spec_compliant";
    30 const CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT = "csp_type_websocket_spec_compliant";
    32 const WARN_FLAG = Ci.nsIScriptError.warningFlag;
    33 const ERROR_FLAG = Ci.nsIScriptError.ERROR_FLAG;
    35 const INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Stylesheets will not apply';
    36 const INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Scripts will not execute';
    37 const EVAL_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Code will not be created from strings';
    38 const SCRIPT_NONCE_VIOLATION_OBSERVER_SUBJECT = 'Inline Script had invalid nonce'
    39 const STYLE_NONCE_VIOLATION_OBSERVER_SUBJECT = 'Inline Style had invalid nonce'
    40 const SCRIPT_HASH_VIOLATION_OBSERVER_SUBJECT = 'Inline Script had invalid hash';
    41 const STYLE_HASH_VIOLATION_OBSERVER_SUBJECT = 'Inline Style had invalid hash';
    43 // The cutoff length of content location in creating CSP cache key.
    44 const CSP_CACHE_URI_CUTOFF_SIZE = 512;
    46 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    47 Cu.import("resource://gre/modules/Services.jsm");
    48 Cu.import("resource://gre/modules/CSPUtils.jsm");
    50 /* ::::: Policy Parsing & Data structures :::::: */
    52 function ContentSecurityPolicy() {
    53   CSPdebug("CSP CREATED");
    54   this._isInitialized = false;
    56   this._policies = [];
    58   this._request = "";
    59   this._requestOrigin = "";
    60   this._weakRequestPrincipal =  { get : function() { return null; } };
    61   this._referrer = "";
    62   this._weakDocRequest = { get : function() { return null; } };
    63   CSPdebug("CSP object initialized, no policies to enforce yet");
    65   this._cache = new Map();
    66 }
    68 /*
    69  * Set up mappings from nsIContentPolicy content types to CSP directives.
    70  */
    71 {
    72   let cp = Ci.nsIContentPolicy;
    73   let csp = ContentSecurityPolicy;
    74   let cspr_sd_old = CSPRep.SRC_DIRECTIVES_OLD;
    75   let cspr_sd_new = CSPRep.SRC_DIRECTIVES_NEW;
    77   csp._MAPPINGS=[];
    79   /* default, catch-all case */
    80   // This is the same in old and new CSP so use the new mapping.
    81   csp._MAPPINGS[cp.TYPE_OTHER]             =  cspr_sd_new.DEFAULT_SRC;
    83   /* self */
    84   csp._MAPPINGS[cp.TYPE_DOCUMENT]          =  null;
    86   /* shouldn't see this one */
    87   csp._MAPPINGS[cp.TYPE_REFRESH]           =  null;
    89   /* categorized content types */
    90   // These are the same in old and new CSP's so just use the new mappings.
    91   csp._MAPPINGS[cp.TYPE_SCRIPT]            = cspr_sd_new.SCRIPT_SRC;
    92   csp._MAPPINGS[cp.TYPE_IMAGE]             = cspr_sd_new.IMG_SRC;
    93   csp._MAPPINGS[cp.TYPE_STYLESHEET]        = cspr_sd_new.STYLE_SRC;
    94   csp._MAPPINGS[cp.TYPE_OBJECT]            = cspr_sd_new.OBJECT_SRC;
    95   csp._MAPPINGS[cp.TYPE_OBJECT_SUBREQUEST] = cspr_sd_new.OBJECT_SRC;
    96   csp._MAPPINGS[cp.TYPE_SUBDOCUMENT]       = cspr_sd_new.FRAME_SRC;
    97   csp._MAPPINGS[cp.TYPE_MEDIA]             = cspr_sd_new.MEDIA_SRC;
    98   csp._MAPPINGS[cp.TYPE_FONT]              = cspr_sd_new.FONT_SRC;
    99   csp._MAPPINGS[cp.TYPE_XSLT]              = cspr_sd_new.SCRIPT_SRC;
   100   csp._MAPPINGS[cp.TYPE_BEACON]            = cspr_sd_new.CONNECT_SRC;
   102   /* Our original CSP implementation's mappings for XHR and websocket
   103    * These should be changed to be = cspr_sd.CONNECT_SRC when we remove
   104    * the original implementation - NOTE: order in this array is important !!!
   105    */
   106   csp._MAPPINGS[cp.TYPE_XMLHTTPREQUEST]    = cspr_sd_old.XHR_SRC;
   107   csp._MAPPINGS[cp.TYPE_WEBSOCKET]         = cspr_sd_old.XHR_SRC;
   109   /* CSP cannot block CSP reports */
   110   csp._MAPPINGS[cp.TYPE_CSP_REPORT]        = null;
   112   /* These must go through the catch-all */
   113   csp._MAPPINGS[cp.TYPE_XBL]               = cspr_sd_new.DEFAULT_SRC;
   114   csp._MAPPINGS[cp.TYPE_PING]              = cspr_sd_new.DEFAULT_SRC;
   115   csp._MAPPINGS[cp.TYPE_DTD]               = cspr_sd_new.DEFAULT_SRC;
   117   /* CSP 1.0 spec compliant mappings for XHR and websocket */
   118   // The directive name for XHR, websocket, and EventSource is different
   119   // in the 1.0 spec than in our original implementation, these mappings
   120   // address this. These won't be needed when we deprecate our original
   121   // implementation.
   122   csp._MAPPINGS[CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT]    = cspr_sd_new.CONNECT_SRC;
   123   csp._MAPPINGS[CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT]         = cspr_sd_new.CONNECT_SRC;
   124   // TODO : EventSource will be here and also will use connect-src
   125   // after we fix Bug 802872 - CSP should restrict EventSource using the connect-src
   126   // directive. For background see Bug 667490 - EventSource should use the same
   127   // nsIContentPolicy type as XHR (which is fixed)
   128 }
   130 ContentSecurityPolicy.prototype = {
   131   classID:          CSP_CLASS_ID,
   132   QueryInterface:   XPCOMUtils.generateQI([Ci.nsIContentSecurityPolicy,
   133                                            Ci.nsISerializable,
   134                                            Ci.nsISupports]),
   136   // Class info is required to be able to serialize
   137   classInfo: XPCOMUtils.generateCI({classID: CSP_CLASS_ID,
   138                                     contractID: CSP_CONTRACT_ID,
   139                                     interfaces: [Ci.nsIContentSecurityPolicy,
   140                                                  Ci.nsISerializable],
   141                                     flags: Ci.nsIClassInfo.MAIN_THREAD_ONLY}),
   143   get isInitialized() {
   144     return this._isInitialized;
   145   },
   147   set isInitialized (foo) {
   148     this._isInitialized = foo;
   149   },
   151   _getPolicyInternal: function(index) {
   152     if (index < 0 || index >= this._policies.length) {
   153       throw Cr.NS_ERROR_FAILURE;
   154     }
   155     return this._policies[index];
   156   },
   158   _buildViolatedDirectiveString:
   159   function(aDirectiveName, aPolicy) {
   160     var SD = CSPRep.SRC_DIRECTIVES_NEW;
   161     var cspContext = (SD[aDirectiveName] in aPolicy._directives) ? SD[aDirectiveName] : SD.DEFAULT_SRC;
   162     var directive = aPolicy._directives[cspContext];
   163     return cspContext + ' ' + directive.toString();
   164   },
   166   /**
   167    * Returns policy string representing the policy at "index".
   168    */
   169   getPolicy: function(index) {
   170     return this._getPolicyInternal(index).toString();
   171   },
   173   /**
   174    * Returns count of policies.
   175    */
   176   get numPolicies() {
   177     return this._policies.length;
   178   },
   180   getAllowsInlineScript: function(shouldReportViolations) {
   181     // report it? (for each policy, is it violated?)
   182     shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsInlineScripts; });
   184     // allow it to execute?  (Do all the policies allow it to execute)?
   185     return this._policies.every(function(a) {
   186       return a._reportOnlyMode || a.allowsInlineScripts;
   187     });
   188   },
   190   getAllowsEval: function(shouldReportViolations) {
   191     // report it? (for each policy, is it violated?)
   192     shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsEvalInScripts; });
   194     // allow it to execute?  (Do all the policies allow it to execute)?
   195     return this._policies.every(function(a) {
   196       return a._reportOnlyMode || a.allowsEvalInScripts;
   197     });
   198   },
   200   getAllowsInlineStyle: function(shouldReportViolations) {
   201     // report it? (for each policy, is it violated?)
   202     shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsInlineStyles; });
   204     // allow it to execute?  (Do all the policies allow it to execute)?
   205     return this._policies.every(function(a) {
   206       return a._reportOnlyMode || a.allowsInlineStyles;
   207     });
   208   },
   210   getAllowsNonce: function(aNonce, aContentType, shouldReportViolation) {
   211     if (!(aContentType == Ci.nsIContentPolicy.TYPE_SCRIPT ||
   212           aContentType == Ci.nsIContentPolicy.TYPE_STYLESHEET)) {
   213       CSPdebug("Nonce check requested for an invalid content type (not script or style): " + aContentType);
   214       return false;
   215     }
   217     let directive = ContentSecurityPolicy._MAPPINGS[aContentType];
   218     let policyAllowsNonce = [ policy.permitsNonce(aNonce, directive)
   219                               for (policy of this._policies) ];
   221     shouldReportViolation.value = this._policies.some(function(policy, i) {
   222       // Don't report a violation if the policy didn't use nonce-source
   223       return policy._directives.hasOwnProperty(directive) &&
   224              policy._directives[directive]._hasNonceSource &&
   225              !policyAllowsNonce[i];
   226     });
   228     // allow it to execute?  (Do all the policies allow it to execute)?
   229     return this._policies.every(function(policy, i) {
   230       return policy._reportOnlyMode || policyAllowsNonce[i];
   231     });
   232   },
   234   getAllowsHash: function(aContent, aContentType, shouldReportViolation) {
   235     if (!(aContentType == Ci.nsIContentPolicy.TYPE_SCRIPT ||
   236           aContentType == Ci.nsIContentPolicy.TYPE_STYLESHEET)) {
   237       CSPdebug("Hash check requested for an invalid content type (not script or style): " + aContentType);
   238       return false;
   239     }
   241     let directive = ContentSecurityPolicy._MAPPINGS[aContentType];
   242     let policyAllowsHash = [ policy.permitsHash(aContent, directive)
   243                              for (policy of this._policies) ];
   245     shouldReportViolation.value = this._policies.some(function(policy, i) {
   246       // Don't report a violation if the policy didn't use hash-source
   247       return policy._directives.hasOwnProperty(directive) &&
   248              policy._directives[directive]._hasHashSource &&
   249              !policyAllowsHash[i];
   250     });
   252     // allow it to execute?  (Do all the policies allow it to execute)?
   253     return this._policies.every(function(policy, i) {
   254       return policy._reportOnlyMode || policyAllowsHash[i];
   255     });
   256   },
   258   /**
   259    * For each policy, log any violation on the Error Console and send a report
   260    * if a report-uri is present in the policy
   261    *
   262    * @param aViolationType
   263    *     one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval
   264    * @param aSourceFile
   265    *     name of the source file containing the violation (if available)
   266    * @param aContentSample
   267    *     sample of the violating content (to aid debugging)
   268    * @param aLineNum
   269    *     source line number of the violation (if available)
   270    * @param aNonce
   271    *     (optional) If this is a nonce violation, include the nonce so we can
   272    *     recheck to determine which policies were violated and send the
   273    *     appropriate reports.
   274    * @param aContent
   275    *     (optional) If this is a hash violation, include contents of the inline
   276    *     resource in the question so we can recheck the hash in order to
   277    *     determine which policies were violated and send the appropriate
   278    *     reports.
   279    */
   280   logViolationDetails:
   281   function(aViolationType, aSourceFile, aScriptSample, aLineNum, aNonce, aContent) {
   282     for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) {
   283       let policy = this._policies[policyIndex];
   285       // call-sites to the eval/inline checks recieve two return values: allows
   286       // and violates.  Policies that are report-only allow the
   287       // loads/compilations but violations should still be reported.  Not all
   288       // policies in this nsIContentSecurityPolicy instance will be violated,
   289       // which is why we must check again here.
   290       switch (aViolationType) {
   291       case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_STYLE:
   292         if (!policy.allowsInlineStyles) {
   293           var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy);
   294           this._asyncReportViolation('self', null, violatedDirective, policyIndex,
   295                                     INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT,
   296                                     aSourceFile, aScriptSample, aLineNum);
   297         }
   298         break;
   299       case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT:
   300         if (!policy.allowsInlineScripts)    {
   301           var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
   302           this._asyncReportViolation('self', null, violatedDirective, policyIndex,
   303                                     INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT,
   304                                     aSourceFile, aScriptSample, aLineNum);
   305           }
   306         break;
   307       case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL:
   308         if (!policy.allowsEvalInScripts) {
   309           var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
   310           this._asyncReportViolation('self', null, violatedDirective, policyIndex,
   311                                     EVAL_VIOLATION_OBSERVER_SUBJECT,
   312                                     aSourceFile, aScriptSample, aLineNum);
   313         }
   314         break;
   315       case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_NONCE_SCRIPT:
   316         var scriptType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_SCRIPT];
   317         if (!policy.permitsNonce(aNonce, scriptType)) {
   318           var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
   319           this._asyncReportViolation('self', null, violatedDirective, policyIndex,
   320                                      SCRIPT_NONCE_VIOLATION_OBSERVER_SUBJECT,
   321                                      aSourceFile, aScriptSample, aLineNum);
   322         }
   323         break;
   324       case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_NONCE_STYLE:
   325         var styleType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_STYLE];
   326         if (!policy.permitsNonce(aNonce, styleType)) {
   327           var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy);
   328           this._asyncReportViolation('self', null, violatedDirective, policyIndex,
   329                                      STYLE_NONCE_VIOLATION_OBSERVER_SUBJECT,
   330                                      aSourceFile, aScriptSample, aLineNum);
   331         }
   332         break;
   333       case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_HASH_SCRIPT:
   334         var scriptType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_SCRIPT];
   335         if (!policy.permitsHash(aContent, scriptType)) {
   336           var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
   337           this._asyncReportViolation('self', null, violatedDirective, policyIndex,
   338                                      SCRIPT_HASH_VIOLATION_OBSERVER_SUBJECT,
   339                                      aSourceFile, aScriptSample, aLineNum);
   340         }
   341         break;
   342       case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_HASH_STYLE:
   343         var styleType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_STYLE];
   344         if (!policy.permitsHash(aContent, styleType)) {
   345           var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy);
   346           this._asyncReportViolation('self', null, violatedDirective, policyIndex,
   347                                      STYLE_HASH_VIOLATION_OBSERVER_SUBJECT,
   348                                      aSourceFile, aScriptSample, aLineNum);
   349         }
   350         break;
   351       }
   352     }
   353   },
   355   /**
   356    * Given an nsIHttpChannel, fill out the appropriate data.
   357    */
   358   setRequestContext:
   359   function(aSelfURI, aReferrerURI, aPrincipal, aChannel) {
   361     // this requires either a self URI or a http channel.
   362     if (!aSelfURI && !aChannel)
   363       return;
   365     if (aChannel) {
   366       // Save the docRequest for fetching a policy-uri
   367       this._weakDocRequest = Cu.getWeakReference(aChannel);
   368     }
   370     // save the document URI (minus <fragment>) and referrer for reporting
   371     let uri = aSelfURI ? aSelfURI.cloneIgnoringRef() : aChannel.URI.cloneIgnoringRef();
   372     try { // GetUserPass throws for some protocols without userPass
   373       uri.userPass = '';
   374     } catch (ex) {}
   375     this._request = uri.asciiSpec;
   376     this._requestOrigin = uri;
   378     // store a reference to the principal, that can later be used in shouldLoad
   379     if (aPrincipal) {
   380       this._weakRequestPrincipal = Cu.getWeakReference(aPrincipal);
   381     } else if (aChannel) {
   382       this._weakRequestPrincipal = Cu.getWeakReference(Cc["@mozilla.org/scriptsecuritymanager;1"]
   383                                                          .getService(Ci.nsIScriptSecurityManager)
   384                                                          .getChannelPrincipal(aChannel));
   385     } else {
   386       CSPdebug("No principal or channel for document context; violation reports may not work.");
   387     }
   389     // pick one: referrerURI, channel's referrer, or null (first available)
   390     let ref = null;
   391     if (aReferrerURI)
   392       ref = aReferrerURI;
   393     else if (aChannel instanceof Ci.nsIHttpChannel)
   394       ref = aChannel.referrer;
   396     if (ref) {
   397       let referrer = aChannel.referrer.cloneIgnoringRef();
   398       try { // GetUserPass throws for some protocols without userPass
   399         referrer.userPass = '';
   400       } catch (ex) {}
   401       this._referrer = referrer.asciiSpec;
   402     }
   403   },
   405 /* ........ Methods .............. */
   407   /**
   408    * Adds a new policy to our list of policies for this CSP context.
   409    * @returns the count of policies.
   410    */
   411   appendPolicy:
   412   function csp_appendPolicy(aPolicy, selfURI, aReportOnly, aSpecCompliant) {
   413     return this._appendPolicyInternal(aPolicy, selfURI, aReportOnly,
   414                                       aSpecCompliant, true);
   415   },
   417   /**
   418    * Adds a new policy to our list of policies for this CSP context.
   419    * Only to be called from this module (not exported)
   420    * @returns the count of policies.
   421    */
   422   _appendPolicyInternal:
   423   function csp_appendPolicy(aPolicy, selfURI, aReportOnly, aSpecCompliant,
   424                             aEnforceSelfChecks) {
   425 #ifndef MOZ_B2G
   426     CSPdebug("APPENDING POLICY: " + aPolicy);
   427     CSPdebug("            SELF: " + (selfURI ? selfURI.asciiSpec : " null"));
   428     CSPdebug("CSP 1.0 COMPLIANT : " + aSpecCompliant);
   429 #endif
   431     // For nested schemes such as view-source: make sure we are taking the
   432     // innermost URI to use as 'self' since that's where we will extract the
   433     // scheme, host and port from
   434     if (selfURI instanceof Ci.nsINestedURI) {
   435 #ifndef MOZ_B2G
   436       CSPdebug("        INNER: " + selfURI.innermostURI.asciiSpec);
   437 #endif
   438       selfURI = selfURI.innermostURI;
   439     }
   441     // stay uninitialized until policy setup is done
   442     var newpolicy;
   444     // If there is a policy-uri, fetch the policy, then re-call this function.
   445     // (1) parse and create a CSPRep object
   446     // Note that we pass the full URI since when it's parsed as 'self' to construct a
   447     // CSPSource only the scheme, host, and port are kept.
   449     // If we want to be CSP 1.0 spec compliant, use the new parser.
   450     // The old one will be deprecated in the future and will be
   451     // removed at that time.
   452     if (aSpecCompliant) {
   453       newpolicy = CSPRep.fromStringSpecCompliant(aPolicy,
   454                                                  selfURI,
   455                                                  aReportOnly,
   456                                                  this._weakDocRequest.get(),
   457                                                  this,
   458                                                  aEnforceSelfChecks);
   459     } else {
   460       newpolicy = CSPRep.fromString(aPolicy,
   461                                     selfURI,
   462                                     aReportOnly,
   463                                     this._weakDocRequest.get(),
   464                                     this,
   465                                     aEnforceSelfChecks);
   466     }
   468     newpolicy._specCompliant = !!aSpecCompliant;
   469     newpolicy._isInitialized = true;
   470     this._policies.push(newpolicy);
   471     this._cache.clear(); // reset cache since effective policy changes
   472   },
   474   /**
   475    * Removes a policy from the array of policies.
   476    */
   477   removePolicy:
   478   function csp_removePolicy(index) {
   479     if (index < 0 || index >= this._policies.length) {
   480       CSPdebug("Cannot remove policy " + index + "; not enough policies.");
   481       return;
   482     }
   483     this._policies.splice(index, 1);
   484     this._cache.clear(); // reset cache since effective policy changes
   485   },
   487   /**
   488    * Generates and sends a violation report to the specified report URIs.
   489    */
   490   sendReports:
   491   function(blockedUri, originalUri, violatedDirective,
   492            violatedPolicyIndex, aSourceFile,
   493            aScriptSample, aLineNum) {
   495     let policy = this._getPolicyInternal(violatedPolicyIndex);
   496     if (!policy) {
   497       CSPdebug("ERROR in SendReports: policy " + violatedPolicyIndex + " is not defined.");
   498       return;
   499     }
   501     var uriString = policy.getReportURIs();
   502     var uris = uriString.split(/\s+/);
   503     if (uris.length > 0) {
   504       // see if we need to sanitize the blocked-uri
   505       let blocked = '';
   506       if (originalUri) {
   507         // We've redirected, only report the blocked origin
   508         try {
   509           let clone = blockedUri.clone();
   510           clone.path = '';
   511           blocked = clone.asciiSpec;
   512         } catch(e) {
   513           CSPdebug(".... blockedUri can't be cloned: " + blockedUri);
   514         }
   515       }
   516       else if (blockedUri instanceof Ci.nsIURI) {
   517         blocked = blockedUri.cloneIgnoringRef().asciiSpec;
   518       }
   519       else {
   520         // blockedUri is a string for eval/inline-script violations
   521         blocked = blockedUri;
   522       }
   524       // Generate report to send composed of
   525       // {
   526       //   csp-report: {
   527       //     document-uri: "http://example.com/file.html?params",
   528       //     referrer: "...",
   529       //     blocked-uri: "...",
   530       //     violated-directive: "..."
   531       //   }
   532       // }
   533       var report = {
   534         'csp-report': {
   535           'document-uri': this._request,
   536           'referrer': this._referrer,
   537           'blocked-uri': blocked,
   538           'violated-directive': violatedDirective
   539         }
   540       }
   542       // extra report fields for script errors (if available)
   543       if (originalUri)
   544         report["csp-report"]["original-uri"] = originalUri.cloneIgnoringRef().asciiSpec;
   545       if (aSourceFile)
   546         report["csp-report"]["source-file"] = aSourceFile;
   547       if (aScriptSample)
   548         report["csp-report"]["script-sample"] = aScriptSample;
   549       if (aLineNum)
   550         report["csp-report"]["line-number"] = aLineNum;
   552       var reportString = JSON.stringify(report);
   553       CSPdebug("Constructed violation report:\n" + reportString);
   555       // For each URI in the report list, send out a report.
   556       // We make the assumption that all of the URIs are absolute URIs; this
   557       // should be taken care of in CSPRep.fromString (where it converts any
   558       // relative URIs into absolute ones based on "self").
   559       for (let i in uris) {
   560         if (uris[i] === "")
   561           continue;
   563         try {
   564           var chan = Services.io.newChannel(uris[i], null, null);
   565           if (!chan) {
   566             CSPdebug("Error creating channel for " + uris[i]);
   567             continue;
   568           }
   570           var content = Cc["@mozilla.org/io/string-input-stream;1"]
   571                           .createInstance(Ci.nsIStringInputStream);
   572           content.data = reportString + "\n\n";
   574           // make sure this is an anonymous request (no cookies) so in case the
   575           // policy URI is injected, it can't be abused for CSRF.
   576           chan.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS;
   578           // we need to set an nsIChannelEventSink on the channel object
   579           // so we can tell it to not follow redirects when posting the reports
   580           chan.notificationCallbacks = new CSPReportRedirectSink(policy);
   581           if (this._weakDocRequest.get()) {
   582             chan.loadGroup = this._weakDocRequest.get().loadGroup;
   583           }
   585           chan.QueryInterface(Ci.nsIUploadChannel)
   586               .setUploadStream(content, "application/json", content.available());
   588           try {
   589             // if this is an HTTP channel, set the request method to post
   590             chan.QueryInterface(Ci.nsIHttpChannel);
   591             chan.requestMethod = "POST";
   592           } catch(e) {} // throws only if chan is not an nsIHttpChannel.
   594           // check with the content policy service to see if we're allowed to
   595           // send this request.
   596           try {
   597             var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"]
   598                                   .getService(Ci.nsIContentPolicy);
   599             if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_CSP_REPORT,
   600                                          chan.URI, this._requestOrigin,
   601                                          null, null, null, this._weakRequestPrincipal.get())
   602                 != Ci.nsIContentPolicy.ACCEPT) {
   603               continue; // skip unauthorized URIs
   604             }
   605           } catch(e) {
   606             continue; // refuse to load if we can't do a security check.
   607           }
   609           //send data (and set up error notifications)
   610           chan.asyncOpen(new CSPViolationReportListener(uris[i]), null);
   611           CSPdebug("Sent violation report to " + uris[i]);
   612         } catch(e) {
   613           // it's possible that the URI was invalid, just log a
   614           // warning and skip over that.
   615           policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("triedToSendReport", [uris[i]]));
   616           policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("errorWas", [e.toString()]));
   617         }
   618       }
   619     }
   620   },
   622   /**
   623    * Logs a meaningful CSP warning to the developer console.
   624    */
   625   logToConsole:
   626   function(blockedUri, originalUri, violatedDirective, aViolatedPolicyIndex,
   627            aSourceFile, aScriptSample, aLineNum, aObserverSubject) {
   628      let policy = this._policies[aViolatedPolicyIndex];
   629      switch(aObserverSubject.data) {
   630       case INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT:
   631         violatedDirective = CSPLocalizer.getStr("inlineStyleBlocked");
   632         break;
   633       case INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT:
   634         violatedDirective = CSPLocalizer.getStr("inlineScriptBlocked");
   635         break;
   636       case EVAL_VIOLATION_OBSERVER_SUBJECT:
   637         violatedDirective = CSPLocalizer.getStr("scriptFromStringBlocked");
   638         break;
   639     }
   640     var violationMessage = null;
   641     if (blockedUri["asciiSpec"]) {
   642       violationMessage = CSPLocalizer.getFormatStr("CSPViolationWithURI", [violatedDirective, blockedUri.asciiSpec]);
   643     } else {
   644       violationMessage = CSPLocalizer.getFormatStr("CSPViolation", [violatedDirective]);
   645     }
   646     policy.log(WARN_FLAG, violationMessage,
   647                (aSourceFile) ? aSourceFile : null,
   648                (aScriptSample) ? decodeURIComponent(aScriptSample) : null,
   649                (aLineNum) ? aLineNum : null);
   650   },
   652 /**
   653    * Exposed Method to analyze docShell for approved frame ancestry.
   654    * NOTE: Also sends violation reports if necessary.
   655    * @param docShell
   656    *    the docShell for this policy's resource.
   657    * @return
   658    *    true if the frame ancestry is allowed by this policy and the load
   659    *    should progress.
   660    */
   661   permitsAncestry:
   662   function(docShell) {
   663     // Cannot shortcut checking all the policies since violation reports have
   664     // to be triggered if any policy wants it.
   665     var permitted = true;
   666     for (let i = 0; i < this._policies.length; i++) {
   667       if (!this._permitsAncestryInternal(docShell, this._policies[i], i)) {
   668         permitted = false;
   669       }
   670     }
   671     return permitted;
   672   },
   674   _permitsAncestryInternal:
   675   function(docShell, policy, policyIndex) {
   676     if (!docShell) { return false; }
   678     // walk up this docShell tree until we hit chrome
   679     var dst = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
   680                       .getInterface(Ci.nsIDocShellTreeItem);
   682     // collect ancestors and make sure they're allowed.
   683     var ancestors = [];
   684     while (dst.parent) {
   685       dst = dst.parent;
   686       let it = dst.QueryInterface(Ci.nsIInterfaceRequestor)
   687                   .getInterface(Ci.nsIWebNavigation);
   688       if (it.currentURI) {
   689         if (it.currentURI.scheme === "chrome") {
   690           break;
   691         }
   692         // delete any userpass
   693         let ancestor = it.currentURI.cloneIgnoringRef();
   694         try { // GetUserPass throws for some protocols without userPass
   695           ancestor.userPass = '';
   696         } catch (ex) {}
   698 #ifndef MOZ_B2G
   699         CSPdebug(" found frame ancestor " + ancestor.asciiSpec);
   700 #endif
   701         ancestors.push(ancestor);
   702       }
   703     }
   705     // scan the discovered ancestors
   706     // frame-ancestors is the same in both old and new source directives,
   707     // so don't need to differentiate here.
   708     let cspContext = CSPRep.SRC_DIRECTIVES_NEW.FRAME_ANCESTORS;
   709     for (let i in ancestors) {
   710       let ancestor = ancestors[i];
   711       if (!policy.permits(ancestor, cspContext)) {
   712         // report the frame-ancestor violation
   713         let directive = policy._directives[cspContext];
   714         let violatedPolicy = 'frame-ancestors ' + directive.toString();
   716         this._asyncReportViolation(ancestors[i], null, violatedPolicy,
   717                                    policyIndex);
   719         // need to lie if we are testing in report-only mode
   720         return policy._reportOnlyMode;
   721       }
   722     }
   723     return true;
   724   },
   726   /**
   727    * Creates a cache key from content location and content type.
   728    */
   729   _createCacheKey:
   730   function (aContentLocation, aContentType) {
   731     if (aContentType != Ci.nsIContentPolicy.TYPE_SCRIPT &&
   732         aContentLocation.scheme == "data") {
   733       // For non-script data: URI, use ("data:", aContentType) as the cache key.
   734       return aContentLocation.scheme + ":" + aContentType;
   735     }
   737     let uri = aContentLocation.spec;
   738     if (uri.length > CSP_CACHE_URI_CUTOFF_SIZE) {
   739       // Don't cache for a URI longer than the cutoff size.
   740       return null;
   741     }
   742     return uri + "!" + aContentType;
   743   },
   745   /**
   746    * Delegate method called by the service when sub-elements of the protected
   747    * document are being loaded.  Given a bit of information about the request,
   748    * decides whether or not the policy is satisfied.
   749    */
   750   shouldLoad:
   751   function csp_shouldLoad(aContentType,
   752                           aContentLocation,
   753                           aRequestOrigin,
   754                           aContext,
   755                           aMimeTypeGuess,
   756                           aOriginalUri) {
   757     let key = this._createCacheKey(aContentLocation, aContentType);
   758     if (key && this._cache.has(key)) {
   759       return this._cache.get(key);
   760     }
   762 #ifndef MOZ_B2G
   763     // Try to remove as much as possible from the hot path on b2g.
   764     CSPdebug("shouldLoad location = " + aContentLocation.asciiSpec);
   765     CSPdebug("shouldLoad content type = " + aContentType);
   766 #endif
   768     // The mapping for XHR and websockets is different between our original
   769     // implementation and the 1.0 spec, we handle this here.
   770     var cspContext;
   772     let cp = Ci.nsIContentPolicy;
   774     // Infer if this is a preload for elements that use nonce-source. Since,
   775     // for preloads, aContext is the document and not the element associated
   776     // with the resource, we cannot determine the nonce. See Bug 612921 and
   777     // Bug 855326.
   778     let nonceSourceValid = aContentType == cp.TYPE_SCRIPT ||
   779                            aContentType == cp.TYPE_STYLESHEET;
   780     var possiblePreloadNonceConflict = nonceSourceValid &&
   781                                        aContext instanceof Ci.nsIDOMHTMLDocument;
   783     // iterate through all the _policies and send reports where a policy is
   784     // violated.  After the check, determine the overall effect (blocked or
   785     // loaded?) and cache it.
   786     let policyAllowsLoadArray = [];
   787     for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) {
   788       let policy = this._policies[policyIndex];
   790 #ifndef MOZ_B2G
   791       CSPdebug("policy is " + (policy._specCompliant ?
   792                               "1.0 compliant" : "pre-1.0"));
   793       CSPdebug("policy is " + (policy._reportOnlyMode ?
   794                               "report-only" : "blocking"));
   795 #endif
   797       if (aContentType == cp.TYPE_XMLHTTPREQUEST && this._policies[policyIndex]._specCompliant) {
   798         cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT];
   799       } else if (aContentType == cp.TYPE_WEBSOCKET && this._policies[policyIndex]._specCompliant) {
   800         cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT];
   801       } else {
   802         cspContext = ContentSecurityPolicy._MAPPINGS[aContentType];
   803       }
   805 #ifndef MOZ_B2G
   806       CSPdebug("shouldLoad cspContext = " + cspContext);
   807 #endif
   809       // if the mapping is null, there's no policy, let it through.
   810       if (!cspContext) {
   811         return Ci.nsIContentPolicy.ACCEPT;
   812       }
   814       // check if location is permitted
   815       let permitted = policy.permits(aContentLocation, cspContext);
   817       // check any valid content type for nonce if location is not permitted
   818       if (!permitted && nonceSourceValid &&
   819           aContext instanceof Ci.nsIDOMHTMLElement &&
   820           aContext.hasAttribute('nonce')) {
   821         permitted = policy.permitsNonce(aContext.getAttribute('nonce'),
   822                                         cspContext);
   823       }
   825       // record whether the thing should be blocked or just reported.
   826       policyAllowsLoadArray.push(permitted || policy._reportOnlyMode);
   827       let res = permitted ? cp.ACCEPT : cp.REJECT_SERVER;
   829       // frame-ancestors is taken care of early on (as this document is loaded)
   831       // If the result is *NOT* ACCEPT, then send report
   832       // Do not send report if this is a nonce-source preload - the decision may
   833       // be wrong and will incorrectly fail the unit tests.
   834       if (res != cp.ACCEPT && !possiblePreloadNonceConflict) {
   835         CSPdebug("blocking request for " + aContentLocation.asciiSpec);
   836         try {
   837           let directive = "unknown directive",
   838               violatedPolicy = "unknown policy";
   840           // The policy might not explicitly declare each source directive (so
   841           // the cspContext may be implicit).  If so, we have to report
   842           // violations as appropriate: specific or the default-src directive.
   843           if (policy._directives.hasOwnProperty(cspContext)) {
   844             directive = policy._directives[cspContext];
   845             violatedPolicy = cspContext + ' ' + directive.toString();
   846           } else if (policy._directives.hasOwnProperty("default-src")) {
   847             directive = policy._directives["default-src"];
   848             violatedPolicy = "default-src " + directive.toString();
   849           } else {
   850             violatedPolicy = "unknown directive";
   851             CSPdebug('ERROR in blocking content: ' +
   852                     'CSP is not sure which part of the policy caused this block');
   853           }
   855           this._asyncReportViolation(aContentLocation,
   856                                      aOriginalUri,
   857                                      violatedPolicy,
   858                                      policyIndex);
   859         } catch(e) {
   860           CSPdebug('---------------- ERROR: ' + e);
   861         }
   862       }
   863     } // end for-each loop over policies
   865     // the ultimate decision is based on whether any policies want to reject
   866     // the load.  The array keeps track of whether the policies allowed the
   867     // loads. If any doesn't, we'll reject the load (and cache the result).
   868     let ret = (policyAllowsLoadArray.some(function(a,b) { return !a; }) ?
   869                cp.REJECT_SERVER : cp.ACCEPT);
   871     // Do not cache the result if this is a nonce-source preload
   872     if (key && !possiblePreloadNonceConflict) {
   873       this._cache.set(key, ret);
   874     }
   875     return ret;
   876   },
   878   shouldProcess:
   879   function csp_shouldProcess(aContentType,
   880                              aContentLocation,
   881                              aRequestOrigin,
   882                              aContext,
   883                              aMimeType,
   884                              aExtra) {
   885     // frame-ancestors check is done outside the ContentPolicy
   886     var res = Ci.nsIContentPolicy.ACCEPT;
   887     CSPdebug("shouldProcess aContext=" + aContext);
   888     return res;
   889   },
   891   /**
   892    * Asynchronously notifies any nsIObservers listening to the CSP violation
   893    * topic that a violation occurred.  Also triggers report sending.  All
   894    * asynchronous on the main thread.
   895    *
   896    * @param aBlockedContentSource
   897    *        Either a CSP Source (like 'self', as string) or nsIURI: the source
   898    *        of the violation.
   899    * @param aOriginalUri
   900    *        The original URI if the blocked content is a redirect, else null
   901    * @param aViolatedDirective
   902    *        the directive that was violated (string).
   903    * @param aViolatedPolicyIndex
   904    *        the index of the policy that was violated (so we know where to send
   905    *        the reports).
   906    * @param aObserverSubject
   907    *        optional, subject sent to the nsIObservers listening to the CSP
   908    *        violation topic.
   909    * @param aSourceFile
   910    *        name of the file containing the inline script violation
   911    * @param aScriptSample
   912    *        a sample of the violating inline script
   913    * @param aLineNum
   914    *        source line number of the violation (if available)
   915    */
   916   _asyncReportViolation:
   917   function(aBlockedContentSource, aOriginalUri, aViolatedDirective,
   918            aViolatedPolicyIndex, aObserverSubject,
   919            aSourceFile, aScriptSample, aLineNum) {
   920     // if optional observerSubject isn't specified, default to the source of
   921     // the violation.
   922     if (!aObserverSubject)
   923       aObserverSubject = aBlockedContentSource;
   925     // gotta wrap things that aren't nsISupports, since it's sent out to
   926     // observers as such.  Objects that are not nsISupports are converted to
   927     // strings and then wrapped into a nsISupportsCString.
   928     if (!(aObserverSubject instanceof Ci.nsISupports)) {
   929       let d = aObserverSubject;
   930       aObserverSubject = Cc["@mozilla.org/supports-cstring;1"]
   931                           .createInstance(Ci.nsISupportsCString);
   932       aObserverSubject.data = d;
   933     }
   935     var reportSender = this;
   936     Services.tm.mainThread.dispatch(
   937       function() {
   938         Services.obs.notifyObservers(aObserverSubject,
   939                                      CSP_VIOLATION_TOPIC,
   940                                      aViolatedDirective);
   941         reportSender.sendReports(aBlockedContentSource, aOriginalUri,
   942                                  aViolatedDirective, aViolatedPolicyIndex,
   943                                  aSourceFile, aScriptSample, aLineNum);
   944         reportSender.logToConsole(aBlockedContentSource, aOriginalUri,
   945                                   aViolatedDirective, aViolatedPolicyIndex,
   946                                   aSourceFile, aScriptSample,
   947                                   aLineNum, aObserverSubject);
   949       }, Ci.nsIThread.DISPATCH_NORMAL);
   950   },
   952 /* ........ nsISerializable Methods: .............. */
   954   read:
   955   function(aStream) {
   957     this._requestOrigin = aStream.readObject(true);
   958     this._requestOrigin.QueryInterface(Ci.nsIURI);
   960     for (let pCount = aStream.read32(); pCount > 0; pCount--) {
   961       let polStr        = aStream.readString();
   962       let reportOnly    = aStream.readBoolean();
   963       let specCompliant = aStream.readBoolean();
   964       // don't need self info because when the policy is turned back into a
   965       // string, 'self' is replaced with the explicit source expression.
   966       this._appendPolicyInternal(polStr, null, reportOnly, specCompliant,
   967                                  false);
   968     }
   970     // NOTE: the document instance that's deserializing this object (via its
   971     // principal) should hook itself into this._principal manually.  If they
   972     // don't, the CSP reports will likely be blocked by nsMixedContentBlocker.
   973   },
   975   write:
   976   function(aStream) {
   977     // need to serialize the context: request origin and such. They are
   978     // used when sending reports.  Since _request and _requestOrigin are just
   979     // different representations of the same thing, only save _requestOrigin
   980     // (an nsIURI).
   981     aStream.writeCompoundObject(this._requestOrigin, Ci.nsIURI, true);
   983     // we can't serialize a reference to the principal that triggered this
   984     // instance to serialize, so when this is deserialized by the principal the
   985     // caller must hook it up manually by calling setRequestContext on this
   986     // instance with the appropriate nsIChannel.
   988     // Finally, serialize all the policies.
   989     aStream.write32(this._policies.length);
   991     for each (var policy in this._policies) {
   992       aStream.writeWStringZ(policy.toString());
   993       aStream.writeBoolean(policy._reportOnlyMode);
   994       aStream.writeBoolean(policy._specCompliant);
   995     }
   996   },
   997 };
   999 // The POST of the violation report (if it happens) should not follow
  1000 // redirects, per the spec. hence, we implement an nsIChannelEventSink
  1001 // with an object so we can tell XHR to abort if a redirect happens.
  1002 function CSPReportRedirectSink(policy) {
  1003   this._policy = policy;
  1006 CSPReportRedirectSink.prototype = {
  1007   QueryInterface: function requestor_qi(iid) {
  1008     if (iid.equals(Ci.nsISupports) ||
  1009         iid.equals(Ci.nsIInterfaceRequestor) ||
  1010         iid.equals(Ci.nsIChannelEventSink))
  1011       return this;
  1012     throw Cr.NS_ERROR_NO_INTERFACE;
  1013   },
  1015   // nsIInterfaceRequestor
  1016   getInterface: function requestor_gi(iid) {
  1017     if (iid.equals(Ci.nsIChannelEventSink))
  1018       return this;
  1020     throw Components.results.NS_ERROR_NO_INTERFACE;
  1021   },
  1023   // nsIChannelEventSink
  1024   asyncOnChannelRedirect: function channel_redirect(oldChannel, newChannel,
  1025                                                     flags, callback) {
  1026     this._policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("reportPostRedirect", [oldChannel.URI.asciiSpec]));
  1028     // cancel the old channel so XHR failure callback happens
  1029     oldChannel.cancel(Cr.NS_ERROR_ABORT);
  1031     // notify an observer that we have blocked the report POST due to a redirect,
  1032     // used in testing, do this async since we're in an async call now to begin with
  1033     Services.tm.mainThread.dispatch(
  1034       function() {
  1035         observerSubject = Cc["@mozilla.org/supports-cstring;1"]
  1036                              .createInstance(Ci.nsISupportsCString);
  1037         observerSubject.data = oldChannel.URI.asciiSpec;
  1039         Services.obs.notifyObservers(observerSubject,
  1040                                      CSP_VIOLATION_TOPIC,
  1041                                      "denied redirect while sending violation report");
  1042       }, Ci.nsIThread.DISPATCH_NORMAL);
  1044     // throw to stop the redirect happening
  1045     throw Cr.NS_BINDING_REDIRECTED;
  1047 };
  1049 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ContentSecurityPolicy]);

mercurial