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"
24 #include "nsWhitespaceTokenizer.h"
26 #include "mozilla/Components.h"
27 #include "mozilla/dom/CSPDictionariesBinding.h"
28 #include "mozilla/dom/Document.h"
29 #include "mozilla/dom/SRIMetadata.h"
30 #include "mozilla/StaticPrefs_security.h"
32 using namespace mozilla
;
33 using mozilla::dom::SRIMetadata
;
35 #define DEFAULT_PORT -1
37 static mozilla::LogModule
* GetCspUtilsLog() {
38 static mozilla::LazyLogModule
gCspUtilsPRLog("CSPUtils");
39 return gCspUtilsPRLog
;
42 #define CSPUTILSLOG(args) \
43 MOZ_LOG(GetCspUtilsLog(), mozilla::LogLevel::Debug, args)
44 #define CSPUTILSLOGENABLED() \
45 MOZ_LOG_TEST(GetCspUtilsLog(), mozilla::LogLevel::Debug)
47 void CSP_PercentDecodeStr(const nsAString
& aEncStr
, nsAString
& outDecStr
) {
50 // helper function that should not be visible outside this methods scope
52 static inline char16_t
convertHexDig(char16_t aHexDig
) {
53 if (isNumberToken(aHexDig
)) {
56 if (aHexDig
>= 'A' && aHexDig
<= 'F') {
57 return aHexDig
- 'A' + 10;
59 // must be a lower case character
60 // (aHexDig >= 'a' && aHexDig <= 'f')
61 return aHexDig
- 'a' + 10;
65 const char16_t
*cur
, *end
, *hexDig1
, *hexDig2
;
66 cur
= aEncStr
.BeginReading();
67 end
= aEncStr
.EndReading();
70 // if it's not a percent sign then there is
71 // nothing to do for that character
72 if (*cur
!= PERCENT_SIGN
) {
73 outDecStr
.Append(*cur
);
78 // get the two hexDigs following the '%'-sign
82 // if there are no hexdigs after the '%' then
83 // there is nothing to do for us.
84 if (hexDig1
== end
|| hexDig2
== end
|| !isValidHexDig(*hexDig1
) ||
85 !isValidHexDig(*hexDig2
)) {
86 outDecStr
.Append(PERCENT_SIGN
);
91 // decode "% hexDig1 hexDig2" into a character.
93 (local::convertHexDig(*hexDig1
) << 4) + local::convertHexDig(*hexDig2
);
94 outDecStr
.Append(decChar
);
96 // increment 'cur' to after the second hexDig
101 // The Content Security Policy should be inherited for
102 // local schemes like: "about", "blob", "data", or "filesystem".
103 // see: https://w3c.github.io/webappsec-csp/#initialize-document-csp
104 bool CSP_ShouldResponseInheritCSP(nsIChannel
* aChannel
) {
109 nsCOMPtr
<nsIURI
> uri
;
110 nsresult rv
= aChannel
->GetURI(getter_AddRefs(uri
));
111 NS_ENSURE_SUCCESS(rv
, false);
113 bool isAbout
= uri
->SchemeIs("about");
115 nsAutoCString aboutSpec
;
116 rv
= uri
->GetSpec(aboutSpec
);
117 NS_ENSURE_SUCCESS(rv
, false);
118 // also allow about:blank#foo
119 if (StringBeginsWith(aboutSpec
, "about:blank"_ns
) ||
120 StringBeginsWith(aboutSpec
, "about:srcdoc"_ns
)) {
125 return uri
->SchemeIs("blob") || uri
->SchemeIs("data") ||
126 uri
->SchemeIs("filesystem") || uri
->SchemeIs("javascript");
129 void CSP_ApplyMetaCSPToDoc(mozilla::dom::Document
& aDoc
,
130 const nsAString
& aPolicyStr
) {
131 if (aDoc
.IsLoadedAsData()) {
135 nsAutoString
policyStr(
136 nsContentUtils::TrimWhitespace
<nsContentUtils::IsHTMLWhitespace
>(
139 if (policyStr
.IsEmpty()) {
143 nsCOMPtr
<nsIContentSecurityPolicy
> csp
= aDoc
.GetCsp();
145 MOZ_ASSERT(false, "how come there is no CSP");
149 // Multiple CSPs (delivered through either header of meta tag) need to
150 // be joined together, see:
151 // https://w3c.github.io/webappsec/specs/content-security-policy/#delivery-html-meta-element
153 csp
->AppendPolicy(policyStr
,
154 false, // csp via meta tag can not be report only
155 true); // delivered through the meta tag
156 NS_ENSURE_SUCCESS_VOID(rv
);
157 if (nsPIDOMWindowInner
* inner
= aDoc
.GetInnerWindow()) {
160 aDoc
.ApplySettingsFromCSP(false);
163 void CSP_GetLocalizedStr(const char* aName
, const nsTArray
<nsString
>& aParams
,
164 nsAString
& outResult
) {
165 nsCOMPtr
<nsIStringBundle
> keyStringBundle
;
166 nsCOMPtr
<nsIStringBundleService
> stringBundleService
=
167 mozilla::components::StringBundle::Service();
169 NS_ASSERTION(stringBundleService
, "String bundle service must be present!");
170 stringBundleService
->CreateBundle(
171 "chrome://global/locale/security/csp.properties",
172 getter_AddRefs(keyStringBundle
));
174 NS_ASSERTION(keyStringBundle
, "Key string bundle must be available!");
176 if (!keyStringBundle
) {
179 keyStringBundle
->FormatStringFromName(aName
, aParams
, outResult
);
182 void CSP_LogStrMessage(const nsAString
& aMsg
) {
183 nsCOMPtr
<nsIConsoleService
> console(
184 do_GetService("@mozilla.org/consoleservice;1"));
190 console
->LogStringMessage(msg
.get());
193 void CSP_LogMessage(const nsAString
& aMessage
, const nsAString
& aSourceName
,
194 const nsAString
& aSourceLine
, uint32_t aLineNumber
,
195 uint32_t aColumnNumber
, uint32_t aFlags
,
196 const nsACString
& aCategory
, uint64_t aInnerWindowID
,
197 bool aFromPrivateWindow
) {
198 nsCOMPtr
<nsIConsoleService
> console(
199 do_GetService(NS_CONSOLESERVICE_CONTRACTID
));
201 nsCOMPtr
<nsIScriptError
> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID
));
203 if (!console
|| !error
) {
207 // Prepending CSP to the outgoing console message
209 CSP_GetLocalizedStr("CSPMessagePrefix",
210 AutoTArray
<nsString
, 1>{nsString(aMessage
)}, cspMsg
);
212 // Currently 'aSourceLine' is not logged to the console, because similar
213 // information is already included within the source link of the message.
214 // For inline violations however, the line and column number are 0 and
215 // information contained within 'aSourceLine' can be really useful for devs.
216 // E.g. 'aSourceLine' might be: 'onclick attribute on DIV element'.
217 // In such cases we append 'aSourceLine' directly to the error message.
218 if (!aSourceLine
.IsEmpty() && aLineNumber
== 0) {
219 cspMsg
.AppendLiteral(u
"\nSource: ");
220 cspMsg
.Append(aSourceLine
);
223 // Since we are leveraging csp errors as the category names which
224 // we pass to devtools, we should prepend them with "CSP_" to
225 // allow easy distincution in devtools code. e.g.
226 // upgradeInsecureRequest -> CSP_upgradeInsecureRequest
227 nsCString
category("CSP_");
228 category
.Append(aCategory
);
231 if (aInnerWindowID
> 0) {
232 rv
= error
->InitWithWindowID(cspMsg
, aSourceName
, aSourceLine
, aLineNumber
,
233 aColumnNumber
, aFlags
, category
,
236 rv
= error
->Init(cspMsg
, aSourceName
, aSourceLine
, aLineNumber
,
237 aColumnNumber
, aFlags
, category
, aFromPrivateWindow
,
238 true /* from chrome context */);
243 console
->LogMessage(error
);
246 CSPDirective
CSP_StringToCSPDirective(const nsAString
& aDir
) {
247 nsString lowerDir
= PromiseFlatString(aDir
);
248 ToLowerCase(lowerDir
);
250 uint32_t numDirs
= (sizeof(CSPStrDirectives
) / sizeof(CSPStrDirectives
[0]));
252 for (uint32_t i
= 1; i
< numDirs
; i
++) {
253 if (lowerDir
.EqualsASCII(CSPStrDirectives
[i
])) {
254 return static_cast<CSPDirective
>(i
);
257 return nsIContentSecurityPolicy::NO_DIRECTIVE
;
261 * Combines CSP_LogMessage and CSP_GetLocalizedStr into one call.
263 void CSP_LogLocalizedStr(const char* aName
, const nsTArray
<nsString
>& aParams
,
264 const nsAString
& aSourceName
,
265 const nsAString
& aSourceLine
, uint32_t aLineNumber
,
266 uint32_t aColumnNumber
, uint32_t aFlags
,
267 const nsACString
& aCategory
, uint64_t aInnerWindowID
,
268 bool aFromPrivateWindow
) {
270 CSP_GetLocalizedStr(aName
, aParams
, logMsg
);
271 CSP_LogMessage(logMsg
, aSourceName
, aSourceLine
, aLineNumber
, aColumnNumber
,
272 aFlags
, aCategory
, aInnerWindowID
, aFromPrivateWindow
);
275 /* ===== Helpers ============================ */
277 // https://w3c.github.io/webappsec-csp/#effective-directive-for-a-request.
278 // However the spec doesn't currently cover all request destinations, which
279 // we roughly represent using nsContentPolicyType.
280 CSPDirective
CSP_ContentTypeToDirective(nsContentPolicyType aType
) {
282 case nsIContentPolicy::TYPE_IMAGE
:
283 case nsIContentPolicy::TYPE_IMAGESET
:
284 case nsIContentPolicy::TYPE_INTERNAL_IMAGE
:
285 case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD
:
286 case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON
:
287 return nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE
;
289 // BLock XSLT as script, see bug 910139
290 case nsIContentPolicy::TYPE_XSLT
:
291 case nsIContentPolicy::TYPE_SCRIPT
:
292 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT
:
293 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD
:
294 case nsIContentPolicy::TYPE_INTERNAL_MODULE
:
295 case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD
:
296 case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS
:
297 case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET
:
298 case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET
:
299 case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT
:
300 case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT
:
301 // (https://github.com/w3c/webappsec-csp/issues/554)
302 // Some of these types are not explicitly defined in the spec.
304 // Chrome seems to use script-src-elem for worklet!
305 return nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE
;
307 case nsIContentPolicy::TYPE_STYLESHEET
:
308 case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
:
309 case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD
:
310 return nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE
;
312 case nsIContentPolicy::TYPE_FONT
:
313 case nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD
:
314 return nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE
;
316 case nsIContentPolicy::TYPE_MEDIA
:
317 case nsIContentPolicy::TYPE_INTERNAL_AUDIO
:
318 case nsIContentPolicy::TYPE_INTERNAL_VIDEO
:
319 case nsIContentPolicy::TYPE_INTERNAL_TRACK
:
320 return nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE
;
322 case nsIContentPolicy::TYPE_WEB_MANIFEST
:
323 return nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE
;
325 case nsIContentPolicy::TYPE_INTERNAL_WORKER
:
326 case nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE
:
327 case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER
:
328 case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER
:
329 return nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE
;
331 case nsIContentPolicy::TYPE_SUBDOCUMENT
:
332 case nsIContentPolicy::TYPE_INTERNAL_FRAME
:
333 case nsIContentPolicy::TYPE_INTERNAL_IFRAME
:
334 return nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE
;
336 case nsIContentPolicy::TYPE_WEBSOCKET
:
337 case nsIContentPolicy::TYPE_XMLHTTPREQUEST
:
338 case nsIContentPolicy::TYPE_BEACON
:
339 case nsIContentPolicy::TYPE_PING
:
340 case nsIContentPolicy::TYPE_FETCH
:
341 case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST
:
342 case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE
:
343 case nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD
:
344 case nsIContentPolicy::TYPE_WEB_IDENTITY
:
345 case nsIContentPolicy::TYPE_WEB_TRANSPORT
:
346 return nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE
;
348 case nsIContentPolicy::TYPE_OBJECT
:
349 case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST
:
350 case nsIContentPolicy::TYPE_INTERNAL_EMBED
:
351 case nsIContentPolicy::TYPE_INTERNAL_OBJECT
:
352 return nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE
;
354 case nsIContentPolicy::TYPE_DTD
:
355 case nsIContentPolicy::TYPE_OTHER
:
356 case nsIContentPolicy::TYPE_SPECULATIVE
:
357 case nsIContentPolicy::TYPE_INTERNAL_DTD
:
358 case nsIContentPolicy::TYPE_INTERNAL_FORCE_ALLOWED_DTD
:
359 return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE
;
361 // CSP does not apply to webrtc connections
362 case nsIContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA
:
363 // csp shold not block top level loads, e.g. in case
365 case nsIContentPolicy::TYPE_DOCUMENT
:
366 // CSP can not block csp reports
367 case nsIContentPolicy::TYPE_CSP_REPORT
:
368 return nsIContentSecurityPolicy::NO_DIRECTIVE
;
370 case nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD
:
371 case nsIContentPolicy::TYPE_UA_FONT
:
372 return nsIContentSecurityPolicy::NO_DIRECTIVE
;
374 // Fall through to error for all other directives
375 case nsIContentPolicy::TYPE_INVALID
:
376 case nsIContentPolicy::TYPE_END
:
377 MOZ_ASSERT(false, "Can not map nsContentPolicyType to CSPDirective");
378 // Do not add default: so that compilers can catch the missing case.
380 return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE
;
383 nsCSPHostSrc
* CSP_CreateHostSrcFromSelfURI(nsIURI
* aSelfURI
) {
384 // Create the host first
386 aSelfURI
->GetAsciiHost(host
);
387 nsCSPHostSrc
* hostsrc
= new nsCSPHostSrc(NS_ConvertUTF8toUTF16(host
));
388 hostsrc
->setGeneratedFromSelfKeyword();
392 aSelfURI
->GetScheme(scheme
);
393 hostsrc
->setScheme(NS_ConvertUTF8toUTF16(scheme
));
395 // An empty host (e.g. for data:) indicates it's effectively a unique origin.
396 // Please note that we still need to set the scheme on hostsrc (see above),
397 // because it's used for reporting.
398 if (host
.EqualsLiteral("")) {
399 hostsrc
->setIsUniqueOrigin();
400 // no need to query the port in that case.
405 aSelfURI
->GetPort(&port
);
406 // Only add port if it's not default port.
408 nsAutoString portStr
;
409 portStr
.AppendInt(port
);
410 hostsrc
->setPort(portStr
);
415 bool CSP_IsEmptyDirective(const nsAString
& aValue
, const nsAString
& aDir
) {
416 return (aDir
.Length() == 0 && aValue
.Length() == 0);
418 bool CSP_IsDirective(const nsAString
& aValue
, CSPDirective aDir
) {
419 return aValue
.LowerCaseEqualsASCII(CSP_CSPDirectiveToString(aDir
));
422 bool CSP_IsKeyword(const nsAString
& aValue
, enum CSPKeyword aKey
) {
423 return aValue
.LowerCaseEqualsASCII(CSP_EnumToUTF8Keyword(aKey
));
426 bool CSP_IsQuotelessKeyword(const nsAString
& aKey
) {
428 ToLowerCase(aKey
, lowerKey
);
430 nsAutoString keyword
;
431 for (uint32_t i
= 0; i
< CSP_LAST_KEYWORD_VALUE
; i
++) {
432 // skipping the leading ' and trimming the trailing '
433 keyword
.AssignASCII(gCSPUTF8Keywords
[i
] + 1);
434 keyword
.Trim("'", false, true);
435 if (lowerKey
.Equals(keyword
)) {
443 * Checks whether the current directive permits a specific
444 * scheme. This function is called from nsCSPSchemeSrc() and
446 * @param aEnforcementScheme
447 * The scheme that this directive allows
449 * The uri of the subresource load.
451 * Whether the enforced policy is report only or not.
452 * @param aUpgradeInsecure
453 * Whether the policy makes use of the directive
454 * 'upgrade-insecure-requests'.
455 * @param aFromSelfURI
456 * Whether a scheme was generated from the keyword 'self'
457 * which then allows schemeless sources to match ws and wss.
460 bool permitsScheme(const nsAString
& aEnforcementScheme
, nsIURI
* aUri
,
461 bool aReportOnly
, bool aUpgradeInsecure
, bool aFromSelfURI
) {
462 nsAutoCString scheme
;
463 nsresult rv
= aUri
->GetScheme(scheme
);
464 NS_ENSURE_SUCCESS(rv
, false);
466 // no scheme to enforce, let's allow the load (e.g. script-src *)
467 if (aEnforcementScheme
.IsEmpty()) {
471 // if the scheme matches, all good - allow the load
472 if (aEnforcementScheme
.EqualsASCII(scheme
.get())) {
476 // allow scheme-less sources where the protected resource is http
477 // and the load is https, see:
478 // http://www.w3.org/TR/CSP2/#match-source-expression
479 if (aEnforcementScheme
.EqualsASCII("http")) {
480 if (scheme
.EqualsASCII("https")) {
483 if ((scheme
.EqualsASCII("ws") || scheme
.EqualsASCII("wss")) &&
488 if (aEnforcementScheme
.EqualsASCII("https")) {
489 if (scheme
.EqualsLiteral("wss") && aFromSelfURI
) {
493 if (aEnforcementScheme
.EqualsASCII("ws") && scheme
.EqualsASCII("wss")) {
497 // Allow the load when enforcing upgrade-insecure-requests with the
498 // promise the request gets upgraded from http to https and ws to wss.
499 // See nsHttpChannel::Connect() and also WebSocket.cpp. Please note,
500 // the report only policies should not allow the load and report
501 // the error back to the page.
503 (aUpgradeInsecure
&& !aReportOnly
) &&
504 ((scheme
.EqualsASCII("http") &&
505 aEnforcementScheme
.EqualsASCII("https")) ||
506 (scheme
.EqualsASCII("ws") && aEnforcementScheme
.EqualsASCII("wss"))));
510 * A helper function for appending a CSP header to an existing CSP
513 * @param aCsp the CSP policy
514 * @param aHeaderValue the header
515 * @param aReportOnly is this a report-only header?
518 nsresult
CSP_AppendCSPFromHeader(nsIContentSecurityPolicy
* aCsp
,
519 const nsAString
& aHeaderValue
,
523 // Need to tokenize the header value since multiple headers could be
524 // concatenated into one comma-separated list of policies.
525 // See RFC2616 section 4.2 (last paragraph)
527 for (const nsAString
& policy
:
528 nsCharSeparatedTokenizer(aHeaderValue
, ',').ToRange()) {
529 rv
= aCsp
->AppendPolicy(policy
, aReportOnly
, false);
530 NS_ENSURE_SUCCESS(rv
, rv
);
532 CSPUTILSLOG(("CSP refined with policy: \"%s\"",
533 NS_ConvertUTF16toUTF8(policy
).get()));
539 /* ===== nsCSPSrc ============================ */
541 nsCSPBaseSrc::nsCSPBaseSrc() {}
543 nsCSPBaseSrc::~nsCSPBaseSrc() = default;
545 // ::permits is only called for external load requests, therefore:
546 // nsCSPKeywordSrc and nsCSPHashSource fall back to this base class
547 // implementation which will never allow the load.
548 bool nsCSPBaseSrc::permits(nsIURI
* aUri
, bool aWasRedirected
, bool aReportOnly
,
549 bool aUpgradeInsecure
) const {
550 if (CSPUTILSLOGENABLED()) {
552 ("nsCSPBaseSrc::permits, aUri: %s", aUri
->GetSpecOrDefault().get()));
557 // ::allows is only called for inlined loads, therefore:
558 // nsCSPSchemeSrc, nsCSPHostSrc fall back
559 // to this base class implementation which will never allow the load.
560 bool nsCSPBaseSrc::allows(enum CSPKeyword aKeyword
,
561 const nsAString
& aHashOrNonce
) const {
562 CSPUTILSLOG(("nsCSPBaseSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
563 aKeyword
== CSP_HASH
? "hash" : CSP_EnumToUTF8Keyword(aKeyword
),
564 NS_ConvertUTF16toUTF8(aHashOrNonce
).get()));
568 /* ====== nsCSPSchemeSrc ===================== */
570 nsCSPSchemeSrc::nsCSPSchemeSrc(const nsAString
& aScheme
) : mScheme(aScheme
) {
571 ToLowerCase(mScheme
);
574 nsCSPSchemeSrc::~nsCSPSchemeSrc() = default;
576 bool nsCSPSchemeSrc::permits(nsIURI
* aUri
, bool aWasRedirected
,
577 bool aReportOnly
, bool aUpgradeInsecure
) const {
578 if (CSPUTILSLOGENABLED()) {
580 ("nsCSPSchemeSrc::permits, aUri: %s", aUri
->GetSpecOrDefault().get()));
582 MOZ_ASSERT((!mScheme
.EqualsASCII("")), "scheme can not be the empty string");
583 return permitsScheme(mScheme
, aUri
, aReportOnly
, aUpgradeInsecure
, false);
586 bool nsCSPSchemeSrc::visit(nsCSPSrcVisitor
* aVisitor
) const {
587 return aVisitor
->visitSchemeSrc(*this);
590 void nsCSPSchemeSrc::toString(nsAString
& outStr
) const {
591 outStr
.Append(mScheme
);
592 outStr
.AppendLiteral(":");
595 /* ===== nsCSPHostSrc ======================== */
597 nsCSPHostSrc::nsCSPHostSrc(const nsAString
& aHost
)
599 mGeneratedFromSelfKeyword(false),
600 mIsUniqueOrigin(false),
601 mWithinFrameAncstorsDir(false) {
605 nsCSPHostSrc::~nsCSPHostSrc() = default;
608 * Checks whether the current directive permits a specific port.
609 * @param aEnforcementScheme
610 * The scheme that this directive allows
611 * (used to query the default port for that scheme)
612 * @param aEnforcementPort
613 * The port that this directive allows
614 * @param aResourceURI
615 * The uri of the subresource load
617 bool permitsPort(const nsAString
& aEnforcementScheme
,
618 const nsAString
& aEnforcementPort
, nsIURI
* aResourceURI
) {
619 // If enforcement port is the wildcard, don't block the load.
620 if (aEnforcementPort
.EqualsASCII("*")) {
624 int32_t resourcePort
;
625 nsresult rv
= aResourceURI
->GetPort(&resourcePort
);
626 if (NS_FAILED(rv
) && aEnforcementPort
.IsEmpty()) {
627 // If we cannot get a Port (e.g. because of an Custom Protocol handler)
628 // We need to check if a default port is associated with the Scheme
629 if (aEnforcementScheme
.IsEmpty()) {
632 int defaultPortforScheme
=
633 NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme
).get());
635 // If there is no default port associated with the Scheme (
636 // defaultPortforScheme == -1) or it is an externally handled protocol (
637 // defaultPortforScheme == 0 ) and the csp does not enforce a port - we can
638 // allow not having a port
639 return (defaultPortforScheme
== -1 || defaultPortforScheme
== -0);
641 // Avoid unnecessary string creation/manipulation and don't block the
642 // load if the resource to be loaded uses the default port for that
643 // scheme and there is no port to be enforced.
644 // Note, this optimization relies on scheme checks within permitsScheme().
645 if (resourcePort
== DEFAULT_PORT
&& aEnforcementPort
.IsEmpty()) {
649 // By now we know at that either the resourcePort does not use the default
650 // port or there is a port restriction to be enforced. A port value of -1
651 // corresponds to the protocol's default port (eg. -1 implies port 80 for
652 // http URIs), in such a case we have to query the default port of the
653 // resource to be loaded.
654 if (resourcePort
== DEFAULT_PORT
) {
655 nsAutoCString resourceScheme
;
656 rv
= aResourceURI
->GetScheme(resourceScheme
);
657 NS_ENSURE_SUCCESS(rv
, false);
658 resourcePort
= NS_GetDefaultPort(resourceScheme
.get());
661 // If there is a port to be enforced and the ports match, then
662 // don't block the load.
663 nsString resourcePortStr
;
664 resourcePortStr
.AppendInt(resourcePort
);
665 if (aEnforcementPort
.Equals(resourcePortStr
)) {
669 // If there is no port to be enforced, query the default port for the load.
670 nsString
enforcementPort(aEnforcementPort
);
671 if (enforcementPort
.IsEmpty()) {
672 // For scheme less sources, our parser always generates a scheme
673 // which is the scheme of the protected resource.
674 MOZ_ASSERT(!aEnforcementScheme
.IsEmpty(),
675 "need a scheme to query default port");
676 int32_t defaultEnforcementPort
=
677 NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme
).get());
678 enforcementPort
.Truncate();
679 enforcementPort
.AppendInt(defaultEnforcementPort
);
682 // If default ports match, don't block the load
683 if (enforcementPort
.Equals(resourcePortStr
)) {
687 // Additional port matching where the regular URL matching algorithm
688 // treats insecure ports as matching their secure variants.
689 // default port for http is :80
690 // default port for https is :443
691 if (enforcementPort
.EqualsLiteral("80") &&
692 resourcePortStr
.EqualsLiteral("443")) {
696 // ports do not match, block the load.
700 bool nsCSPHostSrc::permits(nsIURI
* aUri
, bool aWasRedirected
, bool aReportOnly
,
701 bool aUpgradeInsecure
) const {
702 if (CSPUTILSLOGENABLED()) {
704 ("nsCSPHostSrc::permits, aUri: %s", aUri
->GetSpecOrDefault().get()));
707 if (mIsUniqueOrigin
) {
711 // we are following the enforcement rules from the spec, see:
712 // http://www.w3.org/TR/CSP11/#match-source-expression
714 // 4.3) scheme matching: Check if the scheme matches.
715 if (!permitsScheme(mScheme
, aUri
, aReportOnly
, aUpgradeInsecure
,
716 mGeneratedFromSelfKeyword
)) {
720 // The host in nsCSpHostSrc should never be empty. In case we are enforcing
721 // just a specific scheme, the parser should generate a nsCSPSchemeSource.
722 NS_ASSERTION((!mHost
.IsEmpty()), "host can not be the empty string");
724 // Before we can check if the host matches, we have to
725 // extract the host part from aUri.
726 nsAutoCString uriHost
;
727 nsresult rv
= aUri
->GetAsciiHost(uriHost
);
728 NS_ENSURE_SUCCESS(rv
, false);
730 nsString decodedUriHost
;
731 CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriHost
), decodedUriHost
);
733 // 2) host matching: Enforce a single *
734 if (mHost
.EqualsASCII("*")) {
735 // The single ASTERISK character (*) does not match a URI's scheme of a type
736 // designating a globally unique identifier (such as blob:, data:, or
737 // filesystem:) At the moment firefox does not support filesystem; but for
738 // future compatibility we support it in CSP according to the spec,
739 // see: 4.2.2 Matching Source Expressions Note, that allowlisting any of
740 // these schemes would call nsCSPSchemeSrc::permits().
741 if (aUri
->SchemeIs("blob") || aUri
->SchemeIs("data") ||
742 aUri
->SchemeIs("filesystem")) {
746 // If no scheme is present there also wont be a port and folder to check
747 // which means we can return early
748 if (mScheme
.IsEmpty()) {
752 // 4.5) host matching: Check if the allowed host starts with a wilcard.
753 else if (mHost
.First() == '*') {
756 "Second character needs to be '.' whenever host starts with '*'");
758 // Eliminate leading "*", but keeping the FULL STOP (.) thereafter before
759 // checking if the remaining characters match
760 nsString wildCardHost
= mHost
;
761 wildCardHost
= Substring(wildCardHost
, 1, wildCardHost
.Length() - 1);
762 if (!StringEndsWith(decodedUriHost
, wildCardHost
)) {
766 // 4.6) host matching: Check if hosts match.
767 else if (!mHost
.Equals(decodedUriHost
)) {
771 // Port matching: Check if the ports match.
772 if (!permitsPort(mScheme
, mPort
, aUri
)) {
776 // 4.9) Path matching: If there is a path, we have to enforce
777 // path-level matching, unless the channel got redirected, see:
778 // http://www.w3.org/TR/CSP11/#source-list-paths-and-redirects
779 if (!aWasRedirected
&& !mPath
.IsEmpty()) {
780 // converting aUri into nsIURL so we can strip query and ref
781 // example.com/test#foo -> example.com/test
782 // example.com/test?val=foo -> example.com/test
783 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(aUri
);
785 NS_ASSERTION(false, "can't QI into nsIURI");
788 nsAutoCString uriPath
;
789 rv
= url
->GetFilePath(uriPath
);
790 NS_ENSURE_SUCCESS(rv
, false);
792 if (mWithinFrameAncstorsDir
) {
793 // no path matching for frame-ancestors to not leak any path information.
797 nsString decodedUriPath
;
798 CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriPath
), decodedUriPath
);
800 // check if the last character of mPath is '/'; if so
801 // we just have to check loading resource is within
803 if (mPath
.Last() == '/') {
804 if (!StringBeginsWith(decodedUriPath
, mPath
)) {
808 // otherwise mPath refers to a specific file, and we have to
809 // check if the loading resource matches the file.
811 if (!mPath
.Equals(decodedUriPath
)) {
817 // At the end: scheme, host, port and path match -> allow the load.
821 bool nsCSPHostSrc::visit(nsCSPSrcVisitor
* aVisitor
) const {
822 return aVisitor
->visitHostSrc(*this);
825 void nsCSPHostSrc::toString(nsAString
& outStr
) const {
826 if (mGeneratedFromSelfKeyword
) {
827 outStr
.AppendLiteral("'self'");
831 // If mHost is a single "*", we append the wildcard and return.
832 if (mHost
.EqualsASCII("*") && mScheme
.IsEmpty() && mPort
.IsEmpty()) {
833 outStr
.Append(mHost
);
838 outStr
.Append(mScheme
);
841 outStr
.AppendLiteral("://");
842 outStr
.Append(mHost
);
845 if (!mPort
.IsEmpty()) {
846 outStr
.AppendLiteral(":");
847 outStr
.Append(mPort
);
851 outStr
.Append(mPath
);
854 void nsCSPHostSrc::setScheme(const nsAString
& aScheme
) {
856 ToLowerCase(mScheme
);
859 void nsCSPHostSrc::setPort(const nsAString
& aPort
) { mPort
= aPort
; }
861 void nsCSPHostSrc::appendPath(const nsAString
& aPath
) { mPath
.Append(aPath
); }
863 /* ===== nsCSPKeywordSrc ===================== */
865 nsCSPKeywordSrc::nsCSPKeywordSrc(enum CSPKeyword aKeyword
)
866 : mKeyword(aKeyword
) {
867 NS_ASSERTION((aKeyword
!= CSP_SELF
),
868 "'self' should have been replaced in the parser");
871 nsCSPKeywordSrc::~nsCSPKeywordSrc() = default;
873 bool nsCSPKeywordSrc::allows(enum CSPKeyword aKeyword
,
874 const nsAString
& aHashOrNonce
) const {
875 CSPUTILSLOG(("nsCSPKeywordSrc::allows, aKeyWord: %s, aHashOrNonce: %s",
876 CSP_EnumToUTF8Keyword(aKeyword
),
877 NS_ConvertUTF16toUTF8(aHashOrNonce
).get()));
878 return mKeyword
== aKeyword
;
881 bool nsCSPKeywordSrc::visit(nsCSPSrcVisitor
* aVisitor
) const {
882 return aVisitor
->visitKeywordSrc(*this);
885 void nsCSPKeywordSrc::toString(nsAString
& outStr
) const {
886 outStr
.Append(CSP_EnumToUTF16Keyword(mKeyword
));
889 /* ===== nsCSPNonceSrc ==================== */
891 nsCSPNonceSrc::nsCSPNonceSrc(const nsAString
& aNonce
) : mNonce(aNonce
) {}
893 nsCSPNonceSrc::~nsCSPNonceSrc() = default;
895 bool nsCSPNonceSrc::allows(enum CSPKeyword aKeyword
,
896 const nsAString
& aHashOrNonce
) const {
897 CSPUTILSLOG(("nsCSPNonceSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
898 CSP_EnumToUTF8Keyword(aKeyword
),
899 NS_ConvertUTF16toUTF8(aHashOrNonce
).get()));
901 if (aKeyword
!= CSP_NONCE
) {
904 // nonces can not be invalidated by strict-dynamic
905 return mNonce
.Equals(aHashOrNonce
);
908 bool nsCSPNonceSrc::visit(nsCSPSrcVisitor
* aVisitor
) const {
909 return aVisitor
->visitNonceSrc(*this);
912 void nsCSPNonceSrc::toString(nsAString
& outStr
) const {
913 outStr
.Append(CSP_EnumToUTF16Keyword(CSP_NONCE
));
914 outStr
.Append(mNonce
);
915 outStr
.AppendLiteral("'");
918 /* ===== nsCSPHashSrc ===================== */
920 nsCSPHashSrc::nsCSPHashSrc(const nsAString
& aAlgo
, const nsAString
& aHash
)
921 : mAlgorithm(aAlgo
), mHash(aHash
) {
922 // Only the algo should be rewritten to lowercase, the hash must remain the
924 ToLowerCase(mAlgorithm
);
925 // Normalize the base64url encoding to base64 encoding:
926 char16_t
* cur
= mHash
.BeginWriting();
927 char16_t
* end
= mHash
.EndWriting();
929 for (; cur
< end
; ++cur
) {
930 if (char16_t('-') == *cur
) {
931 *cur
= char16_t('+');
933 if (char16_t('_') == *cur
) {
934 *cur
= char16_t('/');
939 nsCSPHashSrc::~nsCSPHashSrc() = default;
941 bool nsCSPHashSrc::allows(enum CSPKeyword aKeyword
,
942 const nsAString
& aHashOrNonce
) const {
943 CSPUTILSLOG(("nsCSPHashSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
944 CSP_EnumToUTF8Keyword(aKeyword
),
945 NS_ConvertUTF16toUTF8(aHashOrNonce
).get()));
947 if (aKeyword
!= CSP_HASH
) {
951 // hashes can not be invalidated by strict-dynamic
953 // Convert aHashOrNonce to UTF-8
954 NS_ConvertUTF16toUTF8
utf8_hash(aHashOrNonce
);
956 nsCOMPtr
<nsICryptoHash
> hasher
;
957 nsresult rv
= NS_NewCryptoHash(NS_ConvertUTF16toUTF8(mAlgorithm
),
958 getter_AddRefs(hasher
));
959 NS_ENSURE_SUCCESS(rv
, false);
961 rv
= hasher
->Update((uint8_t*)utf8_hash
.get(), utf8_hash
.Length());
962 NS_ENSURE_SUCCESS(rv
, false);
965 rv
= hasher
->Finish(true, hash
);
966 NS_ENSURE_SUCCESS(rv
, false);
968 return NS_ConvertUTF16toUTF8(mHash
).Equals(hash
);
971 bool nsCSPHashSrc::visit(nsCSPSrcVisitor
* aVisitor
) const {
972 return aVisitor
->visitHashSrc(*this);
975 void nsCSPHashSrc::toString(nsAString
& outStr
) const {
976 outStr
.AppendLiteral("'");
977 outStr
.Append(mAlgorithm
);
978 outStr
.AppendLiteral("-");
979 outStr
.Append(mHash
);
980 outStr
.AppendLiteral("'");
983 /* ===== nsCSPReportURI ===================== */
985 nsCSPReportURI::nsCSPReportURI(nsIURI
* aURI
) : mReportURI(aURI
) {}
987 nsCSPReportURI::~nsCSPReportURI() = default;
989 bool nsCSPReportURI::visit(nsCSPSrcVisitor
* aVisitor
) const { return false; }
991 void nsCSPReportURI::toString(nsAString
& outStr
) const {
993 nsresult rv
= mReportURI
->GetSpec(spec
);
997 outStr
.AppendASCII(spec
.get());
1000 /* ===== nsCSPSandboxFlags ===================== */
1002 nsCSPSandboxFlags::nsCSPSandboxFlags(const nsAString
& aFlags
) : mFlags(aFlags
) {
1003 ToLowerCase(mFlags
);
1006 nsCSPSandboxFlags::~nsCSPSandboxFlags() = default;
1008 bool nsCSPSandboxFlags::visit(nsCSPSrcVisitor
* aVisitor
) const { return false; }
1010 void nsCSPSandboxFlags::toString(nsAString
& outStr
) const {
1011 outStr
.Append(mFlags
);
1014 /* ===== nsCSPRequireTrustedTypesForDirectiveValue ===================== */
1016 nsCSPRequireTrustedTypesForDirectiveValue::
1017 nsCSPRequireTrustedTypesForDirectiveValue(const nsAString
& aValue
)
1020 bool nsCSPRequireTrustedTypesForDirectiveValue::visit(
1021 nsCSPSrcVisitor
* aVisitor
) const {
1022 MOZ_ASSERT_UNREACHABLE(
1023 "This method should only be called for other overloads of this method.");
1027 void nsCSPRequireTrustedTypesForDirectiveValue::toString(
1028 nsAString
& aOutStr
) const {
1029 aOutStr
.Append(mValue
);
1032 /* ===== nsCSPDirective ====================== */
1034 nsCSPDirective::nsCSPDirective(CSPDirective aDirective
) {
1035 mDirective
= aDirective
;
1038 nsCSPDirective::~nsCSPDirective() {
1039 for (uint32_t i
= 0; i
< mSrcs
.Length(); i
++) {
1044 // https://w3c.github.io/webappsec-csp/#match-nonce-to-source-list
1045 static bool DoesNonceMatchSourceList(nsILoadInfo
* aLoadInfo
,
1046 const nsTArray
<nsCSPBaseSrc
*>& aSrcs
) {
1047 // Step 1. Assert: source list is not null. (implicit)
1049 // Note: For code-reuse we do "request’s cryptographic nonce metadata" here
1050 // instead of the caller.
1052 MOZ_ALWAYS_SUCCEEDS(aLoadInfo
->GetCspNonce(nonce
));
1054 // Step 2. If nonce is the empty string, return "Does Not Match".
1055 if (nonce
.IsEmpty()) {
1059 // Step 3. For each expression of source list:
1060 for (nsCSPBaseSrc
* src
: aSrcs
) {
1061 // Step 3.1. If expression matches the nonce-source grammar, and nonce is
1062 // identical to expression’s base64-value part, return "Matches".
1063 if (src
->isNonce()) {
1064 nsAutoString srcNonce
;
1065 static_cast<nsCSPNonceSrc
*>(src
)->getNonce(srcNonce
);
1066 if (srcNonce
== nonce
) {
1072 // Step 4. Return "Does Not Match".
1076 // https://www.w3.org/TR/SRI/#parse-metadata
1077 // This function is similar to SRICheck::IntegrityMetadata, but also keeps
1078 // SRI metadata with weaker hashes.
1079 // CSP treats "no metadata" and empty results the same way.
1080 static nsTArray
<SRIMetadata
> ParseSRIMetadata(const nsAString
& aMetadata
) {
1081 // Step 1. Let result be the empty set.
1082 // Step 2. Let empty be equal to true.
1083 nsTArray
<SRIMetadata
> result
;
1085 NS_ConvertUTF16toUTF8
metadataList(aMetadata
);
1086 nsAutoCString token
;
1088 // Step 3. For each token returned by splitting metadata on spaces:
1089 nsCWhitespaceTokenizer
tokenizer(metadataList
);
1090 while (tokenizer
.hasMoreTokens()) {
1091 token
= tokenizer
.nextToken();
1092 // Step 3.1. Set empty to false.
1093 // Step 3.3. Parse token per the grammar in integrity metadata.
1094 SRIMetadata
metadata(token
);
1095 // Step 3.2. If token is not a valid metadata, skip the remaining steps, and
1096 // proceed to the next token.
1097 if (metadata
.IsMalformed()) {
1101 // Step 3.4. Let algorithm be the alg component of token.
1102 // Step 3.5. If algorithm is a hash function recognized by the user agent,
1104 // parsed token to result.
1105 if (metadata
.IsAlgorithmSupported()) {
1106 result
.AppendElement(metadata
);
1110 // Step 4. Return no metadata if empty is true, otherwise return result.
1114 bool nsCSPDirective::permits(CSPDirective aDirective
, nsILoadInfo
* aLoadInfo
,
1115 nsIURI
* aUri
, bool aWasRedirected
,
1116 bool aReportOnly
, bool aUpgradeInsecure
) const {
1117 MOZ_ASSERT(equals(aDirective
) || isDefaultDirective());
1119 if (CSPUTILSLOGENABLED()) {
1120 CSPUTILSLOG(("nsCSPDirective::permits, aUri: %s, aDirective: %s",
1121 aUri
->GetSpecOrDefault().get(),
1122 CSP_CSPDirectiveToString(aDirective
)));
1126 // https://w3c.github.io/webappsec-csp/#style-src-elem-pre-request
1127 if (aDirective
== CSPDirective::STYLE_SRC_ELEM_DIRECTIVE
) {
1128 // Step 3. If the result of executing §6.7.2.3 Does nonce match source
1129 // list? on request’s cryptographic nonce metadata and this directive’s
1130 // value is "Matches", return "Allowed".
1131 if (DoesNonceMatchSourceList(aLoadInfo
, mSrcs
)) {
1132 CSPUTILSLOG((" Allowed by matching nonce (style)"));
1137 // https://w3c.github.io/webappsec-csp/#script-pre-request
1138 // Step 1. If request’s destination is script-like:
1139 else if (aDirective
== CSPDirective::SCRIPT_SRC_ELEM_DIRECTIVE
||
1140 aDirective
== CSPDirective::WORKER_SRC_DIRECTIVE
) {
1141 // Step 1.1. If the result of executing §6.7.2.3 Does nonce match source
1142 // list? on request’s cryptographic nonce metadata and this directive’s
1143 // value is "Matches", return "Allowed".
1144 if (DoesNonceMatchSourceList(aLoadInfo
, mSrcs
)) {
1145 CSPUTILSLOG((" Allowed by matching nonce (script-like)"));
1149 // Step 1.2. Let integrity expressions be the set of source expressions in
1150 // directive’s value that match the hash-source grammar.
1151 nsTArray
<nsCSPHashSrc
*> integrityExpressions
;
1152 bool hasStrictDynamicKeyword
=
1153 false; // Optimization to reduce number of iterations.
1154 for (uint32_t i
= 0; i
< mSrcs
.Length(); i
++) {
1155 if (mSrcs
[i
]->isHash()) {
1156 integrityExpressions
.AppendElement(
1157 static_cast<nsCSPHashSrc
*>(mSrcs
[i
]));
1158 } else if (mSrcs
[i
]->isKeyword(CSP_STRICT_DYNAMIC
)) {
1159 hasStrictDynamicKeyword
= true;
1163 // Step 1.3. If integrity expressions is not empty:
1164 if (!integrityExpressions
.IsEmpty()) {
1165 // Step 1.3.1. Let integrity sources be the result of executing the
1166 // algorithm defined in [SRI 3.3.3 Parse metadata] on request’s
1167 // integrity metadata.
1168 nsAutoString integrityMetadata
;
1169 aLoadInfo
->GetIntegrityMetadata(integrityMetadata
);
1171 nsTArray
<SRIMetadata
> integritySources
=
1172 ParseSRIMetadata(integrityMetadata
);
1174 integritySources
.IsEmpty() == integrityMetadata
.IsEmpty(),
1175 "The integrity metadata should be only be empty, "
1176 "when the parsed string was completely empty, otherwise it should "
1177 "include at least one valid hash");
1179 // Step 1.3.2. If integrity sources is "no metadata" or an empty set,
1180 // skip the remaining substeps.
1181 if (!integritySources
.IsEmpty()) {
1182 // Step 1.3.3. Let bypass due to integrity match be true.
1185 nsAutoCString sourceAlgorithmUTF8
;
1186 nsAutoCString sourceHashUTF8
;
1187 nsAutoString sourceAlgorithm
;
1188 nsAutoString sourceHash
;
1189 nsAutoString algorithm
;
1192 // Step 1.3.4. For each source of integrity sources:
1193 for (const SRIMetadata
& source
: integritySources
) {
1194 source
.GetAlgorithm(&sourceAlgorithmUTF8
);
1195 sourceAlgorithm
= NS_ConvertUTF8toUTF16(sourceAlgorithmUTF8
);
1196 source
.GetHash(0, &sourceHashUTF8
);
1197 sourceHash
= NS_ConvertUTF8toUTF16(sourceHashUTF8
);
1199 // Step 1.3.4.1 If directive’s value does not contain a source
1200 // expression whose hash-algorithm is an ASCII case-insensitive
1201 // match for source’s hash-algorithm, and whose base64-value is
1202 // identical to source’s base64-value, then set bypass due to
1203 // integrity match to false.
1205 for (const nsCSPHashSrc
* hashSrc
: integrityExpressions
) {
1206 hashSrc
->getAlgorithm(algorithm
);
1207 hashSrc
->getHash(hash
);
1209 // The nsCSPHashSrc constructor lowercases algorithm, so this
1210 // is case-insensitive.
1211 if (sourceAlgorithm
== algorithm
&& sourceHash
== hash
) {
1223 // Step 1.3.5. If bypass due to integrity match is true, return
1227 (" Allowed by matching integrity metadata (script-like)"));
1233 // Step 1.4. If directive’s value contains a source expression that is an
1234 // ASCII case-insensitive match for the "'strict-dynamic'" keyword-source:
1236 // XXX I don't think we should apply strict-dynamic to XSLT.
1237 if (hasStrictDynamicKeyword
&& aLoadInfo
->InternalContentPolicyType() !=
1238 nsIContentPolicy::TYPE_XSLT
) {
1239 // Step 1.4.1 If the request’s parser metadata is "parser-inserted",
1240 // return "Blocked". Otherwise, return "Allowed".
1241 if (aLoadInfo
->GetParserCreatedScript()) {
1243 (" Blocked by 'strict-dynamic' because parser-inserted"));
1248 (" Allowed by 'strict-dynamic' because not-parser-inserted"));
1254 for (uint32_t i
= 0; i
< mSrcs
.Length(); i
++) {
1255 if (mSrcs
[i
]->permits(aUri
, aWasRedirected
, aReportOnly
,
1256 aUpgradeInsecure
)) {
1263 bool nsCSPDirective::allows(enum CSPKeyword aKeyword
,
1264 const nsAString
& aHashOrNonce
) const {
1265 CSPUTILSLOG(("nsCSPDirective::allows, aKeyWord: %s, aHashOrNonce: %s",
1266 CSP_EnumToUTF8Keyword(aKeyword
),
1267 NS_ConvertUTF16toUTF8(aHashOrNonce
).get()));
1269 for (uint32_t i
= 0; i
< mSrcs
.Length(); i
++) {
1270 if (mSrcs
[i
]->allows(aKeyword
, aHashOrNonce
)) {
1277 // https://w3c.github.io/webappsec-csp/#allow-all-inline
1278 bool nsCSPDirective::allowsAllInlineBehavior(CSPDirective aDir
) const {
1279 // Step 1. Let allow all inline be false.
1280 bool allowAll
= false;
1282 // Step 2. For each expression of list:
1283 for (nsCSPBaseSrc
* src
: mSrcs
) {
1284 // Step 2.1. If expression matches the nonce-source or hash-source grammar,
1285 // return "Does Not Allow".
1286 if (src
->isNonce() || src
->isHash()) {
1290 // Step 2.2. If type is "script", "script attribute" or "navigation" and
1291 // expression matches the keyword-source "'strict-dynamic'", return "Does
1293 if ((aDir
== nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE
||
1294 aDir
== nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE
) &&
1295 src
->isKeyword(CSP_STRICT_DYNAMIC
)) {
1299 // Step 2.3. If expression is an ASCII case-insensitive match for the
1300 // keyword-source "'unsafe-inline'", set allow all inline to true.
1301 if (src
->isKeyword(CSP_UNSAFE_INLINE
)) {
1306 // Step 3. If allow all inline is true, return "Allows". Otherwise, return
1307 // "Does Not Allow".
1311 void nsCSPDirective::toString(nsAString
& outStr
) const {
1312 // Append directive name
1313 outStr
.AppendASCII(CSP_CSPDirectiveToString(mDirective
));
1314 outStr
.AppendLiteral(" ");
1317 StringJoinAppend(outStr
, u
" "_ns
, mSrcs
,
1318 [](nsAString
& dest
, nsCSPBaseSrc
* cspBaseSrc
) {
1319 cspBaseSrc
->toString(dest
);
1323 void nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP
& outCSP
) const {
1324 mozilla::dom::Sequence
<nsString
> srcs
;
1326 if (NS_WARN_IF(!srcs
.SetCapacity(mSrcs
.Length(), mozilla::fallible
))) {
1328 "Not enough memory for 'sources' sequence in "
1329 "nsCSPDirective::toDomCSPStruct().");
1332 for (uint32_t i
= 0; i
< mSrcs
.Length(); i
++) {
1334 mSrcs
[i
]->toString(src
);
1335 if (!srcs
.AppendElement(src
, mozilla::fallible
)) {
1337 "Failed to append to 'sources' sequence in "
1338 "nsCSPDirective::toDomCSPStruct().");
1342 switch (mDirective
) {
1343 case nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE
:
1344 outCSP
.mDefault_src
.Construct();
1345 outCSP
.mDefault_src
.Value() = std::move(srcs
);
1348 case nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE
:
1349 outCSP
.mScript_src
.Construct();
1350 outCSP
.mScript_src
.Value() = std::move(srcs
);
1353 case nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE
:
1354 outCSP
.mObject_src
.Construct();
1355 outCSP
.mObject_src
.Value() = std::move(srcs
);
1358 case nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE
:
1359 outCSP
.mStyle_src
.Construct();
1360 outCSP
.mStyle_src
.Value() = std::move(srcs
);
1363 case nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE
:
1364 outCSP
.mImg_src
.Construct();
1365 outCSP
.mImg_src
.Value() = std::move(srcs
);
1368 case nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE
:
1369 outCSP
.mMedia_src
.Construct();
1370 outCSP
.mMedia_src
.Value() = std::move(srcs
);
1373 case nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE
:
1374 outCSP
.mFrame_src
.Construct();
1375 outCSP
.mFrame_src
.Value() = std::move(srcs
);
1378 case nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE
:
1379 outCSP
.mFont_src
.Construct();
1380 outCSP
.mFont_src
.Value() = std::move(srcs
);
1383 case nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE
:
1384 outCSP
.mConnect_src
.Construct();
1385 outCSP
.mConnect_src
.Value() = std::move(srcs
);
1388 case nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE
:
1389 outCSP
.mReport_uri
.Construct();
1390 outCSP
.mReport_uri
.Value() = std::move(srcs
);
1393 case nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE
:
1394 outCSP
.mFrame_ancestors
.Construct();
1395 outCSP
.mFrame_ancestors
.Value() = std::move(srcs
);
1398 case nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE
:
1399 outCSP
.mManifest_src
.Construct();
1400 outCSP
.mManifest_src
.Value() = std::move(srcs
);
1402 // not supporting REFLECTED_XSS_DIRECTIVE
1404 case nsIContentSecurityPolicy::BASE_URI_DIRECTIVE
:
1405 outCSP
.mBase_uri
.Construct();
1406 outCSP
.mBase_uri
.Value() = std::move(srcs
);
1409 case nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE
:
1410 outCSP
.mForm_action
.Construct();
1411 outCSP
.mForm_action
.Value() = std::move(srcs
);
1414 case nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT
:
1415 outCSP
.mBlock_all_mixed_content
.Construct();
1416 // does not have any srcs
1419 case nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE
:
1420 outCSP
.mUpgrade_insecure_requests
.Construct();
1421 // does not have any srcs
1424 case nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE
:
1425 outCSP
.mChild_src
.Construct();
1426 outCSP
.mChild_src
.Value() = std::move(srcs
);
1429 case nsIContentSecurityPolicy::SANDBOX_DIRECTIVE
:
1430 outCSP
.mSandbox
.Construct();
1431 outCSP
.mSandbox
.Value() = std::move(srcs
);
1434 case nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE
:
1435 outCSP
.mWorker_src
.Construct();
1436 outCSP
.mWorker_src
.Value() = std::move(srcs
);
1439 case nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE
:
1440 outCSP
.mScript_src_elem
.Construct();
1441 outCSP
.mScript_src_elem
.Value() = std::move(srcs
);
1444 case nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE
:
1445 outCSP
.mScript_src_attr
.Construct();
1446 outCSP
.mScript_src_attr
.Value() = std::move(srcs
);
1449 case nsIContentSecurityPolicy::REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE
:
1450 outCSP
.mRequire_trusted_types_for
.Construct();
1452 // Here, the srcs represent the sink group
1453 // (https://w3c.github.io/trusted-types/dist/spec/#integration-with-content-security-policy).
1454 outCSP
.mRequire_trusted_types_for
.Value() = std::move(srcs
);
1458 NS_ASSERTION(false, "cannot find directive to convert CSP to JSON");
1462 void nsCSPDirective::getReportURIs(nsTArray
<nsString
>& outReportURIs
) const {
1463 NS_ASSERTION((mDirective
== nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE
),
1464 "not a report-uri directive");
1467 nsString tmpReportURI
;
1468 for (uint32_t i
= 0; i
< mSrcs
.Length(); i
++) {
1469 tmpReportURI
.Truncate();
1470 mSrcs
[i
]->toString(tmpReportURI
);
1471 outReportURIs
.AppendElement(tmpReportURI
);
1475 bool nsCSPDirective::visitSrcs(nsCSPSrcVisitor
* aVisitor
) const {
1476 for (uint32_t i
= 0; i
< mSrcs
.Length(); i
++) {
1477 if (!mSrcs
[i
]->visit(aVisitor
)) {
1484 bool nsCSPDirective::equals(CSPDirective aDirective
) const {
1485 return (mDirective
== aDirective
);
1488 void nsCSPDirective::getDirName(nsAString
& outStr
) const {
1489 outStr
.AppendASCII(CSP_CSPDirectiveToString(mDirective
));
1492 bool nsCSPDirective::hasReportSampleKeyword() const {
1493 for (nsCSPBaseSrc
* src
: mSrcs
) {
1494 if (src
->isReportSample()) {
1502 /* =============== nsCSPChildSrcDirective ============= */
1504 nsCSPChildSrcDirective::nsCSPChildSrcDirective(CSPDirective aDirective
)
1505 : nsCSPDirective(aDirective
),
1506 mRestrictFrames(false),
1507 mRestrictWorkers(false) {}
1509 nsCSPChildSrcDirective::~nsCSPChildSrcDirective() = default;
1511 bool nsCSPChildSrcDirective::equals(CSPDirective aDirective
) const {
1512 if (aDirective
== nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE
) {
1513 return mRestrictFrames
;
1515 if (aDirective
== nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE
) {
1516 return mRestrictWorkers
;
1518 return (mDirective
== aDirective
);
1521 /* =============== nsCSPScriptSrcDirective ============= */
1523 nsCSPScriptSrcDirective::nsCSPScriptSrcDirective(CSPDirective aDirective
)
1524 : nsCSPDirective(aDirective
) {}
1526 nsCSPScriptSrcDirective::~nsCSPScriptSrcDirective() = default;
1528 bool nsCSPScriptSrcDirective::equals(CSPDirective aDirective
) const {
1529 if (aDirective
== nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE
) {
1530 return mRestrictWorkers
;
1532 if (aDirective
== nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE
) {
1533 return mRestrictScriptElem
;
1535 if (aDirective
== nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE
) {
1536 return mRestrictScriptAttr
;
1538 return mDirective
== aDirective
;
1541 /* =============== nsCSPStyleSrcDirective ============= */
1543 nsCSPStyleSrcDirective::nsCSPStyleSrcDirective(CSPDirective aDirective
)
1544 : nsCSPDirective(aDirective
) {}
1546 nsCSPStyleSrcDirective::~nsCSPStyleSrcDirective() = default;
1548 bool nsCSPStyleSrcDirective::equals(CSPDirective aDirective
) const {
1549 if (aDirective
== nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE
) {
1550 return mRestrictStyleElem
;
1552 if (aDirective
== nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE
) {
1553 return mRestrictStyleAttr
;
1555 return mDirective
== aDirective
;
1558 /* =============== nsBlockAllMixedContentDirective ============= */
1560 nsBlockAllMixedContentDirective::nsBlockAllMixedContentDirective(
1561 CSPDirective aDirective
)
1562 : nsCSPDirective(aDirective
) {}
1564 nsBlockAllMixedContentDirective::~nsBlockAllMixedContentDirective() = default;
1566 void nsBlockAllMixedContentDirective::toString(nsAString
& outStr
) const {
1567 outStr
.AppendASCII(CSP_CSPDirectiveToString(
1568 nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT
));
1571 void nsBlockAllMixedContentDirective::getDirName(nsAString
& outStr
) const {
1572 outStr
.AppendASCII(CSP_CSPDirectiveToString(
1573 nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT
));
1576 /* =============== nsUpgradeInsecureDirective ============= */
1578 nsUpgradeInsecureDirective::nsUpgradeInsecureDirective(CSPDirective aDirective
)
1579 : nsCSPDirective(aDirective
) {}
1581 nsUpgradeInsecureDirective::~nsUpgradeInsecureDirective() = default;
1583 void nsUpgradeInsecureDirective::toString(nsAString
& outStr
) const {
1584 outStr
.AppendASCII(CSP_CSPDirectiveToString(
1585 nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE
));
1588 void nsUpgradeInsecureDirective::getDirName(nsAString
& outStr
) const {
1589 outStr
.AppendASCII(CSP_CSPDirectiveToString(
1590 nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE
));
1593 /* ===== nsCSPPolicy ========================= */
1595 nsCSPPolicy::nsCSPPolicy()
1596 : mUpgradeInsecDir(nullptr),
1598 mDeliveredViaMetaTag(false) {
1599 CSPUTILSLOG(("nsCSPPolicy::nsCSPPolicy"));
1602 nsCSPPolicy::~nsCSPPolicy() {
1603 CSPUTILSLOG(("nsCSPPolicy::~nsCSPPolicy"));
1605 for (uint32_t i
= 0; i
< mDirectives
.Length(); i
++) {
1606 delete mDirectives
[i
];
1610 bool nsCSPPolicy::permits(CSPDirective aDir
, nsILoadInfo
* aLoadInfo
,
1611 nsIURI
* aUri
, bool aWasRedirected
, bool aSpecific
,
1612 nsAString
& outViolatedDirective
,
1613 nsAString
& outViolatedDirectiveString
) const {
1614 if (CSPUTILSLOGENABLED()) {
1615 CSPUTILSLOG(("nsCSPPolicy::permits, aUri: %s, aDir: %s, aSpecific: %s",
1616 aUri
->GetSpecOrDefault().get(), CSP_CSPDirectiveToString(aDir
),
1617 aSpecific
? "true" : "false"));
1620 NS_ASSERTION(aUri
, "permits needs an uri to perform the check!");
1621 outViolatedDirective
.Truncate();
1622 outViolatedDirectiveString
.Truncate();
1624 nsCSPDirective
* defaultDir
= nullptr;
1626 // Try to find a relevant directive
1627 // These directive arrays are short (1-5 elements), not worth using a
1629 for (uint32_t i
= 0; i
< mDirectives
.Length(); i
++) {
1630 if (mDirectives
[i
]->equals(aDir
)) {
1631 if (!mDirectives
[i
]->permits(aDir
, aLoadInfo
, aUri
, aWasRedirected
,
1632 mReportOnly
, mUpgradeInsecDir
)) {
1633 mDirectives
[i
]->getDirName(outViolatedDirective
);
1634 mDirectives
[i
]->toString(outViolatedDirectiveString
);
1639 if (mDirectives
[i
]->isDefaultDirective()) {
1640 defaultDir
= mDirectives
[i
];
1644 // If the above loop runs through, we haven't found a matching directive.
1645 // Avoid relooping, just store the result of default-src while looping.
1646 if (!aSpecific
&& defaultDir
) {
1647 if (!defaultDir
->permits(aDir
, aLoadInfo
, aUri
, aWasRedirected
, mReportOnly
,
1648 mUpgradeInsecDir
)) {
1649 defaultDir
->getDirName(outViolatedDirective
);
1650 defaultDir
->toString(outViolatedDirectiveString
);
1656 // Nothing restricts this, so we're allowing the load
1661 bool nsCSPPolicy::allows(CSPDirective aDirective
, enum CSPKeyword aKeyword
,
1662 const nsAString
& aHashOrNonce
) const {
1663 CSPUTILSLOG(("nsCSPPolicy::allows, aKeyWord: %s, a HashOrNonce: %s",
1664 CSP_EnumToUTF8Keyword(aKeyword
),
1665 NS_ConvertUTF16toUTF8(aHashOrNonce
).get()));
1667 if (nsCSPDirective
* directive
= matchingOrDefaultDirective(aDirective
)) {
1668 return directive
->allows(aKeyword
, aHashOrNonce
);
1671 // No matching directive or default directive as fallback found, thus
1672 // allowing the load; see Bug 885433
1673 // a) inline scripts (also unsafe eval) should only be blocked
1674 // if there is a [script-src] or [default-src]
1675 // b) inline styles should only be blocked
1676 // if there is a [style-src] or [default-src]
1680 nsCSPDirective
* nsCSPPolicy::matchingOrDefaultDirective(
1681 CSPDirective aDirective
) const {
1682 nsCSPDirective
* defaultDir
= nullptr;
1684 // Try to find a matching directive
1685 for (uint32_t i
= 0; i
< mDirectives
.Length(); i
++) {
1686 if (mDirectives
[i
]->isDefaultDirective()) {
1687 defaultDir
= mDirectives
[i
];
1690 if (mDirectives
[i
]->equals(aDirective
)) {
1691 return mDirectives
[i
];
1698 void nsCSPPolicy::toString(nsAString
& outStr
) const {
1699 StringJoinAppend(outStr
, u
"; "_ns
, mDirectives
,
1700 [](nsAString
& dest
, nsCSPDirective
* cspDirective
) {
1701 cspDirective
->toString(dest
);
1705 void nsCSPPolicy::toDomCSPStruct(mozilla::dom::CSP
& outCSP
) const {
1706 outCSP
.mReport_only
= mReportOnly
;
1708 for (uint32_t i
= 0; i
< mDirectives
.Length(); ++i
) {
1709 mDirectives
[i
]->toDomCSPStruct(outCSP
);
1713 bool nsCSPPolicy::hasDirective(CSPDirective aDir
) const {
1714 for (uint32_t i
= 0; i
< mDirectives
.Length(); i
++) {
1715 if (mDirectives
[i
]->equals(aDir
)) {
1722 bool nsCSPPolicy::allowsAllInlineBehavior(CSPDirective aDir
) const {
1723 nsCSPDirective
* directive
= matchingOrDefaultDirective(aDir
);
1725 // No matching or default directive found thus allow the all inline
1726 // scripts or styles. (See nsCSPPolicy::allows)
1730 return directive
->allowsAllInlineBehavior(aDir
);
1734 * Use this function only after ::allows() returned 'false'. Most and
1735 * foremost it's used to get the violated directive before sending reports.
1736 * The parameter outDirective is the equivalent of 'outViolatedDirective'
1737 * for the ::permits() function family.
1739 void nsCSPPolicy::getViolatedDirectiveInformation(CSPDirective aDirective
,
1740 nsAString
& outDirective
,
1741 nsAString
& outDirectiveString
,
1742 bool* aReportSample
) const {
1743 *aReportSample
= false;
1744 nsCSPDirective
* directive
= matchingOrDefaultDirective(aDirective
);
1746 MOZ_ASSERT_UNREACHABLE("Can not query violated directive");
1747 outDirective
.AppendLiteral("couldNotQueryViolatedDirective");
1748 outDirective
.Truncate();
1752 directive
->getDirName(outDirective
);
1753 directive
->toString(outDirectiveString
);
1754 *aReportSample
= directive
->hasReportSampleKeyword();
1758 * Helper function that returns the underlying bit representation of sandbox
1759 * flags. The function returns SANDBOXED_NONE if there are no sandbox
1762 uint32_t nsCSPPolicy::getSandboxFlags() const {
1763 for (uint32_t i
= 0; i
< mDirectives
.Length(); i
++) {
1764 if (mDirectives
[i
]->equals(nsIContentSecurityPolicy::SANDBOX_DIRECTIVE
)) {
1766 mDirectives
[i
]->toString(flags
);
1768 if (flags
.IsEmpty()) {
1769 return SANDBOX_ALL_FLAGS
;
1773 attr
.ParseAtomArray(flags
);
1775 return nsContentUtils::ParseSandboxAttributeToFlags(&attr
);
1779 return SANDBOXED_NONE
;
1782 void nsCSPPolicy::getReportURIs(nsTArray
<nsString
>& outReportURIs
) const {
1783 for (uint32_t i
= 0; i
< mDirectives
.Length(); i
++) {
1784 if (mDirectives
[i
]->equals(
1785 nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE
)) {
1786 mDirectives
[i
]->getReportURIs(outReportURIs
);
1792 bool nsCSPPolicy::visitDirectiveSrcs(CSPDirective aDir
,
1793 nsCSPSrcVisitor
* aVisitor
) const {
1794 for (uint32_t i
= 0; i
< mDirectives
.Length(); i
++) {
1795 if (mDirectives
[i
]->equals(aDir
)) {
1796 return mDirectives
[i
]->visitSrcs(aVisitor
);