content/base/src/CSPUtils.jsm

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/. */
     5 /**
     6  * Content Security Policy Utilities
     7  *
     8  * Overview
     9  * This contains a set of classes and utilities for CSP.  It is in this
    10  * separate file for testing purposes.
    11  */
    13 const Cu = Components.utils;
    14 const Ci = Components.interfaces;
    16 const WARN_FLAG = Ci.nsIScriptError.warningFlag;
    17 const ERROR_FLAG = Ci.nsIScriptError.ERROR_FLAG;
    19 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    20 Cu.import("resource://gre/modules/Services.jsm");
    22 XPCOMUtils.defineLazyModuleGetter(this, "Services",
    23                                   "resource://gre/modules/Services.jsm");
    25 // Module stuff
    26 this.EXPORTED_SYMBOLS = ["CSPRep", "CSPSourceList", "CSPSource", "CSPHost",
    27                          "CSPdebug", "CSPViolationReportListener", "CSPLocalizer",
    28                          "CSPPrefObserver"];
    30 var STRINGS_URI = "chrome://global/locale/security/csp.properties";
    32 // these are not exported
    33 var gIoService = Components.classes["@mozilla.org/network/io-service;1"]
    34                  .getService(Ci.nsIIOService);
    36 var gETLDService = Components.classes["@mozilla.org/network/effective-tld-service;1"]
    37                    .getService(Ci.nsIEffectiveTLDService);
    39 // These regexps represent the concrete syntax on the w3 spec as of 7-5-2012
    40 // scheme          = <scheme production from RFC 3986>
    41 const R_SCHEME     = new RegExp ("([a-zA-Z0-9\\-]+)", 'i');
    42 const R_GETSCHEME  = new RegExp ("^" + R_SCHEME.source + "(?=\\:)", 'i');
    44 // scheme-source   = scheme ":"
    45 const R_SCHEMESRC  = new RegExp ("^" + R_SCHEME.source + "\\:$", 'i');
    47 // host-char       = ALPHA / DIGIT / "-"
    48 // For the app: protocol, we need to add {} to the valid character set
    49 const HOSTCHAR     = "{}a-zA-Z0-9\\-";
    50 const R_HOSTCHAR   = new RegExp ("[" + HOSTCHAR + "]", 'i');
    52 // Complementary character set of HOSTCHAR (characters that can't appear)
    53 const R_COMP_HCHAR = new RegExp ("[^" + HOSTCHAR + "]", "i");
    55 // Invalid character set for host strings (which can include dots and star)
    56 const R_INV_HCHAR  = new RegExp ("[^" + HOSTCHAR + "\\.\\*]", 'i');
    59 // host            = "*" / [ "*." ] 1*host-char *( "." 1*host-char )
    60 const R_HOST       = new RegExp ("\\*|(((\\*\\.)?" + R_HOSTCHAR.source +
    61                               "+)" + "(\\." + R_HOSTCHAR.source + "+)*)", 'i');
    63 // port            = ":" ( 1*DIGIT / "*" )
    64 const R_PORT       = new RegExp ("(\\:([0-9]+|\\*))", 'i');
    66 // host-source     = [ scheme "://" ] host [ port path file ]
    67 const R_HOSTSRC    = new RegExp ("^((" + R_SCHEME.source + "\\:\\/\\/)?("
    68                                          + R_HOST.source + ")"
    69                                          + R_PORT.source + "?)$", 'i');
    71 function STRIP_INPUTDELIM(re) {
    72   return re.replace(/(^\^)|(\$$)/g, "");
    73 }
    75 // ext-host-source = host-source "/" *( <VCHAR except ";" and ","> )
    76 //                 ; ext-host-source is reserved for future use.
    77 const R_VCHAR_EXCEPT = new RegExp("[!-+--:<-~]"); // ranges exclude , and ;
    78 const R_EXTHOSTSRC   = new RegExp ("^" + STRIP_INPUTDELIM(R_HOSTSRC.source)
    79                                        + "\\/"
    80                                        + R_VCHAR_EXCEPT.source + "*$", 'i');
    82 // keyword-source  = "'self'" / "'unsafe-inline'" / "'unsafe-eval'"
    83 const R_KEYWORDSRC = new RegExp ("^('self'|'unsafe-inline'|'unsafe-eval')$", 'i');
    85 const R_BASE64     = new RegExp ("([a-zA-Z0-9+/]+={0,2})");
    87 // nonce-source      = "'nonce-" nonce-value "'"
    88 // nonce-value       = 1*( ALPHA / DIGIT / "+" / "/" )
    89 const R_NONCESRC = new RegExp ("^'nonce-" + R_BASE64.source + "'$");
    91 // hash-source       = "'" hash-algo "-" hash-value "'"
    92 // hash-algo         = "sha256" / "sha384" / "sha512"
    93 // hash-value        = 1*( ALPHA / DIGIT / "+" / "/" / "=" )
    94 // Each algo must be a valid argument to nsICryptoHash.init
    95 const R_HASH_ALGOS = new RegExp ("(sha256|sha384|sha512)");
    96 const R_HASHSRC    = new RegExp ("^'" + R_HASH_ALGOS.source + "-" + R_BASE64.source + "'$");
    98 // source-exp      = scheme-source / host-source / keyword-source
    99 const R_SOURCEEXP  = new RegExp (R_SCHEMESRC.source + "|" +
   100                                    R_HOSTSRC.source + "|" +
   101                                 R_EXTHOSTSRC.source + "|" +
   102                                 R_KEYWORDSRC.source + "|" +
   103                                   R_NONCESRC.source + "|" +
   104                                    R_HASHSRC.source,  'i');
   106 const R_QUOTELESS_KEYWORDS = new RegExp ("^(self|unsafe-inline|unsafe-eval|" +
   107                                          "inline-script|eval-script|none)$", 'i');
   109 this.CSPPrefObserver = {
   110   get debugEnabled () {
   111     if (!this._branch)
   112       this._initialize();
   113     return this._debugEnabled;
   114   },
   116   get experimentalEnabled () {
   117     if (!this._branch)
   118       this._initialize();
   119     return this._experimentalEnabled;
   120   },
   122   _initialize: function() {
   123     var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
   124                     .getService(Ci.nsIPrefService);
   125     this._branch = prefSvc.getBranch("security.csp.");
   126     this._branch.addObserver("", this, false);
   127     this._debugEnabled = this._branch.getBoolPref("debug");
   128     this._experimentalEnabled = this._branch.getBoolPref("experimentalEnabled");
   129   },
   131   unregister: function() {
   132     if (!this._branch) return;
   133     this._branch.removeObserver("", this);
   134   },
   136   observe: function(aSubject, aTopic, aData) {
   137     if (aTopic != "nsPref:changed") return;
   138     if (aData === "debug")
   139       this._debugEnabled = this._branch.getBoolPref("debug");
   140     if (aData === "experimentalEnabled")
   141       this._experimentalEnabled = this._branch.getBoolPref("experimentalEnabled");
   142   },
   143 };
   145 this.CSPdebug = function CSPdebug(aMsg) {
   146   if (!CSPPrefObserver.debugEnabled) return;
   148   aMsg = 'CSP debug: ' + aMsg + "\n";
   149   Components.classes["@mozilla.org/consoleservice;1"]
   150                     .getService(Ci.nsIConsoleService)
   151                     .logStringMessage(aMsg);
   152 }
   154 // Callback to resume a request once the policy-uri has been fetched
   155 function CSPPolicyURIListener(policyURI, docRequest, csp, reportOnly) {
   156   this._policyURI = policyURI;    // location of remote policy
   157   this._docRequest = docRequest;  // the parent document request
   158   this._csp = csp;                // parent document's CSP
   159   this._policy = "";              // contents fetched from policyURI
   160   this._wrapper = null;           // nsIScriptableInputStream
   161   this._docURI = docRequest.QueryInterface(Ci.nsIChannel)
   162                  .URI;    // parent document URI (to be used as 'self')
   163   this._reportOnly = reportOnly;
   164 }
   166 CSPPolicyURIListener.prototype = {
   168   QueryInterface: function(iid) {
   169     if (iid.equals(Ci.nsIStreamListener) ||
   170         iid.equals(Ci.nsIRequestObserver) ||
   171         iid.equals(Ci.nsISupports))
   172       return this;
   173     throw Components.results.NS_ERROR_NO_INTERFACE;
   174   },
   176   onStartRequest:
   177   function(request, context) {},
   179   onDataAvailable:
   180   function(request, context, inputStream, offset, count) {
   181     if (this._wrapper == null) {
   182       this._wrapper = Components.classes["@mozilla.org/scriptableinputstream;1"]
   183                       .createInstance(Ci.nsIScriptableInputStream);
   184       this._wrapper.init(inputStream);
   185     }
   186     // store the remote policy as it becomes available
   187     this._policy += this._wrapper.read(count);
   188   },
   190   onStopRequest:
   191   function(request, context, status) {
   192     if (Components.isSuccessCode(status)) {
   193       // send the policy we received back to the parent document's CSP
   194       // for parsing
   195       this._csp.appendPolicy(this._policy, this._docURI,
   196                              this._reportOnly, this._csp._specCompliant);
   197     }
   198     else {
   199       // problem fetching policy so fail closed by appending a "block it all"
   200       // policy.  Also toss an error into the console so developers can see why
   201       // this policy is used.
   202       this._csp.log(WARN_FLAG, CSPLocalizer.getFormatStr("errorFetchingPolicy",
   203                                                          [status]));
   204       this._csp.appendPolicy("default-src 'none'", this._docURI,
   205                              this._reportOnly, this._csp._specCompliant);
   206     }
   207     // resume the parent document request
   208     this._docRequest.resume();
   209   }
   210 };
   212 //:::::::::::::::::::::::: CLASSES :::::::::::::::::::::::::://
   214 /**
   215  * Class that represents a parsed policy structure.
   216  *
   217  * @param aSpecCompliant: true: this policy is a CSP 1.0 spec
   218  *                   compliant policy and should be parsed as such.
   219  *                   false or undefined: this is a policy using
   220  *                   our original implementation's CSP syntax.
   221  */
   222 this.CSPRep = function CSPRep(aSpecCompliant) {
   223   // this gets set to true when the policy is done parsing, or when a
   224   // URI-borne policy has finished loading.
   225   this._isInitialized = false;
   227   this._allowEval = false;
   228   this._allowInlineScripts = false;
   229   this._reportOnlyMode = false;
   231   // don't auto-populate _directives, so it is easier to find bugs
   232   this._directives = {};
   234   // Is this a 1.0 spec compliant CSPRep ?
   235   // Default to false if not specified.
   236   this._specCompliant = (aSpecCompliant !== undefined) ? aSpecCompliant : false;
   238   // Only CSP 1.0 spec compliant policies block inline styles by default.
   239   this._allowInlineStyles = !aSpecCompliant;
   240 }
   242 // Source directives for our original CSP implementation.
   243 // These can be removed when the original implementation is deprecated.
   244 CSPRep.SRC_DIRECTIVES_OLD = {
   245   DEFAULT_SRC:      "default-src",
   246   SCRIPT_SRC:       "script-src",
   247   STYLE_SRC:        "style-src",
   248   MEDIA_SRC:        "media-src",
   249   IMG_SRC:          "img-src",
   250   OBJECT_SRC:       "object-src",
   251   FRAME_SRC:        "frame-src",
   252   FRAME_ANCESTORS:  "frame-ancestors",
   253   FONT_SRC:         "font-src",
   254   XHR_SRC:          "xhr-src"
   255 };
   257 // Source directives for our CSP 1.0 spec compliant implementation.
   258 CSPRep.SRC_DIRECTIVES_NEW = {
   259   DEFAULT_SRC:      "default-src",
   260   SCRIPT_SRC:       "script-src",
   261   STYLE_SRC:        "style-src",
   262   MEDIA_SRC:        "media-src",
   263   IMG_SRC:          "img-src",
   264   OBJECT_SRC:       "object-src",
   265   FRAME_SRC:        "frame-src",
   266   FRAME_ANCESTORS:  "frame-ancestors",
   267   FONT_SRC:         "font-src",
   268   CONNECT_SRC:      "connect-src"
   269 };
   271 CSPRep.URI_DIRECTIVES = {
   272   REPORT_URI:       "report-uri", /* list of URIs */
   273   POLICY_URI:       "policy-uri"  /* single URI */
   274 };
   276 // These directives no longer exist in CSP 1.0 and
   277 // later and will eventually be removed when we no longer
   278 // support our original implementation's syntax.
   279 CSPRep.OPTIONS_DIRECTIVE = "options";
   280 CSPRep.ALLOW_DIRECTIVE   = "allow";
   282 /**
   283   * Factory to create a new CSPRep, parsed from a string.
   284   *
   285   * @param aStr
   286   *        string rep of a CSP
   287   * @param self (optional)
   288   *        URI representing the "self" source
   289   * @param reportOnly (optional)
   290   *        whether or not this CSP is report-only (defaults to false)
   291   * @param docRequest (optional)
   292   *        request for the parent document which may need to be suspended
   293   *        while the policy-uri is asynchronously fetched
   294   * @param csp (optional)
   295   *        the CSP object to update once the policy has been fetched
   296   * @param enforceSelfChecks (optional)
   297   *        if present, and "true", will check to be sure "self" has the
   298   *        appropriate values to inherit when they are omitted from the source.
   299   * @returns
   300   *        an instance of CSPRep
   301   */
   302 CSPRep.fromString = function(aStr, self, reportOnly, docRequest, csp,
   303                              enforceSelfChecks) {
   304   var SD = CSPRep.SRC_DIRECTIVES_OLD;
   305   var UD = CSPRep.URI_DIRECTIVES;
   306   var aCSPR = new CSPRep();
   307   aCSPR._originalText = aStr;
   308   aCSPR._innerWindowID = innerWindowFromRequest(docRequest);
   309   if (typeof reportOnly === 'undefined') reportOnly = false;
   310   aCSPR._reportOnlyMode = reportOnly;
   312   var selfUri = null;
   313   if (self instanceof Ci.nsIURI) {
   314     selfUri = self.cloneIgnoringRef();
   315     // clean userpass out of the URI (not used for CSP origin checking, but
   316     // shows up in prePath).
   317     try {
   318       // GetUserPass throws for some protocols without userPass
   319       selfUri.userPass = '';
   320     } catch (ex) {}
   321   }
   323   var dirs = aStr.split(";");
   325   directive:
   326   for each(var dir in dirs) {
   327     dir = dir.trim();
   328     if (dir.length < 1) continue;
   330     var dirname = dir.split(/\s+/)[0].toLowerCase();
   331     var dirvalue = dir.substring(dirname.length).trim();
   333     if (aCSPR._directives.hasOwnProperty(dirname)) {
   334       // Check for (most) duplicate directives
   335       cspError(aCSPR, CSPLocalizer.getFormatStr("duplicateDirective",
   336                                                 [dirname]));
   337       CSPdebug("Skipping duplicate directive: \"" + dir + "\"");
   338       continue directive;
   339     }
   341     // OPTIONS DIRECTIVE ////////////////////////////////////////////////
   342     if (dirname === CSPRep.OPTIONS_DIRECTIVE) {
   343       if (aCSPR._allowInlineScripts || aCSPR._allowEval) {
   344         // Check for duplicate options directives
   345         cspError(aCSPR, CSPLocalizer.getFormatStr("duplicateDirective",
   346                                                   [dirname]));
   347         CSPdebug("Skipping duplicate directive: \"" + dir + "\"");
   348         continue directive;
   349       }
   351       // grab value tokens and interpret them
   352       var options = dirvalue.split(/\s+/);
   353       for each (var opt in options) {
   354         if (opt === "inline-script")
   355           aCSPR._allowInlineScripts = true;
   356         else if (opt === "eval-script")
   357           aCSPR._allowEval = true;
   358         else
   359           cspWarn(aCSPR, CSPLocalizer.getFormatStr("ignoringUnknownOption",
   360                                                    [opt]));
   361       }
   362       continue directive;
   363     }
   365     // ALLOW DIRECTIVE //////////////////////////////////////////////////
   366     // parse "allow" as equivalent to "default-src", at least until the spec
   367     // stabilizes, at which time we can stop parsing "allow"
   368     if (dirname === CSPRep.ALLOW_DIRECTIVE) {
   369       cspWarn(aCSPR, CSPLocalizer.getStr("allowDirectiveIsDeprecated"));
   370       if (aCSPR._directives.hasOwnProperty(SD.DEFAULT_SRC)) {
   371         // Check for duplicate default-src and allow directives
   372         cspError(aCSPR, CSPLocalizer.getFormatStr("duplicateDirective",
   373                                                   [dirname]));
   374         CSPdebug("Skipping duplicate directive: \"" + dir + "\"");
   375         continue directive;
   376       }
   377       var dv = CSPSourceList.fromString(dirvalue, aCSPR, selfUri,
   378                                         enforceSelfChecks);
   379       if (dv) {
   380         aCSPR._directives[SD.DEFAULT_SRC] = dv;
   381         continue directive;
   382       }
   383     }
   385     // SOURCE DIRECTIVES ////////////////////////////////////////////////
   386     for each(var sdi in SD) {
   387       if (dirname === sdi) {
   388         // process dirs, and enforce that 'self' is defined.
   389         var dv = CSPSourceList.fromString(dirvalue, aCSPR, selfUri,
   390                                           enforceSelfChecks);
   391         if (dv) {
   392           aCSPR._directives[sdi] = dv;
   393           continue directive;
   394         }
   395       }
   396     }
   398     // REPORT URI ///////////////////////////////////////////////////////
   399     if (dirname === UD.REPORT_URI) {
   400       // might be space-separated list of URIs
   401       var uriStrings = dirvalue.split(/\s+/);
   402       var okUriStrings = [];
   404       for (let i in uriStrings) {
   405         var uri = null;
   406         try {
   407           // Relative URIs are okay, but to ensure we send the reports to the
   408           // right spot, the relative URIs are expanded here during parsing.
   409           // The resulting CSPRep instance will have only absolute URIs.
   410           uri = gIoService.newURI(uriStrings[i],null,selfUri);
   412           // if there's no host, this will throw NS_ERROR_FAILURE, causing a
   413           // parse failure.
   414           uri.host;
   416           // warn about, but do not prohibit non-http and non-https schemes for
   417           // reporting URIs.  The spec allows unrestricted URIs resolved
   418           // relative to "self", but we should let devs know if the scheme is
   419           // abnormal and may fail a POST.
   420           if (!uri.schemeIs("http") && !uri.schemeIs("https")) {
   421             cspWarn(aCSPR, CSPLocalizer.getFormatStr("reportURInotHttpsOrHttp2",
   422                                                      [uri.asciiSpec]));
   423           }
   424         } catch(e) {
   425           switch (e.result) {
   426             case Components.results.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS:
   427             case Components.results.NS_ERROR_HOST_IS_IP_ADDRESS:
   428               if (uri.host !== selfUri.host) {
   429                 cspWarn(aCSPR,
   430                         CSPLocalizer.getFormatStr("pageCannotSendReportsTo",
   431                                                   [selfUri.host, uri.host]));
   432                 continue;
   433               }
   434               break;
   436             default:
   437               cspWarn(aCSPR, CSPLocalizer.getFormatStr("couldNotParseReportURI",
   438                                                        [uriStrings[i]]));
   439               continue;
   440           }
   441         }
   442         // all verification passed
   443         okUriStrings.push(uri.asciiSpec);
   444       }
   445       aCSPR._directives[UD.REPORT_URI] = okUriStrings.join(' ');
   446       continue directive;
   447     }
   449     // POLICY URI //////////////////////////////////////////////////////////
   450     if (dirname === UD.POLICY_URI) {
   451       // POLICY_URI can only be alone
   452       if (aCSPR._directives.length > 0 || dirs.length > 1) {
   453         cspError(aCSPR, CSPLocalizer.getStr("policyURINotAlone"));
   454         return CSPRep.fromString("default-src 'none'", null, reportOnly);
   455       }
   456       // if we were called without a reference to the parent document request
   457       // we won't be able to suspend it while we fetch the policy -> fail closed
   458       if (!docRequest || !csp) {
   459         cspError(aCSPR, CSPLocalizer.getStr("noParentRequest"));
   460         return CSPRep.fromString("default-src 'none'", null, reportOnly);
   461       }
   463       var uri = '';
   464       try {
   465         uri = gIoService.newURI(dirvalue, null, selfUri);
   466       } catch(e) {
   467         cspError(aCSPR, CSPLocalizer.getFormatStr("policyURIParseError",
   468                                                   [dirvalue]));
   469         return CSPRep.fromString("default-src 'none'", null, reportOnly);
   470       }
   472       // Verify that policy URI comes from the same origin
   473       if (selfUri) {
   474         if (selfUri.host !== uri.host) {
   475           cspError(aCSPR, CSPLocalizer.getFormatStr("nonMatchingHost",
   476                                                     [uri.host]));
   477           return CSPRep.fromString("default-src 'none'", null, reportOnly);
   478         }
   479         if (selfUri.port !== uri.port) {
   480           cspError(aCSPR, CSPLocalizer.getFormatStr("nonMatchingPort",
   481                                                     [uri.port.toString()]));
   482           return CSPRep.fromString("default-src 'none'", null, reportOnly);
   483         }
   484         if (selfUri.scheme !== uri.scheme) {
   485           cspError(aCSPR, CSPLocalizer.getFormatStr("nonMatchingScheme",
   486                                                     [uri.scheme]));
   487           return CSPRep.fromString("default-src 'none'", null, reportOnly);
   488         }
   489       }
   491       // suspend the parent document request while we fetch the policy-uri
   492       try {
   493         docRequest.suspend();
   494         var chan = gIoService.newChannel(uri.asciiSpec, null, null);
   495         // make request anonymous (no cookies, etc.) so the request for the
   496         // policy-uri can't be abused for CSRF
   497         chan.loadFlags |= Ci.nsIChannel.LOAD_ANONYMOUS;
   498         chan.loadGroup = docRequest.loadGroup;
   499         chan.asyncOpen(new CSPPolicyURIListener(uri, docRequest, csp, reportOnly), null);
   500       }
   501       catch (e) {
   502         // resume the document request and apply most restrictive policy
   503         docRequest.resume();
   504         cspError(aCSPR, CSPLocalizer.getFormatStr("errorFetchingPolicy",
   505                                                   [e.toString()]));
   506         return CSPRep.fromString("default-src 'none'", null, reportOnly);
   507       }
   509       // return a fully-open policy to be used until the contents of the
   510       // policy-uri come back.
   511       return CSPRep.fromString("default-src *", null, reportOnly);
   512     }
   514     // UNIDENTIFIED DIRECTIVE /////////////////////////////////////////////
   515     cspWarn(aCSPR, CSPLocalizer.getFormatStr("couldNotProcessUnknownDirective",
   516                                              [dirname]));
   518   } // end directive: loop
   520   // the X-Content-Security-Policy syntax requires an allow or default-src
   521   // directive to be present.
   522   if (!aCSPR._directives[SD.DEFAULT_SRC]) {
   523     cspWarn(aCSPR, CSPLocalizer.getStr("allowOrDefaultSrcRequired"));
   524     return CSPRep.fromString("default-src 'none'", null, reportOnly);
   525   }
   527   // If this is a Report-Only header and report-uri is not in the directive
   528   // list, tell developer either specify report-uri directive or use
   529   // a non-Report-Only CSP header.
   530   if (aCSPR._reportOnlyMode && !aCSPR._directives.hasOwnProperty(UD.REPORT_URI)) {
   531     cspWarn(aCSPR, CSPLocalizer.getFormatStr("reportURInotInReportOnlyHeader",
   532                                              [selfUri ? selfUri.prePath : "undefined"]))
   533   }
   535   return aCSPR;
   536 };
   538 /**
   539   * Factory to create a new CSPRep, parsed from a string, compliant
   540   * with the CSP 1.0 spec.
   541   *
   542   * @param aStr
   543   *        string rep of a CSP
   544   * @param self (optional)
   545   *        URI representing the "self" source
   546   * @param reportOnly (optional)
   547   *        whether or not this CSP is report-only (defaults to false)
   548   * @param docRequest (optional)
   549   *        request for the parent document which may need to be suspended
   550   *        while the policy-uri is asynchronously fetched
   551   * @param csp (optional)
   552   *        the CSP object to update once the policy has been fetched
   553   * @param enforceSelfChecks (optional)
   554   *        if present, and "true", will check to be sure "self" has the
   555   *        appropriate values to inherit when they are omitted from the source.
   556   * @returns
   557   *        an instance of CSPRep
   558   */
   559 // When we deprecate our original CSP implementation, we rename this to
   560 // CSPRep.fromString and remove the existing CSPRep.fromString above.
   561 CSPRep.fromStringSpecCompliant = function(aStr, self, reportOnly, docRequest, csp,
   562                                           enforceSelfChecks) {
   563   var SD = CSPRep.SRC_DIRECTIVES_NEW;
   564   var UD = CSPRep.URI_DIRECTIVES;
   565   var aCSPR = new CSPRep(true);
   566   aCSPR._originalText = aStr;
   567   aCSPR._innerWindowID = innerWindowFromRequest(docRequest);
   568   if (typeof reportOnly === 'undefined') reportOnly = false;
   569   aCSPR._reportOnlyMode = reportOnly;
   571   var selfUri = null;
   572   if (self instanceof Ci.nsIURI) {
   573     selfUri = self.cloneIgnoringRef();
   574     // clean userpass out of the URI (not used for CSP origin checking, but
   575     // shows up in prePath).
   576     try {
   577       // GetUserPass throws for some protocols without userPass
   578       selfUri.userPass = '';
   579     } catch (ex) {}
   580   }
   582   var dirs_list = aStr.split(";");
   583   var dirs = {};
   584   for each(var dir in dirs_list) {
   585     dir = dir.trim();
   586     if (dir.length < 1) continue;
   588     var dirname = dir.split(/\s+/)[0].toLowerCase();
   589     var dirvalue = dir.substring(dirname.length).trim();
   590     dirs[dirname] = dirvalue;
   591   }
   593   // Spec compliant policies have different default behavior for inline
   594   // scripts, styles, and eval. Bug 885433
   595   aCSPR._allowEval = true;
   596   aCSPR._allowInlineScripts = true;
   597   aCSPR._allowInlineStyles = true;
   599   // In CSP 1.0, you need to opt-in to blocking inline scripts and eval by
   600   // specifying either default-src or script-src, and to blocking inline
   601   // styles by specifying either default-src or style-src.
   602   if ("default-src" in dirs) {
   603     // Parse the source list (look ahead) so we can set the defaults properly,
   604     // honoring the 'unsafe-inline' and 'unsafe-eval' keywords
   605     var defaultSrcValue = CSPSourceList.fromString(dirs["default-src"], null, self);
   606     if (!defaultSrcValue._allowUnsafeInline) {
   607       aCSPR._allowInlineScripts = false;
   608       aCSPR._allowInlineStyles = false;
   609     }
   610     if (!defaultSrcValue._allowUnsafeEval) {
   611       aCSPR._allowEval = false;
   612     }
   613   }
   614   if ("script-src" in dirs) {
   615     aCSPR._allowInlineScripts = false;
   616     aCSPR._allowEval = false;
   617   }
   618   if ("style-src" in dirs) {
   619     aCSPR._allowInlineStyles = false;
   620   }
   622   directive:
   623   for (var dirname in dirs) {
   624     var dirvalue = dirs[dirname];
   626     if (aCSPR._directives.hasOwnProperty(dirname)) {
   627       // Check for (most) duplicate directives
   628       cspError(aCSPR, CSPLocalizer.getFormatStr("duplicateDirective",
   629                                                 [dirname]));
   630       CSPdebug("Skipping duplicate directive: \"" + dir + "\"");
   631       continue directive;
   632     }
   634     // SOURCE DIRECTIVES ////////////////////////////////////////////////
   635     for each(var sdi in SD) {
   636       if (dirname === sdi) {
   637         // process dirs, and enforce that 'self' is defined.
   638         var dv = CSPSourceList.fromString(dirvalue, aCSPR, self,
   639                                           enforceSelfChecks);
   640         if (dv) {
   641           // Check for unsafe-inline in style-src
   642           if (sdi === "style-src" && dv._allowUnsafeInline) {
   643              aCSPR._allowInlineStyles = true;
   644           } else if (sdi === "script-src") {
   645             // Check for unsafe-inline and unsafe-eval in script-src
   646             if (dv._allowUnsafeInline) {
   647               aCSPR._allowInlineScripts = true;
   648             }
   649             if (dv._allowUnsafeEval) {
   650               aCSPR._allowEval = true;
   651             }
   652           }
   654           aCSPR._directives[sdi] = dv;
   655           continue directive;
   656         }
   657       }
   658     }
   660     // REPORT URI ///////////////////////////////////////////////////////
   661     if (dirname === UD.REPORT_URI) {
   662       // might be space-separated list of URIs
   663       var uriStrings = dirvalue.split(/\s+/);
   664       var okUriStrings = [];
   666       for (let i in uriStrings) {
   667         var uri = null;
   668         try {
   669           // Relative URIs are okay, but to ensure we send the reports to the
   670           // right spot, the relative URIs are expanded here during parsing.
   671           // The resulting CSPRep instance will have only absolute URIs.
   672           uri = gIoService.newURI(uriStrings[i],null,selfUri);
   674           // if there's no host, this will throw NS_ERROR_FAILURE, causing a
   675           // parse failure.
   676           uri.host;
   678           // warn about, but do not prohibit non-http and non-https schemes for
   679           // reporting URIs.  The spec allows unrestricted URIs resolved
   680           // relative to "self", but we should let devs know if the scheme is
   681           // abnormal and may fail a POST.
   682           if (!uri.schemeIs("http") && !uri.schemeIs("https")) {
   683             cspWarn(aCSPR, CSPLocalizer.getFormatStr("reportURInotHttpsOrHttp2",
   684                                                      [uri.asciiSpec]));
   685           }
   686         } catch(e) {
   687           switch (e.result) {
   688             case Components.results.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS:
   689             case Components.results.NS_ERROR_HOST_IS_IP_ADDRESS:
   690               if (uri.host !== selfUri.host) {
   691                 cspWarn(aCSPR, CSPLocalizer.getFormatStr("pageCannotSendReportsTo",
   692                                                          [selfUri.host, uri.host]));
   693                 continue;
   694               }
   695               break;
   697             default:
   698               cspWarn(aCSPR, CSPLocalizer.getFormatStr("couldNotParseReportURI", 
   699                                                        [uriStrings[i]]));
   700               continue;
   701           }
   702         }
   703         // all verification passed.
   704        okUriStrings.push(uri.asciiSpec);
   705       }
   706       aCSPR._directives[UD.REPORT_URI] = okUriStrings.join(' ');
   707       continue directive;
   708     }
   710     // POLICY URI //////////////////////////////////////////////////////////
   711     if (dirname === UD.POLICY_URI) {
   712       // POLICY_URI can only be alone
   713       if (aCSPR._directives.length > 0 || dirs.length > 1) {
   714         cspError(aCSPR, CSPLocalizer.getStr("policyURINotAlone"));
   715         return CSPRep.fromStringSpecCompliant("default-src 'none'", null, reportOnly);
   716       }
   717       // if we were called without a reference to the parent document request
   718       // we won't be able to suspend it while we fetch the policy -> fail closed
   719       if (!docRequest || !csp) {
   720         cspError(aCSPR, CSPLocalizer.getStr("noParentRequest"));
   721         return CSPRep.fromStringSpecCompliant("default-src 'none'", null, reportOnly);
   722       }
   724       var uri = '';
   725       try {
   726         uri = gIoService.newURI(dirvalue, null, selfUri);
   727       } catch(e) {
   728         cspError(aCSPR, CSPLocalizer.getFormatStr("policyURIParseError", [dirvalue]));
   729         return CSPRep.fromStringSpecCompliant("default-src 'none'", null, reportOnly);
   730       }
   732       // Verify that policy URI comes from the same origin
   733       if (selfUri) {
   734         if (selfUri.host !== uri.host){
   735           cspError(aCSPR, CSPLocalizer.getFormatStr("nonMatchingHost", [uri.host]));
   736           return CSPRep.fromStringSpecCompliant("default-src 'none'", null, reportOnly);
   737         }
   738         if (selfUri.port !== uri.port){
   739           cspError(aCSPR, CSPLocalizer.getFormatStr("nonMatchingPort", [uri.port.toString()]));
   740           return CSPRep.fromStringSpecCompliant("default-src 'none'", null, reportOnly);
   741         }
   742         if (selfUri.scheme !== uri.scheme){
   743           cspError(aCSPR, CSPLocalizer.getFormatStr("nonMatchingScheme", [uri.scheme]));
   744           return CSPRep.fromStringSpecCompliant("default-src 'none'", null, reportOnly);
   745         }
   746       }
   748       // suspend the parent document request while we fetch the policy-uri
   749       try {
   750         docRequest.suspend();
   751         var chan = gIoService.newChannel(uri.asciiSpec, null, null);
   752         // make request anonymous (no cookies, etc.) so the request for the
   753         // policy-uri can't be abused for CSRF
   754         chan.loadFlags |= Components.interfaces.nsIChannel.LOAD_ANONYMOUS;
   755         chan.loadGroup = docRequest.loadGroup;
   756         chan.asyncOpen(new CSPPolicyURIListener(uri, docRequest, csp, reportOnly), null);
   757       }
   758       catch (e) {
   759         // resume the document request and apply most restrictive policy
   760         docRequest.resume();
   761         cspError(aCSPR, CSPLocalizer.getFormatStr("errorFetchingPolicy", [e.toString()]));
   762         return CSPRep.fromStringSpecCompliant("default-src 'none'", null, reportOnly);
   763       }
   765       // return a fully-open policy to be used until the contents of the
   766       // policy-uri come back
   767       return CSPRep.fromStringSpecCompliant("default-src *", null, reportOnly);
   768     }
   770     // UNIDENTIFIED DIRECTIVE /////////////////////////////////////////////
   771     cspWarn(aCSPR, CSPLocalizer.getFormatStr("couldNotProcessUnknownDirective", [dirname]));
   773   } // end directive: loop
   775   // If this is a Report-Only header and report-uri is not in the directive
   776   // list, tell developer either specify report-uri directive or use
   777   // a non-Report-Only CSP header.
   778   if (aCSPR._reportOnlyMode && !aCSPR._directives.hasOwnProperty(UD.REPORT_URI)) {
   779     cspWarn(aCSPR, CSPLocalizer.getFormatStr("reportURInotInReportOnlyHeader",
   780                                              [selfUri ? selfUri.prePath : "undefined"]));
   781   }
   783   return aCSPR;
   784 };
   786 CSPRep.prototype = {
   787   /**
   788    * Returns a space-separated list of all report uris defined, or 'none' if there are none.
   789    */
   790   getReportURIs:
   791   function() {
   792     if (!this._directives[CSPRep.URI_DIRECTIVES.REPORT_URI])
   793       return "";
   794     return this._directives[CSPRep.URI_DIRECTIVES.REPORT_URI];
   795   },
   797   /**
   798    * Compares this CSPRep instance to another.
   799    */
   800   equals:
   801   function(that) {
   802     if (this._directives.length != that._directives.length) {
   803       return false;
   804     }
   805     for (var i in this._directives) {
   806       if (!that._directives[i] || !this._directives[i].equals(that._directives[i])) {
   807         return false;
   808       }
   809     }
   810     return (this.allowsInlineScripts === that.allowsInlineScripts)
   811         && (this.allowsEvalInScripts === that.allowsEvalInScripts)
   812         && (this.allowsInlineStyles === that.allowsInlineStyles);
   813   },
   815   /**
   816    * Generates canonical string representation of the policy.
   817    */
   818   toString:
   819   function csp_toString() {
   820     var dirs = [];
   822     if (!this._specCompliant && (this._allowEval || this._allowInlineScripts)) {
   823       dirs.push("options" + (this._allowEval ? " eval-script" : "")
   824                            + (this._allowInlineScripts ? " inline-script" : ""));
   825     }
   826     for (var i in this._directives) {
   827       if (this._directives[i]) {
   828         dirs.push(i + " " + this._directives[i].toString());
   829       }
   830     }
   831     return dirs.join("; ");
   832   },
   834   permitsNonce:
   835   function csp_permitsNonce(aNonce, aDirective) {
   836     if (!this._directives.hasOwnProperty(aDirective)) return false;
   837     return this._directives[aDirective]._sources.some(function (source) {
   838       return source instanceof CSPNonceSource && source.permits(aNonce);
   839     });
   840   },
   842   permitsHash:
   843   function csp_permitsHash(aContent, aDirective) {
   844     if (!this._directives.hasOwnProperty(aDirective)) return false;
   845     return this._directives[aDirective]._sources.some(function (source) {
   846       return source instanceof CSPHashSource && source.permits(aContent);
   847     });
   848   },
   850   /**
   851    * Determines if this policy accepts a URI.
   852    * @param aURI
   853    *        URI of the requested resource
   854    * @param aDirective
   855    *        one of the SRC_DIRECTIVES defined above
   856    * @returns
   857    *        true if the policy permits the URI in given context.
   858    */
   859   permits:
   860   function csp_permits(aURI, aDirective) {
   861     if (!aURI) return false;
   863     // GLOBALLY ALLOW "about:" SCHEME
   864     if (aURI instanceof String && aURI.substring(0,6) === "about:")
   865       return true;
   866     if (aURI instanceof Ci.nsIURI && aURI.scheme === "about")
   867       return true;
   869     // make sure the right directive set is used
   870     let DIRS = this._specCompliant ? CSPRep.SRC_DIRECTIVES_NEW : CSPRep.SRC_DIRECTIVES_OLD;
   872     let directiveInPolicy = false;
   873     for (var i in DIRS) {
   874       if (DIRS[i] === aDirective) {
   875         // for catching calls with invalid contexts (below)
   876         directiveInPolicy = true;
   877         if (this._directives.hasOwnProperty(aDirective)) {
   878           return this._directives[aDirective].permits(aURI);
   879         }
   880         //found matching dir, can stop looking
   881         break;
   882       }
   883     }
   885     // frame-ancestors is a special case; it doesn't fall back to default-src.
   886     if (aDirective === DIRS.FRAME_ANCESTORS)
   887       return true;
   889     // All directives that don't fall back to default-src should have an escape
   890     // hatch above (like frame-ancestors).
   891     if (!directiveInPolicy) {
   892       // if this code runs, there's probably something calling permits() that
   893       // shouldn't be calling permits().
   894       CSPdebug("permits called with invalid load type: " + aDirective);
   895       return false;
   896     }
   898     // no directives specifically matched, fall back to default-src.
   899     // (default-src may not be present for CSP 1.0-compliant policies, and
   900     // indicates no relevant directives were present and the load should be
   901     // permitted).
   902     if (this._directives.hasOwnProperty(DIRS.DEFAULT_SRC)) {
   903       return this._directives[DIRS.DEFAULT_SRC].permits(aURI);
   904     }
   906     // no relevant directives present -- this means for CSP 1.0 that the load
   907     // should be permitted (and for the old CSP, to block it).
   908     return this._specCompliant;
   909   },
   911   /**
   912    * Returns true if "eval" is enabled through the "eval" keyword.
   913    */
   914   get allowsEvalInScripts () {
   915     return this._allowEval;
   916   },
   918   /**
   919    * Returns true if inline scripts are enabled through the "inline"
   920    * keyword.
   921    */
   922   get allowsInlineScripts () {
   923     return this._allowInlineScripts;
   924   },
   926   /**
   927    * Returns true if inline styles are enabled through the "inline-style"
   928    * keyword.
   929    */
   930   get allowsInlineStyles () {
   931     return this._allowInlineStyles;
   932   },
   934   /**
   935    * Sends a message to the error console and web developer console.
   936    * @param aFlag
   937    *        The nsIScriptError flag constant indicating severity
   938    * @param aMsg
   939    *        The message to send
   940    * @param aSource (optional)
   941    *        The URL of the file in which the error occurred
   942    * @param aScriptLine (optional)
   943    *        The line in the source file which the error occurred
   944    * @param aLineNum (optional)
   945    *        The number of the line where the error occurred
   946    */
   947   log:
   948   function cspd_log(aFlag, aMsg, aSource, aScriptLine, aLineNum) {
   949     var textMessage = "Content Security Policy: " + aMsg;
   950     var consoleMsg = Components.classes["@mozilla.org/scripterror;1"]
   951                                .createInstance(Ci.nsIScriptError);
   952     if (this._innerWindowID) {
   953       consoleMsg.initWithWindowID(textMessage, aSource, aScriptLine, aLineNum,
   954                                   0, aFlag,
   955                                   "CSP",
   956                                   this._innerWindowID);
   957     } else {
   958       consoleMsg.init(textMessage, aSource, aScriptLine, aLineNum, 0,
   959                       aFlag,
   960                       "CSP");
   961     }
   962     Components.classes["@mozilla.org/consoleservice;1"]
   963               .getService(Ci.nsIConsoleService).logMessage(consoleMsg);
   964   },
   966 };
   968 //////////////////////////////////////////////////////////////////////
   969 /**
   970  * Class to represent a list of sources
   971  */
   972 this.CSPSourceList = function CSPSourceList() {
   973   this._sources = [];
   974   this._permitAllSources = false;
   976   // When this is true, the source list contains 'unsafe-inline'.
   977   this._allowUnsafeInline = false;
   979   // When this is true, the source list contains 'unsafe-eval'.
   980   this._allowUnsafeEval = false;
   982   // When this is true, the source list contains at least one nonce-source
   983   this._hasNonceSource = false;
   985   // When this is true, the source list contains at least one hash-source
   986   this._hasHashSource = false;
   987 }
   989 /**
   990  * Factory to create a new CSPSourceList, parsed from a string.
   991  *
   992  * @param aStr
   993  *        string rep of a CSP Source List
   994  * @param aCSPRep
   995  *        the CSPRep to which this souce list belongs. If null, CSP errors and
   996  *        warnings will not be sent to the web console.
   997  * @param self (optional)
   998  *        URI or CSPSource representing the "self" source
   999  * @param enforceSelfChecks (optional)
  1000  *        if present, and "true", will check to be sure "self" has the
  1001  *        appropriate values to inherit when they are omitted from the source.
  1002  * @returns
  1003  *        an instance of CSPSourceList
  1004  */
  1005 CSPSourceList.fromString = function(aStr, aCSPRep, self, enforceSelfChecks) {
  1006   // source-list = *WSP [ source-expression *( 1*WSP source-expression ) *WSP ]
  1007   //             / *WSP "'none'" *WSP
  1009   /* If self parameter is passed, convert to CSPSource,
  1010      unless it is already a CSPSource. */
  1011   if (self && !(self instanceof CSPSource)) {
  1012      self = CSPSource.create(self, aCSPRep);
  1015   var slObj = new CSPSourceList();
  1016   slObj._CSPRep = aCSPRep;
  1017   aStr = aStr.trim();
  1018   // w3 specifies case insensitive equality
  1019   if (aStr.toLowerCase() === "'none'") {
  1020     slObj._permitAllSources = false;
  1021     return slObj;
  1024   var tokens = aStr.split(/\s+/);
  1025   for (var i in tokens) {
  1026     if (!R_SOURCEEXP.test(tokens[i])) {
  1027       cspWarn(aCSPRep,
  1028               CSPLocalizer.getFormatStr("failedToParseUnrecognizedSource",
  1029                                         [tokens[i]]));
  1030       continue;
  1032     var src = CSPSource.create(tokens[i], aCSPRep, self, enforceSelfChecks);
  1033     if (!src) {
  1034       cspWarn(aCSPRep,
  1035               CSPLocalizer.getFormatStr("failedToParseUnrecognizedSource",
  1036                                         [tokens[i]]));
  1037       continue;
  1040     // if a source allows unsafe-inline, set our flag to indicate this.
  1041     if (src._allowUnsafeInline)
  1042       slObj._allowUnsafeInline = true;
  1044     // if a source allows unsafe-eval, set our flag to indicate this.
  1045     if (src._allowUnsafeEval)
  1046       slObj._allowUnsafeEval = true;
  1048     if (src instanceof CSPNonceSource)
  1049       slObj._hasNonceSource = true;
  1051     if (src instanceof CSPHashSource)
  1052       slObj._hasHashSource = true;
  1054     // if a source is a *, then we can permit all sources
  1055     if (src.permitAll) {
  1056       slObj._permitAllSources = true;
  1057     } else {
  1058       slObj._sources.push(src);
  1062   return slObj;
  1063 };
  1065 CSPSourceList.prototype = {
  1066   /**
  1067    * Compares one CSPSourceList to another.
  1069    * @param that
  1070    *        another CSPSourceList
  1071    * @returns
  1072    *        true if they have the same data
  1073    */
  1074   equals:
  1075   function(that) {
  1076     // special case to default-src * and 'none' to look different
  1077     // (both have a ._sources.length of 0).
  1078     if (that._permitAllSources != this._permitAllSources) {
  1079       return false;
  1081     if (that._sources.length != this._sources.length) {
  1082       return false;
  1084     // sort both arrays and compare like a zipper
  1085     // XXX (sid): I think we can make this more efficient
  1086     var sortfn = function(a,b) {
  1087       return a.toString.toLowerCase() > b.toString.toLowerCase();
  1088     };
  1089     var a_sorted = this._sources.sort(sortfn);
  1090     var b_sorted = that._sources.sort(sortfn);
  1091     for (var i in a_sorted) {
  1092       if (!a_sorted[i].equals(b_sorted[i])) {
  1093         return false;
  1096     return true;
  1097   },
  1099   /**
  1100    * Generates canonical string representation of the Source List.
  1101    */
  1102   toString:
  1103   function() {
  1104     if (this.isNone()) {
  1105       return "'none'";
  1107     if (this._permitAllSources) {
  1108       return "*";
  1110     return this._sources.map(function(x) { return x.toString(); }).join(" ");
  1111   },
  1113   /**
  1114    * Returns whether or not this source list represents the "'none'" special
  1115    * case.
  1116    */
  1117   isNone:
  1118   function() {
  1119     return (!this._permitAllSources) && (this._sources.length < 1);
  1120   },
  1122   /**
  1123    * Returns whether or not this source list permits all sources (*).
  1124    */
  1125   isAll:
  1126   function() {
  1127     return this._permitAllSources;
  1128   },
  1130   /**
  1131    * Makes a new deep copy of this object.
  1132    * @returns
  1133    *      a new CSPSourceList
  1134    */
  1135   clone:
  1136   function() {
  1137     var aSL = new CSPSourceList();
  1138     aSL._permitAllSources = this._permitAllSources;
  1139     aSL._CSPRep = this._CSPRep;
  1140     for (var i in this._sources) {
  1141       aSL._sources[i] = this._sources[i].clone();
  1143     return aSL;
  1144   },
  1146   /**
  1147    * Determines if this directive accepts a URI.
  1148    * @param aURI
  1149    *        the URI in question
  1150    * @returns
  1151    *        true if the URI matches a source in this source list.
  1152    */
  1153   permits:
  1154   function cspsd_permits(aURI) {
  1155     if (this.isNone())    return false;
  1156     if (this.isAll())     return true;
  1158     for (var i in this._sources) {
  1159       if (this._sources[i].permits(aURI)) {
  1160         return true;
  1163     return false;
  1167 //////////////////////////////////////////////////////////////////////
  1168 /**
  1169  * Class to model a source (scheme, host, port)
  1170  */
  1171 this.CSPSource = function CSPSource() {
  1172   this._scheme = undefined;
  1173   this._port = undefined;
  1174   this._host = undefined;
  1176   //when set to true, this allows all source
  1177   this._permitAll = false;
  1179   // when set to true, this source represents 'self'
  1180   this._isSelf = false;
  1182   // when set to true, this source allows inline scripts or styles
  1183   this._allowUnsafeInline = false;
  1185   // when set to true, this source allows eval to be used
  1186   this._allowUnsafeEval = false;
  1189 /**
  1190  * General factory method to create a new source from one of the following
  1191  * types:
  1192  *  - nsURI
  1193  *  - string
  1194  *  - CSPSource (clone)
  1195  * @param aData
  1196  *        string, nsURI, or CSPSource
  1197  * @param aCSPRep
  1198  *        The CSPRep this source belongs to. If null, CSP errors and warnings
  1199  *        will not be sent to the web console.
  1200  * @param self (optional)
  1201  *	  if present, string, URI, or CSPSource representing the "self" resource
  1202  * @param enforceSelfChecks (optional)
  1203  *	  if present, and "true", will check to be sure "self" has the
  1204  *        appropriate values to inherit when they are omitted from the source.
  1205  * @returns
  1206  *        an instance of CSPSource
  1207  */
  1208 CSPSource.create = function(aData, aCSPRep, self, enforceSelfChecks) {
  1209   if (typeof aData === 'string')
  1210     return CSPSource.fromString(aData, aCSPRep, self, enforceSelfChecks);
  1212   if (aData instanceof Ci.nsIURI) {
  1213     // clean userpass out of the URI (not used for CSP origin checking, but
  1214     // shows up in prePath).
  1215     let cleanedUri = aData.cloneIgnoringRef();
  1216     try {
  1217       // GetUserPass throws for some protocols without userPass
  1218       cleanedUri.userPass = '';
  1219     } catch (ex) {}
  1221     return CSPSource.fromURI(cleanedUri, aCSPRep, self, enforceSelfChecks);
  1224   if (aData instanceof CSPSource) {
  1225     var ns = aData.clone();
  1226     ns._self = CSPSource.create(self);
  1227     return ns;
  1230   return null;
  1233 /**
  1234  * Factory to create a new CSPSource, from a nsIURI.
  1236  * Don't use this if you want to wildcard ports!
  1238  * @param aURI
  1239  *        nsIURI rep of a URI
  1240  * @param aCSPRep
  1241  *        The policy this source belongs to. If null, CSP errors and warnings
  1242  *        will not be sent to the web console.
  1243  * @param self (optional)
  1244  *        string or CSPSource representing the "self" source
  1245  * @param enforceSelfChecks (optional)
  1246  *        if present, and "true", will check to be sure "self" has the
  1247  *        appropriate values to inherit when they are omitted from aURI.
  1248  * @returns
  1249  *        an instance of CSPSource
  1250  */
  1251 CSPSource.fromURI = function(aURI, aCSPRep, self, enforceSelfChecks) {
  1252   if (!(aURI instanceof Ci.nsIURI)) {
  1253     cspError(aCSPRep, CSPLocalizer.getStr("cspSourceNotURI"));
  1254     return null;
  1257   if (!self && enforceSelfChecks) {
  1258     cspError(aCSPRep, CSPLocalizer.getStr("selfDataNotProvided"));
  1259     return null;
  1262   if (self && !(self instanceof CSPSource)) {
  1263     self = CSPSource.create(self, aCSPRep, undefined, false);
  1266   var sObj = new CSPSource();
  1267   sObj._self = self;
  1268   sObj._CSPRep = aCSPRep;
  1270   // PARSE
  1271   // If 'self' is undefined, then use default port for scheme if there is one.
  1273   // grab scheme (if there is one)
  1274   try {
  1275     sObj._scheme = aURI.scheme;
  1276   } catch(e) {
  1277     sObj._scheme = undefined;
  1278     cspError(aCSPRep, CSPLocalizer.getFormatStr("uriWithoutScheme",
  1279                                                 [aURI.asciiSpec]));
  1280     return null;
  1283   // grab host (if there is one)
  1284   try {
  1285     // if there's no host, an exception will get thrown
  1286     // (NS_ERROR_FAILURE)
  1287     sObj._host = CSPHost.fromString(aURI.host);
  1288   } catch(e) {
  1289     sObj._host = undefined;
  1292   // grab port (if there is one)
  1293   // creating a source from an nsURI is limited in that one cannot specify "*"
  1294   // for port.  In fact, there's no way to represent "*" differently than
  1295   // a blank port in an nsURI, since "*" turns into -1, and so does an
  1296   // absence of port declaration.
  1298   // port is never inherited from self -- this gets too confusing.
  1299   // Instead, whatever scheme is used (an explicit one or the inherited
  1300   // one) dictates the port if no port is explicitly stated.
  1301   // Set it to undefined here and the default port will be resolved in the
  1302   // getter for .port.
  1303   sObj._port = undefined;
  1304   try {
  1305     // if there's no port, an exception will get thrown
  1306     // (NS_ERROR_FAILURE)
  1307     if (aURI.port > 0) {
  1308       sObj._port = aURI.port;
  1310   } catch(e) {
  1311     sObj._port = undefined;
  1314   return sObj;
  1315 };
  1317 /**
  1318  * Factory to create a new CSPSource, parsed from a string.
  1320  * @param aStr
  1321  *        string rep of a CSP Source
  1322  * @param aCSPRep
  1323  *        the CSPRep this CSPSource belongs to
  1324  * @param self (optional)
  1325  *        string, URI, or CSPSource representing the "self" source
  1326  * @param enforceSelfChecks (optional)
  1327  *        if present, and "true", will check to be sure "self" has the
  1328  *        appropriate values to inherit when they are omitted from aURI.
  1329  * @returns
  1330  *        an instance of CSPSource
  1331  */
  1332 CSPSource.fromString = function(aStr, aCSPRep, self, enforceSelfChecks) {
  1333   if (!aStr)
  1334     return null;
  1336   if (!(typeof aStr === 'string')) {
  1337     cspError(aCSPRep, CSPLocalizer.getStr("argumentIsNotString"));
  1338     return null;
  1341   var sObj = new CSPSource();
  1342   sObj._self = self;
  1343   sObj._CSPRep = aCSPRep;
  1346   // if equal, return does match
  1347   if (aStr === "*") {
  1348     sObj._permitAll = true;
  1349     return sObj;
  1352   if (!self && enforceSelfChecks) {
  1353     cspError(aCSPRep, CSPLocalizer.getStr("selfDataNotProvided"));
  1354     return null;
  1357   if (self && !(self instanceof CSPSource)) {
  1358     self = CSPSource.create(self, aCSPRep, undefined, false);
  1361   // check for 'unsafe-inline' (case insensitive)
  1362   if (aStr.toLowerCase() === "'unsafe-inline'"){
  1363     sObj._allowUnsafeInline = true;
  1364     return sObj;
  1367   // check for 'unsafe-eval' (case insensitive)
  1368   if (aStr.toLowerCase() === "'unsafe-eval'"){
  1369     sObj._allowUnsafeEval = true;
  1370     return sObj;
  1373   // Check for scheme-source match - this only matches if the source
  1374   // string is just a scheme with no host.
  1375   if (R_SCHEMESRC.test(aStr)) {
  1376     var schemeSrcMatch = R_GETSCHEME.exec(aStr);
  1377     sObj._scheme = schemeSrcMatch[0];
  1378     if (!sObj._host) sObj._host = CSPHost.fromString("*");
  1379     if (!sObj._port) sObj._port = "*";
  1380     return sObj;
  1383   // check for host-source or ext-host-source match
  1384   if (R_HOSTSRC.test(aStr) || R_EXTHOSTSRC.test(aStr)) {
  1385     var schemeMatch = R_GETSCHEME.exec(aStr);
  1386     // check that the scheme isn't accidentally matching the host. There should
  1387     // be '://' if there is a valid scheme in an (EXT)HOSTSRC
  1388     if (!schemeMatch || aStr.indexOf("://") == -1) {
  1389       sObj._scheme = self.scheme;
  1390       schemeMatch = null;
  1391     } else {
  1392       sObj._scheme = schemeMatch[0];
  1395     // Bug 916054: in CSP 1.0, source-expressions that are paths should have
  1396     // the path after the origin ignored and only the origin enforced.
  1397     if (R_EXTHOSTSRC.test(aStr)) {
  1398       var extHostMatch = R_EXTHOSTSRC.exec(aStr);
  1399       aStr = extHostMatch[1];
  1402     var hostMatch = R_HOSTSRC.exec(aStr);
  1403     if (!hostMatch) {
  1404       cspError(aCSPRep, CSPLocalizer.getFormatStr("couldntParseInvalidSource",
  1405                                                   [aStr]));
  1406       return null;
  1408     // Host regex gets scheme, so remove scheme from aStr. Add 3 for '://'
  1409     if (schemeMatch) {
  1410       hostMatch = R_HOSTSRC.exec(aStr.substring(schemeMatch[0].length + 3));
  1413     var portMatch = R_PORT.exec(hostMatch);
  1414     // Host regex also gets port, so remove the port here.
  1415     if (portMatch) {
  1416       hostMatch = R_HOSTSRC.exec(hostMatch[0].substring(0, hostMatch[0].length - portMatch[0].length));
  1419     sObj._host = CSPHost.fromString(hostMatch[0]);
  1420     if (!portMatch) {
  1421       // gets the default port for the given scheme
  1422       var defPort = Services.io.getProtocolHandler(sObj._scheme).defaultPort;
  1423       if (!defPort) {
  1424         cspError(aCSPRep,
  1425                  CSPLocalizer.getFormatStr("couldntParseInvalidSource",
  1426                                            [aStr]));
  1427         return null;
  1429       sObj._port = defPort;
  1431     else {
  1432       // strip the ':' from the port
  1433       sObj._port = portMatch[0].substr(1);
  1435     // A CSP keyword without quotes is a valid hostname, but this can also be a mistake.
  1436     // Raise a CSP warning in the web console to developer to check his/her intent.
  1437     if (R_QUOTELESS_KEYWORDS.test(aStr)) {
  1438       cspWarn(aCSPRep, CSPLocalizer.getFormatStr("hostNameMightBeKeyword",
  1439                                                  [aStr, aStr.toLowerCase()]));
  1441     return sObj;
  1444   // check for a nonce-source match
  1445   if (R_NONCESRC.test(aStr)) {
  1446     return CSPNonceSource.fromString(aStr, aCSPRep);
  1449   // check for a hash-source match
  1450   if (R_HASHSRC.test(aStr)) {
  1451     return CSPHashSource.fromString(aStr, aCSPRep);
  1454   // check for 'self' (case insensitive)
  1455   if (aStr.toLowerCase() === "'self'") {
  1456     if (!self) {
  1457       cspError(aCSPRep, CSPLocalizer.getStr("selfKeywordNoSelfData"));
  1458       return null;
  1460     sObj._self = self.clone();
  1461     sObj._isSelf = true;
  1462     return sObj;
  1465   cspError(aCSPRep, CSPLocalizer.getFormatStr("couldntParseInvalidSource",
  1466                                               [aStr]));
  1467   return null;
  1468 };
  1470 CSPSource.validSchemeName = function(aStr) {
  1471   // <scheme-name>       ::= <alpha><scheme-suffix>
  1472   // <scheme-suffix>     ::= <scheme-chr>
  1473   //                      | <scheme-suffix><scheme-chr>
  1474   // <scheme-chr>        ::= <letter> | <digit> | "+" | "." | "-"
  1476   return aStr.match(/^[a-zA-Z][a-zA-Z0-9+.-]*$/);
  1477 };
  1479 CSPSource.prototype = {
  1481   get scheme () {
  1482     if (this._isSelf && this._self)
  1483       return this._self.scheme;
  1484     if (!this._scheme && this._self)
  1485       return this._self.scheme;
  1486     return this._scheme;
  1487   },
  1489   get host () {
  1490     if (this._isSelf && this._self)
  1491       return this._self.host;
  1492     if (!this._host && this._self)
  1493       return this._self.host;
  1494     return this._host;
  1495   },
  1497   get permitAll () {
  1498     if (this._isSelf && this._self)
  1499       return this._self.permitAll;
  1500     return this._permitAll;
  1501   },
  1503   /**
  1504    * If this doesn't have a nonstandard port (hard-defined), use the default
  1505    * port for this source's scheme. Should never inherit port from 'self'.
  1506    */
  1507   get port () {
  1508     if (this._isSelf && this._self)
  1509       return this._self.port;
  1510     if (this._port) return this._port;
  1511     // if no port, get the default port for the scheme
  1512     // (which may be inherited from 'self')
  1513     if (this.scheme) {
  1514       try {
  1515         var port = gIoService.getProtocolHandler(this.scheme).defaultPort;
  1516         if (port > 0) return port;
  1517       } catch(e) {
  1518         // if any errors happen, fail gracefully.
  1522     return undefined;
  1523   },
  1525   /**
  1526    * Generates canonical string representation of the Source.
  1527    */
  1528   toString:
  1529   function() {
  1530     if (this._isSelf)
  1531       return this._self.toString();
  1533     if (this._allowUnsafeInline)
  1534       return "'unsafe-inline'";
  1536     if (this._allowUnsafeEval)
  1537       return "'unsafe-eval'";
  1539     var s = "";
  1540     if (this.scheme)
  1541       s = s + this.scheme + "://";
  1542     if (this._host)
  1543       s = s + this._host;
  1544     if (this.port)
  1545       s = s + ":" + this.port;
  1546     return s;
  1547   },
  1549   /**
  1550    * Makes a new deep copy of this object.
  1551    * @returns
  1552    *      a new CSPSource
  1553    */
  1554   clone:
  1555   function() {
  1556     var aClone = new CSPSource();
  1557     aClone._self = this._self ? this._self.clone() : undefined;
  1558     aClone._scheme = this._scheme;
  1559     aClone._port = this._port;
  1560     aClone._host = this._host ? this._host.clone() : undefined;
  1561     aClone._isSelf = this._isSelf;
  1562     aClone._CSPRep = this._CSPRep;
  1563     return aClone;
  1564   },
  1566   /**
  1567    * Determines if this Source accepts a URI.
  1568    * @param aSource
  1569    *        the URI, or CSPSource in question
  1570    * @returns
  1571    *        true if the URI matches a source in this source list.
  1572    */
  1573   permits:
  1574   function(aSource) {
  1575     if (!aSource) return false;
  1577     if (!(aSource instanceof CSPSource))
  1578       aSource = CSPSource.create(aSource, this._CSPRep);
  1580     // verify scheme
  1581     if (this.scheme.toLowerCase() != aSource.scheme.toLowerCase())
  1582       return false;
  1584     // port is defined in 'this' (undefined means it may not be relevant
  1585     // to the scheme) AND this port (implicit or explicit) matches
  1586     // aSource's port
  1587     if (this.port && this.port !== "*" && this.port != aSource.port)
  1588       return false;
  1590     // host is defined in 'this' (undefined means it may not be relevant
  1591     // to the scheme) AND this host (implicit or explicit) permits
  1592     // aSource's host.
  1593     if (this.host && !this.host.permits(aSource.host))
  1594       return false;
  1596     // all scheme, host and port matched!
  1597     return true;
  1598   },
  1600   /**
  1601    * Compares one CSPSource to another.
  1603    * @param that
  1604    *        another CSPSource
  1605    * @param resolveSelf (optional)
  1606    *        if present, and 'true', implied values are obtained from 'self'
  1607    *        instead of assumed to be "anything"
  1608    * @returns
  1609    *        true if they have the same data
  1610    */
  1611   equals:
  1612   function(that, resolveSelf) {
  1613     // 1. schemes match
  1614     // 2. ports match
  1615     // 3. either both hosts are undefined, or one equals the other.
  1616     if (resolveSelf)
  1617       return this.scheme.toLowerCase() === that.scheme.toLowerCase()
  1618           && this.port === that.port
  1619           && (!(this.host || that.host) ||
  1620                (this.host && this.host.equals(that.host)));
  1622     // otherwise, compare raw (non-self-resolved values)
  1623     return this._scheme.toLowerCase() === that._scheme.toLowerCase()
  1624         && this._port === that._port
  1625         && (!(this._host || that._host) ||
  1626               (this._host && this._host.equals(that._host)));
  1627   },
  1629 };
  1631 //////////////////////////////////////////////////////////////////////
  1632 /**
  1633  * Class to model a host *.x.y.
  1634  */
  1635 this.CSPHost = function CSPHost() {
  1636   this._segments = [];
  1639 /**
  1640  * Factory to create a new CSPHost, parsed from a string.
  1642  * @param aStr
  1643  *        string rep of a CSP Host
  1644  * @returns
  1645  *        an instance of CSPHost
  1646  */
  1647 CSPHost.fromString = function(aStr) {
  1648   if (!aStr) return null;
  1650   // host string must be LDH with dots and stars.
  1651   var invalidChar = aStr.match(R_INV_HCHAR);
  1652   if (invalidChar) {
  1653     CSPdebug("Invalid character '" + invalidChar + "' in host " + aStr);
  1654     return null;
  1657   var hObj = new CSPHost();
  1658   hObj._segments = aStr.split(/\./);
  1659   if (hObj._segments.length < 1)
  1660     return null;
  1662   // validate data in segments
  1663   for (var i in hObj._segments) {
  1664     var seg = hObj._segments[i];
  1665     if (seg == "*") {
  1666       if (i > 0) {
  1667         // Wildcard must be FIRST
  1668         CSPdebug("Wildcard char located at invalid position in '" + aStr + "'");
  1669         return null;
  1672     else if (seg.match(R_COMP_HCHAR)) {
  1673       // Non-wildcard segment must be LDH string
  1674       CSPdebug("Invalid segment '" + seg + "' in host value");
  1675       return null;
  1678   return hObj;
  1679 };
  1681 CSPHost.prototype = {
  1682   /**
  1683    * Generates canonical string representation of the Host.
  1684    */
  1685   toString:
  1686   function() {
  1687     return this._segments.join(".");
  1688   },
  1690   /**
  1691    * Makes a new deep copy of this object.
  1692    * @returns
  1693    *      a new CSPHost
  1694    */
  1695   clone:
  1696   function() {
  1697     var aHost = new CSPHost();
  1698     for (var i in this._segments) {
  1699       aHost._segments[i] = this._segments[i];
  1701     return aHost;
  1702   },
  1704   /**
  1705    * Returns true if this host accepts the provided host (or the other way
  1706    * around).
  1707    * @param aHost
  1708    *        the FQDN in question (CSPHost or String)
  1709    * @returns
  1710    */
  1711   permits:
  1712   function(aHost) {
  1713     if (!aHost) {
  1714       aHost = CSPHost.fromString("*");
  1717     if (!(aHost instanceof CSPHost)) {
  1718       // -- compare CSPHost to String
  1719       aHost =  CSPHost.fromString(aHost);
  1721     var thislen = this._segments.length;
  1722     var thatlen = aHost._segments.length;
  1724     // don't accept a less specific host:
  1725     //   \--> *.b.a doesn't accept b.a.
  1726     if (thatlen < thislen) { return false; }
  1728     // check for more specific host (and wildcard):
  1729     //   \--> *.b.a accepts d.c.b.a.
  1730     //   \--> c.b.a doesn't accept d.c.b.a.
  1731     if ((thatlen > thislen) && this._segments[0] != "*") {
  1732       return false;
  1735     // Given the wildcard condition (from above),
  1736     // only necessary to compare elements that are present
  1737     // in this host.  Extra tokens in aHost are ok.
  1738     // * Compare from right to left.
  1739     for (var i=1; i <= thislen; i++) {
  1740       if (this._segments[thislen-i] != "*" &&
  1741           (this._segments[thislen-i].toLowerCase() !=
  1742            aHost._segments[thatlen-i].toLowerCase())) {
  1743         return false;
  1747     // at this point, all conditions are met, so the host is allowed
  1748     return true;
  1749   },
  1751   /**
  1752    * Compares one CSPHost to another.
  1754    * @param that
  1755    *        another CSPHost
  1756    * @returns
  1757    *        true if they have the same data
  1758    */
  1759   equals:
  1760   function(that) {
  1761     if (this._segments.length != that._segments.length)
  1762       return false;
  1764     for (var i=0; i<this._segments.length; i++) {
  1765       if (this._segments[i].toLowerCase() !=
  1766           that._segments[i].toLowerCase()) {
  1767         return false;
  1770     return true;
  1772 };
  1774 this.CSPNonceSource = function CSPNonceSource() {
  1775   this._nonce = undefined;
  1778 CSPNonceSource.fromString = function(aStr, aCSPRep) {
  1779   let nonce = R_NONCESRC.exec(aStr)[1];
  1780   if (!nonce) {
  1781     cspError(aCSPRep, "Error in parsing nonce-source from string: nonce was empty");
  1782     return null;
  1785   let nonceSourceObj = new CSPNonceSource();
  1786   nonceSourceObj._nonce = nonce;
  1787   return nonceSourceObj;
  1788 };
  1790 CSPNonceSource.prototype = {
  1792   permits: function(aContext) {
  1793     if (aContext instanceof Ci.nsIDOMHTMLElement) {
  1794       return this._nonce === aContext.getAttribute('nonce');
  1795     } else if (typeof aContext === 'string') {
  1796       return this._nonce === aContext;
  1798     CSPdebug("permits called on nonce-source, but aContext was not nsIDOMHTMLElement or string (was " + typeof(aContext) + ")");
  1799     return false;
  1800   },
  1802   toString: function() {
  1803     return "'nonce-" + this._nonce + "'";
  1804   },
  1806   clone: function() {
  1807     let clone = new CSPNonceSource();
  1808     clone._nonce = this._nonce;
  1809     return clone;
  1810   },
  1812   equals: function(that) {
  1813     return this._nonce === that._nonce;
  1816 };
  1818 this.CSPHashSource = function CSPHashSource() {
  1819   this._algo = undefined;
  1820   this._hash = undefined;
  1823 CSPHashSource.fromString = function(aStr, aCSPRep) {
  1824   let hashSrcMatch = R_HASHSRC.exec(aStr);
  1825   let algo = hashSrcMatch[1];
  1826   let hash = hashSrcMatch[2];
  1827   if (!algo) {
  1828     cspError(aCSPRep, "Error parsing hash-source from string: algo was empty");
  1829     return null;
  1831   if (!hash) {
  1832     cspError(aCSPRep, "Error parsing hash-source from string: hash was empty");
  1833     return null;
  1836   let hashSourceObj = new CSPHashSource();
  1837   hashSourceObj._algo = algo;
  1838   hashSourceObj._hash = hash;
  1839   return hashSourceObj;
  1840 };
  1842 CSPHashSource.prototype = {
  1844   permits: function(aContext) {
  1845     let ScriptableUnicodeConverter =
  1846       Components.Constructor("@mozilla.org/intl/scriptableunicodeconverter",
  1847                              "nsIScriptableUnicodeConverter");
  1848     let converter = new ScriptableUnicodeConverter();
  1849     converter.charset = 'utf8';
  1850     let utf8InnerHTML = converter.convertToByteArray(aContext);
  1852     let CryptoHash =
  1853       Components.Constructor("@mozilla.org/security/hash;1",
  1854                              "nsICryptoHash",
  1855                              "initWithString");
  1856     let hash = new CryptoHash(this._algo);
  1857     hash.update(utf8InnerHTML, utf8InnerHTML.length);
  1858     // passing true causes a base64-encoded hash to be returned
  1859     let contentHash = hash.finish(true);
  1861     // The NSS Base64 encoder automatically adds linebreaks "\r\n" every 64
  1862     // characters. We need to remove these so we can properly validate longer
  1863     // (SHA-512) base64-encoded hashes
  1864     contentHash = contentHash.replace('\r\n', '');
  1866     return contentHash === this._hash;
  1867   },
  1869   toString: function() {
  1870     return "'" + this._algo + '-' + this._hash + "'";
  1871   },
  1873   clone: function() {
  1874     let clone = new CSPHashSource();
  1875     clone._algo = this._algo;
  1876     clone._hash = this._hash;
  1877     return clone;
  1878   },
  1880   equals: function(that) {
  1881     return this._algo === that._algo && this._hash === that._hash;
  1884 };
  1886 //////////////////////////////////////////////////////////////////////
  1887 /**
  1888  * Class that listens to violation report transmission and logs errors.
  1889  */
  1890 this.CSPViolationReportListener = function CSPViolationReportListener(reportURI) {
  1891   this._reportURI = reportURI;
  1894 CSPViolationReportListener.prototype = {
  1895   _reportURI:   null,
  1897   QueryInterface: function(iid) {
  1898     if (iid.equals(Ci.nsIStreamListener) ||
  1899         iid.equals(Ci.nsIRequestObserver) ||
  1900         iid.equals(Ci.nsISupports))
  1901       return this;
  1902     throw Components.results.NS_ERROR_NO_INTERFACE;
  1903   },
  1905   onStopRequest:
  1906   function(request, context, status) {
  1907     if (!Components.isSuccessCode(status)) {
  1908       CSPdebug("error " + status.toString(16) +
  1909                 " while sending violation report to " +
  1910                 this._reportURI);
  1912   },
  1914   onStartRequest:
  1915   function(request, context) { },
  1917   onDataAvailable:
  1918   function(request, context, inputStream, offset, count) {
  1919     // We MUST read equal to count from the inputStream to avoid an assertion.
  1920     var input = Components.classes['@mozilla.org/scriptableinputstream;1']
  1921                 .createInstance(Ci.nsIScriptableInputStream);
  1923     input.init(inputStream);
  1924     input.read(count);
  1925   },
  1927 };
  1929 //////////////////////////////////////////////////////////////////////
  1931 function innerWindowFromRequest(docRequest) {
  1932   let win = null;
  1933   let loadContext = null;
  1935   try {
  1936     loadContext = docRequest.notificationCallbacks.getInterface(Ci.nsILoadContext);
  1937   } catch (ex) {
  1938     try {
  1939       loadContext = docRequest.loadGroup.notificationCallbacks.getInterface(Ci.nsILoadContext);
  1940     } catch (ex) {
  1941       return null;
  1945   if (loadContext) {
  1946     win = loadContext.associatedWindow;
  1948   if (win) {
  1949     try {
  1950        let winUtils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
  1951        return winUtils.currentInnerWindowID;
  1952     } catch (ex) {
  1953       return null;
  1956   return null;
  1959 function cspError(aCSPRep, aMessage) {
  1960   if (aCSPRep) {
  1961     aCSPRep.log(ERROR_FLAG, aMessage);
  1962   } else {
  1963     (new CSPRep()).log(ERROR_FLAG, aMessage);
  1967 function cspWarn(aCSPRep, aMessage) {
  1968   if (aCSPRep) {
  1969     aCSPRep.log(WARN_FLAG, aMessage);
  1970   } else {
  1971     (new CSPRep()).log(WARN_FLAG, aMessage);
  1975 //////////////////////////////////////////////////////////////////////
  1977 this.CSPLocalizer = {
  1978   /**
  1979    * Retrieve a localized string.
  1981    * @param string aName
  1982    *        The string name you want from the CSP string bundle.
  1983    * @return string
  1984    *         The localized string.
  1985    */
  1986   getStr: function CSPLoc_getStr(aName)
  1988     let result;
  1989     try {
  1990       result = this.stringBundle.GetStringFromName(aName);
  1992     catch (ex) {
  1993       Cu.reportError("Failed to get string: " + aName);
  1994       throw ex;
  1996     return result;
  1997   },
  1999   /**
  2000    * Retrieve a localized string formatted with values coming from the given
  2001    * array.
  2003    * @param string aName
  2004    *        The string name you want from the CSP string bundle.
  2005    * @param array aArray
  2006    *        The array of values you want in the formatted string.
  2007    * @return string
  2008    *         The formatted local string.
  2009    */
  2010   getFormatStr: function CSPLoc_getFormatStr(aName, aArray)
  2012     let result;
  2013     try {
  2014       result = this.stringBundle.formatStringFromName(aName, aArray, aArray.length);
  2016     catch (ex) {
  2017       Cu.reportError("Failed to format string: " + aName);
  2018       throw ex;
  2020     return result;
  2021   },
  2022 };
  2024 XPCOMUtils.defineLazyGetter(CSPLocalizer, "stringBundle", function() {
  2025   return Services.strings.createBundle(STRINGS_URI);
  2026 });

mercurial