content/base/src/contentSecurityPolicy.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/content/base/src/contentSecurityPolicy.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1049 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +
     1.9 +/**
    1.10 + * Content Security Policy
    1.11 + *
    1.12 + * Overview
    1.13 + * This is a stub component that will be fleshed out to do all the fancy stuff
    1.14 + * that ContentSecurityPolicy has to do.
    1.15 + */
    1.16 +
    1.17 +// these identifiers are also defined in contentSecurityPolicy.manifest.
    1.18 +const CSP_CLASS_ID = Components.ID("{d1680bb4-1ac0-4772-9437-1188375e44f2}");
    1.19 +const CSP_CONTRACT_ID = "@mozilla.org/contentsecuritypolicy;1";
    1.20 +
    1.21 +/* :::::::: Constants and Helpers ::::::::::::::: */
    1.22 +
    1.23 +const Cc = Components.classes;
    1.24 +const Ci = Components.interfaces;
    1.25 +const Cr = Components.results;
    1.26 +const Cu = Components.utils;
    1.27 +
    1.28 +const CSP_VIOLATION_TOPIC = "csp-on-violate-policy";
    1.29 +
    1.30 +// Needed to support CSP 1.0 spec and our original CSP implementation - should
    1.31 +// be removed when our original implementation is deprecated.
    1.32 +const CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT = "csp_type_xmlhttprequest_spec_compliant";
    1.33 +const CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT = "csp_type_websocket_spec_compliant";
    1.34 +
    1.35 +const WARN_FLAG = Ci.nsIScriptError.warningFlag;
    1.36 +const ERROR_FLAG = Ci.nsIScriptError.ERROR_FLAG;
    1.37 +
    1.38 +const INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Stylesheets will not apply';
    1.39 +const INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Inline Scripts will not execute';
    1.40 +const EVAL_VIOLATION_OBSERVER_SUBJECT = 'violated base restriction: Code will not be created from strings';
    1.41 +const SCRIPT_NONCE_VIOLATION_OBSERVER_SUBJECT = 'Inline Script had invalid nonce'
    1.42 +const STYLE_NONCE_VIOLATION_OBSERVER_SUBJECT = 'Inline Style had invalid nonce'
    1.43 +const SCRIPT_HASH_VIOLATION_OBSERVER_SUBJECT = 'Inline Script had invalid hash';
    1.44 +const STYLE_HASH_VIOLATION_OBSERVER_SUBJECT = 'Inline Style had invalid hash';
    1.45 +
    1.46 +// The cutoff length of content location in creating CSP cache key.
    1.47 +const CSP_CACHE_URI_CUTOFF_SIZE = 512;
    1.48 +
    1.49 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.50 +Cu.import("resource://gre/modules/Services.jsm");
    1.51 +Cu.import("resource://gre/modules/CSPUtils.jsm");
    1.52 +
    1.53 +/* ::::: Policy Parsing & Data structures :::::: */
    1.54 +
    1.55 +function ContentSecurityPolicy() {
    1.56 +  CSPdebug("CSP CREATED");
    1.57 +  this._isInitialized = false;
    1.58 +
    1.59 +  this._policies = [];
    1.60 +
    1.61 +  this._request = "";
    1.62 +  this._requestOrigin = "";
    1.63 +  this._weakRequestPrincipal =  { get : function() { return null; } };
    1.64 +  this._referrer = "";
    1.65 +  this._weakDocRequest = { get : function() { return null; } };
    1.66 +  CSPdebug("CSP object initialized, no policies to enforce yet");
    1.67 +
    1.68 +  this._cache = new Map();
    1.69 +}
    1.70 +
    1.71 +/*
    1.72 + * Set up mappings from nsIContentPolicy content types to CSP directives.
    1.73 + */
    1.74 +{
    1.75 +  let cp = Ci.nsIContentPolicy;
    1.76 +  let csp = ContentSecurityPolicy;
    1.77 +  let cspr_sd_old = CSPRep.SRC_DIRECTIVES_OLD;
    1.78 +  let cspr_sd_new = CSPRep.SRC_DIRECTIVES_NEW;
    1.79 +
    1.80 +  csp._MAPPINGS=[];
    1.81 +
    1.82 +  /* default, catch-all case */
    1.83 +  // This is the same in old and new CSP so use the new mapping.
    1.84 +  csp._MAPPINGS[cp.TYPE_OTHER]             =  cspr_sd_new.DEFAULT_SRC;
    1.85 +
    1.86 +  /* self */
    1.87 +  csp._MAPPINGS[cp.TYPE_DOCUMENT]          =  null;
    1.88 +
    1.89 +  /* shouldn't see this one */
    1.90 +  csp._MAPPINGS[cp.TYPE_REFRESH]           =  null;
    1.91 +
    1.92 +  /* categorized content types */
    1.93 +  // These are the same in old and new CSP's so just use the new mappings.
    1.94 +  csp._MAPPINGS[cp.TYPE_SCRIPT]            = cspr_sd_new.SCRIPT_SRC;
    1.95 +  csp._MAPPINGS[cp.TYPE_IMAGE]             = cspr_sd_new.IMG_SRC;
    1.96 +  csp._MAPPINGS[cp.TYPE_STYLESHEET]        = cspr_sd_new.STYLE_SRC;
    1.97 +  csp._MAPPINGS[cp.TYPE_OBJECT]            = cspr_sd_new.OBJECT_SRC;
    1.98 +  csp._MAPPINGS[cp.TYPE_OBJECT_SUBREQUEST] = cspr_sd_new.OBJECT_SRC;
    1.99 +  csp._MAPPINGS[cp.TYPE_SUBDOCUMENT]       = cspr_sd_new.FRAME_SRC;
   1.100 +  csp._MAPPINGS[cp.TYPE_MEDIA]             = cspr_sd_new.MEDIA_SRC;
   1.101 +  csp._MAPPINGS[cp.TYPE_FONT]              = cspr_sd_new.FONT_SRC;
   1.102 +  csp._MAPPINGS[cp.TYPE_XSLT]              = cspr_sd_new.SCRIPT_SRC;
   1.103 +  csp._MAPPINGS[cp.TYPE_BEACON]            = cspr_sd_new.CONNECT_SRC;
   1.104 +
   1.105 +  /* Our original CSP implementation's mappings for XHR and websocket
   1.106 +   * These should be changed to be = cspr_sd.CONNECT_SRC when we remove
   1.107 +   * the original implementation - NOTE: order in this array is important !!!
   1.108 +   */
   1.109 +  csp._MAPPINGS[cp.TYPE_XMLHTTPREQUEST]    = cspr_sd_old.XHR_SRC;
   1.110 +  csp._MAPPINGS[cp.TYPE_WEBSOCKET]         = cspr_sd_old.XHR_SRC;
   1.111 +
   1.112 +  /* CSP cannot block CSP reports */
   1.113 +  csp._MAPPINGS[cp.TYPE_CSP_REPORT]        = null;
   1.114 +
   1.115 +  /* These must go through the catch-all */
   1.116 +  csp._MAPPINGS[cp.TYPE_XBL]               = cspr_sd_new.DEFAULT_SRC;
   1.117 +  csp._MAPPINGS[cp.TYPE_PING]              = cspr_sd_new.DEFAULT_SRC;
   1.118 +  csp._MAPPINGS[cp.TYPE_DTD]               = cspr_sd_new.DEFAULT_SRC;
   1.119 +
   1.120 +  /* CSP 1.0 spec compliant mappings for XHR and websocket */
   1.121 +  // The directive name for XHR, websocket, and EventSource is different
   1.122 +  // in the 1.0 spec than in our original implementation, these mappings
   1.123 +  // address this. These won't be needed when we deprecate our original
   1.124 +  // implementation.
   1.125 +  csp._MAPPINGS[CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT]    = cspr_sd_new.CONNECT_SRC;
   1.126 +  csp._MAPPINGS[CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT]         = cspr_sd_new.CONNECT_SRC;
   1.127 +  // TODO : EventSource will be here and also will use connect-src
   1.128 +  // after we fix Bug 802872 - CSP should restrict EventSource using the connect-src
   1.129 +  // directive. For background see Bug 667490 - EventSource should use the same
   1.130 +  // nsIContentPolicy type as XHR (which is fixed)
   1.131 +}
   1.132 +
   1.133 +ContentSecurityPolicy.prototype = {
   1.134 +  classID:          CSP_CLASS_ID,
   1.135 +  QueryInterface:   XPCOMUtils.generateQI([Ci.nsIContentSecurityPolicy,
   1.136 +                                           Ci.nsISerializable,
   1.137 +                                           Ci.nsISupports]),
   1.138 +
   1.139 +  // Class info is required to be able to serialize
   1.140 +  classInfo: XPCOMUtils.generateCI({classID: CSP_CLASS_ID,
   1.141 +                                    contractID: CSP_CONTRACT_ID,
   1.142 +                                    interfaces: [Ci.nsIContentSecurityPolicy,
   1.143 +                                                 Ci.nsISerializable],
   1.144 +                                    flags: Ci.nsIClassInfo.MAIN_THREAD_ONLY}),
   1.145 +
   1.146 +  get isInitialized() {
   1.147 +    return this._isInitialized;
   1.148 +  },
   1.149 +
   1.150 +  set isInitialized (foo) {
   1.151 +    this._isInitialized = foo;
   1.152 +  },
   1.153 +
   1.154 +  _getPolicyInternal: function(index) {
   1.155 +    if (index < 0 || index >= this._policies.length) {
   1.156 +      throw Cr.NS_ERROR_FAILURE;
   1.157 +    }
   1.158 +    return this._policies[index];
   1.159 +  },
   1.160 +
   1.161 +  _buildViolatedDirectiveString:
   1.162 +  function(aDirectiveName, aPolicy) {
   1.163 +    var SD = CSPRep.SRC_DIRECTIVES_NEW;
   1.164 +    var cspContext = (SD[aDirectiveName] in aPolicy._directives) ? SD[aDirectiveName] : SD.DEFAULT_SRC;
   1.165 +    var directive = aPolicy._directives[cspContext];
   1.166 +    return cspContext + ' ' + directive.toString();
   1.167 +  },
   1.168 +
   1.169 +  /**
   1.170 +   * Returns policy string representing the policy at "index".
   1.171 +   */
   1.172 +  getPolicy: function(index) {
   1.173 +    return this._getPolicyInternal(index).toString();
   1.174 +  },
   1.175 +
   1.176 +  /**
   1.177 +   * Returns count of policies.
   1.178 +   */
   1.179 +  get numPolicies() {
   1.180 +    return this._policies.length;
   1.181 +  },
   1.182 +
   1.183 +  getAllowsInlineScript: function(shouldReportViolations) {
   1.184 +    // report it? (for each policy, is it violated?)
   1.185 +    shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsInlineScripts; });
   1.186 +
   1.187 +    // allow it to execute?  (Do all the policies allow it to execute)?
   1.188 +    return this._policies.every(function(a) {
   1.189 +      return a._reportOnlyMode || a.allowsInlineScripts;
   1.190 +    });
   1.191 +  },
   1.192 +
   1.193 +  getAllowsEval: function(shouldReportViolations) {
   1.194 +    // report it? (for each policy, is it violated?)
   1.195 +    shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsEvalInScripts; });
   1.196 +
   1.197 +    // allow it to execute?  (Do all the policies allow it to execute)?
   1.198 +    return this._policies.every(function(a) {
   1.199 +      return a._reportOnlyMode || a.allowsEvalInScripts;
   1.200 +    });
   1.201 +  },
   1.202 +
   1.203 +  getAllowsInlineStyle: function(shouldReportViolations) {
   1.204 +    // report it? (for each policy, is it violated?)
   1.205 +    shouldReportViolations.value = this._policies.some(function(a) { return !a.allowsInlineStyles; });
   1.206 +
   1.207 +    // allow it to execute?  (Do all the policies allow it to execute)?
   1.208 +    return this._policies.every(function(a) {
   1.209 +      return a._reportOnlyMode || a.allowsInlineStyles;
   1.210 +    });
   1.211 +  },
   1.212 +
   1.213 +  getAllowsNonce: function(aNonce, aContentType, shouldReportViolation) {
   1.214 +    if (!(aContentType == Ci.nsIContentPolicy.TYPE_SCRIPT ||
   1.215 +          aContentType == Ci.nsIContentPolicy.TYPE_STYLESHEET)) {
   1.216 +      CSPdebug("Nonce check requested for an invalid content type (not script or style): " + aContentType);
   1.217 +      return false;
   1.218 +    }
   1.219 +
   1.220 +    let directive = ContentSecurityPolicy._MAPPINGS[aContentType];
   1.221 +    let policyAllowsNonce = [ policy.permitsNonce(aNonce, directive)
   1.222 +                              for (policy of this._policies) ];
   1.223 +
   1.224 +    shouldReportViolation.value = this._policies.some(function(policy, i) {
   1.225 +      // Don't report a violation if the policy didn't use nonce-source
   1.226 +      return policy._directives.hasOwnProperty(directive) &&
   1.227 +             policy._directives[directive]._hasNonceSource &&
   1.228 +             !policyAllowsNonce[i];
   1.229 +    });
   1.230 +
   1.231 +    // allow it to execute?  (Do all the policies allow it to execute)?
   1.232 +    return this._policies.every(function(policy, i) {
   1.233 +      return policy._reportOnlyMode || policyAllowsNonce[i];
   1.234 +    });
   1.235 +  },
   1.236 +
   1.237 +  getAllowsHash: function(aContent, aContentType, shouldReportViolation) {
   1.238 +    if (!(aContentType == Ci.nsIContentPolicy.TYPE_SCRIPT ||
   1.239 +          aContentType == Ci.nsIContentPolicy.TYPE_STYLESHEET)) {
   1.240 +      CSPdebug("Hash check requested for an invalid content type (not script or style): " + aContentType);
   1.241 +      return false;
   1.242 +    }
   1.243 +
   1.244 +    let directive = ContentSecurityPolicy._MAPPINGS[aContentType];
   1.245 +    let policyAllowsHash = [ policy.permitsHash(aContent, directive)
   1.246 +                             for (policy of this._policies) ];
   1.247 +
   1.248 +    shouldReportViolation.value = this._policies.some(function(policy, i) {
   1.249 +      // Don't report a violation if the policy didn't use hash-source
   1.250 +      return policy._directives.hasOwnProperty(directive) &&
   1.251 +             policy._directives[directive]._hasHashSource &&
   1.252 +             !policyAllowsHash[i];
   1.253 +    });
   1.254 +
   1.255 +    // allow it to execute?  (Do all the policies allow it to execute)?
   1.256 +    return this._policies.every(function(policy, i) {
   1.257 +      return policy._reportOnlyMode || policyAllowsHash[i];
   1.258 +    });
   1.259 +  },
   1.260 +
   1.261 +  /**
   1.262 +   * For each policy, log any violation on the Error Console and send a report
   1.263 +   * if a report-uri is present in the policy
   1.264 +   *
   1.265 +   * @param aViolationType
   1.266 +   *     one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval
   1.267 +   * @param aSourceFile
   1.268 +   *     name of the source file containing the violation (if available)
   1.269 +   * @param aContentSample
   1.270 +   *     sample of the violating content (to aid debugging)
   1.271 +   * @param aLineNum
   1.272 +   *     source line number of the violation (if available)
   1.273 +   * @param aNonce
   1.274 +   *     (optional) If this is a nonce violation, include the nonce so we can
   1.275 +   *     recheck to determine which policies were violated and send the
   1.276 +   *     appropriate reports.
   1.277 +   * @param aContent
   1.278 +   *     (optional) If this is a hash violation, include contents of the inline
   1.279 +   *     resource in the question so we can recheck the hash in order to
   1.280 +   *     determine which policies were violated and send the appropriate
   1.281 +   *     reports.
   1.282 +   */
   1.283 +  logViolationDetails:
   1.284 +  function(aViolationType, aSourceFile, aScriptSample, aLineNum, aNonce, aContent) {
   1.285 +    for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) {
   1.286 +      let policy = this._policies[policyIndex];
   1.287 +
   1.288 +      // call-sites to the eval/inline checks recieve two return values: allows
   1.289 +      // and violates.  Policies that are report-only allow the
   1.290 +      // loads/compilations but violations should still be reported.  Not all
   1.291 +      // policies in this nsIContentSecurityPolicy instance will be violated,
   1.292 +      // which is why we must check again here.
   1.293 +      switch (aViolationType) {
   1.294 +      case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_STYLE:
   1.295 +        if (!policy.allowsInlineStyles) {
   1.296 +          var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy);
   1.297 +          this._asyncReportViolation('self', null, violatedDirective, policyIndex,
   1.298 +                                    INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT,
   1.299 +                                    aSourceFile, aScriptSample, aLineNum);
   1.300 +        }
   1.301 +        break;
   1.302 +      case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_INLINE_SCRIPT:
   1.303 +        if (!policy.allowsInlineScripts)    {
   1.304 +          var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
   1.305 +          this._asyncReportViolation('self', null, violatedDirective, policyIndex,
   1.306 +                                    INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT,
   1.307 +                                    aSourceFile, aScriptSample, aLineNum);
   1.308 +          }
   1.309 +        break;
   1.310 +      case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_EVAL:
   1.311 +        if (!policy.allowsEvalInScripts) {
   1.312 +          var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
   1.313 +          this._asyncReportViolation('self', null, violatedDirective, policyIndex,
   1.314 +                                    EVAL_VIOLATION_OBSERVER_SUBJECT,
   1.315 +                                    aSourceFile, aScriptSample, aLineNum);
   1.316 +        }
   1.317 +        break;
   1.318 +      case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_NONCE_SCRIPT:
   1.319 +        var scriptType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_SCRIPT];
   1.320 +        if (!policy.permitsNonce(aNonce, scriptType)) {
   1.321 +          var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
   1.322 +          this._asyncReportViolation('self', null, violatedDirective, policyIndex,
   1.323 +                                     SCRIPT_NONCE_VIOLATION_OBSERVER_SUBJECT,
   1.324 +                                     aSourceFile, aScriptSample, aLineNum);
   1.325 +        }
   1.326 +        break;
   1.327 +      case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_NONCE_STYLE:
   1.328 +        var styleType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_STYLE];
   1.329 +        if (!policy.permitsNonce(aNonce, styleType)) {
   1.330 +          var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy);
   1.331 +          this._asyncReportViolation('self', null, violatedDirective, policyIndex,
   1.332 +                                     STYLE_NONCE_VIOLATION_OBSERVER_SUBJECT,
   1.333 +                                     aSourceFile, aScriptSample, aLineNum);
   1.334 +        }
   1.335 +        break;
   1.336 +      case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_HASH_SCRIPT:
   1.337 +        var scriptType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_SCRIPT];
   1.338 +        if (!policy.permitsHash(aContent, scriptType)) {
   1.339 +          var violatedDirective = this._buildViolatedDirectiveString('SCRIPT_SRC', policy);
   1.340 +          this._asyncReportViolation('self', null, violatedDirective, policyIndex,
   1.341 +                                     SCRIPT_HASH_VIOLATION_OBSERVER_SUBJECT,
   1.342 +                                     aSourceFile, aScriptSample, aLineNum);
   1.343 +        }
   1.344 +        break;
   1.345 +      case Ci.nsIContentSecurityPolicy.VIOLATION_TYPE_HASH_STYLE:
   1.346 +        var styleType = ContentSecurityPolicy._MAPPINGS[Ci.nsIContentPolicy.TYPE_STYLE];
   1.347 +        if (!policy.permitsHash(aContent, styleType)) {
   1.348 +          var violatedDirective = this._buildViolatedDirectiveString('STYLE_SRC', policy);
   1.349 +          this._asyncReportViolation('self', null, violatedDirective, policyIndex,
   1.350 +                                     STYLE_HASH_VIOLATION_OBSERVER_SUBJECT,
   1.351 +                                     aSourceFile, aScriptSample, aLineNum);
   1.352 +        }
   1.353 +        break;
   1.354 +      }
   1.355 +    }
   1.356 +  },
   1.357 +
   1.358 +  /**
   1.359 +   * Given an nsIHttpChannel, fill out the appropriate data.
   1.360 +   */
   1.361 +  setRequestContext:
   1.362 +  function(aSelfURI, aReferrerURI, aPrincipal, aChannel) {
   1.363 +
   1.364 +    // this requires either a self URI or a http channel.
   1.365 +    if (!aSelfURI && !aChannel)
   1.366 +      return;
   1.367 +
   1.368 +    if (aChannel) {
   1.369 +      // Save the docRequest for fetching a policy-uri
   1.370 +      this._weakDocRequest = Cu.getWeakReference(aChannel);
   1.371 +    }
   1.372 +
   1.373 +    // save the document URI (minus <fragment>) and referrer for reporting
   1.374 +    let uri = aSelfURI ? aSelfURI.cloneIgnoringRef() : aChannel.URI.cloneIgnoringRef();
   1.375 +    try { // GetUserPass throws for some protocols without userPass
   1.376 +      uri.userPass = '';
   1.377 +    } catch (ex) {}
   1.378 +    this._request = uri.asciiSpec;
   1.379 +    this._requestOrigin = uri;
   1.380 +
   1.381 +    // store a reference to the principal, that can later be used in shouldLoad
   1.382 +    if (aPrincipal) {
   1.383 +      this._weakRequestPrincipal = Cu.getWeakReference(aPrincipal);
   1.384 +    } else if (aChannel) {
   1.385 +      this._weakRequestPrincipal = Cu.getWeakReference(Cc["@mozilla.org/scriptsecuritymanager;1"]
   1.386 +                                                         .getService(Ci.nsIScriptSecurityManager)
   1.387 +                                                         .getChannelPrincipal(aChannel));
   1.388 +    } else {
   1.389 +      CSPdebug("No principal or channel for document context; violation reports may not work.");
   1.390 +    }
   1.391 +
   1.392 +    // pick one: referrerURI, channel's referrer, or null (first available)
   1.393 +    let ref = null;
   1.394 +    if (aReferrerURI)
   1.395 +      ref = aReferrerURI;
   1.396 +    else if (aChannel instanceof Ci.nsIHttpChannel)
   1.397 +      ref = aChannel.referrer;
   1.398 +
   1.399 +    if (ref) {
   1.400 +      let referrer = aChannel.referrer.cloneIgnoringRef();
   1.401 +      try { // GetUserPass throws for some protocols without userPass
   1.402 +        referrer.userPass = '';
   1.403 +      } catch (ex) {}
   1.404 +      this._referrer = referrer.asciiSpec;
   1.405 +    }
   1.406 +  },
   1.407 +
   1.408 +/* ........ Methods .............. */
   1.409 +
   1.410 +  /**
   1.411 +   * Adds a new policy to our list of policies for this CSP context.
   1.412 +   * @returns the count of policies.
   1.413 +   */
   1.414 +  appendPolicy:
   1.415 +  function csp_appendPolicy(aPolicy, selfURI, aReportOnly, aSpecCompliant) {
   1.416 +    return this._appendPolicyInternal(aPolicy, selfURI, aReportOnly,
   1.417 +                                      aSpecCompliant, true);
   1.418 +  },
   1.419 +
   1.420 +  /**
   1.421 +   * Adds a new policy to our list of policies for this CSP context.
   1.422 +   * Only to be called from this module (not exported)
   1.423 +   * @returns the count of policies.
   1.424 +   */
   1.425 +  _appendPolicyInternal:
   1.426 +  function csp_appendPolicy(aPolicy, selfURI, aReportOnly, aSpecCompliant,
   1.427 +                            aEnforceSelfChecks) {
   1.428 +#ifndef MOZ_B2G
   1.429 +    CSPdebug("APPENDING POLICY: " + aPolicy);
   1.430 +    CSPdebug("            SELF: " + (selfURI ? selfURI.asciiSpec : " null"));
   1.431 +    CSPdebug("CSP 1.0 COMPLIANT : " + aSpecCompliant);
   1.432 +#endif
   1.433 +
   1.434 +    // For nested schemes such as view-source: make sure we are taking the
   1.435 +    // innermost URI to use as 'self' since that's where we will extract the
   1.436 +    // scheme, host and port from
   1.437 +    if (selfURI instanceof Ci.nsINestedURI) {
   1.438 +#ifndef MOZ_B2G
   1.439 +      CSPdebug("        INNER: " + selfURI.innermostURI.asciiSpec);
   1.440 +#endif
   1.441 +      selfURI = selfURI.innermostURI;
   1.442 +    }
   1.443 +
   1.444 +    // stay uninitialized until policy setup is done
   1.445 +    var newpolicy;
   1.446 +
   1.447 +    // If there is a policy-uri, fetch the policy, then re-call this function.
   1.448 +    // (1) parse and create a CSPRep object
   1.449 +    // Note that we pass the full URI since when it's parsed as 'self' to construct a
   1.450 +    // CSPSource only the scheme, host, and port are kept.
   1.451 +
   1.452 +    // If we want to be CSP 1.0 spec compliant, use the new parser.
   1.453 +    // The old one will be deprecated in the future and will be
   1.454 +    // removed at that time.
   1.455 +    if (aSpecCompliant) {
   1.456 +      newpolicy = CSPRep.fromStringSpecCompliant(aPolicy,
   1.457 +                                                 selfURI,
   1.458 +                                                 aReportOnly,
   1.459 +                                                 this._weakDocRequest.get(),
   1.460 +                                                 this,
   1.461 +                                                 aEnforceSelfChecks);
   1.462 +    } else {
   1.463 +      newpolicy = CSPRep.fromString(aPolicy,
   1.464 +                                    selfURI,
   1.465 +                                    aReportOnly,
   1.466 +                                    this._weakDocRequest.get(),
   1.467 +                                    this,
   1.468 +                                    aEnforceSelfChecks);
   1.469 +    }
   1.470 +
   1.471 +    newpolicy._specCompliant = !!aSpecCompliant;
   1.472 +    newpolicy._isInitialized = true;
   1.473 +    this._policies.push(newpolicy);
   1.474 +    this._cache.clear(); // reset cache since effective policy changes
   1.475 +  },
   1.476 +
   1.477 +  /**
   1.478 +   * Removes a policy from the array of policies.
   1.479 +   */
   1.480 +  removePolicy:
   1.481 +  function csp_removePolicy(index) {
   1.482 +    if (index < 0 || index >= this._policies.length) {
   1.483 +      CSPdebug("Cannot remove policy " + index + "; not enough policies.");
   1.484 +      return;
   1.485 +    }
   1.486 +    this._policies.splice(index, 1);
   1.487 +    this._cache.clear(); // reset cache since effective policy changes
   1.488 +  },
   1.489 +
   1.490 +  /**
   1.491 +   * Generates and sends a violation report to the specified report URIs.
   1.492 +   */
   1.493 +  sendReports:
   1.494 +  function(blockedUri, originalUri, violatedDirective,
   1.495 +           violatedPolicyIndex, aSourceFile,
   1.496 +           aScriptSample, aLineNum) {
   1.497 +
   1.498 +    let policy = this._getPolicyInternal(violatedPolicyIndex);
   1.499 +    if (!policy) {
   1.500 +      CSPdebug("ERROR in SendReports: policy " + violatedPolicyIndex + " is not defined.");
   1.501 +      return;
   1.502 +    }
   1.503 +
   1.504 +    var uriString = policy.getReportURIs();
   1.505 +    var uris = uriString.split(/\s+/);
   1.506 +    if (uris.length > 0) {
   1.507 +      // see if we need to sanitize the blocked-uri
   1.508 +      let blocked = '';
   1.509 +      if (originalUri) {
   1.510 +        // We've redirected, only report the blocked origin
   1.511 +        try {
   1.512 +          let clone = blockedUri.clone();
   1.513 +          clone.path = '';
   1.514 +          blocked = clone.asciiSpec;
   1.515 +        } catch(e) {
   1.516 +          CSPdebug(".... blockedUri can't be cloned: " + blockedUri);
   1.517 +        }
   1.518 +      }
   1.519 +      else if (blockedUri instanceof Ci.nsIURI) {
   1.520 +        blocked = blockedUri.cloneIgnoringRef().asciiSpec;
   1.521 +      }
   1.522 +      else {
   1.523 +        // blockedUri is a string for eval/inline-script violations
   1.524 +        blocked = blockedUri;
   1.525 +      }
   1.526 +
   1.527 +      // Generate report to send composed of
   1.528 +      // {
   1.529 +      //   csp-report: {
   1.530 +      //     document-uri: "http://example.com/file.html?params",
   1.531 +      //     referrer: "...",
   1.532 +      //     blocked-uri: "...",
   1.533 +      //     violated-directive: "..."
   1.534 +      //   }
   1.535 +      // }
   1.536 +      var report = {
   1.537 +        'csp-report': {
   1.538 +          'document-uri': this._request,
   1.539 +          'referrer': this._referrer,
   1.540 +          'blocked-uri': blocked,
   1.541 +          'violated-directive': violatedDirective
   1.542 +        }
   1.543 +      }
   1.544 +
   1.545 +      // extra report fields for script errors (if available)
   1.546 +      if (originalUri)
   1.547 +        report["csp-report"]["original-uri"] = originalUri.cloneIgnoringRef().asciiSpec;
   1.548 +      if (aSourceFile)
   1.549 +        report["csp-report"]["source-file"] = aSourceFile;
   1.550 +      if (aScriptSample)
   1.551 +        report["csp-report"]["script-sample"] = aScriptSample;
   1.552 +      if (aLineNum)
   1.553 +        report["csp-report"]["line-number"] = aLineNum;
   1.554 +
   1.555 +      var reportString = JSON.stringify(report);
   1.556 +      CSPdebug("Constructed violation report:\n" + reportString);
   1.557 +
   1.558 +      // For each URI in the report list, send out a report.
   1.559 +      // We make the assumption that all of the URIs are absolute URIs; this
   1.560 +      // should be taken care of in CSPRep.fromString (where it converts any
   1.561 +      // relative URIs into absolute ones based on "self").
   1.562 +      for (let i in uris) {
   1.563 +        if (uris[i] === "")
   1.564 +          continue;
   1.565 +
   1.566 +        try {
   1.567 +          var chan = Services.io.newChannel(uris[i], null, null);
   1.568 +          if (!chan) {
   1.569 +            CSPdebug("Error creating channel for " + uris[i]);
   1.570 +            continue;
   1.571 +          }
   1.572 +
   1.573 +          var content = Cc["@mozilla.org/io/string-input-stream;1"]
   1.574 +                          .createInstance(Ci.nsIStringInputStream);
   1.575 +          content.data = reportString + "\n\n";
   1.576 +
   1.577 +          // make sure this is an anonymous request (no cookies) so in case the
   1.578 +          // policy URI is injected, it can't be abused for CSRF.
   1.579 +          chan.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS;
   1.580 +
   1.581 +          // we need to set an nsIChannelEventSink on the channel object
   1.582 +          // so we can tell it to not follow redirects when posting the reports
   1.583 +          chan.notificationCallbacks = new CSPReportRedirectSink(policy);
   1.584 +          if (this._weakDocRequest.get()) {
   1.585 +            chan.loadGroup = this._weakDocRequest.get().loadGroup;
   1.586 +          }
   1.587 +
   1.588 +          chan.QueryInterface(Ci.nsIUploadChannel)
   1.589 +              .setUploadStream(content, "application/json", content.available());
   1.590 +
   1.591 +          try {
   1.592 +            // if this is an HTTP channel, set the request method to post
   1.593 +            chan.QueryInterface(Ci.nsIHttpChannel);
   1.594 +            chan.requestMethod = "POST";
   1.595 +          } catch(e) {} // throws only if chan is not an nsIHttpChannel.
   1.596 +
   1.597 +          // check with the content policy service to see if we're allowed to
   1.598 +          // send this request.
   1.599 +          try {
   1.600 +            var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"]
   1.601 +                                  .getService(Ci.nsIContentPolicy);
   1.602 +            if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_CSP_REPORT,
   1.603 +                                         chan.URI, this._requestOrigin,
   1.604 +                                         null, null, null, this._weakRequestPrincipal.get())
   1.605 +                != Ci.nsIContentPolicy.ACCEPT) {
   1.606 +              continue; // skip unauthorized URIs
   1.607 +            }
   1.608 +          } catch(e) {
   1.609 +            continue; // refuse to load if we can't do a security check.
   1.610 +          }
   1.611 +
   1.612 +          //send data (and set up error notifications)
   1.613 +          chan.asyncOpen(new CSPViolationReportListener(uris[i]), null);
   1.614 +          CSPdebug("Sent violation report to " + uris[i]);
   1.615 +        } catch(e) {
   1.616 +          // it's possible that the URI was invalid, just log a
   1.617 +          // warning and skip over that.
   1.618 +          policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("triedToSendReport", [uris[i]]));
   1.619 +          policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("errorWas", [e.toString()]));
   1.620 +        }
   1.621 +      }
   1.622 +    }
   1.623 +  },
   1.624 +
   1.625 +  /**
   1.626 +   * Logs a meaningful CSP warning to the developer console.
   1.627 +   */
   1.628 +  logToConsole:
   1.629 +  function(blockedUri, originalUri, violatedDirective, aViolatedPolicyIndex,
   1.630 +           aSourceFile, aScriptSample, aLineNum, aObserverSubject) {
   1.631 +     let policy = this._policies[aViolatedPolicyIndex];
   1.632 +     switch(aObserverSubject.data) {
   1.633 +      case INLINE_STYLE_VIOLATION_OBSERVER_SUBJECT:
   1.634 +        violatedDirective = CSPLocalizer.getStr("inlineStyleBlocked");
   1.635 +        break;
   1.636 +      case INLINE_SCRIPT_VIOLATION_OBSERVER_SUBJECT:
   1.637 +        violatedDirective = CSPLocalizer.getStr("inlineScriptBlocked");
   1.638 +        break;
   1.639 +      case EVAL_VIOLATION_OBSERVER_SUBJECT:
   1.640 +        violatedDirective = CSPLocalizer.getStr("scriptFromStringBlocked");
   1.641 +        break;
   1.642 +    }
   1.643 +    var violationMessage = null;
   1.644 +    if (blockedUri["asciiSpec"]) {
   1.645 +      violationMessage = CSPLocalizer.getFormatStr("CSPViolationWithURI", [violatedDirective, blockedUri.asciiSpec]);
   1.646 +    } else {
   1.647 +      violationMessage = CSPLocalizer.getFormatStr("CSPViolation", [violatedDirective]);
   1.648 +    }
   1.649 +    policy.log(WARN_FLAG, violationMessage,
   1.650 +               (aSourceFile) ? aSourceFile : null,
   1.651 +               (aScriptSample) ? decodeURIComponent(aScriptSample) : null,
   1.652 +               (aLineNum) ? aLineNum : null);
   1.653 +  },
   1.654 +
   1.655 +/**
   1.656 +   * Exposed Method to analyze docShell for approved frame ancestry.
   1.657 +   * NOTE: Also sends violation reports if necessary.
   1.658 +   * @param docShell
   1.659 +   *    the docShell for this policy's resource.
   1.660 +   * @return
   1.661 +   *    true if the frame ancestry is allowed by this policy and the load
   1.662 +   *    should progress.
   1.663 +   */
   1.664 +  permitsAncestry:
   1.665 +  function(docShell) {
   1.666 +    // Cannot shortcut checking all the policies since violation reports have
   1.667 +    // to be triggered if any policy wants it.
   1.668 +    var permitted = true;
   1.669 +    for (let i = 0; i < this._policies.length; i++) {
   1.670 +      if (!this._permitsAncestryInternal(docShell, this._policies[i], i)) {
   1.671 +        permitted = false;
   1.672 +      }
   1.673 +    }
   1.674 +    return permitted;
   1.675 +  },
   1.676 +
   1.677 +  _permitsAncestryInternal:
   1.678 +  function(docShell, policy, policyIndex) {
   1.679 +    if (!docShell) { return false; }
   1.680 +
   1.681 +    // walk up this docShell tree until we hit chrome
   1.682 +    var dst = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
   1.683 +                      .getInterface(Ci.nsIDocShellTreeItem);
   1.684 +
   1.685 +    // collect ancestors and make sure they're allowed.
   1.686 +    var ancestors = [];
   1.687 +    while (dst.parent) {
   1.688 +      dst = dst.parent;
   1.689 +      let it = dst.QueryInterface(Ci.nsIInterfaceRequestor)
   1.690 +                  .getInterface(Ci.nsIWebNavigation);
   1.691 +      if (it.currentURI) {
   1.692 +        if (it.currentURI.scheme === "chrome") {
   1.693 +          break;
   1.694 +        }
   1.695 +        // delete any userpass
   1.696 +        let ancestor = it.currentURI.cloneIgnoringRef();
   1.697 +        try { // GetUserPass throws for some protocols without userPass
   1.698 +          ancestor.userPass = '';
   1.699 +        } catch (ex) {}
   1.700 +
   1.701 +#ifndef MOZ_B2G
   1.702 +        CSPdebug(" found frame ancestor " + ancestor.asciiSpec);
   1.703 +#endif
   1.704 +        ancestors.push(ancestor);
   1.705 +      }
   1.706 +    }
   1.707 +
   1.708 +    // scan the discovered ancestors
   1.709 +    // frame-ancestors is the same in both old and new source directives,
   1.710 +    // so don't need to differentiate here.
   1.711 +    let cspContext = CSPRep.SRC_DIRECTIVES_NEW.FRAME_ANCESTORS;
   1.712 +    for (let i in ancestors) {
   1.713 +      let ancestor = ancestors[i];
   1.714 +      if (!policy.permits(ancestor, cspContext)) {
   1.715 +        // report the frame-ancestor violation
   1.716 +        let directive = policy._directives[cspContext];
   1.717 +        let violatedPolicy = 'frame-ancestors ' + directive.toString();
   1.718 +
   1.719 +        this._asyncReportViolation(ancestors[i], null, violatedPolicy,
   1.720 +                                   policyIndex);
   1.721 +
   1.722 +        // need to lie if we are testing in report-only mode
   1.723 +        return policy._reportOnlyMode;
   1.724 +      }
   1.725 +    }
   1.726 +    return true;
   1.727 +  },
   1.728 +
   1.729 +  /**
   1.730 +   * Creates a cache key from content location and content type.
   1.731 +   */
   1.732 +  _createCacheKey:
   1.733 +  function (aContentLocation, aContentType) {
   1.734 +    if (aContentType != Ci.nsIContentPolicy.TYPE_SCRIPT &&
   1.735 +        aContentLocation.scheme == "data") {
   1.736 +      // For non-script data: URI, use ("data:", aContentType) as the cache key.
   1.737 +      return aContentLocation.scheme + ":" + aContentType;
   1.738 +    }
   1.739 +
   1.740 +    let uri = aContentLocation.spec;
   1.741 +    if (uri.length > CSP_CACHE_URI_CUTOFF_SIZE) {
   1.742 +      // Don't cache for a URI longer than the cutoff size.
   1.743 +      return null;
   1.744 +    }
   1.745 +    return uri + "!" + aContentType;
   1.746 +  },
   1.747 +
   1.748 +  /**
   1.749 +   * Delegate method called by the service when sub-elements of the protected
   1.750 +   * document are being loaded.  Given a bit of information about the request,
   1.751 +   * decides whether or not the policy is satisfied.
   1.752 +   */
   1.753 +  shouldLoad:
   1.754 +  function csp_shouldLoad(aContentType,
   1.755 +                          aContentLocation,
   1.756 +                          aRequestOrigin,
   1.757 +                          aContext,
   1.758 +                          aMimeTypeGuess,
   1.759 +                          aOriginalUri) {
   1.760 +    let key = this._createCacheKey(aContentLocation, aContentType);
   1.761 +    if (key && this._cache.has(key)) {
   1.762 +      return this._cache.get(key);
   1.763 +    }
   1.764 +
   1.765 +#ifndef MOZ_B2G
   1.766 +    // Try to remove as much as possible from the hot path on b2g.
   1.767 +    CSPdebug("shouldLoad location = " + aContentLocation.asciiSpec);
   1.768 +    CSPdebug("shouldLoad content type = " + aContentType);
   1.769 +#endif
   1.770 +
   1.771 +    // The mapping for XHR and websockets is different between our original
   1.772 +    // implementation and the 1.0 spec, we handle this here.
   1.773 +    var cspContext;
   1.774 +
   1.775 +    let cp = Ci.nsIContentPolicy;
   1.776 +
   1.777 +    // Infer if this is a preload for elements that use nonce-source. Since,
   1.778 +    // for preloads, aContext is the document and not the element associated
   1.779 +    // with the resource, we cannot determine the nonce. See Bug 612921 and
   1.780 +    // Bug 855326.
   1.781 +    let nonceSourceValid = aContentType == cp.TYPE_SCRIPT ||
   1.782 +                           aContentType == cp.TYPE_STYLESHEET;
   1.783 +    var possiblePreloadNonceConflict = nonceSourceValid &&
   1.784 +                                       aContext instanceof Ci.nsIDOMHTMLDocument;
   1.785 +
   1.786 +    // iterate through all the _policies and send reports where a policy is
   1.787 +    // violated.  After the check, determine the overall effect (blocked or
   1.788 +    // loaded?) and cache it.
   1.789 +    let policyAllowsLoadArray = [];
   1.790 +    for (let policyIndex=0; policyIndex < this._policies.length; policyIndex++) {
   1.791 +      let policy = this._policies[policyIndex];
   1.792 +
   1.793 +#ifndef MOZ_B2G
   1.794 +      CSPdebug("policy is " + (policy._specCompliant ?
   1.795 +                              "1.0 compliant" : "pre-1.0"));
   1.796 +      CSPdebug("policy is " + (policy._reportOnlyMode ?
   1.797 +                              "report-only" : "blocking"));
   1.798 +#endif
   1.799 +
   1.800 +      if (aContentType == cp.TYPE_XMLHTTPREQUEST && this._policies[policyIndex]._specCompliant) {
   1.801 +        cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_XMLHTTPREQUEST_SPEC_COMPLIANT];
   1.802 +      } else if (aContentType == cp.TYPE_WEBSOCKET && this._policies[policyIndex]._specCompliant) {
   1.803 +        cspContext = ContentSecurityPolicy._MAPPINGS[CSP_TYPE_WEBSOCKET_SPEC_COMPLIANT];
   1.804 +      } else {
   1.805 +        cspContext = ContentSecurityPolicy._MAPPINGS[aContentType];
   1.806 +      }
   1.807 +
   1.808 +#ifndef MOZ_B2G
   1.809 +      CSPdebug("shouldLoad cspContext = " + cspContext);
   1.810 +#endif
   1.811 +
   1.812 +      // if the mapping is null, there's no policy, let it through.
   1.813 +      if (!cspContext) {
   1.814 +        return Ci.nsIContentPolicy.ACCEPT;
   1.815 +      }
   1.816 +
   1.817 +      // check if location is permitted
   1.818 +      let permitted = policy.permits(aContentLocation, cspContext);
   1.819 +
   1.820 +      // check any valid content type for nonce if location is not permitted
   1.821 +      if (!permitted && nonceSourceValid &&
   1.822 +          aContext instanceof Ci.nsIDOMHTMLElement &&
   1.823 +          aContext.hasAttribute('nonce')) {
   1.824 +        permitted = policy.permitsNonce(aContext.getAttribute('nonce'),
   1.825 +                                        cspContext);
   1.826 +      }
   1.827 +
   1.828 +      // record whether the thing should be blocked or just reported.
   1.829 +      policyAllowsLoadArray.push(permitted || policy._reportOnlyMode);
   1.830 +      let res = permitted ? cp.ACCEPT : cp.REJECT_SERVER;
   1.831 +
   1.832 +      // frame-ancestors is taken care of early on (as this document is loaded)
   1.833 +
   1.834 +      // If the result is *NOT* ACCEPT, then send report
   1.835 +      // Do not send report if this is a nonce-source preload - the decision may
   1.836 +      // be wrong and will incorrectly fail the unit tests.
   1.837 +      if (res != cp.ACCEPT && !possiblePreloadNonceConflict) {
   1.838 +        CSPdebug("blocking request for " + aContentLocation.asciiSpec);
   1.839 +        try {
   1.840 +          let directive = "unknown directive",
   1.841 +              violatedPolicy = "unknown policy";
   1.842 +
   1.843 +          // The policy might not explicitly declare each source directive (so
   1.844 +          // the cspContext may be implicit).  If so, we have to report
   1.845 +          // violations as appropriate: specific or the default-src directive.
   1.846 +          if (policy._directives.hasOwnProperty(cspContext)) {
   1.847 +            directive = policy._directives[cspContext];
   1.848 +            violatedPolicy = cspContext + ' ' + directive.toString();
   1.849 +          } else if (policy._directives.hasOwnProperty("default-src")) {
   1.850 +            directive = policy._directives["default-src"];
   1.851 +            violatedPolicy = "default-src " + directive.toString();
   1.852 +          } else {
   1.853 +            violatedPolicy = "unknown directive";
   1.854 +            CSPdebug('ERROR in blocking content: ' +
   1.855 +                    'CSP is not sure which part of the policy caused this block');
   1.856 +          }
   1.857 +
   1.858 +          this._asyncReportViolation(aContentLocation,
   1.859 +                                     aOriginalUri,
   1.860 +                                     violatedPolicy,
   1.861 +                                     policyIndex);
   1.862 +        } catch(e) {
   1.863 +          CSPdebug('---------------- ERROR: ' + e);
   1.864 +        }
   1.865 +      }
   1.866 +    } // end for-each loop over policies
   1.867 +
   1.868 +    // the ultimate decision is based on whether any policies want to reject
   1.869 +    // the load.  The array keeps track of whether the policies allowed the
   1.870 +    // loads. If any doesn't, we'll reject the load (and cache the result).
   1.871 +    let ret = (policyAllowsLoadArray.some(function(a,b) { return !a; }) ?
   1.872 +               cp.REJECT_SERVER : cp.ACCEPT);
   1.873 +
   1.874 +    // Do not cache the result if this is a nonce-source preload
   1.875 +    if (key && !possiblePreloadNonceConflict) {
   1.876 +      this._cache.set(key, ret);
   1.877 +    }
   1.878 +    return ret;
   1.879 +  },
   1.880 +
   1.881 +  shouldProcess:
   1.882 +  function csp_shouldProcess(aContentType,
   1.883 +                             aContentLocation,
   1.884 +                             aRequestOrigin,
   1.885 +                             aContext,
   1.886 +                             aMimeType,
   1.887 +                             aExtra) {
   1.888 +    // frame-ancestors check is done outside the ContentPolicy
   1.889 +    var res = Ci.nsIContentPolicy.ACCEPT;
   1.890 +    CSPdebug("shouldProcess aContext=" + aContext);
   1.891 +    return res;
   1.892 +  },
   1.893 +
   1.894 +  /**
   1.895 +   * Asynchronously notifies any nsIObservers listening to the CSP violation
   1.896 +   * topic that a violation occurred.  Also triggers report sending.  All
   1.897 +   * asynchronous on the main thread.
   1.898 +   *
   1.899 +   * @param aBlockedContentSource
   1.900 +   *        Either a CSP Source (like 'self', as string) or nsIURI: the source
   1.901 +   *        of the violation.
   1.902 +   * @param aOriginalUri
   1.903 +   *        The original URI if the blocked content is a redirect, else null
   1.904 +   * @param aViolatedDirective
   1.905 +   *        the directive that was violated (string).
   1.906 +   * @param aViolatedPolicyIndex
   1.907 +   *        the index of the policy that was violated (so we know where to send
   1.908 +   *        the reports).
   1.909 +   * @param aObserverSubject
   1.910 +   *        optional, subject sent to the nsIObservers listening to the CSP
   1.911 +   *        violation topic.
   1.912 +   * @param aSourceFile
   1.913 +   *        name of the file containing the inline script violation
   1.914 +   * @param aScriptSample
   1.915 +   *        a sample of the violating inline script
   1.916 +   * @param aLineNum
   1.917 +   *        source line number of the violation (if available)
   1.918 +   */
   1.919 +  _asyncReportViolation:
   1.920 +  function(aBlockedContentSource, aOriginalUri, aViolatedDirective,
   1.921 +           aViolatedPolicyIndex, aObserverSubject,
   1.922 +           aSourceFile, aScriptSample, aLineNum) {
   1.923 +    // if optional observerSubject isn't specified, default to the source of
   1.924 +    // the violation.
   1.925 +    if (!aObserverSubject)
   1.926 +      aObserverSubject = aBlockedContentSource;
   1.927 +
   1.928 +    // gotta wrap things that aren't nsISupports, since it's sent out to
   1.929 +    // observers as such.  Objects that are not nsISupports are converted to
   1.930 +    // strings and then wrapped into a nsISupportsCString.
   1.931 +    if (!(aObserverSubject instanceof Ci.nsISupports)) {
   1.932 +      let d = aObserverSubject;
   1.933 +      aObserverSubject = Cc["@mozilla.org/supports-cstring;1"]
   1.934 +                          .createInstance(Ci.nsISupportsCString);
   1.935 +      aObserverSubject.data = d;
   1.936 +    }
   1.937 +
   1.938 +    var reportSender = this;
   1.939 +    Services.tm.mainThread.dispatch(
   1.940 +      function() {
   1.941 +        Services.obs.notifyObservers(aObserverSubject,
   1.942 +                                     CSP_VIOLATION_TOPIC,
   1.943 +                                     aViolatedDirective);
   1.944 +        reportSender.sendReports(aBlockedContentSource, aOriginalUri,
   1.945 +                                 aViolatedDirective, aViolatedPolicyIndex,
   1.946 +                                 aSourceFile, aScriptSample, aLineNum);
   1.947 +        reportSender.logToConsole(aBlockedContentSource, aOriginalUri,
   1.948 +                                  aViolatedDirective, aViolatedPolicyIndex,
   1.949 +                                  aSourceFile, aScriptSample,
   1.950 +                                  aLineNum, aObserverSubject);
   1.951 +
   1.952 +      }, Ci.nsIThread.DISPATCH_NORMAL);
   1.953 +  },
   1.954 +
   1.955 +/* ........ nsISerializable Methods: .............. */
   1.956 +
   1.957 +  read:
   1.958 +  function(aStream) {
   1.959 +
   1.960 +    this._requestOrigin = aStream.readObject(true);
   1.961 +    this._requestOrigin.QueryInterface(Ci.nsIURI);
   1.962 +
   1.963 +    for (let pCount = aStream.read32(); pCount > 0; pCount--) {
   1.964 +      let polStr        = aStream.readString();
   1.965 +      let reportOnly    = aStream.readBoolean();
   1.966 +      let specCompliant = aStream.readBoolean();
   1.967 +      // don't need self info because when the policy is turned back into a
   1.968 +      // string, 'self' is replaced with the explicit source expression.
   1.969 +      this._appendPolicyInternal(polStr, null, reportOnly, specCompliant,
   1.970 +                                 false);
   1.971 +    }
   1.972 +
   1.973 +    // NOTE: the document instance that's deserializing this object (via its
   1.974 +    // principal) should hook itself into this._principal manually.  If they
   1.975 +    // don't, the CSP reports will likely be blocked by nsMixedContentBlocker.
   1.976 +  },
   1.977 +
   1.978 +  write:
   1.979 +  function(aStream) {
   1.980 +    // need to serialize the context: request origin and such. They are
   1.981 +    // used when sending reports.  Since _request and _requestOrigin are just
   1.982 +    // different representations of the same thing, only save _requestOrigin
   1.983 +    // (an nsIURI).
   1.984 +    aStream.writeCompoundObject(this._requestOrigin, Ci.nsIURI, true);
   1.985 +
   1.986 +    // we can't serialize a reference to the principal that triggered this
   1.987 +    // instance to serialize, so when this is deserialized by the principal the
   1.988 +    // caller must hook it up manually by calling setRequestContext on this
   1.989 +    // instance with the appropriate nsIChannel.
   1.990 +
   1.991 +    // Finally, serialize all the policies.
   1.992 +    aStream.write32(this._policies.length);
   1.993 +
   1.994 +    for each (var policy in this._policies) {
   1.995 +      aStream.writeWStringZ(policy.toString());
   1.996 +      aStream.writeBoolean(policy._reportOnlyMode);
   1.997 +      aStream.writeBoolean(policy._specCompliant);
   1.998 +    }
   1.999 +  },
  1.1000 +};
  1.1001 +
  1.1002 +// The POST of the violation report (if it happens) should not follow
  1.1003 +// redirects, per the spec. hence, we implement an nsIChannelEventSink
  1.1004 +// with an object so we can tell XHR to abort if a redirect happens.
  1.1005 +function CSPReportRedirectSink(policy) {
  1.1006 +  this._policy = policy;
  1.1007 +}
  1.1008 +
  1.1009 +CSPReportRedirectSink.prototype = {
  1.1010 +  QueryInterface: function requestor_qi(iid) {
  1.1011 +    if (iid.equals(Ci.nsISupports) ||
  1.1012 +        iid.equals(Ci.nsIInterfaceRequestor) ||
  1.1013 +        iid.equals(Ci.nsIChannelEventSink))
  1.1014 +      return this;
  1.1015 +    throw Cr.NS_ERROR_NO_INTERFACE;
  1.1016 +  },
  1.1017 +
  1.1018 +  // nsIInterfaceRequestor
  1.1019 +  getInterface: function requestor_gi(iid) {
  1.1020 +    if (iid.equals(Ci.nsIChannelEventSink))
  1.1021 +      return this;
  1.1022 +
  1.1023 +    throw Components.results.NS_ERROR_NO_INTERFACE;
  1.1024 +  },
  1.1025 +
  1.1026 +  // nsIChannelEventSink
  1.1027 +  asyncOnChannelRedirect: function channel_redirect(oldChannel, newChannel,
  1.1028 +                                                    flags, callback) {
  1.1029 +    this._policy.log(WARN_FLAG, CSPLocalizer.getFormatStr("reportPostRedirect", [oldChannel.URI.asciiSpec]));
  1.1030 +
  1.1031 +    // cancel the old channel so XHR failure callback happens
  1.1032 +    oldChannel.cancel(Cr.NS_ERROR_ABORT);
  1.1033 +
  1.1034 +    // notify an observer that we have blocked the report POST due to a redirect,
  1.1035 +    // used in testing, do this async since we're in an async call now to begin with
  1.1036 +    Services.tm.mainThread.dispatch(
  1.1037 +      function() {
  1.1038 +        observerSubject = Cc["@mozilla.org/supports-cstring;1"]
  1.1039 +                             .createInstance(Ci.nsISupportsCString);
  1.1040 +        observerSubject.data = oldChannel.URI.asciiSpec;
  1.1041 +
  1.1042 +        Services.obs.notifyObservers(observerSubject,
  1.1043 +                                     CSP_VIOLATION_TOPIC,
  1.1044 +                                     "denied redirect while sending violation report");
  1.1045 +      }, Ci.nsIThread.DISPATCH_NORMAL);
  1.1046 +
  1.1047 +    // throw to stop the redirect happening
  1.1048 +    throw Cr.NS_BINDING_REDIRECTED;
  1.1049 +  }
  1.1050 +};
  1.1051 +
  1.1052 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ContentSecurityPolicy]);

mercurial