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/Components.h"
26 #include "mozilla/dom/CSPDictionariesBinding.h"
27 #include "mozilla/dom/Document.h"
28 #include "mozilla/StaticPrefs_security.h"
30 #define DEFAULT_PORT -1
32 static mozilla::LogModule
* GetCspUtilsLog() {
33 static mozilla::LazyLogModule
gCspUtilsPRLog("CSPUtils");
34 return gCspUtilsPRLog
;
37 #define CSPUTILSLOG(args) \
38 MOZ_LOG(GetCspUtilsLog(), mozilla::LogLevel::Debug, args)
39 #define CSPUTILSLOGENABLED() \
40 MOZ_LOG_TEST(GetCspUtilsLog(), mozilla::LogLevel::Debug)
42 void CSP_PercentDecodeStr(const nsAString
& aEncStr
, nsAString
& outDecStr
) {
45 // helper function that should not be visible outside this methods scope
47 static inline char16_t
convertHexDig(char16_t aHexDig
) {
48 if (isNumberToken(aHexDig
)) {
51 if (aHexDig
>= 'A' && aHexDig
<= 'F') {
52 return aHexDig
- 'A' + 10;
54 // must be a lower case character
55 // (aHexDig >= 'a' && aHexDig <= 'f')
56 return aHexDig
- 'a' + 10;
60 const char16_t
*cur
, *end
, *hexDig1
, *hexDig2
;
61 cur
= aEncStr
.BeginReading();
62 end
= aEncStr
.EndReading();
65 // if it's not a percent sign then there is
66 // nothing to do for that character
67 if (*cur
!= PERCENT_SIGN
) {
68 outDecStr
.Append(*cur
);
73 // get the two hexDigs following the '%'-sign
77 // if there are no hexdigs after the '%' then
78 // there is nothing to do for us.
79 if (hexDig1
== end
|| hexDig2
== end
|| !isValidHexDig(*hexDig1
) ||
80 !isValidHexDig(*hexDig2
)) {
81 outDecStr
.Append(PERCENT_SIGN
);
86 // decode "% hexDig1 hexDig2" into a character.
88 (local::convertHexDig(*hexDig1
) << 4) + local::convertHexDig(*hexDig2
);
89 outDecStr
.Append(decChar
);
91 // increment 'cur' to after the second hexDig
96 // The Content Security Policy should be inherited for
97 // local schemes like: "about", "blob", "data", or "filesystem".
98 // see: https://w3c.github.io/webappsec-csp/#initialize-document-csp
99 bool CSP_ShouldResponseInheritCSP(nsIChannel
* aChannel
) {
104 nsCOMPtr
<nsIURI
> uri
;
105 nsresult rv
= aChannel
->GetURI(getter_AddRefs(uri
));
106 NS_ENSURE_SUCCESS(rv
, false);
108 bool isAbout
= uri
->SchemeIs("about");
110 nsAutoCString aboutSpec
;
111 rv
= uri
->GetSpec(aboutSpec
);
112 NS_ENSURE_SUCCESS(rv
, false);
113 // also allow about:blank#foo
114 if (StringBeginsWith(aboutSpec
, "about:blank"_ns
) ||
115 StringBeginsWith(aboutSpec
, "about:srcdoc"_ns
)) {
120 return uri
->SchemeIs("blob") || uri
->SchemeIs("data") ||
121 uri
->SchemeIs("filesystem") || uri
->SchemeIs("javascript");
124 void CSP_ApplyMetaCSPToDoc(mozilla::dom::Document
& aDoc
,
125 const nsAString
& aPolicyStr
) {
126 if (aDoc
.IsLoadedAsData()) {
130 nsAutoString
policyStr(
131 nsContentUtils::TrimWhitespace
<nsContentUtils::IsHTMLWhitespace
>(
134 if (policyStr
.IsEmpty()) {
138 nsCOMPtr
<nsIContentSecurityPolicy
> csp
= aDoc
.GetCsp();
140 MOZ_ASSERT(false, "how come there is no CSP");
144 // Multiple CSPs (delivered through either header of meta tag) need to
145 // be joined together, see:
146 // https://w3c.github.io/webappsec/specs/content-security-policy/#delivery-html-meta-element
148 csp
->AppendPolicy(policyStr
,
149 false, // csp via meta tag can not be report only
150 true); // delivered through the meta tag
151 NS_ENSURE_SUCCESS_VOID(rv
);
152 if (nsPIDOMWindowInner
* inner
= aDoc
.GetInnerWindow()) {
155 aDoc
.ApplySettingsFromCSP(false);
158 void CSP_GetLocalizedStr(const char* aName
, const nsTArray
<nsString
>& aParams
,
159 nsAString
& outResult
) {
160 nsCOMPtr
<nsIStringBundle
> keyStringBundle
;
161 nsCOMPtr
<nsIStringBundleService
> stringBundleService
=
162 mozilla::components::StringBundle::Service();
164 NS_ASSERTION(stringBundleService
, "String bundle service must be present!");
165 stringBundleService
->CreateBundle(
166 "chrome://global/locale/security/csp.properties",
167 getter_AddRefs(keyStringBundle
));
169 NS_ASSERTION(keyStringBundle
, "Key string bundle must be available!");
171 if (!keyStringBundle
) {
174 keyStringBundle
->FormatStringFromName(aName
, aParams
, outResult
);
177 void CSP_LogStrMessage(const nsAString
& aMsg
) {
178 nsCOMPtr
<nsIConsoleService
> console(
179 do_GetService("@mozilla.org/consoleservice;1"));
185 console
->LogStringMessage(msg
.get());
188 void CSP_LogMessage(const nsAString
& aMessage
, const nsAString
& aSourceName
,
189 const nsAString
& aSourceLine
, uint32_t aLineNumber
,
190 uint32_t aColumnNumber
, uint32_t aFlags
,
191 const nsACString
& aCategory
, uint64_t aInnerWindowID
,
192 bool aFromPrivateWindow
) {
193 nsCOMPtr
<nsIConsoleService
> console(
194 do_GetService(NS_CONSOLESERVICE_CONTRACTID
));
196 nsCOMPtr
<nsIScriptError
> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID
));
198 if (!console
|| !error
) {
202 // Prepending CSP to the outgoing console message
204 cspMsg
.AppendLiteral(u
"Content Security Policy: ");
205 cspMsg
.Append(aMessage
);
207 // Currently 'aSourceLine' is not logged to the console, because similar
208 // information is already included within the source link of the message.
209 // For inline violations however, the line and column number are 0 and
210 // information contained within 'aSourceLine' can be really useful for devs.
211 // E.g. 'aSourceLine' might be: 'onclick attribute on DIV element'.
212 // In such cases we append 'aSourceLine' directly to the error message.
213 if (!aSourceLine
.IsEmpty() && aLineNumber
== 0) {
214 cspMsg
.AppendLiteral(u
"\nSource: ");
215 cspMsg
.Append(aSourceLine
);
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
, 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 ============================ */
258 // https://w3c.github.io/webappsec-csp/#effective-directive-for-a-request.
259 // However the spec doesn't currently cover all request destinations, which
260 // we roughly represent using nsContentPolicyType.
261 CSPDirective
CSP_ContentTypeToDirective(nsContentPolicyType aType
) {
263 case nsIContentPolicy::TYPE_IMAGE
:
264 case nsIContentPolicy::TYPE_IMAGESET
:
265 case nsIContentPolicy::TYPE_INTERNAL_IMAGE
:
266 case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD
:
267 case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON
:
268 return nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE
;
270 // BLock XSLT as script, see bug 910139
271 case nsIContentPolicy::TYPE_XSLT
:
272 case nsIContentPolicy::TYPE_SCRIPT
:
273 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT
:
274 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD
:
275 case nsIContentPolicy::TYPE_INTERNAL_MODULE
:
276 case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD
:
277 case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS
:
278 case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET
:
279 case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET
:
280 case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT
:
281 case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT
:
282 // (https://github.com/w3c/webappsec-csp/issues/554)
283 // Some of these types are not explicitly defined in the spec.
285 // Chrome seems to use script-src-elem for worklet!
286 return nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE
;
288 case nsIContentPolicy::TYPE_STYLESHEET
:
289 case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET
:
290 case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD
:
291 return nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE
;
293 case nsIContentPolicy::TYPE_FONT
:
294 case nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD
:
295 return nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE
;
297 case nsIContentPolicy::TYPE_MEDIA
:
298 case nsIContentPolicy::TYPE_INTERNAL_AUDIO
:
299 case nsIContentPolicy::TYPE_INTERNAL_VIDEO
:
300 case nsIContentPolicy::TYPE_INTERNAL_TRACK
:
301 return nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE
;
303 case nsIContentPolicy::TYPE_WEB_MANIFEST
:
304 return nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE
;
306 case nsIContentPolicy::TYPE_INTERNAL_WORKER
:
307 case nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE
:
308 case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER
:
309 case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER
:
310 return nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE
;
312 case nsIContentPolicy::TYPE_SUBDOCUMENT
:
313 case nsIContentPolicy::TYPE_INTERNAL_FRAME
:
314 case nsIContentPolicy::TYPE_INTERNAL_IFRAME
:
315 return nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE
;
317 case nsIContentPolicy::TYPE_WEBSOCKET
:
318 case nsIContentPolicy::TYPE_XMLHTTPREQUEST
:
319 case nsIContentPolicy::TYPE_BEACON
:
320 case nsIContentPolicy::TYPE_PING
:
321 case nsIContentPolicy::TYPE_FETCH
:
322 case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST
:
323 case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE
:
324 case nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD
:
325 case nsIContentPolicy::TYPE_WEB_IDENTITY
:
326 case nsIContentPolicy::TYPE_WEB_TRANSPORT
:
327 return nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE
;
329 case nsIContentPolicy::TYPE_OBJECT
:
330 case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST
:
331 case nsIContentPolicy::TYPE_INTERNAL_EMBED
:
332 case nsIContentPolicy::TYPE_INTERNAL_OBJECT
:
333 return nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE
;
335 case nsIContentPolicy::TYPE_DTD
:
336 case nsIContentPolicy::TYPE_OTHER
:
337 case nsIContentPolicy::TYPE_SPECULATIVE
:
338 case nsIContentPolicy::TYPE_INTERNAL_DTD
:
339 case nsIContentPolicy::TYPE_INTERNAL_FORCE_ALLOWED_DTD
:
340 return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE
;
342 // CSP does not apply to webrtc connections
343 case nsIContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA
:
344 // csp shold not block top level loads, e.g. in case
346 case nsIContentPolicy::TYPE_DOCUMENT
:
347 // CSP can not block csp reports
348 case nsIContentPolicy::TYPE_CSP_REPORT
:
349 return nsIContentSecurityPolicy::NO_DIRECTIVE
;
351 case nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD
:
352 case nsIContentPolicy::TYPE_UA_FONT
:
353 return nsIContentSecurityPolicy::NO_DIRECTIVE
;
355 // Fall through to error for all other directives
356 // Note that we should never end up here for navigate-to
357 case nsIContentPolicy::TYPE_INVALID
:
358 case nsIContentPolicy::TYPE_END
:
359 MOZ_ASSERT(false, "Can not map nsContentPolicyType to CSPDirective");
360 // Do not add default: so that compilers can catch the missing case.
362 return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE
;
365 nsCSPHostSrc
* CSP_CreateHostSrcFromSelfURI(nsIURI
* aSelfURI
) {
366 // Create the host first
368 aSelfURI
->GetAsciiHost(host
);
369 nsCSPHostSrc
* hostsrc
= new nsCSPHostSrc(NS_ConvertUTF8toUTF16(host
));
370 hostsrc
->setGeneratedFromSelfKeyword();
374 aSelfURI
->GetScheme(scheme
);
375 hostsrc
->setScheme(NS_ConvertUTF8toUTF16(scheme
));
377 // An empty host (e.g. for data:) indicates it's effectively a unique origin.
378 // Please note that we still need to set the scheme on hostsrc (see above),
379 // because it's used for reporting.
380 if (host
.EqualsLiteral("")) {
381 hostsrc
->setIsUniqueOrigin();
382 // no need to query the port in that case.
387 aSelfURI
->GetPort(&port
);
388 // Only add port if it's not default port.
390 nsAutoString portStr
;
391 portStr
.AppendInt(port
);
392 hostsrc
->setPort(portStr
);
397 bool CSP_IsEmptyDirective(const nsAString
& aValue
, const nsAString
& aDir
) {
398 return (aDir
.Length() == 0 && aValue
.Length() == 0);
400 bool CSP_IsDirective(const nsAString
& aValue
, CSPDirective aDir
) {
401 return aValue
.LowerCaseEqualsASCII(CSP_CSPDirectiveToString(aDir
));
404 bool CSP_IsKeyword(const nsAString
& aValue
, enum CSPKeyword aKey
) {
405 return aValue
.LowerCaseEqualsASCII(CSP_EnumToUTF8Keyword(aKey
));
408 bool CSP_IsQuotelessKeyword(const nsAString
& aKey
) {
410 ToLowerCase(aKey
, lowerKey
);
412 nsAutoString keyword
;
413 for (uint32_t i
= 0; i
< CSP_LAST_KEYWORD_VALUE
; i
++) {
414 // skipping the leading ' and trimming the trailing '
415 keyword
.AssignASCII(gCSPUTF8Keywords
[i
] + 1);
416 keyword
.Trim("'", false, true);
417 if (lowerKey
.Equals(keyword
)) {
425 * Checks whether the current directive permits a specific
426 * scheme. This function is called from nsCSPSchemeSrc() and
428 * @param aEnforcementScheme
429 * The scheme that this directive allows
431 * The uri of the subresource load.
433 * Whether the enforced policy is report only or not.
434 * @param aUpgradeInsecure
435 * Whether the policy makes use of the directive
436 * 'upgrade-insecure-requests'.
437 * @param aFromSelfURI
438 * Whether a scheme was generated from the keyword 'self'
439 * which then allows schemeless sources to match ws and wss.
442 bool permitsScheme(const nsAString
& aEnforcementScheme
, nsIURI
* aUri
,
443 bool aReportOnly
, bool aUpgradeInsecure
, bool aFromSelfURI
) {
444 nsAutoCString scheme
;
445 nsresult rv
= aUri
->GetScheme(scheme
);
446 NS_ENSURE_SUCCESS(rv
, false);
448 // no scheme to enforce, let's allow the load (e.g. script-src *)
449 if (aEnforcementScheme
.IsEmpty()) {
453 // if the scheme matches, all good - allow the load
454 if (aEnforcementScheme
.EqualsASCII(scheme
.get())) {
458 // allow scheme-less sources where the protected resource is http
459 // and the load is https, see:
460 // http://www.w3.org/TR/CSP2/#match-source-expression
461 if (aEnforcementScheme
.EqualsASCII("http")) {
462 if (scheme
.EqualsASCII("https")) {
465 if ((scheme
.EqualsASCII("ws") || scheme
.EqualsASCII("wss")) &&
470 if (aEnforcementScheme
.EqualsASCII("https")) {
471 if (scheme
.EqualsLiteral("wss") && aFromSelfURI
) {
475 if (aEnforcementScheme
.EqualsASCII("ws") && scheme
.EqualsASCII("wss")) {
479 // Allow the load when enforcing upgrade-insecure-requests with the
480 // promise the request gets upgraded from http to https and ws to wss.
481 // See nsHttpChannel::Connect() and also WebSocket.cpp. Please note,
482 // the report only policies should not allow the load and report
483 // the error back to the page.
485 (aUpgradeInsecure
&& !aReportOnly
) &&
486 ((scheme
.EqualsASCII("http") &&
487 aEnforcementScheme
.EqualsASCII("https")) ||
488 (scheme
.EqualsASCII("ws") && aEnforcementScheme
.EqualsASCII("wss"))));
492 * A helper function for appending a CSP header to an existing CSP
495 * @param aCsp the CSP policy
496 * @param aHeaderValue the header
497 * @param aReportOnly is this a report-only header?
500 nsresult
CSP_AppendCSPFromHeader(nsIContentSecurityPolicy
* aCsp
,
501 const nsAString
& aHeaderValue
,
505 // Need to tokenize the header value since multiple headers could be
506 // concatenated into one comma-separated list of policies.
507 // See RFC2616 section 4.2 (last paragraph)
509 for (const nsAString
& policy
:
510 nsCharSeparatedTokenizer(aHeaderValue
, ',').ToRange()) {
511 rv
= aCsp
->AppendPolicy(policy
, aReportOnly
, false);
512 NS_ENSURE_SUCCESS(rv
, rv
);
514 CSPUTILSLOG(("CSP refined with policy: \"%s\"",
515 NS_ConvertUTF16toUTF8(policy
).get()));
521 /* ===== nsCSPSrc ============================ */
523 nsCSPBaseSrc::nsCSPBaseSrc() : mInvalidated(false) {}
525 nsCSPBaseSrc::~nsCSPBaseSrc() = default;
527 // ::permits is only called for external load requests, therefore:
528 // nsCSPKeywordSrc and nsCSPHashSource fall back to this base class
529 // implementation which will never allow the load.
530 bool nsCSPBaseSrc::permits(nsIURI
* aUri
, const nsAString
& aNonce
,
531 bool aWasRedirected
, bool aReportOnly
,
532 bool aUpgradeInsecure
, bool aParserCreated
) const {
533 if (CSPUTILSLOGENABLED()) {
535 ("nsCSPBaseSrc::permits, aUri: %s", aUri
->GetSpecOrDefault().get()));
540 // ::allows is only called for inlined loads, therefore:
541 // nsCSPSchemeSrc, nsCSPHostSrc fall back
542 // to this base class implementation which will never allow the load.
543 bool nsCSPBaseSrc::allows(enum CSPKeyword aKeyword
,
544 const nsAString
& aHashOrNonce
,
545 bool aParserCreated
) const {
546 CSPUTILSLOG(("nsCSPBaseSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
547 aKeyword
== CSP_HASH
? "hash" : CSP_EnumToUTF8Keyword(aKeyword
),
548 NS_ConvertUTF16toUTF8(aHashOrNonce
).get()));
552 /* ====== nsCSPSchemeSrc ===================== */
554 nsCSPSchemeSrc::nsCSPSchemeSrc(const nsAString
& aScheme
) : mScheme(aScheme
) {
555 ToLowerCase(mScheme
);
558 nsCSPSchemeSrc::~nsCSPSchemeSrc() = default;
560 bool nsCSPSchemeSrc::permits(nsIURI
* aUri
, const nsAString
& aNonce
,
561 bool aWasRedirected
, bool aReportOnly
,
562 bool aUpgradeInsecure
, bool aParserCreated
) const {
563 if (CSPUTILSLOGENABLED()) {
565 ("nsCSPSchemeSrc::permits, aUri: %s", aUri
->GetSpecOrDefault().get()));
567 MOZ_ASSERT((!mScheme
.EqualsASCII("")), "scheme can not be the empty string");
571 return permitsScheme(mScheme
, aUri
, aReportOnly
, aUpgradeInsecure
, false);
574 bool nsCSPSchemeSrc::visit(nsCSPSrcVisitor
* aVisitor
) const {
575 return aVisitor
->visitSchemeSrc(*this);
578 void nsCSPSchemeSrc::toString(nsAString
& outStr
) const {
579 outStr
.Append(mScheme
);
580 outStr
.AppendLiteral(":");
583 /* ===== nsCSPHostSrc ======================== */
585 nsCSPHostSrc::nsCSPHostSrc(const nsAString
& aHost
)
587 mGeneratedFromSelfKeyword(false),
588 mIsUniqueOrigin(false),
589 mWithinFrameAncstorsDir(false) {
593 nsCSPHostSrc::~nsCSPHostSrc() = default;
596 * Checks whether the current directive permits a specific port.
597 * @param aEnforcementScheme
598 * The scheme that this directive allows
599 * (used to query the default port for that scheme)
600 * @param aEnforcementPort
601 * The port that this directive allows
602 * @param aResourceURI
603 * The uri of the subresource load
605 bool permitsPort(const nsAString
& aEnforcementScheme
,
606 const nsAString
& aEnforcementPort
, nsIURI
* aResourceURI
) {
607 // If enforcement port is the wildcard, don't block the load.
608 if (aEnforcementPort
.EqualsASCII("*")) {
612 int32_t resourcePort
;
613 nsresult rv
= aResourceURI
->GetPort(&resourcePort
);
614 if (NS_FAILED(rv
) && aEnforcementPort
.IsEmpty()) {
615 // If we cannot get a Port (e.g. because of an Custom Protocol handler)
616 // We need to check if a default port is associated with the Scheme
617 if (aEnforcementScheme
.IsEmpty()) {
620 int defaultPortforScheme
=
621 NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme
).get());
623 // If there is no default port associated with the Scheme (
624 // defaultPortforScheme == -1) or it is an externally handled protocol (
625 // defaultPortforScheme == 0 ) and the csp does not enforce a port - we can
626 // allow not having a port
627 return (defaultPortforScheme
== -1 || defaultPortforScheme
== -0);
629 // Avoid unnecessary string creation/manipulation and don't block the
630 // load if the resource to be loaded uses the default port for that
631 // scheme and there is no port to be enforced.
632 // Note, this optimization relies on scheme checks within permitsScheme().
633 if (resourcePort
== DEFAULT_PORT
&& aEnforcementPort
.IsEmpty()) {
637 // By now we know at that either the resourcePort does not use the default
638 // port or there is a port restriction to be enforced. A port value of -1
639 // corresponds to the protocol's default port (eg. -1 implies port 80 for
640 // http URIs), in such a case we have to query the default port of the
641 // resource to be loaded.
642 if (resourcePort
== DEFAULT_PORT
) {
643 nsAutoCString resourceScheme
;
644 rv
= aResourceURI
->GetScheme(resourceScheme
);
645 NS_ENSURE_SUCCESS(rv
, false);
646 resourcePort
= NS_GetDefaultPort(resourceScheme
.get());
649 // If there is a port to be enforced and the ports match, then
650 // don't block the load.
651 nsString resourcePortStr
;
652 resourcePortStr
.AppendInt(resourcePort
);
653 if (aEnforcementPort
.Equals(resourcePortStr
)) {
657 // If there is no port to be enforced, query the default port for the load.
658 nsString
enforcementPort(aEnforcementPort
);
659 if (enforcementPort
.IsEmpty()) {
660 // For scheme less sources, our parser always generates a scheme
661 // which is the scheme of the protected resource.
662 MOZ_ASSERT(!aEnforcementScheme
.IsEmpty(),
663 "need a scheme to query default port");
664 int32_t defaultEnforcementPort
=
665 NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme
).get());
666 enforcementPort
.Truncate();
667 enforcementPort
.AppendInt(defaultEnforcementPort
);
670 // If default ports match, don't block the load
671 if (enforcementPort
.Equals(resourcePortStr
)) {
675 // Additional port matching where the regular URL matching algorithm
676 // treats insecure ports as matching their secure variants.
677 // default port for http is :80
678 // default port for https is :443
679 if (enforcementPort
.EqualsLiteral("80") &&
680 resourcePortStr
.EqualsLiteral("443")) {
684 // ports do not match, block the load.
688 bool nsCSPHostSrc::permits(nsIURI
* aUri
, const nsAString
& aNonce
,
689 bool aWasRedirected
, bool aReportOnly
,
690 bool aUpgradeInsecure
, bool aParserCreated
) const {
691 if (CSPUTILSLOGENABLED()) {
693 ("nsCSPHostSrc::permits, aUri: %s", aUri
->GetSpecOrDefault().get()));
696 if (mInvalidated
|| mIsUniqueOrigin
) {
700 // we are following the enforcement rules from the spec, see:
701 // http://www.w3.org/TR/CSP11/#match-source-expression
703 // 4.3) scheme matching: Check if the scheme matches.
704 if (!permitsScheme(mScheme
, aUri
, aReportOnly
, aUpgradeInsecure
,
705 mGeneratedFromSelfKeyword
)) {
709 // The host in nsCSpHostSrc should never be empty. In case we are enforcing
710 // just a specific scheme, the parser should generate a nsCSPSchemeSource.
711 NS_ASSERTION((!mHost
.IsEmpty()), "host can not be the empty string");
713 // Before we can check if the host matches, we have to
714 // extract the host part from aUri.
715 nsAutoCString uriHost
;
716 nsresult rv
= aUri
->GetAsciiHost(uriHost
);
717 NS_ENSURE_SUCCESS(rv
, false);
719 nsString decodedUriHost
;
720 CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriHost
), decodedUriHost
);
722 // 2) host matching: Enforce a single *
723 if (mHost
.EqualsASCII("*")) {
724 // The single ASTERISK character (*) does not match a URI's scheme of a type
725 // designating a globally unique identifier (such as blob:, data:, or
726 // filesystem:) At the moment firefox does not support filesystem; but for
727 // future compatibility we support it in CSP according to the spec,
728 // see: 4.2.2 Matching Source Expressions Note, that allowlisting any of
729 // these schemes would call nsCSPSchemeSrc::permits().
730 if (aUri
->SchemeIs("blob") || aUri
->SchemeIs("data") ||
731 aUri
->SchemeIs("filesystem")) {
735 // If no scheme is present there also wont be a port and folder to check
736 // which means we can return early
737 if (mScheme
.IsEmpty()) {
741 // 4.5) host matching: Check if the allowed host starts with a wilcard.
742 else if (mHost
.First() == '*') {
745 "Second character needs to be '.' whenever host starts with '*'");
747 // Eliminate leading "*", but keeping the FULL STOP (.) thereafter before
748 // checking if the remaining characters match
749 nsString wildCardHost
= mHost
;
750 wildCardHost
= Substring(wildCardHost
, 1, wildCardHost
.Length() - 1);
751 if (!StringEndsWith(decodedUriHost
, wildCardHost
)) {
755 // 4.6) host matching: Check if hosts match.
756 else if (!mHost
.Equals(decodedUriHost
)) {
760 // Port matching: Check if the ports match.
761 if (!permitsPort(mScheme
, mPort
, aUri
)) {
765 // 4.9) Path matching: If there is a path, we have to enforce
766 // path-level matching, unless the channel got redirected, see:
767 // http://www.w3.org/TR/CSP11/#source-list-paths-and-redirects
768 if (!aWasRedirected
&& !mPath
.IsEmpty()) {
769 // converting aUri into nsIURL so we can strip query and ref
770 // example.com/test#foo -> example.com/test
771 // example.com/test?val=foo -> example.com/test
772 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(aUri
);
774 NS_ASSERTION(false, "can't QI into nsIURI");
777 nsAutoCString uriPath
;
778 rv
= url
->GetFilePath(uriPath
);
779 NS_ENSURE_SUCCESS(rv
, false);
781 if (mWithinFrameAncstorsDir
) {
782 // no path matching for frame-ancestors to not leak any path information.
786 nsString decodedUriPath
;
787 CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriPath
), decodedUriPath
);
789 // check if the last character of mPath is '/'; if so
790 // we just have to check loading resource is within
792 if (mPath
.Last() == '/') {
793 if (!StringBeginsWith(decodedUriPath
, mPath
)) {
797 // otherwise mPath refers to a specific file, and we have to
798 // check if the loading resource matches the file.
800 if (!mPath
.Equals(decodedUriPath
)) {
806 // At the end: scheme, host, port and path match -> allow the load.
810 bool nsCSPHostSrc::visit(nsCSPSrcVisitor
* aVisitor
) const {
811 return aVisitor
->visitHostSrc(*this);
814 void nsCSPHostSrc::toString(nsAString
& outStr
) const {
815 if (mGeneratedFromSelfKeyword
) {
816 outStr
.AppendLiteral("'self'");
820 // If mHost is a single "*", we append the wildcard and return.
821 if (mHost
.EqualsASCII("*") && mScheme
.IsEmpty() && mPort
.IsEmpty()) {
822 outStr
.Append(mHost
);
827 outStr
.Append(mScheme
);
830 outStr
.AppendLiteral("://");
831 outStr
.Append(mHost
);
834 if (!mPort
.IsEmpty()) {
835 outStr
.AppendLiteral(":");
836 outStr
.Append(mPort
);
840 outStr
.Append(mPath
);
843 void nsCSPHostSrc::setScheme(const nsAString
& aScheme
) {
845 ToLowerCase(mScheme
);
848 void nsCSPHostSrc::setPort(const nsAString
& aPort
) { mPort
= aPort
; }
850 void nsCSPHostSrc::appendPath(const nsAString
& aPath
) { mPath
.Append(aPath
); }
852 /* ===== nsCSPKeywordSrc ===================== */
854 nsCSPKeywordSrc::nsCSPKeywordSrc(enum CSPKeyword aKeyword
)
855 : mKeyword(aKeyword
) {
856 NS_ASSERTION((aKeyword
!= CSP_SELF
),
857 "'self' should have been replaced in the parser");
860 nsCSPKeywordSrc::~nsCSPKeywordSrc() = default;
862 bool nsCSPKeywordSrc::permits(nsIURI
* aUri
, const nsAString
& aNonce
,
863 bool aWasRedirected
, bool aReportOnly
,
864 bool aUpgradeInsecure
,
865 bool aParserCreated
) const {
866 // no need to check for invalidated, this will always return false unless
867 // it is an nsCSPKeywordSrc for 'strict-dynamic', which should allow non
868 // parser created scripts.
869 return ((mKeyword
== CSP_STRICT_DYNAMIC
) && !aParserCreated
);
872 bool nsCSPKeywordSrc::allows(enum CSPKeyword aKeyword
,
873 const nsAString
& aHashOrNonce
,
874 bool aParserCreated
) const {
876 ("nsCSPKeywordSrc::allows, aKeyWord: %s, aHashOrNonce: %s, mInvalidated: "
878 CSP_EnumToUTF8Keyword(aKeyword
),
879 NS_ConvertUTF16toUTF8(aHashOrNonce
).get(),
880 mInvalidated
? "true" : "false"));
883 // only 'self', 'report-sample' and 'unsafe-inline' are keywords that can be
884 // ignored. Please note that the parser already translates 'self' into a uri
885 // (see assertion in constructor).
886 MOZ_ASSERT(mKeyword
== CSP_UNSAFE_INLINE
|| mKeyword
== CSP_REPORT_SAMPLE
,
887 "should only invalidate unsafe-inline");
890 // either the keyword allows the load or the policy contains 'strict-dynamic',
891 // in which case we have to make sure the script is not parser created before
892 // allowing the load and also eval & wasm-eval should be blocked even if
893 // 'strict-dynamic' is present. Should be allowed only if 'unsafe-eval' is
895 return ((mKeyword
== aKeyword
) ||
896 ((mKeyword
== CSP_STRICT_DYNAMIC
) && !aParserCreated
&&
897 aKeyword
!= CSP_UNSAFE_EVAL
&& aKeyword
!= CSP_WASM_UNSAFE_EVAL
));
900 bool nsCSPKeywordSrc::visit(nsCSPSrcVisitor
* aVisitor
) const {
901 return aVisitor
->visitKeywordSrc(*this);
904 void nsCSPKeywordSrc::toString(nsAString
& outStr
) const {
905 outStr
.Append(CSP_EnumToUTF16Keyword(mKeyword
));
908 /* ===== nsCSPNonceSrc ==================== */
910 nsCSPNonceSrc::nsCSPNonceSrc(const nsAString
& aNonce
) : mNonce(aNonce
) {}
912 nsCSPNonceSrc::~nsCSPNonceSrc() = default;
914 bool nsCSPNonceSrc::permits(nsIURI
* aUri
, const nsAString
& aNonce
,
915 bool aWasRedirected
, bool aReportOnly
,
916 bool aUpgradeInsecure
, bool aParserCreated
) const {
917 if (CSPUTILSLOGENABLED()) {
918 CSPUTILSLOG(("nsCSPNonceSrc::permits, aUri: %s, aNonce: %s",
919 aUri
->GetSpecOrDefault().get(),
920 NS_ConvertUTF16toUTF8(aNonce
).get()));
923 if (aReportOnly
&& aWasRedirected
&& aNonce
.IsEmpty()) {
924 /* Fix for Bug 1505412
925 * If we land here, we're currently handling a script-preload which got
926 * redirected. Preloads do not have any info about the nonce assiociated.
927 * Because of Report-Only the preload passes the 1st CSP-check so the
928 * preload does not get retried with a nonce attached.
929 * Currently we're relying on the script-manager to
930 * provide a fake loadinfo to check the preloads against csp.
931 * So during HTTPChannel->OnRedirect we cant check csp for this case.
932 * But as the script-manager already checked the csp,
933 * a report would already have been send,
934 * if the nonce didnt match.
935 * So we can pass the check here for Report-Only Cases.
937 MOZ_ASSERT(aParserCreated
== false,
938 "Skipping nonce-check is only allowed for Preloads");
942 // nonces can not be invalidated by strict-dynamic
943 return mNonce
.Equals(aNonce
);
946 bool nsCSPNonceSrc::allows(enum CSPKeyword aKeyword
,
947 const nsAString
& aHashOrNonce
,
948 bool aParserCreated
) const {
949 CSPUTILSLOG(("nsCSPNonceSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
950 CSP_EnumToUTF8Keyword(aKeyword
),
951 NS_ConvertUTF16toUTF8(aHashOrNonce
).get()));
953 if (aKeyword
!= CSP_NONCE
) {
956 // nonces can not be invalidated by strict-dynamic
957 return mNonce
.Equals(aHashOrNonce
);
960 bool nsCSPNonceSrc::visit(nsCSPSrcVisitor
* aVisitor
) const {
961 return aVisitor
->visitNonceSrc(*this);
964 void nsCSPNonceSrc::toString(nsAString
& outStr
) const {
965 outStr
.Append(CSP_EnumToUTF16Keyword(CSP_NONCE
));
966 outStr
.Append(mNonce
);
967 outStr
.AppendLiteral("'");
970 /* ===== nsCSPHashSrc ===================== */
972 nsCSPHashSrc::nsCSPHashSrc(const nsAString
& aAlgo
, const nsAString
& aHash
)
973 : mAlgorithm(aAlgo
), mHash(aHash
) {
974 // Only the algo should be rewritten to lowercase, the hash must remain the
976 ToLowerCase(mAlgorithm
);
977 // Normalize the base64url encoding to base64 encoding:
978 char16_t
* cur
= mHash
.BeginWriting();
979 char16_t
* end
= mHash
.EndWriting();
981 for (; cur
< end
; ++cur
) {
982 if (char16_t('-') == *cur
) {
983 *cur
= char16_t('+');
985 if (char16_t('_') == *cur
) {
986 *cur
= char16_t('/');
991 nsCSPHashSrc::~nsCSPHashSrc() = default;
993 bool nsCSPHashSrc::allows(enum CSPKeyword aKeyword
,
994 const nsAString
& aHashOrNonce
,
995 bool aParserCreated
) const {
996 CSPUTILSLOG(("nsCSPHashSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
997 CSP_EnumToUTF8Keyword(aKeyword
),
998 NS_ConvertUTF16toUTF8(aHashOrNonce
).get()));
1000 if (aKeyword
!= CSP_HASH
) {
1004 // hashes can not be invalidated by strict-dynamic
1006 // Convert aHashOrNonce to UTF-8
1007 NS_ConvertUTF16toUTF8
utf8_hash(aHashOrNonce
);
1009 nsCOMPtr
<nsICryptoHash
> hasher
;
1010 nsresult rv
= NS_NewCryptoHash(NS_ConvertUTF16toUTF8(mAlgorithm
),
1011 getter_AddRefs(hasher
));
1012 NS_ENSURE_SUCCESS(rv
, false);
1014 rv
= hasher
->Update((uint8_t*)utf8_hash
.get(), utf8_hash
.Length());
1015 NS_ENSURE_SUCCESS(rv
, false);
1018 rv
= hasher
->Finish(true, hash
);
1019 NS_ENSURE_SUCCESS(rv
, false);
1021 return NS_ConvertUTF16toUTF8(mHash
).Equals(hash
);
1024 bool nsCSPHashSrc::visit(nsCSPSrcVisitor
* aVisitor
) const {
1025 return aVisitor
->visitHashSrc(*this);
1028 void nsCSPHashSrc::toString(nsAString
& outStr
) const {
1029 outStr
.AppendLiteral("'");
1030 outStr
.Append(mAlgorithm
);
1031 outStr
.AppendLiteral("-");
1032 outStr
.Append(mHash
);
1033 outStr
.AppendLiteral("'");
1036 /* ===== nsCSPReportURI ===================== */
1038 nsCSPReportURI::nsCSPReportURI(nsIURI
* aURI
) : mReportURI(aURI
) {}
1040 nsCSPReportURI::~nsCSPReportURI() = default;
1042 bool nsCSPReportURI::visit(nsCSPSrcVisitor
* aVisitor
) const { return false; }
1044 void nsCSPReportURI::toString(nsAString
& outStr
) const {
1046 nsresult rv
= mReportURI
->GetSpec(spec
);
1047 if (NS_FAILED(rv
)) {
1050 outStr
.AppendASCII(spec
.get());
1053 /* ===== nsCSPSandboxFlags ===================== */
1055 nsCSPSandboxFlags::nsCSPSandboxFlags(const nsAString
& aFlags
) : mFlags(aFlags
) {
1056 ToLowerCase(mFlags
);
1059 nsCSPSandboxFlags::~nsCSPSandboxFlags() = default;
1061 bool nsCSPSandboxFlags::visit(nsCSPSrcVisitor
* aVisitor
) const { return false; }
1063 void nsCSPSandboxFlags::toString(nsAString
& outStr
) const {
1064 outStr
.Append(mFlags
);
1067 /* ===== nsCSPDirective ====================== */
1069 nsCSPDirective::nsCSPDirective(CSPDirective aDirective
) {
1070 mDirective
= aDirective
;
1073 nsCSPDirective::~nsCSPDirective() {
1074 for (uint32_t i
= 0; i
< mSrcs
.Length(); i
++) {
1079 bool nsCSPDirective::permits(nsIURI
* aUri
, const nsAString
& aNonce
,
1080 bool aWasRedirected
, bool aReportOnly
,
1081 bool aUpgradeInsecure
, bool aParserCreated
) const {
1082 if (CSPUTILSLOGENABLED()) {
1084 ("nsCSPDirective::permits, aUri: %s", aUri
->GetSpecOrDefault().get()));
1087 for (uint32_t i
= 0; i
< mSrcs
.Length(); i
++) {
1088 if (mSrcs
[i
]->permits(aUri
, aNonce
, aWasRedirected
, aReportOnly
,
1089 aUpgradeInsecure
, aParserCreated
)) {
1096 bool nsCSPDirective::allows(enum CSPKeyword aKeyword
,
1097 const nsAString
& aHashOrNonce
,
1098 bool aParserCreated
) const {
1099 CSPUTILSLOG(("nsCSPDirective::allows, aKeyWord: %s, a HashOrNonce: %s",
1100 CSP_EnumToUTF8Keyword(aKeyword
),
1101 NS_ConvertUTF16toUTF8(aHashOrNonce
).get()));
1103 for (uint32_t i
= 0; i
< mSrcs
.Length(); i
++) {
1104 if (mSrcs
[i
]->allows(aKeyword
, aHashOrNonce
, aParserCreated
)) {
1111 void nsCSPDirective::toString(nsAString
& outStr
) const {
1112 // Append directive name
1113 outStr
.AppendASCII(CSP_CSPDirectiveToString(mDirective
));
1114 outStr
.AppendLiteral(" ");
1117 StringJoinAppend(outStr
, u
" "_ns
, mSrcs
,
1118 [](nsAString
& dest
, nsCSPBaseSrc
* cspBaseSrc
) {
1119 cspBaseSrc
->toString(dest
);
1123 void nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP
& outCSP
) const {
1124 mozilla::dom::Sequence
<nsString
> srcs
;
1126 for (uint32_t i
= 0; i
< mSrcs
.Length(); i
++) {
1128 mSrcs
[i
]->toString(src
);
1129 if (!srcs
.AppendElement(src
, mozilla::fallible
)) {
1130 // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
1131 // involve multiple reallocations) and potentially crashing here,
1132 // SetCapacity could be called outside the loop once.
1133 mozalloc_handle_oom(0);
1137 switch (mDirective
) {
1138 case nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE
:
1139 outCSP
.mDefault_src
.Construct();
1140 outCSP
.mDefault_src
.Value() = std::move(srcs
);
1143 case nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE
:
1144 outCSP
.mScript_src
.Construct();
1145 outCSP
.mScript_src
.Value() = std::move(srcs
);
1148 case nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE
:
1149 outCSP
.mObject_src
.Construct();
1150 outCSP
.mObject_src
.Value() = std::move(srcs
);
1153 case nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE
:
1154 outCSP
.mStyle_src
.Construct();
1155 outCSP
.mStyle_src
.Value() = std::move(srcs
);
1158 case nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE
:
1159 outCSP
.mImg_src
.Construct();
1160 outCSP
.mImg_src
.Value() = std::move(srcs
);
1163 case nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE
:
1164 outCSP
.mMedia_src
.Construct();
1165 outCSP
.mMedia_src
.Value() = std::move(srcs
);
1168 case nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE
:
1169 outCSP
.mFrame_src
.Construct();
1170 outCSP
.mFrame_src
.Value() = std::move(srcs
);
1173 case nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE
:
1174 outCSP
.mFont_src
.Construct();
1175 outCSP
.mFont_src
.Value() = std::move(srcs
);
1178 case nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE
:
1179 outCSP
.mConnect_src
.Construct();
1180 outCSP
.mConnect_src
.Value() = std::move(srcs
);
1183 case nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE
:
1184 outCSP
.mReport_uri
.Construct();
1185 outCSP
.mReport_uri
.Value() = std::move(srcs
);
1188 case nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE
:
1189 outCSP
.mFrame_ancestors
.Construct();
1190 outCSP
.mFrame_ancestors
.Value() = std::move(srcs
);
1193 case nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE
:
1194 outCSP
.mManifest_src
.Construct();
1195 outCSP
.mManifest_src
.Value() = std::move(srcs
);
1197 // not supporting REFLECTED_XSS_DIRECTIVE
1199 case nsIContentSecurityPolicy::BASE_URI_DIRECTIVE
:
1200 outCSP
.mBase_uri
.Construct();
1201 outCSP
.mBase_uri
.Value() = std::move(srcs
);
1204 case nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE
:
1205 outCSP
.mForm_action
.Construct();
1206 outCSP
.mForm_action
.Value() = std::move(srcs
);
1209 case nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT
:
1210 outCSP
.mBlock_all_mixed_content
.Construct();
1211 // does not have any srcs
1214 case nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE
:
1215 outCSP
.mUpgrade_insecure_requests
.Construct();
1216 // does not have any srcs
1219 case nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE
:
1220 outCSP
.mChild_src
.Construct();
1221 outCSP
.mChild_src
.Value() = std::move(srcs
);
1224 case nsIContentSecurityPolicy::SANDBOX_DIRECTIVE
:
1225 outCSP
.mSandbox
.Construct();
1226 outCSP
.mSandbox
.Value() = std::move(srcs
);
1229 case nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE
:
1230 outCSP
.mWorker_src
.Construct();
1231 outCSP
.mWorker_src
.Value() = std::move(srcs
);
1234 case nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE
:
1235 outCSP
.mScript_src_elem
.Construct();
1236 outCSP
.mScript_src_elem
.Value() = std::move(srcs
);
1239 case nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE
:
1240 outCSP
.mScript_src_attr
.Construct();
1241 outCSP
.mScript_src_attr
.Value() = std::move(srcs
);
1245 NS_ASSERTION(false, "cannot find directive to convert CSP to JSON");
1249 void nsCSPDirective::getReportURIs(nsTArray
<nsString
>& outReportURIs
) const {
1250 NS_ASSERTION((mDirective
== nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE
),
1251 "not a report-uri directive");
1254 nsString tmpReportURI
;
1255 for (uint32_t i
= 0; i
< mSrcs
.Length(); i
++) {
1256 tmpReportURI
.Truncate();
1257 mSrcs
[i
]->toString(tmpReportURI
);
1258 outReportURIs
.AppendElement(tmpReportURI
);
1262 bool nsCSPDirective::visitSrcs(nsCSPSrcVisitor
* aVisitor
) const {
1263 for (uint32_t i
= 0; i
< mSrcs
.Length(); i
++) {
1264 if (!mSrcs
[i
]->visit(aVisitor
)) {
1271 bool nsCSPDirective::equals(CSPDirective aDirective
) const {
1272 return (mDirective
== aDirective
);
1275 void nsCSPDirective::getDirName(nsAString
& outStr
) const {
1276 outStr
.AppendASCII(CSP_CSPDirectiveToString(mDirective
));
1279 bool nsCSPDirective::hasReportSampleKeyword() const {
1280 for (nsCSPBaseSrc
* src
: mSrcs
) {
1281 if (src
->isReportSample()) {
1289 /* =============== nsCSPChildSrcDirective ============= */
1291 nsCSPChildSrcDirective::nsCSPChildSrcDirective(CSPDirective aDirective
)
1292 : nsCSPDirective(aDirective
),
1293 mRestrictFrames(false),
1294 mRestrictWorkers(false) {}
1296 nsCSPChildSrcDirective::~nsCSPChildSrcDirective() = default;
1298 bool nsCSPChildSrcDirective::equals(CSPDirective aDirective
) const {
1299 if (aDirective
== nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE
) {
1300 return mRestrictFrames
;
1302 if (aDirective
== nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE
) {
1303 return mRestrictWorkers
;
1305 return (mDirective
== aDirective
);
1308 /* =============== nsCSPScriptSrcDirective ============= */
1310 nsCSPScriptSrcDirective::nsCSPScriptSrcDirective(CSPDirective aDirective
)
1311 : nsCSPDirective(aDirective
) {}
1313 nsCSPScriptSrcDirective::~nsCSPScriptSrcDirective() = default;
1315 bool nsCSPScriptSrcDirective::equals(CSPDirective aDirective
) const {
1316 if (aDirective
== nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE
) {
1317 return mRestrictWorkers
;
1319 if (aDirective
== nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE
) {
1320 return mRestrictScriptElem
;
1322 if (aDirective
== nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE
) {
1323 return mRestrictScriptAttr
;
1325 return mDirective
== aDirective
;
1328 /* =============== nsCSPStyleSrcDirective ============= */
1330 nsCSPStyleSrcDirective::nsCSPStyleSrcDirective(CSPDirective aDirective
)
1331 : nsCSPDirective(aDirective
) {}
1333 nsCSPStyleSrcDirective::~nsCSPStyleSrcDirective() = default;
1335 bool nsCSPStyleSrcDirective::equals(CSPDirective aDirective
) const {
1336 if (aDirective
== nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE
) {
1337 return mRestrictStyleElem
;
1339 if (aDirective
== nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE
) {
1340 return mRestrictStyleAttr
;
1342 return mDirective
== aDirective
;
1345 /* =============== nsBlockAllMixedContentDirective ============= */
1347 nsBlockAllMixedContentDirective::nsBlockAllMixedContentDirective(
1348 CSPDirective aDirective
)
1349 : nsCSPDirective(aDirective
) {}
1351 nsBlockAllMixedContentDirective::~nsBlockAllMixedContentDirective() = default;
1353 void nsBlockAllMixedContentDirective::toString(nsAString
& outStr
) const {
1354 outStr
.AppendASCII(CSP_CSPDirectiveToString(
1355 nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT
));
1358 void nsBlockAllMixedContentDirective::getDirName(nsAString
& outStr
) const {
1359 outStr
.AppendASCII(CSP_CSPDirectiveToString(
1360 nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT
));
1363 /* =============== nsUpgradeInsecureDirective ============= */
1365 nsUpgradeInsecureDirective::nsUpgradeInsecureDirective(CSPDirective aDirective
)
1366 : nsCSPDirective(aDirective
) {}
1368 nsUpgradeInsecureDirective::~nsUpgradeInsecureDirective() = default;
1370 void nsUpgradeInsecureDirective::toString(nsAString
& outStr
) const {
1371 outStr
.AppendASCII(CSP_CSPDirectiveToString(
1372 nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE
));
1375 void nsUpgradeInsecureDirective::getDirName(nsAString
& outStr
) const {
1376 outStr
.AppendASCII(CSP_CSPDirectiveToString(
1377 nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE
));
1380 /* ===== nsCSPPolicy ========================= */
1382 nsCSPPolicy::nsCSPPolicy()
1383 : mUpgradeInsecDir(nullptr),
1385 mDeliveredViaMetaTag(false) {
1386 CSPUTILSLOG(("nsCSPPolicy::nsCSPPolicy"));
1389 nsCSPPolicy::~nsCSPPolicy() {
1390 CSPUTILSLOG(("nsCSPPolicy::~nsCSPPolicy"));
1392 for (uint32_t i
= 0; i
< mDirectives
.Length(); i
++) {
1393 delete mDirectives
[i
];
1397 bool nsCSPPolicy::permits(CSPDirective aDir
, nsIURI
* aUri
,
1398 const nsAString
& aNonce
, bool aWasRedirected
,
1399 bool aSpecific
, bool aParserCreated
,
1400 nsAString
& outViolatedDirective
) const {
1401 if (CSPUTILSLOGENABLED()) {
1402 CSPUTILSLOG(("nsCSPPolicy::permits, aUri: %s, aDir: %d, aSpecific: %s",
1403 aUri
->GetSpecOrDefault().get(), aDir
,
1404 aSpecific
? "true" : "false"));
1407 NS_ASSERTION(aUri
, "permits needs an uri to perform the check!");
1408 outViolatedDirective
.Truncate();
1410 nsCSPDirective
* defaultDir
= nullptr;
1412 // Try to find a relevant directive
1413 // These directive arrays are short (1-5 elements), not worth using a
1415 for (uint32_t i
= 0; i
< mDirectives
.Length(); i
++) {
1416 if (mDirectives
[i
]->equals(aDir
)) {
1417 if (!mDirectives
[i
]->permits(aUri
, aNonce
, aWasRedirected
, mReportOnly
,
1418 mUpgradeInsecDir
, aParserCreated
)) {
1419 mDirectives
[i
]->getDirName(outViolatedDirective
);
1424 if (mDirectives
[i
]->isDefaultDirective()) {
1425 defaultDir
= mDirectives
[i
];
1429 // If the above loop runs through, we haven't found a matching directive.
1430 // Avoid relooping, just store the result of default-src while looping.
1431 if (!aSpecific
&& defaultDir
) {
1432 if (!defaultDir
->permits(aUri
, aNonce
, aWasRedirected
, mReportOnly
,
1433 mUpgradeInsecDir
, aParserCreated
)) {
1434 defaultDir
->getDirName(outViolatedDirective
);
1440 // Nothing restricts this, so we're allowing the load
1445 bool nsCSPPolicy::allows(CSPDirective aDirective
, enum CSPKeyword aKeyword
,
1446 const nsAString
& aHashOrNonce
,
1447 bool aParserCreated
) const {
1448 CSPUTILSLOG(("nsCSPPolicy::allows, aKeyWord: %s, a HashOrNonce: %s",
1449 CSP_EnumToUTF8Keyword(aKeyword
),
1450 NS_ConvertUTF16toUTF8(aHashOrNonce
).get()));
1452 nsCSPDirective
* defaultDir
= nullptr;
1454 // Try to find a matching directive
1455 for (uint32_t i
= 0; i
< mDirectives
.Length(); i
++) {
1456 if (mDirectives
[i
]->isDefaultDirective()) {
1457 defaultDir
= mDirectives
[i
];
1460 if (mDirectives
[i
]->equals(aDirective
)) {
1461 if (mDirectives
[i
]->allows(aKeyword
, aHashOrNonce
, aParserCreated
)) {
1468 // If the above loop runs through, we haven't found a matching directive.
1469 // Avoid relooping, just store the result of default-src while looping.
1471 return defaultDir
->allows(aKeyword
, aHashOrNonce
, aParserCreated
);
1474 // Allowing the load; see Bug 885433
1475 // a) inline scripts (also unsafe eval) should only be blocked
1476 // if there is a [script-src] or [default-src]
1477 // b) inline styles should only be blocked
1478 // if there is a [style-src] or [default-src]
1482 void nsCSPPolicy::toString(nsAString
& outStr
) const {
1483 StringJoinAppend(outStr
, u
"; "_ns
, mDirectives
,
1484 [](nsAString
& dest
, nsCSPDirective
* cspDirective
) {
1485 cspDirective
->toString(dest
);
1489 void nsCSPPolicy::toDomCSPStruct(mozilla::dom::CSP
& outCSP
) const {
1490 outCSP
.mReport_only
= mReportOnly
;
1492 for (uint32_t i
= 0; i
< mDirectives
.Length(); ++i
) {
1493 mDirectives
[i
]->toDomCSPStruct(outCSP
);
1497 bool nsCSPPolicy::hasDirective(CSPDirective aDir
) const {
1498 for (uint32_t i
= 0; i
< mDirectives
.Length(); i
++) {
1499 if (mDirectives
[i
]->equals(aDir
)) {
1506 bool nsCSPPolicy::allowsNavigateTo(nsIURI
* aURI
, bool aWasRedirected
,
1507 bool aEnforceAllowlist
) const {
1508 bool allowsNavigateTo
= true;
1510 for (unsigned long i
= 0; i
< mDirectives
.Length(); i
++) {
1511 if (mDirectives
[i
]->equals(
1512 nsIContentSecurityPolicy::NAVIGATE_TO_DIRECTIVE
)) {
1513 // Early return if we can skip the allowlist AND 'unsafe-allow-redirects'
1515 if (!aEnforceAllowlist
&&
1516 mDirectives
[i
]->allows(CSP_UNSAFE_ALLOW_REDIRECTS
, u
""_ns
, false)) {
1519 // Otherwise, check against the allowlist.
1520 if (!mDirectives
[i
]->permits(aURI
, u
""_ns
, aWasRedirected
, false, false,
1522 allowsNavigateTo
= false;
1527 return allowsNavigateTo
;
1531 * Use this function only after ::allows() returned 'false'. Most and
1532 * foremost it's used to get the violated directive before sending reports.
1533 * The parameter outDirective is the equivalent of 'outViolatedDirective'
1534 * for the ::permits() function family.
1536 void nsCSPPolicy::getDirectiveStringAndReportSampleForContentType(
1537 CSPDirective aDirective
, nsAString
& outDirective
,
1538 bool* aReportSample
) const {
1539 MOZ_ASSERT(aReportSample
);
1540 *aReportSample
= false;
1542 nsCSPDirective
* defaultDir
= nullptr;
1543 for (uint32_t i
= 0; i
< mDirectives
.Length(); i
++) {
1544 if (mDirectives
[i
]->isDefaultDirective()) {
1545 defaultDir
= mDirectives
[i
];
1548 if (mDirectives
[i
]->equals(aDirective
)) {
1549 mDirectives
[i
]->getDirName(outDirective
);
1550 *aReportSample
= mDirectives
[i
]->hasReportSampleKeyword();
1554 // if we haven't found a matching directive yet,
1555 // the contentType must be restricted by the default directive
1557 defaultDir
->getDirName(outDirective
);
1558 *aReportSample
= defaultDir
->hasReportSampleKeyword();
1561 NS_ASSERTION(false, "Can not query directive string for contentType!");
1562 outDirective
.AppendLiteral("couldNotQueryViolatedDirective");
1565 void nsCSPPolicy::getDirectiveAsString(CSPDirective aDir
,
1566 nsAString
& outDirective
) const {
1567 for (uint32_t i
= 0; i
< mDirectives
.Length(); i
++) {
1568 if (mDirectives
[i
]->equals(aDir
)) {
1569 mDirectives
[i
]->toString(outDirective
);
1576 * Helper function that returns the underlying bit representation of sandbox
1577 * flags. The function returns SANDBOXED_NONE if there are no sandbox
1580 uint32_t nsCSPPolicy::getSandboxFlags() const {
1581 for (uint32_t i
= 0; i
< mDirectives
.Length(); i
++) {
1582 if (mDirectives
[i
]->equals(nsIContentSecurityPolicy::SANDBOX_DIRECTIVE
)) {
1584 mDirectives
[i
]->toString(flags
);
1586 if (flags
.IsEmpty()) {
1587 return SANDBOX_ALL_FLAGS
;
1591 attr
.ParseAtomArray(flags
);
1593 return nsContentUtils::ParseSandboxAttributeToFlags(&attr
);
1597 return SANDBOXED_NONE
;
1600 void nsCSPPolicy::getReportURIs(nsTArray
<nsString
>& outReportURIs
) const {
1601 for (uint32_t i
= 0; i
< mDirectives
.Length(); i
++) {
1602 if (mDirectives
[i
]->equals(
1603 nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE
)) {
1604 mDirectives
[i
]->getReportURIs(outReportURIs
);
1610 bool nsCSPPolicy::visitDirectiveSrcs(CSPDirective aDir
,
1611 nsCSPSrcVisitor
* aVisitor
) const {
1612 for (uint32_t i
= 0; i
< mDirectives
.Length(); i
++) {
1613 if (mDirectives
[i
]->equals(aDir
)) {
1614 return mDirectives
[i
]->visitSrcs(aVisitor
);