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 "mozilla/ArrayUtils.h"
8 #include "mozilla/TextUtils.h"
9 #include "mozilla/dom/Document.h"
10 #include "mozilla/Preferences.h"
11 #include "mozilla/StaticPrefs_dom.h"
12 #include "mozilla/StaticPrefs_security.h"
14 #include "nsContentUtils.h"
15 #include "nsCSPParser.h"
16 #include "nsCSPUtils.h"
17 #include "nsIScriptError.h"
18 #include "nsNetUtil.h"
19 #include "nsReadableUtils.h"
20 #include "nsServiceManagerUtils.h"
21 #include "nsUnicharUtils.h"
26 using namespace mozilla
;
27 using namespace mozilla::dom
;
29 static LogModule
* GetCspParserLog() {
30 static LazyLogModule
gCspParserPRLog("CSPParser");
31 return gCspParserPRLog
;
34 #define CSPPARSERLOG(args) \
35 MOZ_LOG(GetCspParserLog(), mozilla::LogLevel::Debug, args)
36 #define CSPPARSERLOGENABLED() \
37 MOZ_LOG_TEST(GetCspParserLog(), mozilla::LogLevel::Debug)
39 static const uint32_t kSubHostPathCharacterCutoff
= 512;
41 static const char* const kHashSourceValidFns
[] = {"sha256", "sha384", "sha512"};
42 static const uint32_t kHashSourceValidFnsLen
= 3;
44 /* ===== nsCSPParser ==================== */
46 nsCSPParser::nsCSPParser(policyTokens
& aTokens
, nsIURI
* aSelfURI
,
47 nsCSPContext
* aCSPContext
, bool aDeliveredViaMetaTag
,
48 bool aSuppressLogMessages
)
51 mHasHashOrNonce(false),
52 mHasAnyUnsafeEval(false),
53 mStrictDynamic(false),
54 mUnsafeInlineKeywordSrc(nullptr),
60 mParsingFrameAncestorsDir(false),
61 mTokens(aTokens
.Clone()),
64 mCSPContext(aCSPContext
),
65 mDeliveredViaMetaTag(aDeliveredViaMetaTag
),
66 mSuppressLogMessages(aSuppressLogMessages
) {
67 CSPPARSERLOG(("nsCSPParser::nsCSPParser"));
70 nsCSPParser::~nsCSPParser() { CSPPARSERLOG(("nsCSPParser::~nsCSPParser")); }
72 static bool isCharacterToken(char16_t aSymbol
) {
73 return (aSymbol
>= 'a' && aSymbol
<= 'z') ||
74 (aSymbol
>= 'A' && aSymbol
<= 'Z');
77 bool isNumberToken(char16_t aSymbol
) {
78 return (aSymbol
>= '0' && aSymbol
<= '9');
81 bool isValidHexDig(char16_t aHexDig
) {
82 return (isNumberToken(aHexDig
) || (aHexDig
>= 'A' && aHexDig
<= 'F') ||
83 (aHexDig
>= 'a' && aHexDig
<= 'f'));
86 static bool isValidBase64Value(const char16_t
* cur
, const char16_t
* end
) {
88 // https://w3c.github.io/webappsec-csp/#grammardef-nonce-source
90 // May end with one or two =
91 if (end
> cur
&& *(end
- 1) == EQUALS
) end
--;
92 if (end
> cur
&& *(end
- 1) == EQUALS
) end
--;
94 // Must have at least one character aside from any =
99 // Rest must all be A-Za-z0-9+/-_
100 for (; cur
< end
; ++cur
) {
101 if (!(isCharacterToken(*cur
) || isNumberToken(*cur
) || *cur
== PLUS
||
102 *cur
== SLASH
|| *cur
== DASH
|| *cur
== UNDERLINE
)) {
110 void nsCSPParser::resetCurChar(const nsAString
& aToken
) {
111 mCurChar
= aToken
.BeginReading();
112 mEndChar
= aToken
.EndReading();
116 // The path is terminated by the first question mark ("?") or
117 // number sign ("#") character, or by the end of the URI.
118 // http://tools.ietf.org/html/rfc3986#section-3.3
119 bool nsCSPParser::atEndOfPath() {
120 return (atEnd() || peek(QUESTIONMARK
) || peek(NUMBER_SIGN
));
123 // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
124 bool nsCSPParser::atValidUnreservedChar() {
125 return (peek(isCharacterToken
) || peek(isNumberToken
) || peek(DASH
) ||
126 peek(DOT
) || peek(UNDERLINE
) || peek(TILDE
));
129 // sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
130 // / "*" / "+" / "," / ";" / "="
131 // Please note that even though ',' and ';' appear to be
132 // valid sub-delims according to the RFC production of paths,
133 // both can not appear here by itself, they would need to be
134 // pct-encoded in order to be part of the path.
135 bool nsCSPParser::atValidSubDelimChar() {
136 return (peek(EXCLAMATION
) || peek(DOLLAR
) || peek(AMPERSAND
) ||
137 peek(SINGLEQUOTE
) || peek(OPENBRACE
) || peek(CLOSINGBRACE
) ||
138 peek(WILDCARD
) || peek(PLUS
) || peek(EQUALS
));
141 // pct-encoded = "%" HEXDIG HEXDIG
142 bool nsCSPParser::atValidPctEncodedChar() {
143 const char16_t
* pctCurChar
= mCurChar
;
145 if ((pctCurChar
+ 2) >= mEndChar
) {
146 // string too short, can't be a valid pct-encoded char.
150 // Any valid pct-encoding must follow the following format:
152 if (PERCENT_SIGN
!= *pctCurChar
|| !isValidHexDig(*(pctCurChar
+ 1)) ||
153 !isValidHexDig(*(pctCurChar
+ 2))) {
159 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
160 // http://tools.ietf.org/html/rfc3986#section-3.3
161 bool nsCSPParser::atValidPathChar() {
162 return (atValidUnreservedChar() || atValidSubDelimChar() ||
163 atValidPctEncodedChar() || peek(COLON
) || peek(ATSYMBOL
));
166 void nsCSPParser::logWarningErrorToConsole(uint32_t aSeverityFlag
,
167 const char* aProperty
,
168 const nsTArray
<nsString
>& aParams
) {
169 CSPPARSERLOG(("nsCSPParser::logWarningErrorToConsole: %s", aProperty
));
171 if (mSuppressLogMessages
) {
175 // send console messages off to the context and let the context
176 // deal with it (potentially messages need to be queued up)
177 mCSPContext
->logToConsole(aProperty
, aParams
,
178 u
""_ns
, // aSourceName
179 u
""_ns
, // aSourceLine
182 aSeverityFlag
); // aFlags
185 bool nsCSPParser::hostChar() {
189 return accept(isCharacterToken
) || accept(isNumberToken
) || accept(DASH
);
192 // (ALPHA / DIGIT / "+" / "-" / "." )
193 bool nsCSPParser::schemeChar() {
197 return accept(isCharacterToken
) || accept(isNumberToken
) || accept(PLUS
) ||
198 accept(DASH
) || accept(DOT
);
201 // port = ":" ( 1*DIGIT / "*" )
202 bool nsCSPParser::port() {
203 CSPPARSERLOG(("nsCSPParser::port, mCurToken: %s, mCurValue: %s",
204 NS_ConvertUTF16toUTF8(mCurToken
).get(),
205 NS_ConvertUTF16toUTF8(mCurValue
).get()));
207 // Consume the COLON we just peeked at in houstSource
210 // Resetting current value since we start to parse a port now.
211 // e.g; "http://www.example.com:8888" then we have already parsed
212 // everything up to (including) ":";
216 if (accept(WILDCARD
)) {
220 // Port must start with a number
221 if (!accept(isNumberToken
)) {
222 AutoTArray
<nsString
, 1> params
= {mCurToken
};
223 logWarningErrorToConsole(nsIScriptError::warningFlag
, "couldntParsePort",
227 // Consume more numbers and set parsed port to the nsCSPHost
228 while (accept(isNumberToken
)) { /* consume */
233 bool nsCSPParser::subPath(nsCSPHostSrc
* aCspHost
) {
234 CSPPARSERLOG(("nsCSPParser::subPath, mCurToken: %s, mCurValue: %s",
235 NS_ConvertUTF16toUTF8(mCurToken
).get(),
236 NS_ConvertUTF16toUTF8(mCurValue
).get()));
238 // Emergency exit to avoid endless loops in case a path in a CSP policy
239 // is longer than 512 characters, or also to avoid endless loops
240 // in case we are parsing unrecognized characters in the following loop.
241 uint32_t charCounter
= 0;
242 nsString pctDecodedSubPath
;
244 while (!atEndOfPath()) {
246 // before appendig any additional portion of a subpath we have to
247 // pct-decode that portion of the subpath. atValidPathChar() already
248 // verified a correct pct-encoding, now we can safely decode and append
249 // the decoded-sub path.
250 CSP_PercentDecodeStr(mCurValue
, pctDecodedSubPath
);
251 aCspHost
->appendPath(pctDecodedSubPath
);
252 // Resetting current value since we are appending parts of the path
253 // to aCspHost, e.g; "http://www.example.com/path1/path2" then the
254 // first part is "/path1", second part "/path2"
256 } else if (!atValidPathChar()) {
257 AutoTArray
<nsString
, 1> params
= {mCurToken
};
258 logWarningErrorToConsole(nsIScriptError::warningFlag
,
259 "couldntParseInvalidSource", params
);
262 // potentially we have encountred a valid pct-encoded character in
263 // atValidPathChar(); if so, we have to account for "% HEXDIG HEXDIG" and
264 // advance the pointer past the pct-encoded char.
265 if (peek(PERCENT_SIGN
)) {
270 if (++charCounter
> kSubHostPathCharacterCutoff
) {
274 // before appendig any additional portion of a subpath we have to pct-decode
275 // that portion of the subpath. atValidPathChar() already verified a correct
276 // pct-encoding, now we can safely decode and append the decoded-sub path.
277 CSP_PercentDecodeStr(mCurValue
, pctDecodedSubPath
);
278 aCspHost
->appendPath(pctDecodedSubPath
);
283 bool nsCSPParser::path(nsCSPHostSrc
* aCspHost
) {
284 CSPPARSERLOG(("nsCSPParser::path, mCurToken: %s, mCurValue: %s",
285 NS_ConvertUTF16toUTF8(mCurToken
).get(),
286 NS_ConvertUTF16toUTF8(mCurValue
).get()));
288 // Resetting current value and forgetting everything we have parsed so far
289 // e.g. parsing "http://www.example.com/path1/path2", then
290 // "http://www.example.com" has already been parsed so far
294 if (!accept(SLASH
)) {
295 AutoTArray
<nsString
, 1> params
= {mCurToken
};
296 logWarningErrorToConsole(nsIScriptError::warningFlag
,
297 "couldntParseInvalidSource", params
);
301 // one slash right after host [port] is also considered a path, e.g.
302 // www.example.com/ should result in www.example.com/
303 // please note that we do not have to perform any pct-decoding here
304 // because we are just appending a '/' and not any actual chars.
305 aCspHost
->appendPath(u
"/"_ns
);
308 // path can begin with "/" but not "//"
309 // see http://tools.ietf.org/html/rfc3986#section-3.3
311 AutoTArray
<nsString
, 1> params
= {mCurToken
};
312 logWarningErrorToConsole(nsIScriptError::warningFlag
,
313 "couldntParseInvalidSource", params
);
316 return subPath(aCspHost
);
319 bool nsCSPParser::subHost() {
320 CSPPARSERLOG(("nsCSPParser::subHost, mCurToken: %s, mCurValue: %s",
321 NS_ConvertUTF16toUTF8(mCurToken
).get(),
322 NS_ConvertUTF16toUTF8(mCurValue
).get()));
324 // Emergency exit to avoid endless loops in case a host in a CSP policy
325 // is longer than 512 characters, or also to avoid endless loops
326 // in case we are parsing unrecognized characters in the following loop.
327 uint32_t charCounter
= 0;
329 while (!atEndOfPath() && !peek(COLON
) && !peek(SLASH
)) {
335 if (accept(DOT
) && !hostChar()) {
338 if (charCounter
> kSubHostPathCharacterCutoff
) {
345 // host = "*" / [ "*." ] 1*host-char *( "." 1*host-char )
346 nsCSPHostSrc
* nsCSPParser::host() {
347 CSPPARSERLOG(("nsCSPParser::host, mCurToken: %s, mCurValue: %s",
348 NS_ConvertUTF16toUTF8(mCurToken
).get(),
349 NS_ConvertUTF16toUTF8(mCurValue
).get()));
351 // Check if the token starts with "*"; please remember that we handle
352 // a single "*" as host in sourceExpression, but we still have to handle
353 // the case where a scheme was defined, e.g., as:
354 // "https://*", "*.example.com", "*:*", etc.
355 if (accept(WILDCARD
)) {
356 // Might solely be the wildcard
357 if (atEnd() || peek(COLON
)) {
358 return new nsCSPHostSrc(mCurValue
);
360 // If the token is not only the "*", a "." must follow right after
362 AutoTArray
<nsString
, 1> params
= {mCurToken
};
363 logWarningErrorToConsole(nsIScriptError::warningFlag
,
364 "couldntParseInvalidHost", params
);
369 // Expecting at least one host-char
371 AutoTArray
<nsString
, 1> params
= {mCurToken
};
372 logWarningErrorToConsole(nsIScriptError::warningFlag
,
373 "couldntParseInvalidHost", params
);
377 // There might be several sub hosts defined.
379 AutoTArray
<nsString
, 1> params
= {mCurToken
};
380 logWarningErrorToConsole(nsIScriptError::warningFlag
,
381 "couldntParseInvalidHost", params
);
385 // HostName might match a keyword, log to the console.
386 if (CSP_IsQuotelessKeyword(mCurValue
)) {
387 nsString keyword
= mCurValue
;
388 ToLowerCase(keyword
);
389 AutoTArray
<nsString
, 2> params
= {mCurToken
, keyword
};
390 logWarningErrorToConsole(nsIScriptError::warningFlag
,
391 "hostNameMightBeKeyword", params
);
394 // Create a new nsCSPHostSrc with the parsed host.
395 return new nsCSPHostSrc(mCurValue
);
398 // keyword-source = "'self'" / "'unsafe-inline'" / "'unsafe-eval'" /
399 // "'wasm-unsafe-eval'"
400 nsCSPBaseSrc
* nsCSPParser::keywordSource() {
401 CSPPARSERLOG(("nsCSPParser::keywordSource, mCurToken: %s, mCurValue: %s",
402 NS_ConvertUTF16toUTF8(mCurToken
).get(),
403 NS_ConvertUTF16toUTF8(mCurValue
).get()));
405 // Special case handling for 'self' which is not stored internally as a
406 // keyword, but rather creates a nsCSPHostSrc using the selfURI
407 if (CSP_IsKeyword(mCurToken
, CSP_SELF
)) {
408 return CSP_CreateHostSrcFromSelfURI(mSelfURI
);
411 if (CSP_IsKeyword(mCurToken
, CSP_REPORT_SAMPLE
)) {
412 return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken
));
415 if (CSP_IsKeyword(mCurToken
, CSP_STRICT_DYNAMIC
)) {
416 if (!CSP_IsDirective(mCurDir
[0],
417 nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE
) &&
418 !CSP_IsDirective(mCurDir
[0],
419 nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE
) &&
420 !CSP_IsDirective(mCurDir
[0],
421 nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE
) &&
422 !CSP_IsDirective(mCurDir
[0],
423 nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE
)) {
424 AutoTArray
<nsString
, 1> params
= {u
"strict-dynamic"_ns
};
425 logWarningErrorToConsole(nsIScriptError::warningFlag
,
426 "ignoringStrictDynamic", params
);
429 mStrictDynamic
= true;
430 return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken
));
433 if (CSP_IsKeyword(mCurToken
, CSP_UNSAFE_INLINE
)) {
434 // make sure script-src only contains 'unsafe-inline' once;
435 // ignore duplicates and log warning
436 if (mUnsafeInlineKeywordSrc
) {
437 AutoTArray
<nsString
, 1> params
= {mCurToken
};
438 logWarningErrorToConsole(nsIScriptError::warningFlag
,
439 "ignoringDuplicateSrc", params
);
442 // cache if we encounter 'unsafe-inline' so we can invalidate (ignore) it in
443 // case that script-src directive also contains hash- or nonce-.
444 mUnsafeInlineKeywordSrc
=
445 new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken
));
446 return mUnsafeInlineKeywordSrc
;
449 if (CSP_IsKeyword(mCurToken
, CSP_UNSAFE_EVAL
)) {
450 mHasAnyUnsafeEval
= true;
451 return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken
));
454 if (CSP_IsKeyword(mCurToken
, CSP_WASM_UNSAFE_EVAL
)) {
455 mHasAnyUnsafeEval
= true;
456 return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken
));
459 if (CSP_IsKeyword(mCurToken
, CSP_UNSAFE_HASHES
)) {
460 return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken
));
466 // host-source = [ scheme "://" ] host [ port ] [ path ]
467 nsCSPHostSrc
* nsCSPParser::hostSource() {
468 CSPPARSERLOG(("nsCSPParser::hostSource, mCurToken: %s, mCurValue: %s",
469 NS_ConvertUTF16toUTF8(mCurToken
).get(),
470 NS_ConvertUTF16toUTF8(mCurValue
).get()));
472 nsCSPHostSrc
* cspHost
= host();
474 // Error was reported in host()
478 // Calling port() to see if there is a port to parse, if an error
479 // occurs, port() reports the error, if port() returns true;
480 // we have a valid port, so we add it to cspHost.
486 cspHost
->setPort(mCurValue
);
493 // Calling path() to see if there is a path to parse, if an error
494 // occurs, path() reports the error; handing cspHost as an argument
495 // which simplifies parsing of several paths.
496 if (!path(cspHost
)) {
497 // If the host [port] is followed by a path, it has to be a valid path,
498 // otherwise we pass the nullptr, indicating an error, up the callstack.
499 // see also http://www.w3.org/TR/CSP11/#source-list
506 // scheme-source = scheme ":"
507 nsCSPSchemeSrc
* nsCSPParser::schemeSource() {
508 CSPPARSERLOG(("nsCSPParser::schemeSource, mCurToken: %s, mCurValue: %s",
509 NS_ConvertUTF16toUTF8(mCurToken
).get(),
510 NS_ConvertUTF16toUTF8(mCurValue
).get()));
512 if (!accept(isCharacterToken
)) {
515 while (schemeChar()) { /* consume */
517 nsString scheme
= mCurValue
;
519 // If the potential scheme is not followed by ":" - it's not a scheme
520 if (!accept(COLON
)) {
524 // If the chraracter following the ":" is a number or the "*"
525 // then we are not parsing a scheme; but rather a host;
526 if (peek(isNumberToken
) || peek(WILDCARD
)) {
530 return new nsCSPSchemeSrc(scheme
);
533 // nonce-source = "'nonce-" nonce-value "'"
534 nsCSPNonceSrc
* nsCSPParser::nonceSource() {
535 CSPPARSERLOG(("nsCSPParser::nonceSource, mCurToken: %s, mCurValue: %s",
536 NS_ConvertUTF16toUTF8(mCurToken
).get(),
537 NS_ConvertUTF16toUTF8(mCurValue
).get()));
539 // Check if mCurToken begins with "'nonce-" and ends with "'"
540 if (!StringBeginsWith(mCurToken
,
541 nsDependentString(CSP_EnumToUTF16Keyword(CSP_NONCE
)),
542 nsASCIICaseInsensitiveStringComparator
) ||
543 mCurToken
.Last() != SINGLEQUOTE
) {
547 // Trim surrounding single quotes
548 const nsAString
& expr
= Substring(mCurToken
, 1, mCurToken
.Length() - 2);
550 int32_t dashIndex
= expr
.FindChar(DASH
);
554 if (!isValidBase64Value(expr
.BeginReading() + dashIndex
+ 1,
555 expr
.EndReading())) {
559 // cache if encountering hash or nonce to invalidate unsafe-inline
560 mHasHashOrNonce
= true;
561 return new nsCSPNonceSrc(
562 Substring(expr
, dashIndex
+ 1, expr
.Length() - dashIndex
+ 1));
565 // hash-source = "'" hash-algo "-" base64-value "'"
566 nsCSPHashSrc
* nsCSPParser::hashSource() {
567 CSPPARSERLOG(("nsCSPParser::hashSource, mCurToken: %s, mCurValue: %s",
568 NS_ConvertUTF16toUTF8(mCurToken
).get(),
569 NS_ConvertUTF16toUTF8(mCurValue
).get()));
571 // Check if mCurToken starts and ends with "'"
572 if (mCurToken
.First() != SINGLEQUOTE
|| mCurToken
.Last() != SINGLEQUOTE
) {
576 // Trim surrounding single quotes
577 const nsAString
& expr
= Substring(mCurToken
, 1, mCurToken
.Length() - 2);
579 int32_t dashIndex
= expr
.FindChar(DASH
);
584 if (!isValidBase64Value(expr
.BeginReading() + dashIndex
+ 1,
585 expr
.EndReading())) {
589 nsAutoString
algo(Substring(expr
, 0, dashIndex
));
591 Substring(expr
, dashIndex
+ 1, expr
.Length() - dashIndex
+ 1));
593 for (uint32_t i
= 0; i
< kHashSourceValidFnsLen
; i
++) {
594 if (algo
.LowerCaseEqualsASCII(kHashSourceValidFns
[i
])) {
595 // cache if encountering hash or nonce to invalidate unsafe-inline
596 mHasHashOrNonce
= true;
597 return new nsCSPHashSrc(algo
, hash
);
603 // source-expression = scheme-source / host-source / keyword-source
604 // / nonce-source / hash-source
605 nsCSPBaseSrc
* nsCSPParser::sourceExpression() {
606 CSPPARSERLOG(("nsCSPParser::sourceExpression, mCurToken: %s, mCurValue: %s",
607 NS_ConvertUTF16toUTF8(mCurToken
).get(),
608 NS_ConvertUTF16toUTF8(mCurValue
).get()));
610 // Check if it is a keyword
611 if (nsCSPBaseSrc
* cspKeyword
= keywordSource()) {
615 // Check if it is a nonce-source
616 if (nsCSPNonceSrc
* cspNonce
= nonceSource()) {
620 // Check if it is a hash-source
621 if (nsCSPHashSrc
* cspHash
= hashSource()) {
625 // We handle a single "*" as host here, to avoid any confusion when applying
626 // the default scheme. However, we still would need to apply the default
627 // scheme in case we would parse "*:80".
628 if (mCurToken
.EqualsASCII("*")) {
629 return new nsCSPHostSrc(u
"*"_ns
);
632 // Calling resetCurChar allows us to use mCurChar and mEndChar
633 // to parse mCurToken; e.g. mCurToken = "http://www.example.com", then
635 // mEndChar = points just after the last 'm'
637 resetCurChar(mCurToken
);
639 // Check if mCurToken starts with a scheme
640 nsAutoString parsedScheme
;
641 if (nsCSPSchemeSrc
* cspScheme
= schemeSource()) {
642 // mCurToken might only enforce a specific scheme
646 // If something follows the scheme, we do not create
647 // a nsCSPSchemeSrc, but rather a nsCSPHostSrc, which
648 // needs to know the scheme to enforce; remember the
649 // scheme and delete cspScheme;
650 cspScheme
->toString(parsedScheme
);
651 parsedScheme
.Trim(":", false, true);
654 // If mCurToken provides not only a scheme, but also a host, we have to
655 // check if two slashes follow the scheme.
656 if (!accept(SLASH
) || !accept(SLASH
)) {
657 AutoTArray
<nsString
, 1> params
= {mCurToken
};
658 logWarningErrorToConsole(nsIScriptError::warningFlag
,
659 "failedToParseUnrecognizedSource", params
);
664 // Calling resetCurValue allows us to keep pointers for mCurChar and mEndChar
665 // alive, but resets mCurValue; e.g. mCurToken = "http://www.example.com",
666 // then mCurChar = 'w' mEndChar = 'm' mCurValue = ""
669 // If mCurToken does not provide a scheme (scheme-less source), we apply the
670 // scheme from selfURI
671 if (parsedScheme
.IsEmpty()) {
672 // Resetting internal helpers, because we might already have parsed some of
673 // the host when trying to parse a scheme.
674 resetCurChar(mCurToken
);
675 nsAutoCString selfScheme
;
676 mSelfURI
->GetScheme(selfScheme
);
677 parsedScheme
.AssignASCII(selfScheme
.get());
680 // At this point we are expecting a host to be parsed.
681 // Trying to create a new nsCSPHost.
682 if (nsCSPHostSrc
* cspHost
= hostSource()) {
683 // Do not forget to set the parsed scheme.
684 cspHost
->setScheme(parsedScheme
);
685 cspHost
->setWithinFrameAncestorsDir(mParsingFrameAncestorsDir
);
688 // Error was reported in hostSource()
692 void nsCSPParser::logWarningForIgnoringNoneKeywordToConsole() {
693 AutoTArray
<nsString
, 1> params
;
694 params
.AppendElement(CSP_EnumToUTF16Keyword(CSP_NONE
));
695 logWarningErrorToConsole(nsIScriptError::warningFlag
, "ignoringUnknownOption",
699 // source-list = *WSP [ source-expression *( 1*WSP source-expression ) *WSP ]
700 // / *WSP "'none'" *WSP
701 void nsCSPParser::sourceList(nsTArray
<nsCSPBaseSrc
*>& outSrcs
) {
704 // remember, srcs start at index 1
705 for (uint32_t i
= 1; i
< mCurDir
.Length(); i
++) {
706 // mCurToken is only set here and remains the current token
707 // to be processed, which avoid passing arguments between functions.
708 mCurToken
= mCurDir
[i
];
711 CSPPARSERLOG(("nsCSPParser::sourceList, mCurToken: %s, mCurValue: %s",
712 NS_ConvertUTF16toUTF8(mCurToken
).get(),
713 NS_ConvertUTF16toUTF8(mCurValue
).get()));
715 // Special case handling for none:
716 // Ignore 'none' if any other src is available.
717 // (See http://www.w3.org/TR/CSP11/#parsing)
718 if (CSP_IsKeyword(mCurToken
, CSP_NONE
)) {
722 // Must be a regular source expression
723 nsCSPBaseSrc
* src
= sourceExpression();
725 outSrcs
.AppendElement(src
);
729 // Check if the directive contains a 'none'
731 // If the directive contains no other srcs, then we set the 'none'
732 if (outSrcs
.IsEmpty() ||
733 (outSrcs
.Length() == 1 && outSrcs
[0]->isReportSample())) {
734 nsCSPKeywordSrc
* keyword
= new nsCSPKeywordSrc(CSP_NONE
);
735 outSrcs
.InsertElementAt(0, keyword
);
737 // Otherwise, we ignore 'none' and report a warning
739 logWarningForIgnoringNoneKeywordToConsole();
744 void nsCSPParser::reportURIList(nsCSPDirective
* aDir
) {
745 CSPPARSERLOG(("nsCSPParser::reportURIList"));
747 nsTArray
<nsCSPBaseSrc
*> srcs
;
748 nsCOMPtr
<nsIURI
> uri
;
751 // remember, srcs start at index 1
752 for (uint32_t i
= 1; i
< mCurDir
.Length(); i
++) {
753 mCurToken
= mCurDir
[i
];
755 CSPPARSERLOG(("nsCSPParser::reportURIList, mCurToken: %s, mCurValue: %s",
756 NS_ConvertUTF16toUTF8(mCurToken
).get(),
757 NS_ConvertUTF16toUTF8(mCurValue
).get()));
759 rv
= NS_NewURI(getter_AddRefs(uri
), mCurToken
, "", mSelfURI
);
761 // If creating the URI casued an error, skip this URI
763 AutoTArray
<nsString
, 1> params
= {mCurToken
};
764 logWarningErrorToConsole(nsIScriptError::warningFlag
,
765 "couldNotParseReportURI", params
);
769 // Create new nsCSPReportURI and append to the list.
770 nsCSPReportURI
* reportURI
= new nsCSPReportURI(uri
);
771 srcs
.AppendElement(reportURI
);
774 if (srcs
.Length() == 0) {
775 AutoTArray
<nsString
, 1> directiveName
= {mCurToken
};
776 logWarningErrorToConsole(nsIScriptError::warningFlag
,
777 "ignoringDirectiveWithNoValues", directiveName
);
783 mPolicy
->addDirective(aDir
);
786 /* Helper function for parsing sandbox flags. This function solely concatenates
787 * all the source list tokens (the sandbox flags) so the attribute parser
788 * (nsContentUtils::ParseSandboxAttributeToFlags) can parse them.
790 void nsCSPParser::sandboxFlagList(nsCSPDirective
* aDir
) {
791 CSPPARSERLOG(("nsCSPParser::sandboxFlagList"));
795 // remember, srcs start at index 1
796 for (uint32_t i
= 1; i
< mCurDir
.Length(); i
++) {
797 mCurToken
= mCurDir
[i
];
799 CSPPARSERLOG(("nsCSPParser::sandboxFlagList, mCurToken: %s, mCurValue: %s",
800 NS_ConvertUTF16toUTF8(mCurToken
).get(),
801 NS_ConvertUTF16toUTF8(mCurValue
).get()));
803 if (!nsContentUtils::IsValidSandboxFlag(mCurToken
)) {
804 AutoTArray
<nsString
, 1> params
= {mCurToken
};
805 logWarningErrorToConsole(nsIScriptError::warningFlag
,
806 "couldntParseInvalidSandboxFlag", params
);
810 flags
.Append(mCurToken
);
811 if (i
!= mCurDir
.Length() - 1) {
812 flags
.AppendLiteral(" ");
816 // Please note that the sandbox directive can exist
817 // by itself (not containing any flags).
818 nsTArray
<nsCSPBaseSrc
*> srcs
;
819 srcs
.AppendElement(new nsCSPSandboxFlags(flags
));
821 mPolicy
->addDirective(aDir
);
824 // https://w3c.github.io/trusted-types/dist/spec/#integration-with-content-security-policy
825 static constexpr nsLiteralString kValidRequireTrustedTypesForDirectiveValue
=
828 static bool IsValidRequireTrustedTypesForDirectiveValue(
829 const nsAString
& aToken
) {
830 return aToken
.Equals(kValidRequireTrustedTypesForDirectiveValue
);
833 void nsCSPParser::handleRequireTrustedTypesForDirective(nsCSPDirective
* aDir
) {
834 // "srcs" start at index 1. Here "srcs" should represent Trusted Types' sink
836 // (https://w3c.github.io/trusted-types/dist/spec/#require-trusted-types-for-csp-directive).
838 if (mCurDir
.Length() != 2) {
839 nsString numberOfTokensStr
;
841 // Casting is required to avoid ambiguous function calls on some platforms.
842 numberOfTokensStr
.AppendInt(static_cast<uint64_t>(mCurDir
.Length()));
844 AutoTArray
<nsString
, 1> numberOfTokensArr
= {std::move(numberOfTokensStr
)};
845 logWarningErrorToConsole(nsIScriptError::errorFlag
,
846 "invalidNumberOfTrustedTypesForDirectiveValues",
851 mCurToken
= mCurDir
.LastElement();
854 ("nsCSPParser::handleRequireTrustedTypesForDirective, mCurToken: %s",
855 NS_ConvertUTF16toUTF8(mCurToken
).get()));
857 if (!IsValidRequireTrustedTypesForDirectiveValue(mCurToken
)) {
858 AutoTArray
<nsString
, 1> token
= {mCurToken
};
859 logWarningErrorToConsole(nsIScriptError::errorFlag
,
860 "invalidRequireTrustedTypesForDirectiveValue",
865 nsTArray
<nsCSPBaseSrc
*> srcs
= {
866 new nsCSPRequireTrustedTypesForDirectiveValue(mCurToken
)};
869 mPolicy
->addDirective(aDir
);
872 // https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive
873 static bool IsValidTrustedTypesWildcard(const nsAString
& aToken
) {
875 return aToken
.Length() == 1 && aToken
.First() == WILDCARD
;
878 static bool IsValidTrustedTypesPolicyNameChar(char16_t aChar
) {
879 // tt-policy-name = 1*( ALPHA / DIGIT / "-" / "#" / "=" / "_" / "/" / "@" /
881 return nsContentUtils::IsAlphanumeric(aChar
) || aChar
== DASH
||
882 aChar
== NUMBER_SIGN
|| aChar
== EQUALS
|| aChar
== UNDERLINE
||
883 aChar
== SLASH
|| aChar
== ATSYMBOL
|| aChar
== DOT
||
884 aChar
== PERCENT_SIGN
;
887 // https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive
888 static bool IsValidTrustedTypesPolicyName(const nsAString
& aToken
) {
889 // tt-policy-name = 1*( ALPHA / DIGIT / "-" / "#" / "=" / "_" / "/" / "@" /
892 if (aToken
.IsEmpty()) {
896 for (uint32_t i
= 0; i
< aToken
.Length(); ++i
) {
897 if (!IsValidTrustedTypesPolicyNameChar(aToken
.CharAt(i
))) {
905 void nsCSPParser::handleTrustedTypesDirective(nsCSPDirective
* aDir
) {
906 CSPPARSERLOG(("nsCSPParser::handleTrustedTypesDirective"));
908 nsTArray
<nsCSPBaseSrc
*> trustedTypesExpressions
;
910 bool containsKeywordNone
= false;
912 // "srcs" start and index 1. Here they should represent the tt-expressions
913 // (https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive).
914 for (uint32_t i
= 1; i
< mCurDir
.Length(); ++i
) {
915 mCurToken
= mCurDir
[i
];
917 CSPPARSERLOG(("nsCSPParser::handleTrustedTypesDirective, mCurToken: %s",
918 NS_ConvertUTF16toUTF8(mCurToken
).get()));
920 // tt-expression = tt-policy-name / tt-keyword / tt-wildcard
921 if (IsValidTrustedTypesPolicyName(mCurToken
)) {
922 trustedTypesExpressions
.AppendElement(
923 new nsCSPTrustedTypesDirectivePolicyName(mCurToken
));
924 } else if (CSP_IsKeyword(mCurToken
, CSP_NONE
)) {
925 containsKeywordNone
= true;
926 } else if (CSP_IsKeyword(mCurToken
, CSP_ALLOW_DUPLICATES
)) {
927 trustedTypesExpressions
.AppendElement(
928 new nsCSPKeywordSrc(CSP_ALLOW_DUPLICATES
));
929 } else if (IsValidTrustedTypesWildcard(mCurToken
)) {
930 trustedTypesExpressions
.AppendElement(
931 new nsCSPTrustedTypesDirectivePolicyName(mCurToken
));
933 AutoTArray
<nsString
, 1> token
= {mCurToken
};
934 logWarningErrorToConsole(nsIScriptError::errorFlag
,
935 "invalidTrustedTypesExpression", token
);
937 for (auto* trustedTypeExpression
: trustedTypesExpressions
) {
938 delete trustedTypeExpression
;
945 if (trustedTypesExpressions
.IsEmpty()) {
946 // No tt-expression is equivalent to 'none', see
947 // <https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive>.
948 trustedTypesExpressions
.AppendElement(new nsCSPKeywordSrc(CSP_NONE
));
949 } else if (containsKeywordNone
) {
950 // See step 2.4's note at
951 // <https://w3c.github.io/trusted-types/dist/spec/#should-block-create-policy>.
952 logWarningForIgnoringNoneKeywordToConsole();
955 aDir
->addSrcs(trustedTypesExpressions
);
956 mPolicy
->addDirective(aDir
);
959 // directive-value = *( WSP / <VCHAR except ";" and ","> )
960 void nsCSPParser::directiveValue(nsTArray
<nsCSPBaseSrc
*>& outSrcs
) {
961 CSPPARSERLOG(("nsCSPParser::directiveValue"));
963 // Just forward to sourceList
967 // directive-name = 1*( ALPHA / DIGIT / "-" )
968 nsCSPDirective
* nsCSPParser::directiveName() {
969 CSPPARSERLOG(("nsCSPParser::directiveName, mCurToken: %s, mCurValue: %s",
970 NS_ConvertUTF16toUTF8(mCurToken
).get(),
971 NS_ConvertUTF16toUTF8(mCurValue
).get()));
973 // Check if it is a valid directive
974 CSPDirective directive
= CSP_StringToCSPDirective(mCurToken
);
975 if (directive
== nsIContentSecurityPolicy::NO_DIRECTIVE
||
976 (!StaticPrefs::dom_security_trusted_types_enabled() &&
978 nsIContentSecurityPolicy::REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE
||
979 directive
== nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE
))) {
980 AutoTArray
<nsString
, 1> params
= {mCurToken
};
981 logWarningErrorToConsole(nsIScriptError::warningFlag
,
982 "couldNotProcessUnknownDirective", params
);
986 // The directive 'reflected-xss' is part of CSP 1.1, see:
987 // http://www.w3.org/TR/2014/WD-CSP11-20140211/#reflected-xss
988 // Currently we are not supporting that directive, hence we log a
989 // warning to the console and ignore the directive including its values.
990 if (directive
== nsIContentSecurityPolicy::REFLECTED_XSS_DIRECTIVE
) {
991 AutoTArray
<nsString
, 1> params
= {mCurToken
};
992 logWarningErrorToConsole(nsIScriptError::warningFlag
,
993 "notSupportingDirective", params
);
997 // Make sure the directive does not already exist
998 // (see http://www.w3.org/TR/CSP11/#parsing)
999 if (mPolicy
->hasDirective(directive
)) {
1000 AutoTArray
<nsString
, 1> params
= {mCurToken
};
1001 logWarningErrorToConsole(nsIScriptError::warningFlag
, "duplicateDirective",
1006 // CSP delivered via meta tag should ignore the following directives:
1007 // report-uri, frame-ancestors, and sandbox, see:
1008 // http://www.w3.org/TR/CSP11/#delivery-html-meta-element
1009 if (mDeliveredViaMetaTag
&&
1010 ((directive
== nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE
) ||
1011 (directive
== nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE
) ||
1012 (directive
== nsIContentSecurityPolicy::SANDBOX_DIRECTIVE
))) {
1013 // log to the console to indicate that meta CSP is ignoring the directive
1014 AutoTArray
<nsString
, 1> params
= {mCurToken
};
1015 logWarningErrorToConsole(nsIScriptError::warningFlag
,
1016 "ignoringSrcFromMetaCSP", params
);
1020 // special case handling for block-all-mixed-content
1021 if (directive
== nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT
) {
1022 // If mixed content upgrade is enabled for all types block-all-mixed-content
1024 if (mozilla::StaticPrefs::
1025 security_mixed_content_upgrade_display_content() &&
1026 mozilla::StaticPrefs::
1027 security_mixed_content_upgrade_display_content_image() &&
1028 mozilla::StaticPrefs::
1029 security_mixed_content_upgrade_display_content_audio() &&
1030 mozilla::StaticPrefs::
1031 security_mixed_content_upgrade_display_content_video()) {
1032 // log to the console that if mixed content display upgrading is enabled
1033 // block-all-mixed-content is obsolete.
1034 AutoTArray
<nsString
, 1> params
= {mCurToken
};
1035 logWarningErrorToConsole(nsIScriptError::warningFlag
,
1036 "obsoleteBlockAllMixedContent", params
);
1038 return new nsBlockAllMixedContentDirective(directive
);
1041 // special case handling for upgrade-insecure-requests
1042 if (directive
== nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE
) {
1043 return new nsUpgradeInsecureDirective(directive
);
1046 // if we have a child-src, cache it as a fallback for
1047 // * workers (if worker-src is not explicitly specified)
1048 // * frames (if frame-src is not explicitly specified)
1049 if (directive
== nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE
) {
1050 mChildSrc
= new nsCSPChildSrcDirective(directive
);
1054 // if we have a frame-src, cache it so we can discard child-src for frames
1055 if (directive
== nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE
) {
1056 mFrameSrc
= new nsCSPDirective(directive
);
1060 // if we have a worker-src, cache it so we can discard child-src for workers
1061 if (directive
== nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE
) {
1062 mWorkerSrc
= new nsCSPDirective(directive
);
1066 // if we have a script-src, cache it as a fallback for worker-src
1067 // in case child-src is not present. It is also used as a fallback for
1068 // script-src-elem and script-src-attr.
1069 if (directive
== nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE
) {
1070 mScriptSrc
= new nsCSPScriptSrcDirective(directive
);
1074 // If we have a style-src, cache it as a fallback for style-src-elem and
1076 if (directive
== nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE
) {
1077 mStyleSrc
= new nsCSPStyleSrcDirective(directive
);
1081 return new nsCSPDirective(directive
);
1084 // directive = *WSP [ directive-name [ WSP directive-value ] ]
1085 void nsCSPParser::directive() {
1086 // Make sure that the directive-srcs-array contains at least
1088 if (mCurDir
.Length() == 0) {
1089 AutoTArray
<nsString
, 1> params
= {u
"directive missing"_ns
};
1090 logWarningErrorToConsole(nsIScriptError::warningFlag
,
1091 "failedToParseUnrecognizedSource", params
);
1095 // Set the directiveName to mCurToken
1096 // Remember, the directive name is stored at index 0
1097 mCurToken
= mCurDir
[0];
1099 CSPPARSERLOG(("nsCSPParser::directive, mCurToken: %s, mCurValue: %s",
1100 NS_ConvertUTF16toUTF8(mCurToken
).get(),
1101 NS_ConvertUTF16toUTF8(mCurValue
).get()));
1103 if (CSP_IsEmptyDirective(mCurValue
, mCurToken
)) {
1107 // Try to create a new CSPDirective
1108 nsCSPDirective
* cspDir
= directiveName();
1110 // if we can not create a CSPDirective, we can skip parsing the srcs for
1115 // special case handling for block-all-mixed-content, which is only specified
1116 // by a directive name but does not include any srcs.
1117 if (cspDir
->equals(nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT
)) {
1118 if (mCurDir
.Length() > 1) {
1119 AutoTArray
<nsString
, 1> params
= {u
"block-all-mixed-content"_ns
};
1120 logWarningErrorToConsole(nsIScriptError::warningFlag
,
1121 "ignoreSrcForDirective", params
);
1123 // add the directive and return
1124 mPolicy
->addDirective(cspDir
);
1128 // special case handling for upgrade-insecure-requests, which is only
1129 // specified by a directive name but does not include any srcs.
1130 if (cspDir
->equals(nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE
)) {
1131 if (mCurDir
.Length() > 1) {
1132 AutoTArray
<nsString
, 1> params
= {u
"upgrade-insecure-requests"_ns
};
1133 logWarningErrorToConsole(nsIScriptError::warningFlag
,
1134 "ignoreSrcForDirective", params
);
1136 // add the directive and return
1137 mPolicy
->addUpgradeInsecDir(
1138 static_cast<nsUpgradeInsecureDirective
*>(cspDir
));
1142 // special case handling for report-uri directive (since it doesn't contain
1143 // a valid source list but rather actual URIs)
1144 if (CSP_IsDirective(mCurDir
[0],
1145 nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE
)) {
1146 reportURIList(cspDir
);
1150 // special case handling for sandbox directive (since it doe4sn't contain
1151 // a valid source list but rather special sandbox flags)
1152 if (CSP_IsDirective(mCurDir
[0],
1153 nsIContentSecurityPolicy::SANDBOX_DIRECTIVE
)) {
1154 sandboxFlagList(cspDir
);
1158 // Special case handling since these directives don't contain source lists.
1159 if (CSP_IsDirective(
1161 nsIContentSecurityPolicy::REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE
)) {
1162 handleRequireTrustedTypesForDirective(cspDir
);
1166 if (cspDir
->equals(nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE
)) {
1167 handleTrustedTypesDirective(cspDir
);
1171 // make sure to reset cache variables when trying to invalidate unsafe-inline;
1172 // unsafe-inline might not only appear in script-src, but also in default-src
1173 mHasHashOrNonce
= false;
1174 mHasAnyUnsafeEval
= false;
1175 mStrictDynamic
= false;
1176 mUnsafeInlineKeywordSrc
= nullptr;
1178 mParsingFrameAncestorsDir
= CSP_IsDirective(
1179 mCurDir
[0], nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE
);
1181 // Try to parse all the srcs by handing the array off to directiveValue
1182 nsTArray
<nsCSPBaseSrc
*> srcs
;
1183 directiveValue(srcs
);
1185 // If we can not parse any srcs; we let the source expression be the empty set
1186 // ('none') see, http://www.w3.org/TR/CSP11/#source-list-parsing
1187 if (srcs
.IsEmpty() || (srcs
.Length() == 1 && srcs
[0]->isReportSample())) {
1188 nsCSPKeywordSrc
* keyword
= new nsCSPKeywordSrc(CSP_NONE
);
1189 srcs
.InsertElementAt(0, keyword
);
1192 MaybeWarnAboutIgnoredSources(srcs
);
1193 MaybeWarnAboutUnsafeInline(*cspDir
);
1194 MaybeWarnAboutUnsafeEval(*cspDir
);
1196 // Add the newly created srcs to the directive and add the directive to the
1198 cspDir
->addSrcs(srcs
);
1199 mPolicy
->addDirective(cspDir
);
1202 void nsCSPParser::MaybeWarnAboutIgnoredSources(
1203 const nsTArray
<nsCSPBaseSrc
*>& aSrcs
) {
1204 // If policy contains 'strict-dynamic' warn about ignored sources.
1205 if (mStrictDynamic
&&
1206 !CSP_IsDirective(mCurDir
[0],
1207 nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE
)) {
1208 for (uint32_t i
= 0; i
< aSrcs
.Length(); i
++) {
1209 nsAutoString srcStr
;
1210 aSrcs
[i
]->toString(srcStr
);
1211 // Hashes and nonces continue to apply with 'strict-dynamic', as well as
1212 // 'unsafe-eval', 'wasm-unsafe-eval' and 'unsafe-hashes'.
1213 if (!aSrcs
[i
]->isKeyword(CSP_STRICT_DYNAMIC
) &&
1214 !aSrcs
[i
]->isKeyword(CSP_UNSAFE_EVAL
) &&
1215 !aSrcs
[i
]->isKeyword(CSP_WASM_UNSAFE_EVAL
) &&
1216 !aSrcs
[i
]->isKeyword(CSP_UNSAFE_HASHES
) && !aSrcs
[i
]->isNonce() &&
1217 !aSrcs
[i
]->isHash()) {
1218 AutoTArray
<nsString
, 2> params
= {srcStr
, mCurDir
[0]};
1219 logWarningErrorToConsole(nsIScriptError::warningFlag
,
1220 "ignoringScriptSrcForStrictDynamic", params
);
1224 // Log a warning that all scripts might be blocked because the policy
1225 // contains 'strict-dynamic' but no valid nonce or hash.
1226 if (!mHasHashOrNonce
) {
1227 AutoTArray
<nsString
, 1> params
= {mCurDir
[0]};
1228 logWarningErrorToConsole(nsIScriptError::warningFlag
,
1229 "strictDynamicButNoHashOrNonce", params
);
1234 void nsCSPParser::MaybeWarnAboutUnsafeInline(const nsCSPDirective
& aDirective
) {
1235 // From https://w3c.github.io/webappsec-csp/#allow-all-inline
1236 // follows that when either a hash or nonce is specified, 'unsafe-inline'
1237 // should not apply.
1238 if (mHasHashOrNonce
&& mUnsafeInlineKeywordSrc
&&
1239 (aDirective
.isDefaultDirective() ||
1240 aDirective
.equals(nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE
) ||
1241 aDirective
.equals(nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE
) ||
1242 aDirective
.equals(nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE
) ||
1243 aDirective
.equals(nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE
) ||
1244 aDirective
.equals(nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE
) ||
1245 aDirective
.equals(nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE
))) {
1246 // Log to the console that unsafe-inline will be ignored.
1247 AutoTArray
<nsString
, 2> params
= {u
"'unsafe-inline'"_ns
, mCurDir
[0]};
1248 logWarningErrorToConsole(nsIScriptError::warningFlag
,
1249 "ignoringSrcWithinNonceOrHashDirective", params
);
1253 void nsCSPParser::MaybeWarnAboutUnsafeEval(const nsCSPDirective
& aDirective
) {
1254 if (mHasAnyUnsafeEval
&&
1255 (aDirective
.equals(nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE
) ||
1257 nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE
))) {
1258 // Log to the console that (wasm-)unsafe-eval will be ignored.
1259 AutoTArray
<nsString
, 1> params
= {mCurDir
[0]};
1260 logWarningErrorToConsole(nsIScriptError::warningFlag
, "ignoringUnsafeEval",
1265 // policy = [ directive *( ";" [ directive ] ) ]
1266 nsCSPPolicy
* nsCSPParser::policy() {
1267 CSPPARSERLOG(("nsCSPParser::policy"));
1269 mPolicy
= new nsCSPPolicy();
1270 for (uint32_t i
= 0; i
< mTokens
.Length(); i
++) {
1271 // https://w3c.github.io/webappsec-csp/#parse-serialized-policy
1272 // Step 2.2. ..., or if token is not an ASCII string, continue.
1274 // Note: In the spec the token isn't split by whitespace yet.
1275 bool isAscii
= true;
1276 for (const auto& token
: mTokens
[i
]) {
1277 if (!IsAscii(token
)) {
1278 AutoTArray
<nsString
, 1> params
= {mTokens
[i
][0], token
};
1279 logWarningErrorToConsole(nsIScriptError::warningFlag
,
1280 "ignoringNonAsciiToken", params
);
1289 // All input is already tokenized; set one tokenized array in the form of
1290 // [ name, src, src, ... ]
1291 // to mCurDir and call directive which processes the current directive.
1292 mCurDir
= mTokens
[i
].Clone();
1298 // if frame-src is specified explicitly for that policy than child-src
1299 // should not restrict frames; if not, than child-src needs to restrict
1301 mChildSrc
->setRestrictFrames();
1304 // if worker-src is specified explicitly for that policy than child-src
1305 // should not restrict workers; if not, than child-src needs to restrict
1307 mChildSrc
->setRestrictWorkers();
1311 // if script-src is specified, but not worker-src and also no child-src, then
1312 // script-src has to govern workers.
1313 if (mScriptSrc
&& !mWorkerSrc
&& !mChildSrc
) {
1314 mScriptSrc
->setRestrictWorkers();
1317 // If script-src is specified and script-src-elem is not specified, then
1318 // script-src has to govern script requests and script blocks.
1319 if (mScriptSrc
&& !mPolicy
->hasDirective(
1320 nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE
)) {
1321 mScriptSrc
->setRestrictScriptElem();
1324 // If script-src is specified and script-src-attr is not specified, then
1325 // script-src has to govern script attr (event handlers).
1326 if (mScriptSrc
&& !mPolicy
->hasDirective(
1327 nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE
)) {
1328 mScriptSrc
->setRestrictScriptAttr();
1331 // If style-src is specified and style-src-elem is not specified, then
1332 // style-src serves as a fallback.
1333 if (mStyleSrc
&& !mPolicy
->hasDirective(
1334 nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE
)) {
1335 mStyleSrc
->setRestrictStyleElem();
1338 // If style-src is specified and style-attr-elem is not specified, then
1339 // style-src serves as a fallback.
1340 if (mStyleSrc
&& !mPolicy
->hasDirective(
1341 nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE
)) {
1342 mStyleSrc
->setRestrictStyleAttr();
1348 nsCSPPolicy
* nsCSPParser::parseContentSecurityPolicy(
1349 const nsAString
& aPolicyString
, nsIURI
* aSelfURI
, bool aReportOnly
,
1350 nsCSPContext
* aCSPContext
, bool aDeliveredViaMetaTag
,
1351 bool aSuppressLogMessages
) {
1352 if (CSPPARSERLOGENABLED()) {
1353 CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, policy: %s",
1354 NS_ConvertUTF16toUTF8(aPolicyString
).get()));
1355 CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, selfURI: %s",
1356 aSelfURI
->GetSpecOrDefault().get()));
1357 CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, reportOnly: %s",
1358 (aReportOnly
? "true" : "false")));
1360 ("nsCSPParser::parseContentSecurityPolicy, deliveredViaMetaTag: %s",
1361 (aDeliveredViaMetaTag
? "true" : "false")));
1364 NS_ASSERTION(aSelfURI
, "Can not parseContentSecurityPolicy without aSelfURI");
1366 // Separate all input into tokens and store them in the form of:
1367 // [ [ name, src, src, ... ], [ name, src, src, ... ], ... ]
1368 // The tokenizer itself can not fail; all eventual errors
1369 // are detected in the parser itself.
1371 nsTArray
<CopyableTArray
<nsString
> > tokens
;
1372 PolicyTokenizer::tokenizePolicy(aPolicyString
, tokens
);
1374 nsCSPParser
parser(tokens
, aSelfURI
, aCSPContext
, aDeliveredViaMetaTag
,
1375 aSuppressLogMessages
);
1377 // Start the parser to generate a new CSPPolicy using the generated tokens.
1378 nsCSPPolicy
* policy
= parser
.policy();
1380 // Check that report-only policies define a report-uri, otherwise log warning.
1382 policy
->setReportOnlyFlag(true);
1383 if (!policy
->hasDirective(nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE
)) {
1384 nsAutoCString prePath
;
1385 nsresult rv
= aSelfURI
->GetPrePath(prePath
);
1386 NS_ENSURE_SUCCESS(rv
, policy
);
1387 AutoTArray
<nsString
, 1> params
;
1388 CopyUTF8toUTF16(prePath
, *params
.AppendElement());
1389 parser
.logWarningErrorToConsole(nsIScriptError::warningFlag
,
1390 "reportURInotInReportOnlyHeader", params
);
1394 policy
->setDeliveredViaMetaTagFlag(aDeliveredViaMetaTag
);
1396 if (policy
->getNumDirectives() == 0) {
1397 // Individual errors were already reported in the parser, but if
1398 // we do not have an enforcable directive at all, we return null.
1403 if (CSPPARSERLOGENABLED()) {
1404 nsString parsedPolicy
;
1405 policy
->toString(parsedPolicy
);
1406 CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, parsedPolicy: %s",
1407 NS_ConvertUTF16toUTF8(parsedPolicy
).get()));