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"
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"
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
) {
44 // helper function that should not be visible outside this methods scope
46 static inline char16_t
convertHexDig(char16_t aHexDig
) {
47 if (isNumberToken(aHexDig
)) {
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();
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
);
72 // get the two hexDigs following the '%'-sign
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
);
85 // decode "% hexDig1 hexDig2" into a character.
87 (local::convertHexDig(*hexDig1
) << 4) + local::convertHexDig(*hexDig2
);
88 outDecStr
.Append(decChar
);
90 // increment 'cur' to after the second hexDig
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
) {
103 nsCOMPtr
<nsIURI
> uri
;
104 nsresult rv
= aChannel
->GetURI(getter_AddRefs(uri
));
105 NS_ENSURE_SUCCESS(rv
, false);
107 bool isAbout
= uri
->SchemeIs("about");
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
)) {
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()) {
129 nsAutoString
policyStr(
130 nsContentUtils::TrimWhitespace
<nsContentUtils::IsHTMLWhitespace
>(
133 if (policyStr
.IsEmpty()) {
137 nsCOMPtr
<nsIContentSecurityPolicy
> csp
= aDoc
.GetCsp();
139 MOZ_ASSERT(false, "how come there is no CSP");
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
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()) {
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
) {
173 keyStringBundle
->FormatStringFromName(aName
, aParams
, outResult
);
176 void CSP_LogStrMessage(const nsAString
& aMsg
) {
177 nsCOMPtr
<nsIConsoleService
> console(
178 do_GetService("@mozilla.org/consoleservice;1"));
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
) {
201 // Prepending CSP to the outgoing console message
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
);
226 if (aInnerWindowID
> 0) {
227 rv
= error
->InitWithWindowID(cspMsg
, aSourceName
, aSourceLine
, aLineNumber
,
228 aColumnNumber
, aFlags
, category
,
231 rv
= error
->Init(cspMsg
, aSourceName
, aSourceLine
, aLineNumber
,
232 aColumnNumber
, aFlags
, category
.get(), aFromPrivateWindow
,
233 true /* from chrome context */);
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
) {
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
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
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
355 aSelfURI
->GetAsciiHost(host
);
356 nsCSPHostSrc
* hostsrc
= new nsCSPHostSrc(NS_ConvertUTF8toUTF16(host
));
357 hostsrc
->setGeneratedFromSelfKeyword();
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.
374 aSelfURI
->GetPort(&port
);
375 // Only add port if it's not default port.
377 nsAutoString portStr
;
378 portStr
.AppendInt(port
);
379 hostsrc
->setPort(portStr
);
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
) {
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
)) {
412 * Checks whether the current directive permits a specific
413 * scheme. This function is called from nsCSPSchemeSrc() and
415 * @param aEnforcementScheme
416 * The scheme that this directive allows
418 * The uri of the subresource load.
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()) {
440 // if the scheme matches, all good - allow the load
441 if (aEnforcementScheme
.EqualsASCII(scheme
.get())) {
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")) {
452 if ((scheme
.EqualsASCII("ws") || scheme
.EqualsASCII("wss")) &&
457 if (aEnforcementScheme
.EqualsASCII("https")) {
458 if (scheme
.EqualsLiteral("wss") && aFromSelfURI
) {
462 if (aEnforcementScheme
.EqualsASCII("ws") && scheme
.EqualsASCII("wss")) {
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.
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
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
,
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)
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()));
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()) {
522 ("nsCSPBaseSrc::permits, aUri: %s", aUri
->GetSpecOrDefault().get()));
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()));
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()) {
552 ("nsCSPSchemeSrc::permits, aUri: %s", aUri
->GetSpecOrDefault().get()));
554 MOZ_ASSERT((!mScheme
.EqualsASCII("")), "scheme can not be the empty string");
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
)
574 mGeneratedFromSelfKeyword(false),
575 mIsUniqueOrigin(false),
576 mWithinFrameAncstorsDir(false) {
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("*")) {
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()) {
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()) {
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
)) {
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
)) {
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")) {
671 // ports do not match, block the load.
675 bool nsCSPHostSrc::permits(nsIURI
* aUri
, const nsAString
& aNonce
,
676 bool aWasRedirected
, bool aReportOnly
,
677 bool aUpgradeInsecure
, bool aParserCreated
) const {
678 if (CSPUTILSLOGENABLED()) {
680 ("nsCSPHostSrc::permits, aUri: %s", aUri
->GetSpecOrDefault().get()));
683 if (mInvalidated
|| mIsUniqueOrigin
) {
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
)) {
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")) {
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()) {
728 // 4.5) host matching: Check if the allowed host starts with a wilcard.
729 else if (mHost
.First() == '*') {
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
)) {
742 // 4.6) host matching: Check if hosts match.
743 else if (!mHost
.Equals(decodedUriHost
)) {
747 // Port matching: Check if the ports match.
748 if (!permitsPort(mScheme
, mPort
, aUri
)) {
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
);
761 NS_ASSERTION(false, "can't QI into nsIURI");
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.
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
779 if (mPath
.Last() == '/') {
780 if (!StringBeginsWith(decodedUriPath
, mPath
)) {
784 // otherwise mPath refers to a specific file, and we have to
785 // check if the loading resource matches the file.
787 if (!mPath
.Equals(decodedUriPath
)) {
793 // At the end: scheme, host, port and path match -> allow the load.
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'");
807 // If mHost is a single "*", we append the wildcard and return.
808 if (mHost
.EqualsASCII("*") && mScheme
.IsEmpty() && mPort
.IsEmpty()) {
809 outStr
.Append(mHost
);
814 outStr
.Append(mScheme
);
817 outStr
.AppendLiteral("://");
818 outStr
.Append(mHost
);
821 if (!mPort
.IsEmpty()) {
822 outStr
.AppendLiteral(":");
823 outStr
.Append(mPort
);
827 outStr
.Append(mPath
);
830 void nsCSPHostSrc::setScheme(const nsAString
& 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 {
863 ("nsCSPKeywordSrc::allows, aKeyWord: %s, aHashOrNonce: %s, mInvalidated: "
865 CSP_EnumToUTF8Keyword(aKeyword
),
866 NS_ConvertUTF16toUTF8(aHashOrNonce
).get(),
867 mInvalidated
? "yes" : "false"));
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");
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");
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
) {
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
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
) {
978 // hashes can not be invalidated by strict-dynamic
980 // Convert aHashOrNonce to UTF-8
981 NS_ConvertUTF16toUTF8
utf8_hash(aHashOrNonce
);
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);
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 {
1023 nsresult rv
= mReportURI
->GetSpec(spec
);
1024 if (NS_FAILED(rv
)) {
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
++) {
1056 bool nsCSPDirective::permits(nsIURI
* aUri
, const nsAString
& aNonce
,
1057 bool aWasRedirected
, bool aReportOnly
,
1058 bool aUpgradeInsecure
, bool aParserCreated
) const {
1059 if (CSPUTILSLOGENABLED()) {
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
)) {
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
)) {
1088 void nsCSPDirective::toString(nsAString
& outStr
) const {
1089 // Append directive name
1090 outStr
.AppendASCII(CSP_CSPDirectiveToString(mDirective
));
1091 outStr
.AppendLiteral(" ");
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
;
1103 for (uint32_t i
= 0; i
< mSrcs
.Length(); i
++) {
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
);
1120 case nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE
:
1121 outCSP
.mScript_src
.Construct();
1122 outCSP
.mScript_src
.Value() = std::move(srcs
);
1125 case nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE
:
1126 outCSP
.mObject_src
.Construct();
1127 outCSP
.mObject_src
.Value() = std::move(srcs
);
1130 case nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE
:
1131 outCSP
.mStyle_src
.Construct();
1132 outCSP
.mStyle_src
.Value() = std::move(srcs
);
1135 case nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE
:
1136 outCSP
.mImg_src
.Construct();
1137 outCSP
.mImg_src
.Value() = std::move(srcs
);
1140 case nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE
:
1141 outCSP
.mMedia_src
.Construct();
1142 outCSP
.mMedia_src
.Value() = std::move(srcs
);
1145 case nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE
:
1146 outCSP
.mFrame_src
.Construct();
1147 outCSP
.mFrame_src
.Value() = std::move(srcs
);
1150 case nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE
:
1151 outCSP
.mFont_src
.Construct();
1152 outCSP
.mFont_src
.Value() = std::move(srcs
);
1155 case nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE
:
1156 outCSP
.mConnect_src
.Construct();
1157 outCSP
.mConnect_src
.Value() = std::move(srcs
);
1160 case nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE
:
1161 outCSP
.mReport_uri
.Construct();
1162 outCSP
.mReport_uri
.Value() = std::move(srcs
);
1165 case nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE
:
1166 outCSP
.mFrame_ancestors
.Construct();
1167 outCSP
.mFrame_ancestors
.Value() = std::move(srcs
);
1170 case nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE
:
1171 outCSP
.mManifest_src
.Construct();
1172 outCSP
.mManifest_src
.Value() = std::move(srcs
);
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
);
1181 case nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE
:
1182 outCSP
.mForm_action
.Construct();
1183 outCSP
.mForm_action
.Value() = std::move(srcs
);
1186 case nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT
:
1187 outCSP
.mBlock_all_mixed_content
.Construct();
1188 // does not have any srcs
1191 case nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE
:
1192 outCSP
.mUpgrade_insecure_requests
.Construct();
1193 // does not have any srcs
1196 case nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE
:
1197 outCSP
.mChild_src
.Construct();
1198 outCSP
.mChild_src
.Value() = std::move(srcs
);
1201 case nsIContentSecurityPolicy::SANDBOX_DIRECTIVE
:
1202 outCSP
.mSandbox
.Construct();
1203 outCSP
.mSandbox
.Value() = std::move(srcs
);
1206 case nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE
:
1207 outCSP
.mWorker_src
.Construct();
1208 outCSP
.mWorker_src
.Value() = std::move(srcs
);
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");
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
)) {
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()) {
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),
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
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
);
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
);
1384 // Nothing restricts this, so we're allowing the load
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
];
1404 if (mDirectives
[i
]->equals(aDirective
)) {
1405 if (mDirectives
[i
]->allows(aKeyword
, aHashOrNonce
, aParserCreated
)) {
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
) {
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.
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]
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
)) {
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'
1469 if (!aEnforceAllowlist
&&
1470 mDirectives
[i
]->allows(CSP_UNSAFE_ALLOW_REDIRECTS
, u
""_ns
, false)) {
1473 // Otherwise, check against the allowlist.
1474 if (!mDirectives
[i
]->permits(aURI
, u
""_ns
, aWasRedirected
, false, 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
];
1502 if (mDirectives
[i
]->equals(aDirective
)) {
1503 mDirectives
[i
]->getDirName(outDirective
);
1504 *aReportSample
= mDirectives
[i
]->hasReportSampleKeyword();
1508 // if we haven't found a matching directive yet,
1509 // the contentType must be restricted by the default directive
1511 defaultDir
->getDirName(outDirective
);
1512 *aReportSample
= defaultDir
->hasReportSampleKeyword();
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
);
1530 * Helper function that returns the underlying bit representation of sandbox
1531 * flags. The function returns SANDBOXED_NONE if there are no sandbox
1534 uint32_t nsCSPPolicy::getSandboxFlags() const {
1535 for (uint32_t i
= 0; i
< mDirectives
.Length(); i
++) {
1536 if (mDirectives
[i
]->equals(nsIContentSecurityPolicy::SANDBOX_DIRECTIVE
)) {
1538 mDirectives
[i
]->toString(flags
);
1540 if (flags
.IsEmpty()) {
1541 return SANDBOX_ALL_FLAGS
;
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
);
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
);