Bug 1904979 - Update codespell to 2.3.0 r=linter-reviewers,Standard8
[gecko.git] / dom / security / nsCSPParser.cpp
blobf314a734d118db41b84553f5559d7d2a63bf0a48
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"
13 #include "nsCOMPtr.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"
23 #include <cstdint>
24 #include <utility>
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)
49 : mCurChar(nullptr),
50 mEndChar(nullptr),
51 mHasHashOrNonce(false),
52 mHasAnyUnsafeEval(false),
53 mStrictDynamic(false),
54 mUnsafeInlineKeywordSrc(nullptr),
55 mChildSrc(nullptr),
56 mFrameSrc(nullptr),
57 mWorkerSrc(nullptr),
58 mScriptSrc(nullptr),
59 mStyleSrc(nullptr),
60 mParsingFrameAncestorsDir(false),
61 mTokens(aTokens.Clone()),
62 mSelfURI(aSelfURI),
63 mPolicy(nullptr),
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) {
87 // Using grammar at
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 =
95 if (end == cur) {
96 return false;
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)) {
103 return false;
107 return true;
110 void nsCSPParser::resetCurChar(const nsAString& aToken) {
111 mCurChar = aToken.BeginReading();
112 mEndChar = aToken.EndReading();
113 resetCurValue();
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.
147 return false;
150 // Any valid pct-encoding must follow the following format:
151 // "% HEXDIG HEXDIG"
152 if (PERCENT_SIGN != *pctCurChar || !isValidHexDig(*(pctCurChar + 1)) ||
153 !isValidHexDig(*(pctCurChar + 2))) {
154 return false;
156 return true;
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) {
172 return;
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
180 0, // aLineNumber
181 1, // aColumnNumber
182 aSeverityFlag); // aFlags
185 bool nsCSPParser::hostChar() {
186 if (atEnd()) {
187 return false;
189 return accept(isCharacterToken) || accept(isNumberToken) || accept(DASH);
192 // (ALPHA / DIGIT / "+" / "-" / "." )
193 bool nsCSPParser::schemeChar() {
194 if (atEnd()) {
195 return false;
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
208 accept(COLON);
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) ":";
213 resetCurValue();
215 // Port might be "*"
216 if (accept(WILDCARD)) {
217 return true;
220 // Port must start with a number
221 if (!accept(isNumberToken)) {
222 AutoTArray<nsString, 1> params = {mCurToken};
223 logWarningErrorToConsole(nsIScriptError::warningFlag, "couldntParsePort",
224 params);
225 return false;
227 // Consume more numbers and set parsed port to the nsCSPHost
228 while (accept(isNumberToken)) { /* consume */
230 return true;
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()) {
245 if (peek(SLASH)) {
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"
255 resetCurValue();
256 } else if (!atValidPathChar()) {
257 AutoTArray<nsString, 1> params = {mCurToken};
258 logWarningErrorToConsole(nsIScriptError::warningFlag,
259 "couldntParseInvalidSource", params);
260 return false;
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)) {
266 advance();
267 advance();
269 advance();
270 if (++charCounter > kSubHostPathCharacterCutoff) {
271 return false;
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);
279 resetCurValue();
280 return true;
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
291 // forget about it.
292 resetCurValue();
294 if (!accept(SLASH)) {
295 AutoTArray<nsString, 1> params = {mCurToken};
296 logWarningErrorToConsole(nsIScriptError::warningFlag,
297 "couldntParseInvalidSource", params);
298 return false;
300 if (atEndOfPath()) {
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);
306 return true;
308 // path can begin with "/" but not "//"
309 // see http://tools.ietf.org/html/rfc3986#section-3.3
310 if (peek(SLASH)) {
311 AutoTArray<nsString, 1> params = {mCurToken};
312 logWarningErrorToConsole(nsIScriptError::warningFlag,
313 "couldntParseInvalidSource", params);
314 return false;
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)) {
330 ++charCounter;
331 while (hostChar()) {
332 /* consume */
333 ++charCounter;
335 if (accept(DOT) && !hostChar()) {
336 return false;
338 if (charCounter > kSubHostPathCharacterCutoff) {
339 return false;
342 return true;
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
361 if (!accept(DOT)) {
362 AutoTArray<nsString, 1> params = {mCurToken};
363 logWarningErrorToConsole(nsIScriptError::warningFlag,
364 "couldntParseInvalidHost", params);
365 return nullptr;
369 // Expecting at least one host-char
370 if (!hostChar()) {
371 AutoTArray<nsString, 1> params = {mCurToken};
372 logWarningErrorToConsole(nsIScriptError::warningFlag,
373 "couldntParseInvalidHost", params);
374 return nullptr;
377 // There might be several sub hosts defined.
378 if (!subHost()) {
379 AutoTArray<nsString, 1> params = {mCurToken};
380 logWarningErrorToConsole(nsIScriptError::warningFlag,
381 "couldntParseInvalidHost", params);
382 return nullptr;
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);
440 return nullptr;
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));
463 return nullptr;
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();
473 if (!cspHost) {
474 // Error was reported in host()
475 return nullptr;
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.
481 if (peek(COLON)) {
482 if (!port()) {
483 delete cspHost;
484 return nullptr;
486 cspHost->setPort(mCurValue);
489 if (atEndOfPath()) {
490 return cspHost;
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
500 delete cspHost;
501 return nullptr;
503 return cspHost;
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)) {
513 return nullptr;
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)) {
521 return nullptr;
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)) {
527 return nullptr;
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) {
544 return nullptr;
547 // Trim surrounding single quotes
548 const nsAString& expr = Substring(mCurToken, 1, mCurToken.Length() - 2);
550 int32_t dashIndex = expr.FindChar(DASH);
551 if (dashIndex < 0) {
552 return nullptr;
554 if (!isValidBase64Value(expr.BeginReading() + dashIndex + 1,
555 expr.EndReading())) {
556 return nullptr;
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) {
573 return nullptr;
576 // Trim surrounding single quotes
577 const nsAString& expr = Substring(mCurToken, 1, mCurToken.Length() - 2);
579 int32_t dashIndex = expr.FindChar(DASH);
580 if (dashIndex < 0) {
581 return nullptr;
584 if (!isValidBase64Value(expr.BeginReading() + dashIndex + 1,
585 expr.EndReading())) {
586 return nullptr;
589 nsAutoString algo(Substring(expr, 0, dashIndex));
590 nsAutoString hash(
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);
600 return nullptr;
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()) {
612 return cspKeyword;
615 // Check if it is a nonce-source
616 if (nsCSPNonceSrc* cspNonce = nonceSource()) {
617 return cspNonce;
620 // Check if it is a hash-source
621 if (nsCSPHashSrc* cspHash = hashSource()) {
622 return cspHash;
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
634 // mCurChar = 'h'
635 // mEndChar = points just after the last 'm'
636 // mCurValue = ""
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
643 if (atEnd()) {
644 return cspScheme;
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);
652 delete cspScheme;
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);
660 return nullptr;
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 = ""
667 resetCurValue();
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);
686 return cspHost;
688 // Error was reported in hostSource()
689 return nullptr;
692 void nsCSPParser::logWarningForIgnoringNoneKeywordToConsole() {
693 AutoTArray<nsString, 1> params;
694 params.AppendElement(CSP_EnumToUTF16Keyword(CSP_NONE));
695 logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringUnknownOption",
696 params);
699 // source-list = *WSP [ source-expression *( 1*WSP source-expression ) *WSP ]
700 // / *WSP "'none'" *WSP
701 void nsCSPParser::sourceList(nsTArray<nsCSPBaseSrc*>& outSrcs) {
702 bool isNone = false;
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];
709 resetCurValue();
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)) {
719 isNone = true;
720 continue;
722 // Must be a regular source expression
723 nsCSPBaseSrc* src = sourceExpression();
724 if (src) {
725 outSrcs.AppendElement(src);
729 // Check if the directive contains a 'none'
730 if (isNone) {
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
738 else {
739 logWarningForIgnoringNoneKeywordToConsole();
744 void nsCSPParser::reportURIList(nsCSPDirective* aDir) {
745 CSPPARSERLOG(("nsCSPParser::reportURIList"));
747 nsTArray<nsCSPBaseSrc*> srcs;
748 nsCOMPtr<nsIURI> uri;
749 nsresult rv;
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
762 if (NS_FAILED(rv)) {
763 AutoTArray<nsString, 1> params = {mCurToken};
764 logWarningErrorToConsole(nsIScriptError::warningFlag,
765 "couldNotParseReportURI", params);
766 continue;
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);
778 delete aDir;
779 return;
782 aDir->addSrcs(srcs);
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"));
793 nsAutoString flags;
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);
807 continue;
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));
820 aDir->addSrcs(srcs);
821 mPolicy->addDirective(aDir);
824 // https://w3c.github.io/trusted-types/dist/spec/#integration-with-content-security-policy
825 static constexpr nsLiteralString kValidRequireTrustedTypesForDirectiveValue =
826 u"'script'"_ns;
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
835 // groups
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",
847 numberOfTokensArr);
848 return;
851 mCurToken = mCurDir.LastElement();
853 CSPPARSERLOG(
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",
861 token);
862 return;
865 nsTArray<nsCSPBaseSrc*> srcs = {
866 new nsCSPRequireTrustedTypesForDirectiveValue(mCurToken)};
868 aDir->addSrcs(srcs);
869 mPolicy->addDirective(aDir);
872 // https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive
873 static bool IsValidTrustedTypesWildcard(const nsAString& aToken) {
874 // tt-wildcard = "*"
875 return aToken.Length() == 1 && aToken.First() == WILDCARD;
878 static bool IsValidTrustedTypesPolicyNameChar(char16_t aChar) {
879 // tt-policy-name = 1*( ALPHA / DIGIT / "-" / "#" / "=" / "_" / "/" / "@" /
880 // "." / "%")
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 / "-" / "#" / "=" / "_" / "/" / "@" /
890 // "." / "%")
892 if (aToken.IsEmpty()) {
893 return false;
896 for (uint32_t i = 0; i < aToken.Length(); ++i) {
897 if (!IsValidTrustedTypesPolicyNameChar(aToken.CharAt(i))) {
898 return false;
902 return true;
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));
932 } else {
933 AutoTArray<nsString, 1> token = {mCurToken};
934 logWarningErrorToConsole(nsIScriptError::errorFlag,
935 "invalidTrustedTypesExpression", token);
937 for (auto* trustedTypeExpression : trustedTypesExpressions) {
938 delete trustedTypeExpression;
941 return;
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
964 sourceList(outSrcs);
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() &&
977 (directive ==
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);
983 return nullptr;
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);
994 return nullptr;
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",
1002 params);
1003 return nullptr;
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);
1017 return nullptr;
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
1023 // is obsolete
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);
1051 return mChildSrc;
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);
1057 return mFrameSrc;
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);
1063 return mWorkerSrc;
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);
1071 return mScriptSrc;
1074 // If we have a style-src, cache it as a fallback for style-src-elem and
1075 // style-src-attr.
1076 if (directive == nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE) {
1077 mStyleSrc = new nsCSPStyleSrcDirective(directive);
1078 return mStyleSrc;
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
1087 // one directive.
1088 if (mCurDir.Length() == 0) {
1089 AutoTArray<nsString, 1> params = {u"directive missing"_ns};
1090 logWarningErrorToConsole(nsIScriptError::warningFlag,
1091 "failedToParseUnrecognizedSource", params);
1092 return;
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)) {
1104 return;
1107 // Try to create a new CSPDirective
1108 nsCSPDirective* cspDir = directiveName();
1109 if (!cspDir) {
1110 // if we can not create a CSPDirective, we can skip parsing the srcs for
1111 // that array
1112 return;
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);
1125 return;
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));
1139 return;
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);
1147 return;
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);
1155 return;
1158 // Special case handling since these directives don't contain source lists.
1159 if (CSP_IsDirective(
1160 mCurDir[0],
1161 nsIContentSecurityPolicy::REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE)) {
1162 handleRequireTrustedTypesForDirective(cspDir);
1163 return;
1166 if (cspDir->equals(nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE)) {
1167 handleTrustedTypesDirective(cspDir);
1168 return;
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
1197 // policy
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) ||
1256 aDirective.equals(
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",
1261 params);
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);
1281 isAscii = false;
1282 break;
1285 if (!isAscii) {
1286 continue;
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();
1293 directive();
1296 if (mChildSrc) {
1297 if (!mFrameSrc) {
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
1300 // frames.
1301 mChildSrc->setRestrictFrames();
1303 if (!mWorkerSrc) {
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
1306 // workers.
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();
1345 return mPolicy;
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")));
1359 CSPPARSERLOG(
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.
1381 if (aReportOnly) {
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.
1399 delete policy;
1400 return nullptr;
1403 if (CSPPARSERLOGENABLED()) {
1404 nsString parsedPolicy;
1405 policy->toString(parsedPolicy);
1406 CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, parsedPolicy: %s",
1407 NS_ConvertUTF16toUTF8(parsedPolicy).get()));
1410 return policy;