Bug 1885580 - Add a MenuGroup component for the menu redesign r=android-reviewers,007
[gecko.git] / dom / security / nsCSPUtils.cpp
blob11d09909f73fee425fd0f50b384c396a52e02a36
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsAttrValue.h"
8 #include "nsCharSeparatedTokenizer.h"
9 #include "nsContentUtils.h"
10 #include "nsCSPUtils.h"
11 #include "nsDebug.h"
12 #include "nsCSPParser.h"
13 #include "nsComponentManagerUtils.h"
14 #include "nsIConsoleService.h"
15 #include "nsIChannel.h"
16 #include "nsICryptoHash.h"
17 #include "nsIScriptError.h"
18 #include "nsIStringBundle.h"
19 #include "nsIURL.h"
20 #include "nsNetUtil.h"
21 #include "nsReadableUtils.h"
22 #include "nsSandboxFlags.h"
23 #include "nsServiceManagerUtils.h"
24 #include "nsWhitespaceTokenizer.h"
26 #include "mozilla/Components.h"
27 #include "mozilla/dom/CSPDictionariesBinding.h"
28 #include "mozilla/dom/Document.h"
29 #include "mozilla/dom/SRIMetadata.h"
30 #include "mozilla/StaticPrefs_security.h"
32 using namespace mozilla;
33 using mozilla::dom::SRIMetadata;
35 #define DEFAULT_PORT -1
37 static mozilla::LogModule* GetCspUtilsLog() {
38 static mozilla::LazyLogModule gCspUtilsPRLog("CSPUtils");
39 return gCspUtilsPRLog;
42 #define CSPUTILSLOG(args) \
43 MOZ_LOG(GetCspUtilsLog(), mozilla::LogLevel::Debug, args)
44 #define CSPUTILSLOGENABLED() \
45 MOZ_LOG_TEST(GetCspUtilsLog(), mozilla::LogLevel::Debug)
47 void CSP_PercentDecodeStr(const nsAString& aEncStr, nsAString& outDecStr) {
48 outDecStr.Truncate();
50 // helper function that should not be visible outside this methods scope
51 struct local {
52 static inline char16_t convertHexDig(char16_t aHexDig) {
53 if (isNumberToken(aHexDig)) {
54 return aHexDig - '0';
56 if (aHexDig >= 'A' && aHexDig <= 'F') {
57 return aHexDig - 'A' + 10;
59 // must be a lower case character
60 // (aHexDig >= 'a' && aHexDig <= 'f')
61 return aHexDig - 'a' + 10;
65 const char16_t *cur, *end, *hexDig1, *hexDig2;
66 cur = aEncStr.BeginReading();
67 end = aEncStr.EndReading();
69 while (cur != end) {
70 // if it's not a percent sign then there is
71 // nothing to do for that character
72 if (*cur != PERCENT_SIGN) {
73 outDecStr.Append(*cur);
74 cur++;
75 continue;
78 // get the two hexDigs following the '%'-sign
79 hexDig1 = cur + 1;
80 hexDig2 = cur + 2;
82 // if there are no hexdigs after the '%' then
83 // there is nothing to do for us.
84 if (hexDig1 == end || hexDig2 == end || !isValidHexDig(*hexDig1) ||
85 !isValidHexDig(*hexDig2)) {
86 outDecStr.Append(PERCENT_SIGN);
87 cur++;
88 continue;
91 // decode "% hexDig1 hexDig2" into a character.
92 char16_t decChar =
93 (local::convertHexDig(*hexDig1) << 4) + local::convertHexDig(*hexDig2);
94 outDecStr.Append(decChar);
96 // increment 'cur' to after the second hexDig
97 cur = ++hexDig2;
101 // The Content Security Policy should be inherited for
102 // local schemes like: "about", "blob", "data", or "filesystem".
103 // see: https://w3c.github.io/webappsec-csp/#initialize-document-csp
104 bool CSP_ShouldResponseInheritCSP(nsIChannel* aChannel) {
105 if (!aChannel) {
106 return false;
109 nsCOMPtr<nsIURI> uri;
110 nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
111 NS_ENSURE_SUCCESS(rv, false);
113 bool isAbout = uri->SchemeIs("about");
114 if (isAbout) {
115 nsAutoCString aboutSpec;
116 rv = uri->GetSpec(aboutSpec);
117 NS_ENSURE_SUCCESS(rv, false);
118 // also allow about:blank#foo
119 if (StringBeginsWith(aboutSpec, "about:blank"_ns) ||
120 StringBeginsWith(aboutSpec, "about:srcdoc"_ns)) {
121 return true;
125 return uri->SchemeIs("blob") || uri->SchemeIs("data") ||
126 uri->SchemeIs("filesystem") || uri->SchemeIs("javascript");
129 void CSP_ApplyMetaCSPToDoc(mozilla::dom::Document& aDoc,
130 const nsAString& aPolicyStr) {
131 if (aDoc.IsLoadedAsData()) {
132 return;
135 nsAutoString policyStr(
136 nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
137 aPolicyStr));
139 if (policyStr.IsEmpty()) {
140 return;
143 nsCOMPtr<nsIContentSecurityPolicy> csp = aDoc.GetCsp();
144 if (!csp) {
145 MOZ_ASSERT(false, "how come there is no CSP");
146 return;
149 // Multiple CSPs (delivered through either header of meta tag) need to
150 // be joined together, see:
151 // https://w3c.github.io/webappsec/specs/content-security-policy/#delivery-html-meta-element
152 nsresult rv =
153 csp->AppendPolicy(policyStr,
154 false, // csp via meta tag can not be report only
155 true); // delivered through the meta tag
156 NS_ENSURE_SUCCESS_VOID(rv);
157 if (nsPIDOMWindowInner* inner = aDoc.GetInnerWindow()) {
158 inner->SetCsp(csp);
160 aDoc.ApplySettingsFromCSP(false);
163 void CSP_GetLocalizedStr(const char* aName, const nsTArray<nsString>& aParams,
164 nsAString& outResult) {
165 nsCOMPtr<nsIStringBundle> keyStringBundle;
166 nsCOMPtr<nsIStringBundleService> stringBundleService =
167 mozilla::components::StringBundle::Service();
169 NS_ASSERTION(stringBundleService, "String bundle service must be present!");
170 stringBundleService->CreateBundle(
171 "chrome://global/locale/security/csp.properties",
172 getter_AddRefs(keyStringBundle));
174 NS_ASSERTION(keyStringBundle, "Key string bundle must be available!");
176 if (!keyStringBundle) {
177 return;
179 keyStringBundle->FormatStringFromName(aName, aParams, outResult);
182 void CSP_LogStrMessage(const nsAString& aMsg) {
183 nsCOMPtr<nsIConsoleService> console(
184 do_GetService("@mozilla.org/consoleservice;1"));
186 if (!console) {
187 return;
189 nsString msg(aMsg);
190 console->LogStringMessage(msg.get());
193 void CSP_LogMessage(const nsAString& aMessage, const nsAString& aSourceName,
194 const nsAString& aSourceLine, uint32_t aLineNumber,
195 uint32_t aColumnNumber, uint32_t aFlags,
196 const nsACString& aCategory, uint64_t aInnerWindowID,
197 bool aFromPrivateWindow) {
198 nsCOMPtr<nsIConsoleService> console(
199 do_GetService(NS_CONSOLESERVICE_CONTRACTID));
201 nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
203 if (!console || !error) {
204 return;
207 // Prepending CSP to the outgoing console message
208 nsString cspMsg;
209 CSP_GetLocalizedStr("CSPMessagePrefix",
210 AutoTArray<nsString, 1>{nsString(aMessage)}, cspMsg);
212 // Currently 'aSourceLine' is not logged to the console, because similar
213 // information is already included within the source link of the message.
214 // For inline violations however, the line and column number are 0 and
215 // information contained within 'aSourceLine' can be really useful for devs.
216 // E.g. 'aSourceLine' might be: 'onclick attribute on DIV element'.
217 // In such cases we append 'aSourceLine' directly to the error message.
218 if (!aSourceLine.IsEmpty() && aLineNumber == 0) {
219 cspMsg.AppendLiteral(u"\nSource: ");
220 cspMsg.Append(aSourceLine);
223 // Since we are leveraging csp errors as the category names which
224 // we pass to devtools, we should prepend them with "CSP_" to
225 // allow easy distincution in devtools code. e.g.
226 // upgradeInsecureRequest -> CSP_upgradeInsecureRequest
227 nsCString category("CSP_");
228 category.Append(aCategory);
230 nsresult rv;
231 if (aInnerWindowID > 0) {
232 rv = error->InitWithWindowID(cspMsg, aSourceName, aSourceLine, aLineNumber,
233 aColumnNumber, aFlags, category,
234 aInnerWindowID);
235 } else {
236 rv = error->Init(cspMsg, aSourceName, aSourceLine, aLineNumber,
237 aColumnNumber, aFlags, category, aFromPrivateWindow,
238 true /* from chrome context */);
240 if (NS_FAILED(rv)) {
241 return;
243 console->LogMessage(error);
247 * Combines CSP_LogMessage and CSP_GetLocalizedStr into one call.
249 void CSP_LogLocalizedStr(const char* aName, const nsTArray<nsString>& aParams,
250 const nsAString& aSourceName,
251 const nsAString& aSourceLine, uint32_t aLineNumber,
252 uint32_t aColumnNumber, uint32_t aFlags,
253 const nsACString& aCategory, uint64_t aInnerWindowID,
254 bool aFromPrivateWindow) {
255 nsAutoString logMsg;
256 CSP_GetLocalizedStr(aName, aParams, logMsg);
257 CSP_LogMessage(logMsg, aSourceName, aSourceLine, aLineNumber, aColumnNumber,
258 aFlags, aCategory, aInnerWindowID, aFromPrivateWindow);
261 /* ===== Helpers ============================ */
262 // This implements
263 // https://w3c.github.io/webappsec-csp/#effective-directive-for-a-request.
264 // However the spec doesn't currently cover all request destinations, which
265 // we roughly represent using nsContentPolicyType.
266 CSPDirective CSP_ContentTypeToDirective(nsContentPolicyType aType) {
267 switch (aType) {
268 case nsIContentPolicy::TYPE_IMAGE:
269 case nsIContentPolicy::TYPE_IMAGESET:
270 case nsIContentPolicy::TYPE_INTERNAL_IMAGE:
271 case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD:
272 case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON:
273 return nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE;
275 // BLock XSLT as script, see bug 910139
276 case nsIContentPolicy::TYPE_XSLT:
277 case nsIContentPolicy::TYPE_SCRIPT:
278 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
279 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
280 case nsIContentPolicy::TYPE_INTERNAL_MODULE:
281 case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD:
282 case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS:
283 case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET:
284 case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET:
285 case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT:
286 case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT:
287 // (https://github.com/w3c/webappsec-csp/issues/554)
288 // Some of these types are not explicitly defined in the spec.
290 // Chrome seems to use script-src-elem for worklet!
291 return nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE;
293 case nsIContentPolicy::TYPE_STYLESHEET:
294 case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET:
295 case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD:
296 return nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE;
298 case nsIContentPolicy::TYPE_FONT:
299 case nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD:
300 return nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE;
302 case nsIContentPolicy::TYPE_MEDIA:
303 case nsIContentPolicy::TYPE_INTERNAL_AUDIO:
304 case nsIContentPolicy::TYPE_INTERNAL_VIDEO:
305 case nsIContentPolicy::TYPE_INTERNAL_TRACK:
306 return nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE;
308 case nsIContentPolicy::TYPE_WEB_MANIFEST:
309 return nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE;
311 case nsIContentPolicy::TYPE_INTERNAL_WORKER:
312 case nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE:
313 case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
314 case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
315 return nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE;
317 case nsIContentPolicy::TYPE_SUBDOCUMENT:
318 case nsIContentPolicy::TYPE_INTERNAL_FRAME:
319 case nsIContentPolicy::TYPE_INTERNAL_IFRAME:
320 return nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE;
322 case nsIContentPolicy::TYPE_WEBSOCKET:
323 case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
324 case nsIContentPolicy::TYPE_BEACON:
325 case nsIContentPolicy::TYPE_PING:
326 case nsIContentPolicy::TYPE_FETCH:
327 case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST:
328 case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE:
329 case nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD:
330 case nsIContentPolicy::TYPE_WEB_IDENTITY:
331 case nsIContentPolicy::TYPE_WEB_TRANSPORT:
332 return nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE;
334 case nsIContentPolicy::TYPE_OBJECT:
335 case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST:
336 case nsIContentPolicy::TYPE_INTERNAL_EMBED:
337 case nsIContentPolicy::TYPE_INTERNAL_OBJECT:
338 return nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE;
340 case nsIContentPolicy::TYPE_DTD:
341 case nsIContentPolicy::TYPE_OTHER:
342 case nsIContentPolicy::TYPE_SPECULATIVE:
343 case nsIContentPolicy::TYPE_INTERNAL_DTD:
344 case nsIContentPolicy::TYPE_INTERNAL_FORCE_ALLOWED_DTD:
345 return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
347 // CSP does not apply to webrtc connections
348 case nsIContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA:
349 // csp shold not block top level loads, e.g. in case
350 // of a redirect.
351 case nsIContentPolicy::TYPE_DOCUMENT:
352 // CSP can not block csp reports
353 case nsIContentPolicy::TYPE_CSP_REPORT:
354 return nsIContentSecurityPolicy::NO_DIRECTIVE;
356 case nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD:
357 case nsIContentPolicy::TYPE_UA_FONT:
358 return nsIContentSecurityPolicy::NO_DIRECTIVE;
360 // Fall through to error for all other directives
361 case nsIContentPolicy::TYPE_INVALID:
362 case nsIContentPolicy::TYPE_END:
363 MOZ_ASSERT(false, "Can not map nsContentPolicyType to CSPDirective");
364 // Do not add default: so that compilers can catch the missing case.
366 return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
369 nsCSPHostSrc* CSP_CreateHostSrcFromSelfURI(nsIURI* aSelfURI) {
370 // Create the host first
371 nsCString host;
372 aSelfURI->GetAsciiHost(host);
373 nsCSPHostSrc* hostsrc = new nsCSPHostSrc(NS_ConvertUTF8toUTF16(host));
374 hostsrc->setGeneratedFromSelfKeyword();
376 // Add the scheme.
377 nsCString scheme;
378 aSelfURI->GetScheme(scheme);
379 hostsrc->setScheme(NS_ConvertUTF8toUTF16(scheme));
381 // An empty host (e.g. for data:) indicates it's effectively a unique origin.
382 // Please note that we still need to set the scheme on hostsrc (see above),
383 // because it's used for reporting.
384 if (host.EqualsLiteral("")) {
385 hostsrc->setIsUniqueOrigin();
386 // no need to query the port in that case.
387 return hostsrc;
390 int32_t port;
391 aSelfURI->GetPort(&port);
392 // Only add port if it's not default port.
393 if (port > 0) {
394 nsAutoString portStr;
395 portStr.AppendInt(port);
396 hostsrc->setPort(portStr);
398 return hostsrc;
401 bool CSP_IsEmptyDirective(const nsAString& aValue, const nsAString& aDir) {
402 return (aDir.Length() == 0 && aValue.Length() == 0);
404 bool CSP_IsDirective(const nsAString& aValue, CSPDirective aDir) {
405 return aValue.LowerCaseEqualsASCII(CSP_CSPDirectiveToString(aDir));
408 bool CSP_IsKeyword(const nsAString& aValue, enum CSPKeyword aKey) {
409 return aValue.LowerCaseEqualsASCII(CSP_EnumToUTF8Keyword(aKey));
412 bool CSP_IsQuotelessKeyword(const nsAString& aKey) {
413 nsString lowerKey;
414 ToLowerCase(aKey, lowerKey);
416 nsAutoString keyword;
417 for (uint32_t i = 0; i < CSP_LAST_KEYWORD_VALUE; i++) {
418 // skipping the leading ' and trimming the trailing '
419 keyword.AssignASCII(gCSPUTF8Keywords[i] + 1);
420 keyword.Trim("'", false, true);
421 if (lowerKey.Equals(keyword)) {
422 return true;
425 return false;
429 * Checks whether the current directive permits a specific
430 * scheme. This function is called from nsCSPSchemeSrc() and
431 * also nsCSPHostSrc.
432 * @param aEnforcementScheme
433 * The scheme that this directive allows
434 * @param aUri
435 * The uri of the subresource load.
436 * @param aReportOnly
437 * Whether the enforced policy is report only or not.
438 * @param aUpgradeInsecure
439 * Whether the policy makes use of the directive
440 * 'upgrade-insecure-requests'.
441 * @param aFromSelfURI
442 * Whether a scheme was generated from the keyword 'self'
443 * which then allows schemeless sources to match ws and wss.
446 bool permitsScheme(const nsAString& aEnforcementScheme, nsIURI* aUri,
447 bool aReportOnly, bool aUpgradeInsecure, bool aFromSelfURI) {
448 nsAutoCString scheme;
449 nsresult rv = aUri->GetScheme(scheme);
450 NS_ENSURE_SUCCESS(rv, false);
452 // no scheme to enforce, let's allow the load (e.g. script-src *)
453 if (aEnforcementScheme.IsEmpty()) {
454 return true;
457 // if the scheme matches, all good - allow the load
458 if (aEnforcementScheme.EqualsASCII(scheme.get())) {
459 return true;
462 // allow scheme-less sources where the protected resource is http
463 // and the load is https, see:
464 // http://www.w3.org/TR/CSP2/#match-source-expression
465 if (aEnforcementScheme.EqualsASCII("http")) {
466 if (scheme.EqualsASCII("https")) {
467 return true;
469 if ((scheme.EqualsASCII("ws") || scheme.EqualsASCII("wss")) &&
470 aFromSelfURI) {
471 return true;
474 if (aEnforcementScheme.EqualsASCII("https")) {
475 if (scheme.EqualsLiteral("wss") && aFromSelfURI) {
476 return true;
479 if (aEnforcementScheme.EqualsASCII("ws") && scheme.EqualsASCII("wss")) {
480 return true;
483 // Allow the load when enforcing upgrade-insecure-requests with the
484 // promise the request gets upgraded from http to https and ws to wss.
485 // See nsHttpChannel::Connect() and also WebSocket.cpp. Please note,
486 // the report only policies should not allow the load and report
487 // the error back to the page.
488 return (
489 (aUpgradeInsecure && !aReportOnly) &&
490 ((scheme.EqualsASCII("http") &&
491 aEnforcementScheme.EqualsASCII("https")) ||
492 (scheme.EqualsASCII("ws") && aEnforcementScheme.EqualsASCII("wss"))));
496 * A helper function for appending a CSP header to an existing CSP
497 * policy.
499 * @param aCsp the CSP policy
500 * @param aHeaderValue the header
501 * @param aReportOnly is this a report-only header?
504 nsresult CSP_AppendCSPFromHeader(nsIContentSecurityPolicy* aCsp,
505 const nsAString& aHeaderValue,
506 bool aReportOnly) {
507 NS_ENSURE_ARG(aCsp);
509 // Need to tokenize the header value since multiple headers could be
510 // concatenated into one comma-separated list of policies.
511 // See RFC2616 section 4.2 (last paragraph)
512 nsresult rv = NS_OK;
513 for (const nsAString& policy :
514 nsCharSeparatedTokenizer(aHeaderValue, ',').ToRange()) {
515 rv = aCsp->AppendPolicy(policy, aReportOnly, false);
516 NS_ENSURE_SUCCESS(rv, rv);
518 CSPUTILSLOG(("CSP refined with policy: \"%s\"",
519 NS_ConvertUTF16toUTF8(policy).get()));
522 return NS_OK;
525 /* ===== nsCSPSrc ============================ */
527 nsCSPBaseSrc::nsCSPBaseSrc() {}
529 nsCSPBaseSrc::~nsCSPBaseSrc() = default;
531 // ::permits is only called for external load requests, therefore:
532 // nsCSPKeywordSrc and nsCSPHashSource fall back to this base class
533 // implementation which will never allow the load.
534 bool nsCSPBaseSrc::permits(nsIURI* aUri, bool aWasRedirected, bool aReportOnly,
535 bool aUpgradeInsecure) const {
536 if (CSPUTILSLOGENABLED()) {
537 CSPUTILSLOG(
538 ("nsCSPBaseSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
540 return false;
543 // ::allows is only called for inlined loads, therefore:
544 // nsCSPSchemeSrc, nsCSPHostSrc fall back
545 // to this base class implementation which will never allow the load.
546 bool nsCSPBaseSrc::allows(enum CSPKeyword aKeyword,
547 const nsAString& aHashOrNonce) const {
548 CSPUTILSLOG(("nsCSPBaseSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
549 aKeyword == CSP_HASH ? "hash" : CSP_EnumToUTF8Keyword(aKeyword),
550 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
551 return false;
554 /* ====== nsCSPSchemeSrc ===================== */
556 nsCSPSchemeSrc::nsCSPSchemeSrc(const nsAString& aScheme) : mScheme(aScheme) {
557 ToLowerCase(mScheme);
560 nsCSPSchemeSrc::~nsCSPSchemeSrc() = default;
562 bool nsCSPSchemeSrc::permits(nsIURI* aUri, bool aWasRedirected,
563 bool aReportOnly, bool aUpgradeInsecure) const {
564 if (CSPUTILSLOGENABLED()) {
565 CSPUTILSLOG(
566 ("nsCSPSchemeSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
568 MOZ_ASSERT((!mScheme.EqualsASCII("")), "scheme can not be the empty string");
569 return permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure, false);
572 bool nsCSPSchemeSrc::visit(nsCSPSrcVisitor* aVisitor) const {
573 return aVisitor->visitSchemeSrc(*this);
576 void nsCSPSchemeSrc::toString(nsAString& outStr) const {
577 outStr.Append(mScheme);
578 outStr.AppendLiteral(":");
581 /* ===== nsCSPHostSrc ======================== */
583 nsCSPHostSrc::nsCSPHostSrc(const nsAString& aHost)
584 : mHost(aHost),
585 mGeneratedFromSelfKeyword(false),
586 mIsUniqueOrigin(false),
587 mWithinFrameAncstorsDir(false) {
588 ToLowerCase(mHost);
591 nsCSPHostSrc::~nsCSPHostSrc() = default;
594 * Checks whether the current directive permits a specific port.
595 * @param aEnforcementScheme
596 * The scheme that this directive allows
597 * (used to query the default port for that scheme)
598 * @param aEnforcementPort
599 * The port that this directive allows
600 * @param aResourceURI
601 * The uri of the subresource load
603 bool permitsPort(const nsAString& aEnforcementScheme,
604 const nsAString& aEnforcementPort, nsIURI* aResourceURI) {
605 // If enforcement port is the wildcard, don't block the load.
606 if (aEnforcementPort.EqualsASCII("*")) {
607 return true;
610 int32_t resourcePort;
611 nsresult rv = aResourceURI->GetPort(&resourcePort);
612 if (NS_FAILED(rv) && aEnforcementPort.IsEmpty()) {
613 // If we cannot get a Port (e.g. because of an Custom Protocol handler)
614 // We need to check if a default port is associated with the Scheme
615 if (aEnforcementScheme.IsEmpty()) {
616 return false;
618 int defaultPortforScheme =
619 NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme).get());
621 // If there is no default port associated with the Scheme (
622 // defaultPortforScheme == -1) or it is an externally handled protocol (
623 // defaultPortforScheme == 0 ) and the csp does not enforce a port - we can
624 // allow not having a port
625 return (defaultPortforScheme == -1 || defaultPortforScheme == -0);
627 // Avoid unnecessary string creation/manipulation and don't block the
628 // load if the resource to be loaded uses the default port for that
629 // scheme and there is no port to be enforced.
630 // Note, this optimization relies on scheme checks within permitsScheme().
631 if (resourcePort == DEFAULT_PORT && aEnforcementPort.IsEmpty()) {
632 return true;
635 // By now we know at that either the resourcePort does not use the default
636 // port or there is a port restriction to be enforced. A port value of -1
637 // corresponds to the protocol's default port (eg. -1 implies port 80 for
638 // http URIs), in such a case we have to query the default port of the
639 // resource to be loaded.
640 if (resourcePort == DEFAULT_PORT) {
641 nsAutoCString resourceScheme;
642 rv = aResourceURI->GetScheme(resourceScheme);
643 NS_ENSURE_SUCCESS(rv, false);
644 resourcePort = NS_GetDefaultPort(resourceScheme.get());
647 // If there is a port to be enforced and the ports match, then
648 // don't block the load.
649 nsString resourcePortStr;
650 resourcePortStr.AppendInt(resourcePort);
651 if (aEnforcementPort.Equals(resourcePortStr)) {
652 return true;
655 // If there is no port to be enforced, query the default port for the load.
656 nsString enforcementPort(aEnforcementPort);
657 if (enforcementPort.IsEmpty()) {
658 // For scheme less sources, our parser always generates a scheme
659 // which is the scheme of the protected resource.
660 MOZ_ASSERT(!aEnforcementScheme.IsEmpty(),
661 "need a scheme to query default port");
662 int32_t defaultEnforcementPort =
663 NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme).get());
664 enforcementPort.Truncate();
665 enforcementPort.AppendInt(defaultEnforcementPort);
668 // If default ports match, don't block the load
669 if (enforcementPort.Equals(resourcePortStr)) {
670 return true;
673 // Additional port matching where the regular URL matching algorithm
674 // treats insecure ports as matching their secure variants.
675 // default port for http is :80
676 // default port for https is :443
677 if (enforcementPort.EqualsLiteral("80") &&
678 resourcePortStr.EqualsLiteral("443")) {
679 return true;
682 // ports do not match, block the load.
683 return false;
686 bool nsCSPHostSrc::permits(nsIURI* aUri, bool aWasRedirected, bool aReportOnly,
687 bool aUpgradeInsecure) const {
688 if (CSPUTILSLOGENABLED()) {
689 CSPUTILSLOG(
690 ("nsCSPHostSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
693 if (mIsUniqueOrigin) {
694 return false;
697 // we are following the enforcement rules from the spec, see:
698 // http://www.w3.org/TR/CSP11/#match-source-expression
700 // 4.3) scheme matching: Check if the scheme matches.
701 if (!permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure,
702 mGeneratedFromSelfKeyword)) {
703 return false;
706 // The host in nsCSpHostSrc should never be empty. In case we are enforcing
707 // just a specific scheme, the parser should generate a nsCSPSchemeSource.
708 NS_ASSERTION((!mHost.IsEmpty()), "host can not be the empty string");
710 // Before we can check if the host matches, we have to
711 // extract the host part from aUri.
712 nsAutoCString uriHost;
713 nsresult rv = aUri->GetAsciiHost(uriHost);
714 NS_ENSURE_SUCCESS(rv, false);
716 nsString decodedUriHost;
717 CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriHost), decodedUriHost);
719 // 2) host matching: Enforce a single *
720 if (mHost.EqualsASCII("*")) {
721 // The single ASTERISK character (*) does not match a URI's scheme of a type
722 // designating a globally unique identifier (such as blob:, data:, or
723 // filesystem:) At the moment firefox does not support filesystem; but for
724 // future compatibility we support it in CSP according to the spec,
725 // see: 4.2.2 Matching Source Expressions Note, that allowlisting any of
726 // these schemes would call nsCSPSchemeSrc::permits().
727 if (aUri->SchemeIs("blob") || aUri->SchemeIs("data") ||
728 aUri->SchemeIs("filesystem")) {
729 return false;
732 // If no scheme is present there also wont be a port and folder to check
733 // which means we can return early
734 if (mScheme.IsEmpty()) {
735 return true;
738 // 4.5) host matching: Check if the allowed host starts with a wilcard.
739 else if (mHost.First() == '*') {
740 NS_ASSERTION(
741 mHost[1] == '.',
742 "Second character needs to be '.' whenever host starts with '*'");
744 // Eliminate leading "*", but keeping the FULL STOP (.) thereafter before
745 // checking if the remaining characters match
746 nsString wildCardHost = mHost;
747 wildCardHost = Substring(wildCardHost, 1, wildCardHost.Length() - 1);
748 if (!StringEndsWith(decodedUriHost, wildCardHost)) {
749 return false;
752 // 4.6) host matching: Check if hosts match.
753 else if (!mHost.Equals(decodedUriHost)) {
754 return false;
757 // Port matching: Check if the ports match.
758 if (!permitsPort(mScheme, mPort, aUri)) {
759 return false;
762 // 4.9) Path matching: If there is a path, we have to enforce
763 // path-level matching, unless the channel got redirected, see:
764 // http://www.w3.org/TR/CSP11/#source-list-paths-and-redirects
765 if (!aWasRedirected && !mPath.IsEmpty()) {
766 // converting aUri into nsIURL so we can strip query and ref
767 // example.com/test#foo -> example.com/test
768 // example.com/test?val=foo -> example.com/test
769 nsCOMPtr<nsIURL> url = do_QueryInterface(aUri);
770 if (!url) {
771 NS_ASSERTION(false, "can't QI into nsIURI");
772 return false;
774 nsAutoCString uriPath;
775 rv = url->GetFilePath(uriPath);
776 NS_ENSURE_SUCCESS(rv, false);
778 if (mWithinFrameAncstorsDir) {
779 // no path matching for frame-ancestors to not leak any path information.
780 return true;
783 nsString decodedUriPath;
784 CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriPath), decodedUriPath);
786 // check if the last character of mPath is '/'; if so
787 // we just have to check loading resource is within
788 // the allowed path.
789 if (mPath.Last() == '/') {
790 if (!StringBeginsWith(decodedUriPath, mPath)) {
791 return false;
794 // otherwise mPath refers to a specific file, and we have to
795 // check if the loading resource matches the file.
796 else {
797 if (!mPath.Equals(decodedUriPath)) {
798 return false;
803 // At the end: scheme, host, port and path match -> allow the load.
804 return true;
807 bool nsCSPHostSrc::visit(nsCSPSrcVisitor* aVisitor) const {
808 return aVisitor->visitHostSrc(*this);
811 void nsCSPHostSrc::toString(nsAString& outStr) const {
812 if (mGeneratedFromSelfKeyword) {
813 outStr.AppendLiteral("'self'");
814 return;
817 // If mHost is a single "*", we append the wildcard and return.
818 if (mHost.EqualsASCII("*") && mScheme.IsEmpty() && mPort.IsEmpty()) {
819 outStr.Append(mHost);
820 return;
823 // append scheme
824 outStr.Append(mScheme);
826 // append host
827 outStr.AppendLiteral("://");
828 outStr.Append(mHost);
830 // append port
831 if (!mPort.IsEmpty()) {
832 outStr.AppendLiteral(":");
833 outStr.Append(mPort);
836 // append path
837 outStr.Append(mPath);
840 void nsCSPHostSrc::setScheme(const nsAString& aScheme) {
841 mScheme = aScheme;
842 ToLowerCase(mScheme);
845 void nsCSPHostSrc::setPort(const nsAString& aPort) { mPort = aPort; }
847 void nsCSPHostSrc::appendPath(const nsAString& aPath) { mPath.Append(aPath); }
849 /* ===== nsCSPKeywordSrc ===================== */
851 nsCSPKeywordSrc::nsCSPKeywordSrc(enum CSPKeyword aKeyword)
852 : mKeyword(aKeyword) {
853 NS_ASSERTION((aKeyword != CSP_SELF),
854 "'self' should have been replaced in the parser");
857 nsCSPKeywordSrc::~nsCSPKeywordSrc() = default;
859 bool nsCSPKeywordSrc::allows(enum CSPKeyword aKeyword,
860 const nsAString& aHashOrNonce) const {
861 CSPUTILSLOG(("nsCSPKeywordSrc::allows, aKeyWord: %s, aHashOrNonce: %s",
862 CSP_EnumToUTF8Keyword(aKeyword),
863 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
864 return mKeyword == aKeyword;
867 bool nsCSPKeywordSrc::visit(nsCSPSrcVisitor* aVisitor) const {
868 return aVisitor->visitKeywordSrc(*this);
871 void nsCSPKeywordSrc::toString(nsAString& outStr) const {
872 outStr.Append(CSP_EnumToUTF16Keyword(mKeyword));
875 /* ===== nsCSPNonceSrc ==================== */
877 nsCSPNonceSrc::nsCSPNonceSrc(const nsAString& aNonce) : mNonce(aNonce) {}
879 nsCSPNonceSrc::~nsCSPNonceSrc() = default;
881 bool nsCSPNonceSrc::allows(enum CSPKeyword aKeyword,
882 const nsAString& aHashOrNonce) const {
883 CSPUTILSLOG(("nsCSPNonceSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
884 CSP_EnumToUTF8Keyword(aKeyword),
885 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
887 if (aKeyword != CSP_NONCE) {
888 return false;
890 // nonces can not be invalidated by strict-dynamic
891 return mNonce.Equals(aHashOrNonce);
894 bool nsCSPNonceSrc::visit(nsCSPSrcVisitor* aVisitor) const {
895 return aVisitor->visitNonceSrc(*this);
898 void nsCSPNonceSrc::toString(nsAString& outStr) const {
899 outStr.Append(CSP_EnumToUTF16Keyword(CSP_NONCE));
900 outStr.Append(mNonce);
901 outStr.AppendLiteral("'");
904 /* ===== nsCSPHashSrc ===================== */
906 nsCSPHashSrc::nsCSPHashSrc(const nsAString& aAlgo, const nsAString& aHash)
907 : mAlgorithm(aAlgo), mHash(aHash) {
908 // Only the algo should be rewritten to lowercase, the hash must remain the
909 // same.
910 ToLowerCase(mAlgorithm);
911 // Normalize the base64url encoding to base64 encoding:
912 char16_t* cur = mHash.BeginWriting();
913 char16_t* end = mHash.EndWriting();
915 for (; cur < end; ++cur) {
916 if (char16_t('-') == *cur) {
917 *cur = char16_t('+');
919 if (char16_t('_') == *cur) {
920 *cur = char16_t('/');
925 nsCSPHashSrc::~nsCSPHashSrc() = default;
927 bool nsCSPHashSrc::allows(enum CSPKeyword aKeyword,
928 const nsAString& aHashOrNonce) const {
929 CSPUTILSLOG(("nsCSPHashSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
930 CSP_EnumToUTF8Keyword(aKeyword),
931 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
933 if (aKeyword != CSP_HASH) {
934 return false;
937 // hashes can not be invalidated by strict-dynamic
939 // Convert aHashOrNonce to UTF-8
940 NS_ConvertUTF16toUTF8 utf8_hash(aHashOrNonce);
942 nsCOMPtr<nsICryptoHash> hasher;
943 nsresult rv = NS_NewCryptoHash(NS_ConvertUTF16toUTF8(mAlgorithm),
944 getter_AddRefs(hasher));
945 NS_ENSURE_SUCCESS(rv, false);
947 rv = hasher->Update((uint8_t*)utf8_hash.get(), utf8_hash.Length());
948 NS_ENSURE_SUCCESS(rv, false);
950 nsAutoCString hash;
951 rv = hasher->Finish(true, hash);
952 NS_ENSURE_SUCCESS(rv, false);
954 return NS_ConvertUTF16toUTF8(mHash).Equals(hash);
957 bool nsCSPHashSrc::visit(nsCSPSrcVisitor* aVisitor) const {
958 return aVisitor->visitHashSrc(*this);
961 void nsCSPHashSrc::toString(nsAString& outStr) const {
962 outStr.AppendLiteral("'");
963 outStr.Append(mAlgorithm);
964 outStr.AppendLiteral("-");
965 outStr.Append(mHash);
966 outStr.AppendLiteral("'");
969 /* ===== nsCSPReportURI ===================== */
971 nsCSPReportURI::nsCSPReportURI(nsIURI* aURI) : mReportURI(aURI) {}
973 nsCSPReportURI::~nsCSPReportURI() = default;
975 bool nsCSPReportURI::visit(nsCSPSrcVisitor* aVisitor) const { return false; }
977 void nsCSPReportURI::toString(nsAString& outStr) const {
978 nsAutoCString spec;
979 nsresult rv = mReportURI->GetSpec(spec);
980 if (NS_FAILED(rv)) {
981 return;
983 outStr.AppendASCII(spec.get());
986 /* ===== nsCSPSandboxFlags ===================== */
988 nsCSPSandboxFlags::nsCSPSandboxFlags(const nsAString& aFlags) : mFlags(aFlags) {
989 ToLowerCase(mFlags);
992 nsCSPSandboxFlags::~nsCSPSandboxFlags() = default;
994 bool nsCSPSandboxFlags::visit(nsCSPSrcVisitor* aVisitor) const { return false; }
996 void nsCSPSandboxFlags::toString(nsAString& outStr) const {
997 outStr.Append(mFlags);
1000 /* ===== nsCSPDirective ====================== */
1002 nsCSPDirective::nsCSPDirective(CSPDirective aDirective) {
1003 mDirective = aDirective;
1006 nsCSPDirective::~nsCSPDirective() {
1007 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1008 delete mSrcs[i];
1012 // https://w3c.github.io/webappsec-csp/#match-nonce-to-source-list
1013 static bool DoesNonceMatchSourceList(nsILoadInfo* aLoadInfo,
1014 const nsTArray<nsCSPBaseSrc*>& aSrcs) {
1015 // Step 1. Assert: source list is not null. (implicit)
1017 // Note: For code-reuse we do "request’s cryptographic nonce metadata" here
1018 // instead of the caller.
1019 nsAutoString nonce;
1020 MOZ_ALWAYS_SUCCEEDS(aLoadInfo->GetCspNonce(nonce));
1022 // Step 2. If nonce is the empty string, return "Does Not Match".
1023 if (nonce.IsEmpty()) {
1024 return false;
1027 // Step 3. For each expression of source list:
1028 for (nsCSPBaseSrc* src : aSrcs) {
1029 // Step 3.1. If expression matches the nonce-source grammar, and nonce is
1030 // identical to expression’s base64-value part, return "Matches".
1031 if (src->isNonce()) {
1032 nsAutoString srcNonce;
1033 static_cast<nsCSPNonceSrc*>(src)->getNonce(srcNonce);
1034 if (srcNonce == nonce) {
1035 return true;
1040 // Step 4. Return "Does Not Match".
1041 return false;
1044 // https://www.w3.org/TR/SRI/#parse-metadata
1045 // This function is similar to SRICheck::IntegrityMetadata, but also keeps
1046 // SRI metadata with weaker hashes.
1047 // CSP treats "no metadata" and empty results the same way.
1048 static nsTArray<SRIMetadata> ParseSRIMetadata(const nsAString& aMetadata) {
1049 // Step 1. Let result be the empty set.
1050 // Step 2. Let empty be equal to true.
1051 nsTArray<SRIMetadata> result;
1053 NS_ConvertUTF16toUTF8 metadataList(aMetadata);
1054 nsAutoCString token;
1056 // Step 3. For each token returned by splitting metadata on spaces:
1057 nsCWhitespaceTokenizer tokenizer(metadataList);
1058 while (tokenizer.hasMoreTokens()) {
1059 token = tokenizer.nextToken();
1060 // Step 3.1. Set empty to false.
1061 // Step 3.3. Parse token per the grammar in integrity metadata.
1062 SRIMetadata metadata(token);
1063 // Step 3.2. If token is not a valid metadata, skip the remaining steps, and
1064 // proceed to the next token.
1065 if (metadata.IsMalformed()) {
1066 continue;
1069 // Step 3.4. Let algorithm be the alg component of token.
1070 // Step 3.5. If algorithm is a hash function recognized by the user agent,
1071 // add the
1072 // parsed token to result.
1073 if (metadata.IsAlgorithmSupported()) {
1074 result.AppendElement(metadata);
1078 // Step 4. Return no metadata if empty is true, otherwise return result.
1079 return result;
1082 bool nsCSPDirective::permits(CSPDirective aDirective, nsILoadInfo* aLoadInfo,
1083 nsIURI* aUri, bool aWasRedirected,
1084 bool aReportOnly, bool aUpgradeInsecure) const {
1085 MOZ_ASSERT(equals(aDirective) || isDefaultDirective());
1087 if (CSPUTILSLOGENABLED()) {
1088 CSPUTILSLOG(("nsCSPDirective::permits, aUri: %s, aDirective: %s",
1089 aUri->GetSpecOrDefault().get(),
1090 CSP_CSPDirectiveToString(aDirective)));
1093 if (aLoadInfo) {
1094 // https://w3c.github.io/webappsec-csp/#style-src-elem-pre-request
1095 if (aDirective == CSPDirective::STYLE_SRC_ELEM_DIRECTIVE) {
1096 // Step 3. If the result of executing §6.7.2.3 Does nonce match source
1097 // list? on request’s cryptographic nonce metadata and this directive’s
1098 // value is "Matches", return "Allowed".
1099 if (DoesNonceMatchSourceList(aLoadInfo, mSrcs)) {
1100 CSPUTILSLOG((" Allowed by matching nonce (style)"));
1101 return true;
1105 // https://w3c.github.io/webappsec-csp/#script-pre-request
1106 // Step 1. If request’s destination is script-like:
1107 else if (aDirective == CSPDirective::SCRIPT_SRC_ELEM_DIRECTIVE ||
1108 aDirective == CSPDirective::WORKER_SRC_DIRECTIVE) {
1109 // Step 1.1. If the result of executing §6.7.2.3 Does nonce match source
1110 // list? on request’s cryptographic nonce metadata and this directive’s
1111 // value is "Matches", return "Allowed".
1112 if (DoesNonceMatchSourceList(aLoadInfo, mSrcs)) {
1113 CSPUTILSLOG((" Allowed by matching nonce (script-like)"));
1114 return true;
1117 // Step 1.2. Let integrity expressions be the set of source expressions in
1118 // directive’s value that match the hash-source grammar.
1119 nsTArray<nsCSPHashSrc*> integrityExpressions;
1120 bool hasStrictDynamicKeyword =
1121 false; // Optimization to reduce number of iterations.
1122 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1123 if (mSrcs[i]->isHash()) {
1124 integrityExpressions.AppendElement(
1125 static_cast<nsCSPHashSrc*>(mSrcs[i]));
1126 } else if (mSrcs[i]->isKeyword(CSP_STRICT_DYNAMIC)) {
1127 hasStrictDynamicKeyword = true;
1131 // Step 1.3. If integrity expressions is not empty:
1132 if (!integrityExpressions.IsEmpty()) {
1133 // Step 1.3.1. Let integrity sources be the result of executing the
1134 // algorithm defined in [SRI 3.3.3 Parse metadata] on request’s
1135 // integrity metadata.
1136 nsAutoString integrityMetadata;
1137 aLoadInfo->GetIntegrityMetadata(integrityMetadata);
1139 nsTArray<SRIMetadata> integritySources =
1140 ParseSRIMetadata(integrityMetadata);
1141 MOZ_ASSERT(
1142 integritySources.IsEmpty() == integrityMetadata.IsEmpty(),
1143 "The integrity metadata should be only be empty, "
1144 "when the parsed string was completely empty, otherwise it should "
1145 "include at least one valid hash");
1147 // Step 1.3.2. If integrity sources is "no metadata" or an empty set,
1148 // skip the remaining substeps.
1149 if (!integritySources.IsEmpty()) {
1150 // Step 1.3.3. Let bypass due to integrity match be true.
1151 bool bypass = true;
1153 nsAutoCString sourceAlgorithmUTF8;
1154 nsAutoCString sourceHashUTF8;
1155 nsAutoString sourceAlgorithm;
1156 nsAutoString sourceHash;
1157 nsAutoString algorithm;
1158 nsAutoString hash;
1160 // Step 1.3.4. For each source of integrity sources:
1161 for (const SRIMetadata& source : integritySources) {
1162 source.GetAlgorithm(&sourceAlgorithmUTF8);
1163 sourceAlgorithm = NS_ConvertUTF8toUTF16(sourceAlgorithmUTF8);
1164 source.GetHash(0, &sourceHashUTF8);
1165 sourceHash = NS_ConvertUTF8toUTF16(sourceHashUTF8);
1167 // Step 1.3.4.1 If directive’s value does not contain a source
1168 // expression whose hash-algorithm is an ASCII case-insensitive
1169 // match for source’s hash-algorithm, and whose base64-value is
1170 // identical to source’s base64-value, then set bypass due to
1171 // integrity match to false.
1172 bool found = false;
1173 for (const nsCSPHashSrc* hashSrc : integrityExpressions) {
1174 hashSrc->getAlgorithm(algorithm);
1175 hashSrc->getHash(hash);
1177 // The nsCSPHashSrc constructor lowercases algorithm, so this
1178 // is case-insensitive.
1179 if (sourceAlgorithm == algorithm && sourceHash == hash) {
1180 found = true;
1181 break;
1185 if (!found) {
1186 bypass = false;
1187 break;
1191 // Step 1.3.5. If bypass due to integrity match is true, return
1192 // "Allowed".
1193 if (bypass) {
1194 CSPUTILSLOG(
1195 (" Allowed by matching integrity metadata (script-like)"));
1196 return true;
1201 // Step 1.4. If directive’s value contains a source expression that is an
1202 // ASCII case-insensitive match for the "'strict-dynamic'" keyword-source:
1204 // XXX I don't think we should apply strict-dynamic to XSLT.
1205 if (hasStrictDynamicKeyword && aLoadInfo->InternalContentPolicyType() !=
1206 nsIContentPolicy::TYPE_XSLT) {
1207 // Step 1.4.1 If the request’s parser metadata is "parser-inserted",
1208 // return "Blocked". Otherwise, return "Allowed".
1209 if (aLoadInfo->GetParserCreatedScript()) {
1210 CSPUTILSLOG(
1211 (" Blocked by 'strict-dynamic' because parser-inserted"));
1212 return false;
1215 CSPUTILSLOG(
1216 (" Allowed by 'strict-dynamic' because not-parser-inserted"));
1217 return true;
1222 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1223 if (mSrcs[i]->permits(aUri, aWasRedirected, aReportOnly,
1224 aUpgradeInsecure)) {
1225 return true;
1228 return false;
1231 bool nsCSPDirective::allows(enum CSPKeyword aKeyword,
1232 const nsAString& aHashOrNonce) const {
1233 CSPUTILSLOG(("nsCSPDirective::allows, aKeyWord: %s, aHashOrNonce: %s",
1234 CSP_EnumToUTF8Keyword(aKeyword),
1235 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
1237 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1238 if (mSrcs[i]->allows(aKeyword, aHashOrNonce)) {
1239 return true;
1242 return false;
1245 // https://w3c.github.io/webappsec-csp/#allow-all-inline
1246 bool nsCSPDirective::allowsAllInlineBehavior(CSPDirective aDir) const {
1247 // Step 1. Let allow all inline be false.
1248 bool allowAll = false;
1250 // Step 2. For each expression of list:
1251 for (nsCSPBaseSrc* src : mSrcs) {
1252 // Step 2.1. If expression matches the nonce-source or hash-source grammar,
1253 // return "Does Not Allow".
1254 if (src->isNonce() || src->isHash()) {
1255 return false;
1258 // Step 2.2. If type is "script", "script attribute" or "navigation" and
1259 // expression matches the keyword-source "'strict-dynamic'", return "Does
1260 // Not Allow".
1261 if ((aDir == nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE ||
1262 aDir == nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) &&
1263 src->isKeyword(CSP_STRICT_DYNAMIC)) {
1264 return false;
1267 // Step 2.3. If expression is an ASCII case-insensitive match for the
1268 // keyword-source "'unsafe-inline'", set allow all inline to true.
1269 if (src->isKeyword(CSP_UNSAFE_INLINE)) {
1270 allowAll = true;
1274 // Step 3. If allow all inline is true, return "Allows". Otherwise, return
1275 // "Does Not Allow".
1276 return allowAll;
1279 void nsCSPDirective::toString(nsAString& outStr) const {
1280 // Append directive name
1281 outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective));
1282 outStr.AppendLiteral(" ");
1284 // Append srcs
1285 StringJoinAppend(outStr, u" "_ns, mSrcs,
1286 [](nsAString& dest, nsCSPBaseSrc* cspBaseSrc) {
1287 cspBaseSrc->toString(dest);
1291 void nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP& outCSP) const {
1292 mozilla::dom::Sequence<nsString> srcs;
1293 nsString src;
1294 if (NS_WARN_IF(!srcs.SetCapacity(mSrcs.Length(), mozilla::fallible))) {
1295 MOZ_ASSERT(false,
1296 "Not enough memory for 'sources' sequence in "
1297 "nsCSPDirective::toDomCSPStruct().");
1298 return;
1300 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1301 src.Truncate();
1302 mSrcs[i]->toString(src);
1303 if (!srcs.AppendElement(src, mozilla::fallible)) {
1304 MOZ_ASSERT(false,
1305 "Failed to append to 'sources' sequence in "
1306 "nsCSPDirective::toDomCSPStruct().");
1310 switch (mDirective) {
1311 case nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE:
1312 outCSP.mDefault_src.Construct();
1313 outCSP.mDefault_src.Value() = std::move(srcs);
1314 return;
1316 case nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE:
1317 outCSP.mScript_src.Construct();
1318 outCSP.mScript_src.Value() = std::move(srcs);
1319 return;
1321 case nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE:
1322 outCSP.mObject_src.Construct();
1323 outCSP.mObject_src.Value() = std::move(srcs);
1324 return;
1326 case nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE:
1327 outCSP.mStyle_src.Construct();
1328 outCSP.mStyle_src.Value() = std::move(srcs);
1329 return;
1331 case nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE:
1332 outCSP.mImg_src.Construct();
1333 outCSP.mImg_src.Value() = std::move(srcs);
1334 return;
1336 case nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE:
1337 outCSP.mMedia_src.Construct();
1338 outCSP.mMedia_src.Value() = std::move(srcs);
1339 return;
1341 case nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE:
1342 outCSP.mFrame_src.Construct();
1343 outCSP.mFrame_src.Value() = std::move(srcs);
1344 return;
1346 case nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE:
1347 outCSP.mFont_src.Construct();
1348 outCSP.mFont_src.Value() = std::move(srcs);
1349 return;
1351 case nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE:
1352 outCSP.mConnect_src.Construct();
1353 outCSP.mConnect_src.Value() = std::move(srcs);
1354 return;
1356 case nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE:
1357 outCSP.mReport_uri.Construct();
1358 outCSP.mReport_uri.Value() = std::move(srcs);
1359 return;
1361 case nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE:
1362 outCSP.mFrame_ancestors.Construct();
1363 outCSP.mFrame_ancestors.Value() = std::move(srcs);
1364 return;
1366 case nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE:
1367 outCSP.mManifest_src.Construct();
1368 outCSP.mManifest_src.Value() = std::move(srcs);
1369 return;
1370 // not supporting REFLECTED_XSS_DIRECTIVE
1372 case nsIContentSecurityPolicy::BASE_URI_DIRECTIVE:
1373 outCSP.mBase_uri.Construct();
1374 outCSP.mBase_uri.Value() = std::move(srcs);
1375 return;
1377 case nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE:
1378 outCSP.mForm_action.Construct();
1379 outCSP.mForm_action.Value() = std::move(srcs);
1380 return;
1382 case nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT:
1383 outCSP.mBlock_all_mixed_content.Construct();
1384 // does not have any srcs
1385 return;
1387 case nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE:
1388 outCSP.mUpgrade_insecure_requests.Construct();
1389 // does not have any srcs
1390 return;
1392 case nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE:
1393 outCSP.mChild_src.Construct();
1394 outCSP.mChild_src.Value() = std::move(srcs);
1395 return;
1397 case nsIContentSecurityPolicy::SANDBOX_DIRECTIVE:
1398 outCSP.mSandbox.Construct();
1399 outCSP.mSandbox.Value() = std::move(srcs);
1400 return;
1402 case nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE:
1403 outCSP.mWorker_src.Construct();
1404 outCSP.mWorker_src.Value() = std::move(srcs);
1405 return;
1407 case nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE:
1408 outCSP.mScript_src_elem.Construct();
1409 outCSP.mScript_src_elem.Value() = std::move(srcs);
1410 return;
1412 case nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE:
1413 outCSP.mScript_src_attr.Construct();
1414 outCSP.mScript_src_attr.Value() = std::move(srcs);
1415 return;
1417 default:
1418 NS_ASSERTION(false, "cannot find directive to convert CSP to JSON");
1422 void nsCSPDirective::getReportURIs(nsTArray<nsString>& outReportURIs) const {
1423 NS_ASSERTION((mDirective == nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE),
1424 "not a report-uri directive");
1426 // append uris
1427 nsString tmpReportURI;
1428 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1429 tmpReportURI.Truncate();
1430 mSrcs[i]->toString(tmpReportURI);
1431 outReportURIs.AppendElement(tmpReportURI);
1435 bool nsCSPDirective::visitSrcs(nsCSPSrcVisitor* aVisitor) const {
1436 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1437 if (!mSrcs[i]->visit(aVisitor)) {
1438 return false;
1441 return true;
1444 bool nsCSPDirective::equals(CSPDirective aDirective) const {
1445 return (mDirective == aDirective);
1448 void nsCSPDirective::getDirName(nsAString& outStr) const {
1449 outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective));
1452 bool nsCSPDirective::hasReportSampleKeyword() const {
1453 for (nsCSPBaseSrc* src : mSrcs) {
1454 if (src->isReportSample()) {
1455 return true;
1459 return false;
1462 /* =============== nsCSPChildSrcDirective ============= */
1464 nsCSPChildSrcDirective::nsCSPChildSrcDirective(CSPDirective aDirective)
1465 : nsCSPDirective(aDirective),
1466 mRestrictFrames(false),
1467 mRestrictWorkers(false) {}
1469 nsCSPChildSrcDirective::~nsCSPChildSrcDirective() = default;
1471 bool nsCSPChildSrcDirective::equals(CSPDirective aDirective) const {
1472 if (aDirective == nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE) {
1473 return mRestrictFrames;
1475 if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) {
1476 return mRestrictWorkers;
1478 return (mDirective == aDirective);
1481 /* =============== nsCSPScriptSrcDirective ============= */
1483 nsCSPScriptSrcDirective::nsCSPScriptSrcDirective(CSPDirective aDirective)
1484 : nsCSPDirective(aDirective) {}
1486 nsCSPScriptSrcDirective::~nsCSPScriptSrcDirective() = default;
1488 bool nsCSPScriptSrcDirective::equals(CSPDirective aDirective) const {
1489 if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) {
1490 return mRestrictWorkers;
1492 if (aDirective == nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) {
1493 return mRestrictScriptElem;
1495 if (aDirective == nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) {
1496 return mRestrictScriptAttr;
1498 return mDirective == aDirective;
1501 /* =============== nsCSPStyleSrcDirective ============= */
1503 nsCSPStyleSrcDirective::nsCSPStyleSrcDirective(CSPDirective aDirective)
1504 : nsCSPDirective(aDirective) {}
1506 nsCSPStyleSrcDirective::~nsCSPStyleSrcDirective() = default;
1508 bool nsCSPStyleSrcDirective::equals(CSPDirective aDirective) const {
1509 if (aDirective == nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE) {
1510 return mRestrictStyleElem;
1512 if (aDirective == nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE) {
1513 return mRestrictStyleAttr;
1515 return mDirective == aDirective;
1518 /* =============== nsBlockAllMixedContentDirective ============= */
1520 nsBlockAllMixedContentDirective::nsBlockAllMixedContentDirective(
1521 CSPDirective aDirective)
1522 : nsCSPDirective(aDirective) {}
1524 nsBlockAllMixedContentDirective::~nsBlockAllMixedContentDirective() = default;
1526 void nsBlockAllMixedContentDirective::toString(nsAString& outStr) const {
1527 outStr.AppendASCII(CSP_CSPDirectiveToString(
1528 nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT));
1531 void nsBlockAllMixedContentDirective::getDirName(nsAString& outStr) const {
1532 outStr.AppendASCII(CSP_CSPDirectiveToString(
1533 nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT));
1536 /* =============== nsUpgradeInsecureDirective ============= */
1538 nsUpgradeInsecureDirective::nsUpgradeInsecureDirective(CSPDirective aDirective)
1539 : nsCSPDirective(aDirective) {}
1541 nsUpgradeInsecureDirective::~nsUpgradeInsecureDirective() = default;
1543 void nsUpgradeInsecureDirective::toString(nsAString& outStr) const {
1544 outStr.AppendASCII(CSP_CSPDirectiveToString(
1545 nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE));
1548 void nsUpgradeInsecureDirective::getDirName(nsAString& outStr) const {
1549 outStr.AppendASCII(CSP_CSPDirectiveToString(
1550 nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE));
1553 /* ===== nsCSPPolicy ========================= */
1555 nsCSPPolicy::nsCSPPolicy()
1556 : mUpgradeInsecDir(nullptr),
1557 mReportOnly(false),
1558 mDeliveredViaMetaTag(false) {
1559 CSPUTILSLOG(("nsCSPPolicy::nsCSPPolicy"));
1562 nsCSPPolicy::~nsCSPPolicy() {
1563 CSPUTILSLOG(("nsCSPPolicy::~nsCSPPolicy"));
1565 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1566 delete mDirectives[i];
1570 bool nsCSPPolicy::permits(CSPDirective aDir, nsILoadInfo* aLoadInfo,
1571 nsIURI* aUri, bool aWasRedirected, bool aSpecific,
1572 nsAString& outViolatedDirective,
1573 nsAString& outViolatedDirectiveString) const {
1574 if (CSPUTILSLOGENABLED()) {
1575 CSPUTILSLOG(("nsCSPPolicy::permits, aUri: %s, aDir: %s, aSpecific: %s",
1576 aUri->GetSpecOrDefault().get(), CSP_CSPDirectiveToString(aDir),
1577 aSpecific ? "true" : "false"));
1580 NS_ASSERTION(aUri, "permits needs an uri to perform the check!");
1581 outViolatedDirective.Truncate();
1582 outViolatedDirectiveString.Truncate();
1584 nsCSPDirective* defaultDir = nullptr;
1586 // Try to find a relevant directive
1587 // These directive arrays are short (1-5 elements), not worth using a
1588 // hashtable.
1589 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1590 if (mDirectives[i]->equals(aDir)) {
1591 if (!mDirectives[i]->permits(aDir, aLoadInfo, aUri, aWasRedirected,
1592 mReportOnly, mUpgradeInsecDir)) {
1593 mDirectives[i]->getDirName(outViolatedDirective);
1594 mDirectives[i]->toString(outViolatedDirectiveString);
1595 return false;
1597 return true;
1599 if (mDirectives[i]->isDefaultDirective()) {
1600 defaultDir = mDirectives[i];
1604 // If the above loop runs through, we haven't found a matching directive.
1605 // Avoid relooping, just store the result of default-src while looping.
1606 if (!aSpecific && defaultDir) {
1607 if (!defaultDir->permits(aDir, aLoadInfo, aUri, aWasRedirected, mReportOnly,
1608 mUpgradeInsecDir)) {
1609 defaultDir->getDirName(outViolatedDirective);
1610 defaultDir->toString(outViolatedDirectiveString);
1611 return false;
1613 return true;
1616 // Nothing restricts this, so we're allowing the load
1617 // See bug 764937
1618 return true;
1621 bool nsCSPPolicy::allows(CSPDirective aDirective, enum CSPKeyword aKeyword,
1622 const nsAString& aHashOrNonce) const {
1623 CSPUTILSLOG(("nsCSPPolicy::allows, aKeyWord: %s, a HashOrNonce: %s",
1624 CSP_EnumToUTF8Keyword(aKeyword),
1625 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
1627 if (nsCSPDirective* directive = matchingOrDefaultDirective(aDirective)) {
1628 return directive->allows(aKeyword, aHashOrNonce);
1631 // No matching directive or default directive as fallback found, thus
1632 // allowing the load; see Bug 885433
1633 // a) inline scripts (also unsafe eval) should only be blocked
1634 // if there is a [script-src] or [default-src]
1635 // b) inline styles should only be blocked
1636 // if there is a [style-src] or [default-src]
1637 return true;
1640 nsCSPDirective* nsCSPPolicy::matchingOrDefaultDirective(
1641 CSPDirective aDirective) const {
1642 nsCSPDirective* defaultDir = nullptr;
1644 // Try to find a matching directive
1645 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1646 if (mDirectives[i]->isDefaultDirective()) {
1647 defaultDir = mDirectives[i];
1648 continue;
1650 if (mDirectives[i]->equals(aDirective)) {
1651 return mDirectives[i];
1655 return defaultDir;
1658 void nsCSPPolicy::toString(nsAString& outStr) const {
1659 StringJoinAppend(outStr, u"; "_ns, mDirectives,
1660 [](nsAString& dest, nsCSPDirective* cspDirective) {
1661 cspDirective->toString(dest);
1665 void nsCSPPolicy::toDomCSPStruct(mozilla::dom::CSP& outCSP) const {
1666 outCSP.mReport_only = mReportOnly;
1668 for (uint32_t i = 0; i < mDirectives.Length(); ++i) {
1669 mDirectives[i]->toDomCSPStruct(outCSP);
1673 bool nsCSPPolicy::hasDirective(CSPDirective aDir) const {
1674 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1675 if (mDirectives[i]->equals(aDir)) {
1676 return true;
1679 return false;
1682 bool nsCSPPolicy::allowsAllInlineBehavior(CSPDirective aDir) const {
1683 nsCSPDirective* directive = matchingOrDefaultDirective(aDir);
1684 if (!directive) {
1685 // No matching or default directive found thus allow the all inline
1686 // scripts or styles. (See nsCSPPolicy::allows)
1687 return true;
1690 return directive->allowsAllInlineBehavior(aDir);
1694 * Use this function only after ::allows() returned 'false'. Most and
1695 * foremost it's used to get the violated directive before sending reports.
1696 * The parameter outDirective is the equivalent of 'outViolatedDirective'
1697 * for the ::permits() function family.
1699 void nsCSPPolicy::getViolatedDirectiveInformation(CSPDirective aDirective,
1700 nsAString& outDirective,
1701 nsAString& outDirectiveString,
1702 bool* aReportSample) const {
1703 *aReportSample = false;
1704 nsCSPDirective* directive = matchingOrDefaultDirective(aDirective);
1705 if (!directive) {
1706 MOZ_ASSERT_UNREACHABLE("Can not query violated directive");
1707 outDirective.AppendLiteral("couldNotQueryViolatedDirective");
1708 outDirective.Truncate();
1709 return;
1712 directive->getDirName(outDirective);
1713 directive->toString(outDirectiveString);
1714 *aReportSample = directive->hasReportSampleKeyword();
1718 * Helper function that returns the underlying bit representation of sandbox
1719 * flags. The function returns SANDBOXED_NONE if there are no sandbox
1720 * directives.
1722 uint32_t nsCSPPolicy::getSandboxFlags() const {
1723 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1724 if (mDirectives[i]->equals(nsIContentSecurityPolicy::SANDBOX_DIRECTIVE)) {
1725 nsAutoString flags;
1726 mDirectives[i]->toString(flags);
1728 if (flags.IsEmpty()) {
1729 return SANDBOX_ALL_FLAGS;
1732 nsAttrValue attr;
1733 attr.ParseAtomArray(flags);
1735 return nsContentUtils::ParseSandboxAttributeToFlags(&attr);
1739 return SANDBOXED_NONE;
1742 void nsCSPPolicy::getReportURIs(nsTArray<nsString>& outReportURIs) const {
1743 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1744 if (mDirectives[i]->equals(
1745 nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
1746 mDirectives[i]->getReportURIs(outReportURIs);
1747 return;
1752 bool nsCSPPolicy::visitDirectiveSrcs(CSPDirective aDir,
1753 nsCSPSrcVisitor* aVisitor) const {
1754 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1755 if (mDirectives[i]->equals(aDir)) {
1756 return mDirectives[i]->visitSrcs(aVisitor);
1759 return false;