Thu, 15 Jan 2015 21:03:48 +0100
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);
1013 }
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;
1022 }
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;
1031 }
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;
1038 }
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);
1059 }
1060 }
1062 return slObj;
1063 };
1065 CSPSourceList.prototype = {
1066 /**
1067 * Compares one CSPSourceList to another.
1068 *
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;
1080 }
1081 if (that._sources.length != this._sources.length) {
1082 return false;
1083 }
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;
1094 }
1095 }
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'";
1106 }
1107 if (this._permitAllSources) {
1108 return "*";
1109 }
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();
1142 }
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;
1161 }
1162 }
1163 return false;
1164 }
1165 }
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;
1187 }
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);
1222 }
1224 if (aData instanceof CSPSource) {
1225 var ns = aData.clone();
1226 ns._self = CSPSource.create(self);
1227 return ns;
1228 }
1230 return null;
1231 }
1233 /**
1234 * Factory to create a new CSPSource, from a nsIURI.
1235 *
1236 * Don't use this if you want to wildcard ports!
1237 *
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;
1255 }
1257 if (!self && enforceSelfChecks) {
1258 cspError(aCSPRep, CSPLocalizer.getStr("selfDataNotProvided"));
1259 return null;
1260 }
1262 if (self && !(self instanceof CSPSource)) {
1263 self = CSPSource.create(self, aCSPRep, undefined, false);
1264 }
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;
1281 }
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;
1290 }
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;
1309 }
1310 } catch(e) {
1311 sObj._port = undefined;
1312 }
1314 return sObj;
1315 };
1317 /**
1318 * Factory to create a new CSPSource, parsed from a string.
1319 *
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;
1339 }
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;
1350 }
1352 if (!self && enforceSelfChecks) {
1353 cspError(aCSPRep, CSPLocalizer.getStr("selfDataNotProvided"));
1354 return null;
1355 }
1357 if (self && !(self instanceof CSPSource)) {
1358 self = CSPSource.create(self, aCSPRep, undefined, false);
1359 }
1361 // check for 'unsafe-inline' (case insensitive)
1362 if (aStr.toLowerCase() === "'unsafe-inline'"){
1363 sObj._allowUnsafeInline = true;
1364 return sObj;
1365 }
1367 // check for 'unsafe-eval' (case insensitive)
1368 if (aStr.toLowerCase() === "'unsafe-eval'"){
1369 sObj._allowUnsafeEval = true;
1370 return sObj;
1371 }
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;
1381 }
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];
1393 }
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];
1400 }
1402 var hostMatch = R_HOSTSRC.exec(aStr);
1403 if (!hostMatch) {
1404 cspError(aCSPRep, CSPLocalizer.getFormatStr("couldntParseInvalidSource",
1405 [aStr]));
1406 return null;
1407 }
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));
1411 }
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));
1417 }
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;
1428 }
1429 sObj._port = defPort;
1430 }
1431 else {
1432 // strip the ':' from the port
1433 sObj._port = portMatch[0].substr(1);
1434 }
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()]));
1440 }
1441 return sObj;
1442 }
1444 // check for a nonce-source match
1445 if (R_NONCESRC.test(aStr)) {
1446 return CSPNonceSource.fromString(aStr, aCSPRep);
1447 }
1449 // check for a hash-source match
1450 if (R_HASHSRC.test(aStr)) {
1451 return CSPHashSource.fromString(aStr, aCSPRep);
1452 }
1454 // check for 'self' (case insensitive)
1455 if (aStr.toLowerCase() === "'self'") {
1456 if (!self) {
1457 cspError(aCSPRep, CSPLocalizer.getStr("selfKeywordNoSelfData"));
1458 return null;
1459 }
1460 sObj._self = self.clone();
1461 sObj._isSelf = true;
1462 return sObj;
1463 }
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.
1519 }
1520 }
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.
1602 *
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 = [];
1637 }
1639 /**
1640 * Factory to create a new CSPHost, parsed from a string.
1641 *
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;
1655 }
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;
1670 }
1671 }
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;
1676 }
1677 }
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];
1700 }
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("*");
1715 }
1717 if (!(aHost instanceof CSPHost)) {
1718 // -- compare CSPHost to String
1719 aHost = CSPHost.fromString(aHost);
1720 }
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;
1733 }
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;
1744 }
1745 }
1747 // at this point, all conditions are met, so the host is allowed
1748 return true;
1749 },
1751 /**
1752 * Compares one CSPHost to another.
1753 *
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;
1768 }
1769 }
1770 return true;
1771 }
1772 };
1774 this.CSPNonceSource = function CSPNonceSource() {
1775 this._nonce = undefined;
1776 }
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;
1783 }
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;
1797 }
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;
1814 }
1816 };
1818 this.CSPHashSource = function CSPHashSource() {
1819 this._algo = undefined;
1820 this._hash = undefined;
1821 }
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;
1830 }
1831 if (!hash) {
1832 cspError(aCSPRep, "Error parsing hash-source from string: hash was empty");
1833 return null;
1834 }
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;
1882 }
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;
1892 }
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);
1911 }
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;
1942 }
1943 }
1945 if (loadContext) {
1946 win = loadContext.associatedWindow;
1947 }
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;
1954 }
1955 }
1956 return null;
1957 }
1959 function cspError(aCSPRep, aMessage) {
1960 if (aCSPRep) {
1961 aCSPRep.log(ERROR_FLAG, aMessage);
1962 } else {
1963 (new CSPRep()).log(ERROR_FLAG, aMessage);
1964 }
1965 }
1967 function cspWarn(aCSPRep, aMessage) {
1968 if (aCSPRep) {
1969 aCSPRep.log(WARN_FLAG, aMessage);
1970 } else {
1971 (new CSPRep()).log(WARN_FLAG, aMessage);
1972 }
1973 }
1975 //////////////////////////////////////////////////////////////////////
1977 this.CSPLocalizer = {
1978 /**
1979 * Retrieve a localized string.
1980 *
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)
1987 {
1988 let result;
1989 try {
1990 result = this.stringBundle.GetStringFromName(aName);
1991 }
1992 catch (ex) {
1993 Cu.reportError("Failed to get string: " + aName);
1994 throw ex;
1995 }
1996 return result;
1997 },
1999 /**
2000 * Retrieve a localized string formatted with values coming from the given
2001 * array.
2002 *
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)
2011 {
2012 let result;
2013 try {
2014 result = this.stringBundle.formatStringFromName(aName, aArray, aArray.length);
2015 }
2016 catch (ex) {
2017 Cu.reportError("Failed to format string: " + aName);
2018 throw ex;
2019 }
2020 return result;
2021 },
2022 };
2024 XPCOMUtils.defineLazyGetter(CSPLocalizer, "stringBundle", function() {
2025 return Services.strings.createBundle(STRINGS_URI);
2026 });