Bug 1685822 [wpt PR 27117] - [Import Maps] Add tests for rejecting multiple import...
[gecko.git] / dom / security / nsCSPUtils.cpp
blob002382db7ee643b3044b21f4b52d72f59bb70171
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"
25 #include "mozilla/dom/CSPDictionariesBinding.h"
26 #include "mozilla/dom/Document.h"
27 #include "mozilla/StaticPrefs_security.h"
29 #define DEFAULT_PORT -1
31 static mozilla::LogModule* GetCspUtilsLog() {
32 static mozilla::LazyLogModule gCspUtilsPRLog("CSPUtils");
33 return gCspUtilsPRLog;
36 #define CSPUTILSLOG(args) \
37 MOZ_LOG(GetCspUtilsLog(), mozilla::LogLevel::Debug, args)
38 #define CSPUTILSLOGENABLED() \
39 MOZ_LOG_TEST(GetCspUtilsLog(), mozilla::LogLevel::Debug)
41 void CSP_PercentDecodeStr(const nsAString& aEncStr, nsAString& outDecStr) {
42 outDecStr.Truncate();
44 // helper function that should not be visible outside this methods scope
45 struct local {
46 static inline char16_t convertHexDig(char16_t aHexDig) {
47 if (isNumberToken(aHexDig)) {
48 return aHexDig - '0';
50 if (aHexDig >= 'A' && aHexDig <= 'F') {
51 return aHexDig - 'A' + 10;
53 // must be a lower case character
54 // (aHexDig >= 'a' && aHexDig <= 'f')
55 return aHexDig - 'a' + 10;
59 const char16_t *cur, *end, *hexDig1, *hexDig2;
60 cur = aEncStr.BeginReading();
61 end = aEncStr.EndReading();
63 while (cur != end) {
64 // if it's not a percent sign then there is
65 // nothing to do for that character
66 if (*cur != PERCENT_SIGN) {
67 outDecStr.Append(*cur);
68 cur++;
69 continue;
72 // get the two hexDigs following the '%'-sign
73 hexDig1 = cur + 1;
74 hexDig2 = cur + 2;
76 // if there are no hexdigs after the '%' then
77 // there is nothing to do for us.
78 if (hexDig1 == end || hexDig2 == end || !isValidHexDig(*hexDig1) ||
79 !isValidHexDig(*hexDig2)) {
80 outDecStr.Append(PERCENT_SIGN);
81 cur++;
82 continue;
85 // decode "% hexDig1 hexDig2" into a character.
86 char16_t decChar =
87 (local::convertHexDig(*hexDig1) << 4) + local::convertHexDig(*hexDig2);
88 outDecStr.Append(decChar);
90 // increment 'cur' to after the second hexDig
91 cur = ++hexDig2;
95 // The Content Security Policy should be inherited for
96 // local schemes like: "about", "blob", "data", or "filesystem".
97 // see: https://w3c.github.io/webappsec-csp/#initialize-document-csp
98 bool CSP_ShouldResponseInheritCSP(nsIChannel* aChannel) {
99 if (!aChannel) {
100 return false;
103 nsCOMPtr<nsIURI> uri;
104 nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
105 NS_ENSURE_SUCCESS(rv, false);
107 bool isAbout = uri->SchemeIs("about");
108 if (isAbout) {
109 nsAutoCString aboutSpec;
110 rv = uri->GetSpec(aboutSpec);
111 NS_ENSURE_SUCCESS(rv, false);
112 // also allow about:blank#foo
113 if (StringBeginsWith(aboutSpec, "about:blank"_ns) ||
114 StringBeginsWith(aboutSpec, "about:srcdoc"_ns)) {
115 return true;
119 return uri->SchemeIs("blob") || uri->SchemeIs("data") ||
120 uri->SchemeIs("filesystem") || uri->SchemeIs("javascript");
123 void CSP_ApplyMetaCSPToDoc(mozilla::dom::Document& aDoc,
124 const nsAString& aPolicyStr) {
125 if (!mozilla::StaticPrefs::security_csp_enable() || aDoc.IsLoadedAsData()) {
126 return;
129 nsAutoString policyStr(
130 nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
131 aPolicyStr));
133 if (policyStr.IsEmpty()) {
134 return;
137 nsCOMPtr<nsIContentSecurityPolicy> csp = aDoc.GetCsp();
138 if (!csp) {
139 MOZ_ASSERT(false, "how come there is no CSP");
140 return;
143 // Multiple CSPs (delivered through either header of meta tag) need to
144 // be joined together, see:
145 // https://w3c.github.io/webappsec/specs/content-security-policy/#delivery-html-meta-element
146 nsresult rv =
147 csp->AppendPolicy(policyStr,
148 false, // csp via meta tag can not be report only
149 true); // delivered through the meta tag
150 NS_ENSURE_SUCCESS_VOID(rv);
151 if (nsPIDOMWindowInner* inner = aDoc.GetInnerWindow()) {
152 inner->SetCsp(csp);
154 aDoc.ApplySettingsFromCSP(false);
157 void CSP_GetLocalizedStr(const char* aName, const nsTArray<nsString>& aParams,
158 nsAString& outResult) {
159 nsCOMPtr<nsIStringBundle> keyStringBundle;
160 nsCOMPtr<nsIStringBundleService> stringBundleService =
161 mozilla::services::GetStringBundleService();
163 NS_ASSERTION(stringBundleService, "String bundle service must be present!");
164 stringBundleService->CreateBundle(
165 "chrome://global/locale/security/csp.properties",
166 getter_AddRefs(keyStringBundle));
168 NS_ASSERTION(keyStringBundle, "Key string bundle must be available!");
170 if (!keyStringBundle) {
171 return;
173 keyStringBundle->FormatStringFromName(aName, aParams, outResult);
176 void CSP_LogStrMessage(const nsAString& aMsg) {
177 nsCOMPtr<nsIConsoleService> console(
178 do_GetService("@mozilla.org/consoleservice;1"));
180 if (!console) {
181 return;
183 nsString msg(aMsg);
184 console->LogStringMessage(msg.get());
187 void CSP_LogMessage(const nsAString& aMessage, const nsAString& aSourceName,
188 const nsAString& aSourceLine, uint32_t aLineNumber,
189 uint32_t aColumnNumber, uint32_t aFlags,
190 const nsACString& aCategory, uint64_t aInnerWindowID,
191 bool aFromPrivateWindow) {
192 nsCOMPtr<nsIConsoleService> console(
193 do_GetService(NS_CONSOLESERVICE_CONTRACTID));
195 nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
197 if (!console || !error) {
198 return;
201 // Prepending CSP to the outgoing console message
202 nsString cspMsg;
203 cspMsg.AppendLiteral(u"Content Security Policy: ");
204 cspMsg.Append(aMessage);
206 // Currently 'aSourceLine' is not logged to the console, because similar
207 // information is already included within the source link of the message.
208 // For inline violations however, the line and column number are 0 and
209 // information contained within 'aSourceLine' can be really useful for devs.
210 // E.g. 'aSourceLine' might be: 'onclick attribute on DIV element'.
211 // In such cases we append 'aSourceLine' directly to the error message.
212 if (!aSourceLine.IsEmpty()) {
213 cspMsg.AppendLiteral(u" Source: ");
214 cspMsg.Append(aSourceLine);
215 cspMsg.AppendLiteral(u".");
218 // Since we are leveraging csp errors as the category names which
219 // we pass to devtools, we should prepend them with "CSP_" to
220 // allow easy distincution in devtools code. e.g.
221 // upgradeInsecureRequest -> CSP_upgradeInsecureRequest
222 nsCString category("CSP_");
223 category.Append(aCategory);
225 nsresult rv;
226 if (aInnerWindowID > 0) {
227 rv = error->InitWithWindowID(cspMsg, aSourceName, aSourceLine, aLineNumber,
228 aColumnNumber, aFlags, category,
229 aInnerWindowID);
230 } else {
231 rv = error->Init(cspMsg, aSourceName, aSourceLine, aLineNumber,
232 aColumnNumber, aFlags, category.get(), aFromPrivateWindow,
233 true /* from chrome context */);
235 if (NS_FAILED(rv)) {
236 return;
238 console->LogMessage(error);
242 * Combines CSP_LogMessage and CSP_GetLocalizedStr into one call.
244 void CSP_LogLocalizedStr(const char* aName, const nsTArray<nsString>& aParams,
245 const nsAString& aSourceName,
246 const nsAString& aSourceLine, uint32_t aLineNumber,
247 uint32_t aColumnNumber, uint32_t aFlags,
248 const nsACString& aCategory, uint64_t aInnerWindowID,
249 bool aFromPrivateWindow) {
250 nsAutoString logMsg;
251 CSP_GetLocalizedStr(aName, aParams, logMsg);
252 CSP_LogMessage(logMsg, aSourceName, aSourceLine, aLineNumber, aColumnNumber,
253 aFlags, aCategory, aInnerWindowID, aFromPrivateWindow);
256 /* ===== Helpers ============================ */
257 CSPDirective CSP_ContentTypeToDirective(nsContentPolicyType aType) {
258 // We need to know if this is a worker so child-src can handle that case
259 // correctly.
260 switch (aType) {
261 case nsIContentPolicy::TYPE_IMAGE:
262 case nsIContentPolicy::TYPE_IMAGESET:
263 case nsIContentPolicy::TYPE_INTERNAL_IMAGE:
264 case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD:
265 case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON:
266 return nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE;
268 // BLock XSLT as script, see bug 910139
269 case nsIContentPolicy::TYPE_XSLT:
270 case nsIContentPolicy::TYPE_SCRIPT:
271 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
272 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
273 case nsIContentPolicy::TYPE_INTERNAL_MODULE:
274 case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD:
275 case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS:
276 case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET:
277 case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET:
278 case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT:
279 case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT:
280 return nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE;
282 case nsIContentPolicy::TYPE_STYLESHEET:
283 case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET:
284 case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD:
285 return nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE;
287 case nsIContentPolicy::TYPE_FONT:
288 case nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD:
289 return nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE;
291 case nsIContentPolicy::TYPE_MEDIA:
292 case nsIContentPolicy::TYPE_INTERNAL_AUDIO:
293 case nsIContentPolicy::TYPE_INTERNAL_VIDEO:
294 case nsIContentPolicy::TYPE_INTERNAL_TRACK:
295 return nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE;
297 case nsIContentPolicy::TYPE_WEB_MANIFEST:
298 return nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE;
300 case nsIContentPolicy::TYPE_INTERNAL_WORKER:
301 case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
302 case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
303 return nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE;
305 case nsIContentPolicy::TYPE_SUBDOCUMENT:
306 case nsIContentPolicy::TYPE_INTERNAL_FRAME:
307 case nsIContentPolicy::TYPE_INTERNAL_IFRAME:
308 return nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE;
310 case nsIContentPolicy::TYPE_WEBSOCKET:
311 case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
312 case nsIContentPolicy::TYPE_BEACON:
313 case nsIContentPolicy::TYPE_PING:
314 case nsIContentPolicy::TYPE_FETCH:
315 case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST:
316 case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE:
317 case nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD:
318 return nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE;
320 case nsIContentPolicy::TYPE_OBJECT:
321 case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST:
322 case nsIContentPolicy::TYPE_INTERNAL_EMBED:
323 case nsIContentPolicy::TYPE_INTERNAL_OBJECT:
324 return nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE;
326 case nsIContentPolicy::TYPE_DTD:
327 case nsIContentPolicy::TYPE_OTHER:
328 case nsIContentPolicy::TYPE_SPECULATIVE:
329 case nsIContentPolicy::TYPE_INTERNAL_DTD:
330 case nsIContentPolicy::TYPE_INTERNAL_FORCE_ALLOWED_DTD:
331 return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
333 // csp shold not block top level loads, e.g. in case
334 // of a redirect.
335 case nsIContentPolicy::TYPE_DOCUMENT:
336 // CSP can not block csp reports
337 case nsIContentPolicy::TYPE_CSP_REPORT:
338 return nsIContentSecurityPolicy::NO_DIRECTIVE;
340 case nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD:
341 return nsIContentSecurityPolicy::NO_DIRECTIVE;
343 // Fall through to error for all other directives
344 // Note that we should never end up here for navigate-to
345 case nsIContentPolicy::TYPE_INVALID:
346 case nsIContentPolicy::TYPE_REFRESH:
347 MOZ_ASSERT(false, "Can not map nsContentPolicyType to CSPDirective");
349 return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
352 nsCSPHostSrc* CSP_CreateHostSrcFromSelfURI(nsIURI* aSelfURI) {
353 // Create the host first
354 nsCString host;
355 aSelfURI->GetAsciiHost(host);
356 nsCSPHostSrc* hostsrc = new nsCSPHostSrc(NS_ConvertUTF8toUTF16(host));
357 hostsrc->setGeneratedFromSelfKeyword();
359 // Add the scheme.
360 nsCString scheme;
361 aSelfURI->GetScheme(scheme);
362 hostsrc->setScheme(NS_ConvertUTF8toUTF16(scheme));
364 // An empty host (e.g. for data:) indicates it's effectively a unique origin.
365 // Please note that we still need to set the scheme on hostsrc (see above),
366 // because it's used for reporting.
367 if (host.EqualsLiteral("")) {
368 hostsrc->setIsUniqueOrigin();
369 // no need to query the port in that case.
370 return hostsrc;
373 int32_t port;
374 aSelfURI->GetPort(&port);
375 // Only add port if it's not default port.
376 if (port > 0) {
377 nsAutoString portStr;
378 portStr.AppendInt(port);
379 hostsrc->setPort(portStr);
381 return hostsrc;
384 bool CSP_IsEmptyDirective(const nsAString& aValue, const nsAString& aDir) {
385 return (aDir.Length() == 0 && aValue.Length() == 0);
387 bool CSP_IsDirective(const nsAString& aValue, CSPDirective aDir) {
388 return aValue.LowerCaseEqualsASCII(CSP_CSPDirectiveToString(aDir));
391 bool CSP_IsKeyword(const nsAString& aValue, enum CSPKeyword aKey) {
392 return aValue.LowerCaseEqualsASCII(CSP_EnumToUTF8Keyword(aKey));
395 bool CSP_IsQuotelessKeyword(const nsAString& aKey) {
396 nsString lowerKey;
397 ToLowerCase(aKey, lowerKey);
399 nsAutoString keyword;
400 for (uint32_t i = 0; i < CSP_LAST_KEYWORD_VALUE; i++) {
401 // skipping the leading ' and trimming the trailing '
402 keyword.AssignASCII(gCSPUTF8Keywords[i] + 1);
403 keyword.Trim("'", false, true);
404 if (lowerKey.Equals(keyword)) {
405 return true;
408 return false;
412 * Checks whether the current directive permits a specific
413 * scheme. This function is called from nsCSPSchemeSrc() and
414 * also nsCSPHostSrc.
415 * @param aEnforcementScheme
416 * The scheme that this directive allows
417 * @param aUri
418 * The uri of the subresource load.
419 * @param aReportOnly
420 * Whether the enforced policy is report only or not.
421 * @param aUpgradeInsecure
422 * Whether the policy makes use of the directive
423 * 'upgrade-insecure-requests'.
424 * @param aFromSelfURI
425 * Whether a scheme was generated from the keyword 'self'
426 * which then allows schemeless sources to match ws and wss.
429 bool permitsScheme(const nsAString& aEnforcementScheme, nsIURI* aUri,
430 bool aReportOnly, bool aUpgradeInsecure, bool aFromSelfURI) {
431 nsAutoCString scheme;
432 nsresult rv = aUri->GetScheme(scheme);
433 NS_ENSURE_SUCCESS(rv, false);
435 // no scheme to enforce, let's allow the load (e.g. script-src *)
436 if (aEnforcementScheme.IsEmpty()) {
437 return true;
440 // if the scheme matches, all good - allow the load
441 if (aEnforcementScheme.EqualsASCII(scheme.get())) {
442 return true;
445 // allow scheme-less sources where the protected resource is http
446 // and the load is https, see:
447 // http://www.w3.org/TR/CSP2/#match-source-expression
448 if (aEnforcementScheme.EqualsASCII("http")) {
449 if (scheme.EqualsASCII("https")) {
450 return true;
452 if ((scheme.EqualsASCII("ws") || scheme.EqualsASCII("wss")) &&
453 aFromSelfURI) {
454 return true;
457 if (aEnforcementScheme.EqualsASCII("https")) {
458 if (scheme.EqualsLiteral("wss") && aFromSelfURI) {
459 return true;
462 if (aEnforcementScheme.EqualsASCII("ws") && scheme.EqualsASCII("wss")) {
463 return true;
466 // Allow the load when enforcing upgrade-insecure-requests with the
467 // promise the request gets upgraded from http to https and ws to wss.
468 // See nsHttpChannel::Connect() and also WebSocket.cpp. Please note,
469 // the report only policies should not allow the load and report
470 // the error back to the page.
471 return (
472 (aUpgradeInsecure && !aReportOnly) &&
473 ((scheme.EqualsASCII("http") &&
474 aEnforcementScheme.EqualsASCII("https")) ||
475 (scheme.EqualsASCII("ws") && aEnforcementScheme.EqualsASCII("wss"))));
479 * A helper function for appending a CSP header to an existing CSP
480 * policy.
482 * @param aCsp the CSP policy
483 * @param aHeaderValue the header
484 * @param aReportOnly is this a report-only header?
487 nsresult CSP_AppendCSPFromHeader(nsIContentSecurityPolicy* aCsp,
488 const nsAString& aHeaderValue,
489 bool aReportOnly) {
490 NS_ENSURE_ARG(aCsp);
492 // Need to tokenize the header value since multiple headers could be
493 // concatenated into one comma-separated list of policies.
494 // See RFC2616 section 4.2 (last paragraph)
495 nsresult rv = NS_OK;
496 for (const nsAString& policy :
497 nsCharSeparatedTokenizer(aHeaderValue, ',').ToRange()) {
498 rv = aCsp->AppendPolicy(policy, aReportOnly, false);
499 NS_ENSURE_SUCCESS(rv, rv);
501 CSPUTILSLOG(("CSP refined with policy: \"%s\"",
502 NS_ConvertUTF16toUTF8(policy).get()));
505 return NS_OK;
508 /* ===== nsCSPSrc ============================ */
510 nsCSPBaseSrc::nsCSPBaseSrc() : mInvalidated(false) {}
512 nsCSPBaseSrc::~nsCSPBaseSrc() = default;
514 // ::permits is only called for external load requests, therefore:
515 // nsCSPKeywordSrc and nsCSPHashSource fall back to this base class
516 // implementation which will never allow the load.
517 bool nsCSPBaseSrc::permits(nsIURI* aUri, const nsAString& aNonce,
518 bool aWasRedirected, bool aReportOnly,
519 bool aUpgradeInsecure, bool aParserCreated) const {
520 if (CSPUTILSLOGENABLED()) {
521 CSPUTILSLOG(
522 ("nsCSPBaseSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
524 return false;
527 // ::allows is only called for inlined loads, therefore:
528 // nsCSPSchemeSrc, nsCSPHostSrc fall back
529 // to this base class implementation which will never allow the load.
530 bool nsCSPBaseSrc::allows(enum CSPKeyword aKeyword,
531 const nsAString& aHashOrNonce,
532 bool aParserCreated) const {
533 CSPUTILSLOG(("nsCSPBaseSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
534 aKeyword == CSP_HASH ? "hash" : CSP_EnumToUTF8Keyword(aKeyword),
535 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
536 return false;
539 /* ====== nsCSPSchemeSrc ===================== */
541 nsCSPSchemeSrc::nsCSPSchemeSrc(const nsAString& aScheme) : mScheme(aScheme) {
542 ToLowerCase(mScheme);
545 nsCSPSchemeSrc::~nsCSPSchemeSrc() = default;
547 bool nsCSPSchemeSrc::permits(nsIURI* aUri, const nsAString& aNonce,
548 bool aWasRedirected, bool aReportOnly,
549 bool aUpgradeInsecure, bool aParserCreated) const {
550 if (CSPUTILSLOGENABLED()) {
551 CSPUTILSLOG(
552 ("nsCSPSchemeSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
554 MOZ_ASSERT((!mScheme.EqualsASCII("")), "scheme can not be the empty string");
555 if (mInvalidated) {
556 return false;
558 return permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure, false);
561 bool nsCSPSchemeSrc::visit(nsCSPSrcVisitor* aVisitor) const {
562 return aVisitor->visitSchemeSrc(*this);
565 void nsCSPSchemeSrc::toString(nsAString& outStr) const {
566 outStr.Append(mScheme);
567 outStr.AppendLiteral(":");
570 /* ===== nsCSPHostSrc ======================== */
572 nsCSPHostSrc::nsCSPHostSrc(const nsAString& aHost)
573 : mHost(aHost),
574 mGeneratedFromSelfKeyword(false),
575 mIsUniqueOrigin(false),
576 mWithinFrameAncstorsDir(false) {
577 ToLowerCase(mHost);
580 nsCSPHostSrc::~nsCSPHostSrc() = default;
583 * Checks whether the current directive permits a specific port.
584 * @param aEnforcementScheme
585 * The scheme that this directive allows
586 * (used to query the default port for that scheme)
587 * @param aEnforcementPort
588 * The port that this directive allows
589 * @param aResourceURI
590 * The uri of the subresource load
592 bool permitsPort(const nsAString& aEnforcementScheme,
593 const nsAString& aEnforcementPort, nsIURI* aResourceURI) {
594 // If enforcement port is the wildcard, don't block the load.
595 if (aEnforcementPort.EqualsASCII("*")) {
596 return true;
599 int32_t resourcePort;
600 nsresult rv = aResourceURI->GetPort(&resourcePort);
601 if (NS_FAILED(rv) && aEnforcementPort.IsEmpty()) {
602 // If we cannot get a Port (e.g. because of an Custom Protocol handler)
603 // We need to check if a default port is associated with the Scheme
604 if (aEnforcementScheme.IsEmpty()) {
605 return false;
607 int defaultPortforScheme =
608 NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme).get());
610 // If there is no default port associated with the Scheme (
611 // defaultPortforScheme == -1) or it is an externally handled protocol (
612 // defaultPortforScheme == 0 ) and the csp does not enforce a port - we can
613 // allow not having a port
614 return (defaultPortforScheme == -1 || defaultPortforScheme == -0);
616 // Avoid unnecessary string creation/manipulation and don't block the
617 // load if the resource to be loaded uses the default port for that
618 // scheme and there is no port to be enforced.
619 // Note, this optimization relies on scheme checks within permitsScheme().
620 if (resourcePort == DEFAULT_PORT && aEnforcementPort.IsEmpty()) {
621 return true;
624 // By now we know at that either the resourcePort does not use the default
625 // port or there is a port restriction to be enforced. A port value of -1
626 // corresponds to the protocol's default port (eg. -1 implies port 80 for
627 // http URIs), in such a case we have to query the default port of the
628 // resource to be loaded.
629 if (resourcePort == DEFAULT_PORT) {
630 nsAutoCString resourceScheme;
631 rv = aResourceURI->GetScheme(resourceScheme);
632 NS_ENSURE_SUCCESS(rv, false);
633 resourcePort = NS_GetDefaultPort(resourceScheme.get());
636 // If there is a port to be enforced and the ports match, then
637 // don't block the load.
638 nsString resourcePortStr;
639 resourcePortStr.AppendInt(resourcePort);
640 if (aEnforcementPort.Equals(resourcePortStr)) {
641 return true;
644 // If there is no port to be enforced, query the default port for the load.
645 nsString enforcementPort(aEnforcementPort);
646 if (enforcementPort.IsEmpty()) {
647 // For scheme less sources, our parser always generates a scheme
648 // which is the scheme of the protected resource.
649 MOZ_ASSERT(!aEnforcementScheme.IsEmpty(),
650 "need a scheme to query default port");
651 int32_t defaultEnforcementPort =
652 NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme).get());
653 enforcementPort.Truncate();
654 enforcementPort.AppendInt(defaultEnforcementPort);
657 // If default ports match, don't block the load
658 if (enforcementPort.Equals(resourcePortStr)) {
659 return true;
662 // Additional port matching where the regular URL matching algorithm
663 // treats insecure ports as matching their secure variants.
664 // default port for http is :80
665 // default port for https is :443
666 if (enforcementPort.EqualsLiteral("80") &&
667 resourcePortStr.EqualsLiteral("443")) {
668 return true;
671 // ports do not match, block the load.
672 return false;
675 bool nsCSPHostSrc::permits(nsIURI* aUri, const nsAString& aNonce,
676 bool aWasRedirected, bool aReportOnly,
677 bool aUpgradeInsecure, bool aParserCreated) const {
678 if (CSPUTILSLOGENABLED()) {
679 CSPUTILSLOG(
680 ("nsCSPHostSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
683 if (mInvalidated || mIsUniqueOrigin) {
684 return false;
687 // we are following the enforcement rules from the spec, see:
688 // http://www.w3.org/TR/CSP11/#match-source-expression
690 // 4.3) scheme matching: Check if the scheme matches.
691 if (!permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure,
692 mGeneratedFromSelfKeyword)) {
693 return false;
696 // The host in nsCSpHostSrc should never be empty. In case we are enforcing
697 // just a specific scheme, the parser should generate a nsCSPSchemeSource.
698 NS_ASSERTION((!mHost.IsEmpty()), "host can not be the empty string");
700 // Before we can check if the host matches, we have to
701 // extract the host part from aUri.
702 nsAutoCString uriHost;
703 nsresult rv = aUri->GetAsciiHost(uriHost);
704 NS_ENSURE_SUCCESS(rv, false);
706 nsString decodedUriHost;
707 CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriHost), decodedUriHost);
709 // 2) host matching: Enforce a single *
710 if (mHost.EqualsASCII("*")) {
711 // The single ASTERISK character (*) does not match a URI's scheme of a type
712 // designating a globally unique identifier (such as blob:, data:, or
713 // filesystem:) At the moment firefox does not support filesystem; but for
714 // future compatibility we support it in CSP according to the spec,
715 // see: 4.2.2 Matching Source Expressions Note, that allowlisting any of
716 // these schemes would call nsCSPSchemeSrc::permits().
717 if (aUri->SchemeIs("blob") || aUri->SchemeIs("data") ||
718 aUri->SchemeIs("filesystem")) {
719 return false;
722 // If no scheme is present there also wont be a port and folder to check
723 // which means we can return early
724 if (mScheme.IsEmpty()) {
725 return true;
728 // 4.5) host matching: Check if the allowed host starts with a wilcard.
729 else if (mHost.First() == '*') {
730 NS_ASSERTION(
731 mHost[1] == '.',
732 "Second character needs to be '.' whenever host starts with '*'");
734 // Eliminate leading "*", but keeping the FULL STOP (.) thereafter before
735 // checking if the remaining characters match
736 nsString wildCardHost = mHost;
737 wildCardHost = Substring(wildCardHost, 1, wildCardHost.Length() - 1);
738 if (!StringEndsWith(decodedUriHost, wildCardHost)) {
739 return false;
742 // 4.6) host matching: Check if hosts match.
743 else if (!mHost.Equals(decodedUriHost)) {
744 return false;
747 // Port matching: Check if the ports match.
748 if (!permitsPort(mScheme, mPort, aUri)) {
749 return false;
752 // 4.9) Path matching: If there is a path, we have to enforce
753 // path-level matching, unless the channel got redirected, see:
754 // http://www.w3.org/TR/CSP11/#source-list-paths-and-redirects
755 if (!aWasRedirected && !mPath.IsEmpty()) {
756 // converting aUri into nsIURL so we can strip query and ref
757 // example.com/test#foo -> example.com/test
758 // example.com/test?val=foo -> example.com/test
759 nsCOMPtr<nsIURL> url = do_QueryInterface(aUri);
760 if (!url) {
761 NS_ASSERTION(false, "can't QI into nsIURI");
762 return false;
764 nsAutoCString uriPath;
765 rv = url->GetFilePath(uriPath);
766 NS_ENSURE_SUCCESS(rv, false);
768 if (mWithinFrameAncstorsDir) {
769 // no path matching for frame-ancestors to not leak any path information.
770 return true;
773 nsString decodedUriPath;
774 CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriPath), decodedUriPath);
776 // check if the last character of mPath is '/'; if so
777 // we just have to check loading resource is within
778 // the allowed path.
779 if (mPath.Last() == '/') {
780 if (!StringBeginsWith(decodedUriPath, mPath)) {
781 return false;
784 // otherwise mPath refers to a specific file, and we have to
785 // check if the loading resource matches the file.
786 else {
787 if (!mPath.Equals(decodedUriPath)) {
788 return false;
793 // At the end: scheme, host, port and path match -> allow the load.
794 return true;
797 bool nsCSPHostSrc::visit(nsCSPSrcVisitor* aVisitor) const {
798 return aVisitor->visitHostSrc(*this);
801 void nsCSPHostSrc::toString(nsAString& outStr) const {
802 if (mGeneratedFromSelfKeyword) {
803 outStr.AppendLiteral("'self'");
804 return;
807 // If mHost is a single "*", we append the wildcard and return.
808 if (mHost.EqualsASCII("*") && mScheme.IsEmpty() && mPort.IsEmpty()) {
809 outStr.Append(mHost);
810 return;
813 // append scheme
814 outStr.Append(mScheme);
816 // append host
817 outStr.AppendLiteral("://");
818 outStr.Append(mHost);
820 // append port
821 if (!mPort.IsEmpty()) {
822 outStr.AppendLiteral(":");
823 outStr.Append(mPort);
826 // append path
827 outStr.Append(mPath);
830 void nsCSPHostSrc::setScheme(const nsAString& aScheme) {
831 mScheme = aScheme;
832 ToLowerCase(mScheme);
835 void nsCSPHostSrc::setPort(const nsAString& aPort) { mPort = aPort; }
837 void nsCSPHostSrc::appendPath(const nsAString& aPath) { mPath.Append(aPath); }
839 /* ===== nsCSPKeywordSrc ===================== */
841 nsCSPKeywordSrc::nsCSPKeywordSrc(enum CSPKeyword aKeyword)
842 : mKeyword(aKeyword) {
843 NS_ASSERTION((aKeyword != CSP_SELF),
844 "'self' should have been replaced in the parser");
847 nsCSPKeywordSrc::~nsCSPKeywordSrc() = default;
849 bool nsCSPKeywordSrc::permits(nsIURI* aUri, const nsAString& aNonce,
850 bool aWasRedirected, bool aReportOnly,
851 bool aUpgradeInsecure,
852 bool aParserCreated) const {
853 // no need to check for invalidated, this will always return false unless
854 // it is an nsCSPKeywordSrc for 'strict-dynamic', which should allow non
855 // parser created scripts.
856 return ((mKeyword == CSP_STRICT_DYNAMIC) && !aParserCreated);
859 bool nsCSPKeywordSrc::allows(enum CSPKeyword aKeyword,
860 const nsAString& aHashOrNonce,
861 bool aParserCreated) const {
862 CSPUTILSLOG(
863 ("nsCSPKeywordSrc::allows, aKeyWord: %s, aHashOrNonce: %s, mInvalidated: "
864 "%s",
865 CSP_EnumToUTF8Keyword(aKeyword),
866 NS_ConvertUTF16toUTF8(aHashOrNonce).get(),
867 mInvalidated ? "yes" : "false"));
869 if (mInvalidated) {
870 // only 'self', 'report-sample' and 'unsafe-inline' are keywords that can be
871 // ignored. Please note that the parser already translates 'self' into a uri
872 // (see assertion in constructor).
873 MOZ_ASSERT(mKeyword == CSP_UNSAFE_INLINE || mKeyword == CSP_REPORT_SAMPLE,
874 "should only invalidate unsafe-inline");
875 return false;
877 // either the keyword allows the load or the policy contains 'strict-dynamic',
878 // in which case we have to make sure the script is not parser created before
879 // allowing the load and also eval should be blocked even if 'strict-dynamic'
880 // is present. Should be allowed only if 'unsafe-eval' is present.
881 return ((mKeyword == aKeyword) ||
882 ((mKeyword == CSP_STRICT_DYNAMIC) && !aParserCreated &&
883 aKeyword != CSP_UNSAFE_EVAL));
886 bool nsCSPKeywordSrc::visit(nsCSPSrcVisitor* aVisitor) const {
887 return aVisitor->visitKeywordSrc(*this);
890 void nsCSPKeywordSrc::toString(nsAString& outStr) const {
891 outStr.Append(CSP_EnumToUTF16Keyword(mKeyword));
894 /* ===== nsCSPNonceSrc ==================== */
896 nsCSPNonceSrc::nsCSPNonceSrc(const nsAString& aNonce) : mNonce(aNonce) {}
898 nsCSPNonceSrc::~nsCSPNonceSrc() = default;
900 bool nsCSPNonceSrc::permits(nsIURI* aUri, const nsAString& aNonce,
901 bool aWasRedirected, bool aReportOnly,
902 bool aUpgradeInsecure, bool aParserCreated) const {
903 if (CSPUTILSLOGENABLED()) {
904 CSPUTILSLOG(("nsCSPNonceSrc::permits, aUri: %s, aNonce: %s",
905 aUri->GetSpecOrDefault().get(),
906 NS_ConvertUTF16toUTF8(aNonce).get()));
909 if (aReportOnly && aWasRedirected && aNonce.IsEmpty()) {
910 /* Fix for Bug 1505412
911 * If we land here, we're currently handling a script-preload which got
912 * redirected. Preloads do not have any info about the nonce assiociated.
913 * Because of Report-Only the preload passes the 1st CSP-check so the
914 * preload does not get retried with a nonce attached.
915 * Currently we're relying on the script-manager to
916 * provide a fake loadinfo to check the preloads against csp.
917 * So during HTTPChannel->OnRedirect we cant check csp for this case.
918 * But as the script-manager already checked the csp,
919 * a report would already have been send,
920 * if the nonce didnt match.
921 * So we can pass the check here for Report-Only Cases.
923 MOZ_ASSERT(aParserCreated == false,
924 "Skipping nonce-check is only allowed for Preloads");
925 return true;
928 // nonces can not be invalidated by strict-dynamic
929 return mNonce.Equals(aNonce);
932 bool nsCSPNonceSrc::allows(enum CSPKeyword aKeyword,
933 const nsAString& aHashOrNonce,
934 bool aParserCreated) const {
935 CSPUTILSLOG(("nsCSPNonceSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
936 CSP_EnumToUTF8Keyword(aKeyword),
937 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
939 if (aKeyword != CSP_NONCE) {
940 return false;
942 // nonces can not be invalidated by strict-dynamic
943 return mNonce.Equals(aHashOrNonce);
946 bool nsCSPNonceSrc::visit(nsCSPSrcVisitor* aVisitor) const {
947 return aVisitor->visitNonceSrc(*this);
950 void nsCSPNonceSrc::toString(nsAString& outStr) const {
951 outStr.Append(CSP_EnumToUTF16Keyword(CSP_NONCE));
952 outStr.Append(mNonce);
953 outStr.AppendLiteral("'");
956 /* ===== nsCSPHashSrc ===================== */
958 nsCSPHashSrc::nsCSPHashSrc(const nsAString& aAlgo, const nsAString& aHash)
959 : mAlgorithm(aAlgo), mHash(aHash) {
960 // Only the algo should be rewritten to lowercase, the hash must remain the
961 // same.
962 ToLowerCase(mAlgorithm);
965 nsCSPHashSrc::~nsCSPHashSrc() = default;
967 bool nsCSPHashSrc::allows(enum CSPKeyword aKeyword,
968 const nsAString& aHashOrNonce,
969 bool aParserCreated) const {
970 CSPUTILSLOG(("nsCSPHashSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
971 CSP_EnumToUTF8Keyword(aKeyword),
972 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
974 if (aKeyword != CSP_HASH) {
975 return false;
978 // hashes can not be invalidated by strict-dynamic
980 // Convert aHashOrNonce to UTF-8
981 NS_ConvertUTF16toUTF8 utf8_hash(aHashOrNonce);
983 nsresult rv;
984 nsCOMPtr<nsICryptoHash> hasher;
985 hasher = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
986 NS_ENSURE_SUCCESS(rv, false);
988 rv = hasher->InitWithString(NS_ConvertUTF16toUTF8(mAlgorithm));
989 NS_ENSURE_SUCCESS(rv, false);
991 rv = hasher->Update((uint8_t*)utf8_hash.get(), utf8_hash.Length());
992 NS_ENSURE_SUCCESS(rv, false);
994 nsAutoCString hash;
995 rv = hasher->Finish(true, hash);
996 NS_ENSURE_SUCCESS(rv, false);
998 return NS_ConvertUTF16toUTF8(mHash).Equals(hash);
1001 bool nsCSPHashSrc::visit(nsCSPSrcVisitor* aVisitor) const {
1002 return aVisitor->visitHashSrc(*this);
1005 void nsCSPHashSrc::toString(nsAString& outStr) const {
1006 outStr.AppendLiteral("'");
1007 outStr.Append(mAlgorithm);
1008 outStr.AppendLiteral("-");
1009 outStr.Append(mHash);
1010 outStr.AppendLiteral("'");
1013 /* ===== nsCSPReportURI ===================== */
1015 nsCSPReportURI::nsCSPReportURI(nsIURI* aURI) : mReportURI(aURI) {}
1017 nsCSPReportURI::~nsCSPReportURI() = default;
1019 bool nsCSPReportURI::visit(nsCSPSrcVisitor* aVisitor) const { return false; }
1021 void nsCSPReportURI::toString(nsAString& outStr) const {
1022 nsAutoCString spec;
1023 nsresult rv = mReportURI->GetSpec(spec);
1024 if (NS_FAILED(rv)) {
1025 return;
1027 outStr.AppendASCII(spec.get());
1030 /* ===== nsCSPSandboxFlags ===================== */
1032 nsCSPSandboxFlags::nsCSPSandboxFlags(const nsAString& aFlags) : mFlags(aFlags) {
1033 ToLowerCase(mFlags);
1036 nsCSPSandboxFlags::~nsCSPSandboxFlags() = default;
1038 bool nsCSPSandboxFlags::visit(nsCSPSrcVisitor* aVisitor) const { return false; }
1040 void nsCSPSandboxFlags::toString(nsAString& outStr) const {
1041 outStr.Append(mFlags);
1044 /* ===== nsCSPDirective ====================== */
1046 nsCSPDirective::nsCSPDirective(CSPDirective aDirective) {
1047 mDirective = aDirective;
1050 nsCSPDirective::~nsCSPDirective() {
1051 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1052 delete mSrcs[i];
1056 bool nsCSPDirective::permits(nsIURI* aUri, const nsAString& aNonce,
1057 bool aWasRedirected, bool aReportOnly,
1058 bool aUpgradeInsecure, bool aParserCreated) const {
1059 if (CSPUTILSLOGENABLED()) {
1060 CSPUTILSLOG(
1061 ("nsCSPDirective::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
1064 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1065 if (mSrcs[i]->permits(aUri, aNonce, aWasRedirected, aReportOnly,
1066 aUpgradeInsecure, aParserCreated)) {
1067 return true;
1070 return false;
1073 bool nsCSPDirective::allows(enum CSPKeyword aKeyword,
1074 const nsAString& aHashOrNonce,
1075 bool aParserCreated) const {
1076 CSPUTILSLOG(("nsCSPDirective::allows, aKeyWord: %s, a HashOrNonce: %s",
1077 CSP_EnumToUTF8Keyword(aKeyword),
1078 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
1080 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1081 if (mSrcs[i]->allows(aKeyword, aHashOrNonce, aParserCreated)) {
1082 return true;
1085 return false;
1088 void nsCSPDirective::toString(nsAString& outStr) const {
1089 // Append directive name
1090 outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective));
1091 outStr.AppendLiteral(" ");
1093 // Append srcs
1094 StringJoinAppend(outStr, u" "_ns, mSrcs,
1095 [](nsAString& dest, nsCSPBaseSrc* cspBaseSrc) {
1096 cspBaseSrc->toString(dest);
1100 void nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP& outCSP) const {
1101 mozilla::dom::Sequence<nsString> srcs;
1102 nsString src;
1103 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1104 src.Truncate();
1105 mSrcs[i]->toString(src);
1106 if (!srcs.AppendElement(src, mozilla::fallible)) {
1107 // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
1108 // involve multiple reallocations) and potentially crashing here,
1109 // SetCapacity could be called outside the loop once.
1110 mozalloc_handle_oom(0);
1114 switch (mDirective) {
1115 case nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE:
1116 outCSP.mDefault_src.Construct();
1117 outCSP.mDefault_src.Value() = std::move(srcs);
1118 return;
1120 case nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE:
1121 outCSP.mScript_src.Construct();
1122 outCSP.mScript_src.Value() = std::move(srcs);
1123 return;
1125 case nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE:
1126 outCSP.mObject_src.Construct();
1127 outCSP.mObject_src.Value() = std::move(srcs);
1128 return;
1130 case nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE:
1131 outCSP.mStyle_src.Construct();
1132 outCSP.mStyle_src.Value() = std::move(srcs);
1133 return;
1135 case nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE:
1136 outCSP.mImg_src.Construct();
1137 outCSP.mImg_src.Value() = std::move(srcs);
1138 return;
1140 case nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE:
1141 outCSP.mMedia_src.Construct();
1142 outCSP.mMedia_src.Value() = std::move(srcs);
1143 return;
1145 case nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE:
1146 outCSP.mFrame_src.Construct();
1147 outCSP.mFrame_src.Value() = std::move(srcs);
1148 return;
1150 case nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE:
1151 outCSP.mFont_src.Construct();
1152 outCSP.mFont_src.Value() = std::move(srcs);
1153 return;
1155 case nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE:
1156 outCSP.mConnect_src.Construct();
1157 outCSP.mConnect_src.Value() = std::move(srcs);
1158 return;
1160 case nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE:
1161 outCSP.mReport_uri.Construct();
1162 outCSP.mReport_uri.Value() = std::move(srcs);
1163 return;
1165 case nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE:
1166 outCSP.mFrame_ancestors.Construct();
1167 outCSP.mFrame_ancestors.Value() = std::move(srcs);
1168 return;
1170 case nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE:
1171 outCSP.mManifest_src.Construct();
1172 outCSP.mManifest_src.Value() = std::move(srcs);
1173 return;
1174 // not supporting REFLECTED_XSS_DIRECTIVE
1176 case nsIContentSecurityPolicy::BASE_URI_DIRECTIVE:
1177 outCSP.mBase_uri.Construct();
1178 outCSP.mBase_uri.Value() = std::move(srcs);
1179 return;
1181 case nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE:
1182 outCSP.mForm_action.Construct();
1183 outCSP.mForm_action.Value() = std::move(srcs);
1184 return;
1186 case nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT:
1187 outCSP.mBlock_all_mixed_content.Construct();
1188 // does not have any srcs
1189 return;
1191 case nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE:
1192 outCSP.mUpgrade_insecure_requests.Construct();
1193 // does not have any srcs
1194 return;
1196 case nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE:
1197 outCSP.mChild_src.Construct();
1198 outCSP.mChild_src.Value() = std::move(srcs);
1199 return;
1201 case nsIContentSecurityPolicy::SANDBOX_DIRECTIVE:
1202 outCSP.mSandbox.Construct();
1203 outCSP.mSandbox.Value() = std::move(srcs);
1204 return;
1206 case nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE:
1207 outCSP.mWorker_src.Construct();
1208 outCSP.mWorker_src.Value() = std::move(srcs);
1209 return;
1211 default:
1212 NS_ASSERTION(false, "cannot find directive to convert CSP to JSON");
1216 void nsCSPDirective::getReportURIs(nsTArray<nsString>& outReportURIs) const {
1217 NS_ASSERTION((mDirective == nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE),
1218 "not a report-uri directive");
1220 // append uris
1221 nsString tmpReportURI;
1222 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1223 tmpReportURI.Truncate();
1224 mSrcs[i]->toString(tmpReportURI);
1225 outReportURIs.AppendElement(tmpReportURI);
1229 bool nsCSPDirective::visitSrcs(nsCSPSrcVisitor* aVisitor) const {
1230 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1231 if (!mSrcs[i]->visit(aVisitor)) {
1232 return false;
1235 return true;
1238 bool nsCSPDirective::equals(CSPDirective aDirective) const {
1239 return (mDirective == aDirective);
1242 void nsCSPDirective::getDirName(nsAString& outStr) const {
1243 outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective));
1246 bool nsCSPDirective::hasReportSampleKeyword() const {
1247 for (nsCSPBaseSrc* src : mSrcs) {
1248 if (src->isReportSample()) {
1249 return true;
1253 return false;
1256 /* =============== nsCSPChildSrcDirective ============= */
1258 nsCSPChildSrcDirective::nsCSPChildSrcDirective(CSPDirective aDirective)
1259 : nsCSPDirective(aDirective),
1260 mRestrictFrames(false),
1261 mRestrictWorkers(false) {}
1263 nsCSPChildSrcDirective::~nsCSPChildSrcDirective() = default;
1265 bool nsCSPChildSrcDirective::equals(CSPDirective aDirective) const {
1266 if (aDirective == nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE) {
1267 return mRestrictFrames;
1269 if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) {
1270 return mRestrictWorkers;
1272 return (mDirective == aDirective);
1275 /* =============== nsCSPScriptSrcDirective ============= */
1277 nsCSPScriptSrcDirective::nsCSPScriptSrcDirective(CSPDirective aDirective)
1278 : nsCSPDirective(aDirective), mRestrictWorkers(false) {}
1280 nsCSPScriptSrcDirective::~nsCSPScriptSrcDirective() = default;
1282 bool nsCSPScriptSrcDirective::equals(CSPDirective aDirective) const {
1283 if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) {
1284 return mRestrictWorkers;
1286 return (mDirective == aDirective);
1289 /* =============== nsBlockAllMixedContentDirective ============= */
1291 nsBlockAllMixedContentDirective::nsBlockAllMixedContentDirective(
1292 CSPDirective aDirective)
1293 : nsCSPDirective(aDirective) {}
1295 nsBlockAllMixedContentDirective::~nsBlockAllMixedContentDirective() = default;
1297 void nsBlockAllMixedContentDirective::toString(nsAString& outStr) const {
1298 outStr.AppendASCII(CSP_CSPDirectiveToString(
1299 nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT));
1302 void nsBlockAllMixedContentDirective::getDirName(nsAString& outStr) const {
1303 outStr.AppendASCII(CSP_CSPDirectiveToString(
1304 nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT));
1307 /* =============== nsUpgradeInsecureDirective ============= */
1309 nsUpgradeInsecureDirective::nsUpgradeInsecureDirective(CSPDirective aDirective)
1310 : nsCSPDirective(aDirective) {}
1312 nsUpgradeInsecureDirective::~nsUpgradeInsecureDirective() = default;
1314 void nsUpgradeInsecureDirective::toString(nsAString& outStr) const {
1315 outStr.AppendASCII(CSP_CSPDirectiveToString(
1316 nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE));
1319 void nsUpgradeInsecureDirective::getDirName(nsAString& outStr) const {
1320 outStr.AppendASCII(CSP_CSPDirectiveToString(
1321 nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE));
1324 /* ===== nsCSPPolicy ========================= */
1326 nsCSPPolicy::nsCSPPolicy()
1327 : mUpgradeInsecDir(nullptr),
1328 mReportOnly(false),
1329 mDeliveredViaMetaTag(false) {
1330 CSPUTILSLOG(("nsCSPPolicy::nsCSPPolicy"));
1333 nsCSPPolicy::~nsCSPPolicy() {
1334 CSPUTILSLOG(("nsCSPPolicy::~nsCSPPolicy"));
1336 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1337 delete mDirectives[i];
1341 bool nsCSPPolicy::permits(CSPDirective aDir, nsIURI* aUri,
1342 const nsAString& aNonce, bool aWasRedirected,
1343 bool aSpecific, bool aParserCreated,
1344 nsAString& outViolatedDirective) const {
1345 if (CSPUTILSLOGENABLED()) {
1346 CSPUTILSLOG(("nsCSPPolicy::permits, aUri: %s, aDir: %d, aSpecific: %s",
1347 aUri->GetSpecOrDefault().get(), aDir,
1348 aSpecific ? "true" : "false"));
1351 NS_ASSERTION(aUri, "permits needs an uri to perform the check!");
1352 outViolatedDirective.Truncate();
1354 nsCSPDirective* defaultDir = nullptr;
1356 // Try to find a relevant directive
1357 // These directive arrays are short (1-5 elements), not worth using a
1358 // hashtable.
1359 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1360 if (mDirectives[i]->equals(aDir)) {
1361 if (!mDirectives[i]->permits(aUri, aNonce, aWasRedirected, mReportOnly,
1362 mUpgradeInsecDir, aParserCreated)) {
1363 mDirectives[i]->getDirName(outViolatedDirective);
1364 return false;
1366 return true;
1368 if (mDirectives[i]->isDefaultDirective()) {
1369 defaultDir = mDirectives[i];
1373 // If the above loop runs through, we haven't found a matching directive.
1374 // Avoid relooping, just store the result of default-src while looping.
1375 if (!aSpecific && defaultDir) {
1376 if (!defaultDir->permits(aUri, aNonce, aWasRedirected, mReportOnly,
1377 mUpgradeInsecDir, aParserCreated)) {
1378 defaultDir->getDirName(outViolatedDirective);
1379 return false;
1381 return true;
1384 // Nothing restricts this, so we're allowing the load
1385 // See bug 764937
1386 return true;
1389 bool nsCSPPolicy::allows(CSPDirective aDirective, enum CSPKeyword aKeyword,
1390 const nsAString& aHashOrNonce,
1391 bool aParserCreated) const {
1392 CSPUTILSLOG(("nsCSPPolicy::allows, aKeyWord: %s, a HashOrNonce: %s",
1393 CSP_EnumToUTF8Keyword(aKeyword),
1394 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
1396 nsCSPDirective* defaultDir = nullptr;
1398 // Try to find a matching directive
1399 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1400 if (mDirectives[i]->isDefaultDirective()) {
1401 defaultDir = mDirectives[i];
1402 continue;
1404 if (mDirectives[i]->equals(aDirective)) {
1405 if (mDirectives[i]->allows(aKeyword, aHashOrNonce, aParserCreated)) {
1406 return true;
1408 return false;
1412 // {nonce,hash}-source should not consult default-src:
1413 // * return false if default-src is specified
1414 // * but allow the load if default-src is *not* specified (Bug 1198422)
1415 if (aKeyword == CSP_NONCE || aKeyword == CSP_HASH) {
1416 if (!defaultDir) {
1417 return true;
1419 return false;
1422 // If the above loop runs through, we haven't found a matching directive.
1423 // Avoid relooping, just store the result of default-src while looping.
1424 if (defaultDir) {
1425 return defaultDir->allows(aKeyword, aHashOrNonce, aParserCreated);
1428 // Allowing the load; see Bug 885433
1429 // a) inline scripts (also unsafe eval) should only be blocked
1430 // if there is a [script-src] or [default-src]
1431 // b) inline styles should only be blocked
1432 // if there is a [style-src] or [default-src]
1433 return true;
1436 void nsCSPPolicy::toString(nsAString& outStr) const {
1437 StringJoinAppend(outStr, u"; "_ns, mDirectives,
1438 [](nsAString& dest, nsCSPDirective* cspDirective) {
1439 cspDirective->toString(dest);
1443 void nsCSPPolicy::toDomCSPStruct(mozilla::dom::CSP& outCSP) const {
1444 outCSP.mReport_only = mReportOnly;
1446 for (uint32_t i = 0; i < mDirectives.Length(); ++i) {
1447 mDirectives[i]->toDomCSPStruct(outCSP);
1451 bool nsCSPPolicy::hasDirective(CSPDirective aDir) const {
1452 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1453 if (mDirectives[i]->equals(aDir)) {
1454 return true;
1457 return false;
1460 bool nsCSPPolicy::allowsNavigateTo(nsIURI* aURI, bool aWasRedirected,
1461 bool aEnforceAllowlist) const {
1462 bool allowsNavigateTo = true;
1464 for (unsigned long i = 0; i < mDirectives.Length(); i++) {
1465 if (mDirectives[i]->equals(
1466 nsIContentSecurityPolicy::NAVIGATE_TO_DIRECTIVE)) {
1467 // Early return if we can skip the allowlist AND 'unsafe-allow-redirects'
1468 // is present.
1469 if (!aEnforceAllowlist &&
1470 mDirectives[i]->allows(CSP_UNSAFE_ALLOW_REDIRECTS, u""_ns, false)) {
1471 return true;
1473 // Otherwise, check against the allowlist.
1474 if (!mDirectives[i]->permits(aURI, u""_ns, aWasRedirected, false, false,
1475 false)) {
1476 allowsNavigateTo = false;
1481 return allowsNavigateTo;
1485 * Use this function only after ::allows() returned 'false'. Most and
1486 * foremost it's used to get the violated directive before sending reports.
1487 * The parameter outDirective is the equivalent of 'outViolatedDirective'
1488 * for the ::permits() function family.
1490 void nsCSPPolicy::getDirectiveStringAndReportSampleForContentType(
1491 CSPDirective aDirective, nsAString& outDirective,
1492 bool* aReportSample) const {
1493 MOZ_ASSERT(aReportSample);
1494 *aReportSample = false;
1496 nsCSPDirective* defaultDir = nullptr;
1497 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1498 if (mDirectives[i]->isDefaultDirective()) {
1499 defaultDir = mDirectives[i];
1500 continue;
1502 if (mDirectives[i]->equals(aDirective)) {
1503 mDirectives[i]->getDirName(outDirective);
1504 *aReportSample = mDirectives[i]->hasReportSampleKeyword();
1505 return;
1508 // if we haven't found a matching directive yet,
1509 // the contentType must be restricted by the default directive
1510 if (defaultDir) {
1511 defaultDir->getDirName(outDirective);
1512 *aReportSample = defaultDir->hasReportSampleKeyword();
1513 return;
1515 NS_ASSERTION(false, "Can not query directive string for contentType!");
1516 outDirective.AppendLiteral("couldNotQueryViolatedDirective");
1519 void nsCSPPolicy::getDirectiveAsString(CSPDirective aDir,
1520 nsAString& outDirective) const {
1521 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1522 if (mDirectives[i]->equals(aDir)) {
1523 mDirectives[i]->toString(outDirective);
1524 return;
1530 * Helper function that returns the underlying bit representation of sandbox
1531 * flags. The function returns SANDBOXED_NONE if there are no sandbox
1532 * directives.
1534 uint32_t nsCSPPolicy::getSandboxFlags() const {
1535 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1536 if (mDirectives[i]->equals(nsIContentSecurityPolicy::SANDBOX_DIRECTIVE)) {
1537 nsAutoString flags;
1538 mDirectives[i]->toString(flags);
1540 if (flags.IsEmpty()) {
1541 return SANDBOX_ALL_FLAGS;
1544 nsAttrValue attr;
1545 attr.ParseAtomArray(flags);
1547 return nsContentUtils::ParseSandboxAttributeToFlags(&attr);
1551 return SANDBOXED_NONE;
1554 void nsCSPPolicy::getReportURIs(nsTArray<nsString>& outReportURIs) const {
1555 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1556 if (mDirectives[i]->equals(
1557 nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
1558 mDirectives[i]->getReportURIs(outReportURIs);
1559 return;
1564 bool nsCSPPolicy::visitDirectiveSrcs(CSPDirective aDir,
1565 nsCSPSrcVisitor* aVisitor) const {
1566 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1567 if (mDirectives[i]->equals(aDir)) {
1568 return mDirectives[i]->visitSrcs(aVisitor);
1571 return false;