tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsCSPParser.cpp (54088B)


      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/. */
      6 
      7 #include "nsCSPParser.h"
      8 
      9 #include <cstdint>
     10 #include <utility>
     11 
     12 #include "mozilla/Preferences.h"
     13 #include "mozilla/StaticPrefs_dom.h"
     14 #include "mozilla/StaticPrefs_security.h"
     15 #include "mozilla/dom/Document.h"
     16 #include "mozilla/dom/TrustedTypesConstants.h"
     17 #include "nsCOMPtr.h"
     18 #include "nsCSPUtils.h"
     19 #include "nsContentUtils.h"
     20 #include "nsIScriptError.h"
     21 #include "nsNetUtil.h"
     22 #include "nsReadableUtils.h"
     23 #include "nsServiceManagerUtils.h"
     24 #include "nsUnicharUtils.h"
     25 
     26 using namespace mozilla;
     27 using namespace mozilla::dom;
     28 
     29 static LogModule* GetCspParserLog() {
     30  static LazyLogModule gCspParserPRLog("CSPParser");
     31  return gCspParserPRLog;
     32 }
     33 
     34 #define CSPPARSERLOG(args) \
     35  MOZ_LOG(GetCspParserLog(), mozilla::LogLevel::Debug, args)
     36 #define CSPPARSERLOGENABLED() \
     37  MOZ_LOG_TEST(GetCspParserLog(), mozilla::LogLevel::Debug)
     38 
     39 static const uint32_t kSubHostPathCharacterCutoff = 512;
     40 
     41 static const char* const kHashSourceValidFns[] = {"sha256", "sha384", "sha512"};
     42 static const uint32_t kHashSourceValidFnsLen = 3;
     43 
     44 /* ===== nsCSPParser ==================== */
     45 
     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"));
     68 }
     69 
     70 nsCSPParser::~nsCSPParser() { CSPPARSERLOG(("nsCSPParser::~nsCSPParser")); }
     71 
     72 static bool isCharacterToken(char16_t aSymbol) {
     73  return (aSymbol >= 'a' && aSymbol <= 'z') ||
     74         (aSymbol >= 'A' && aSymbol <= 'Z');
     75 }
     76 
     77 bool isNumberToken(char16_t aSymbol) {
     78  return (aSymbol >= '0' && aSymbol <= '9');
     79 }
     80 
     81 bool isValidHexDig(char16_t aHexDig) {
     82  return (isNumberToken(aHexDig) || (aHexDig >= 'A' && aHexDig <= 'F') ||
     83          (aHexDig >= 'a' && aHexDig <= 'f'));
     84 }
     85 
     86 bool isGroupDelim(char16_t aSymbol) {
     87  return (aSymbol == '{' || aSymbol == '}' || aSymbol == ',' ||
     88          aSymbol == '/' || aSymbol == ':' || aSymbol == ';' ||
     89          aSymbol == '<' || aSymbol == '=' || aSymbol == '>' ||
     90          aSymbol == '?' || aSymbol == '@' || aSymbol == '[' ||
     91          aSymbol == '\\' || aSymbol == ']' || aSymbol == '"');
     92 }
     93 
     94 static bool isValidBase64Value(const char16_t* cur, const char16_t* end) {
     95  // Using grammar at
     96  // https://w3c.github.io/webappsec-csp/#grammardef-nonce-source
     97 
     98  // May end with one or two =
     99  if (end > cur && *(end - 1) == EQUALS) end--;
    100  if (end > cur && *(end - 1) == EQUALS) end--;
    101 
    102  // Must have at least one character aside from any =
    103  if (end == cur) {
    104    return false;
    105  }
    106 
    107  // Rest must all be A-Za-z0-9+/-_
    108  for (; cur < end; ++cur) {
    109    if (!(isCharacterToken(*cur) || isNumberToken(*cur) || *cur == PLUS ||
    110          *cur == SLASH || *cur == DASH || *cur == UNDERLINE)) {
    111      return false;
    112    }
    113  }
    114 
    115  return true;
    116 }
    117 
    118 void nsCSPParser::resetCurChar(const nsAString& aToken) {
    119  mCurChar = aToken.BeginReading();
    120  mEndChar = aToken.EndReading();
    121  resetCurValue();
    122 }
    123 
    124 // The path is terminated by the first question mark ("?") or
    125 // number sign ("#") character, or by the end of the URI.
    126 // http://tools.ietf.org/html/rfc3986#section-3.3
    127 bool nsCSPParser::atEndOfPath() {
    128  return (atEnd() || peek(QUESTIONMARK) || peek(NUMBER_SIGN));
    129 }
    130 
    131 // unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
    132 bool nsCSPParser::atValidUnreservedChar() {
    133  return (peek(isCharacterToken) || peek(isNumberToken) || peek(DASH) ||
    134          peek(DOT) || peek(UNDERLINE) || peek(TILDE));
    135 }
    136 
    137 // sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
    138 //                 / "*" / "+" / "," / ";" / "="
    139 // Please note that even though ',' and ';' appear to be
    140 // valid sub-delims according to the RFC production of paths,
    141 // both can not appear here by itself, they would need to be
    142 // pct-encoded in order to be part of the path.
    143 bool nsCSPParser::atValidSubDelimChar() {
    144  return (peek(EXCLAMATION) || peek(DOLLAR) || peek(AMPERSAND) ||
    145          peek(SINGLEQUOTE) || peek(OPENBRACE) || peek(CLOSINGBRACE) ||
    146          peek(WILDCARD) || peek(PLUS) || peek(EQUALS));
    147 }
    148 
    149 // pct-encoded   = "%" HEXDIG HEXDIG
    150 bool nsCSPParser::atValidPctEncodedChar() {
    151  const char16_t* pctCurChar = mCurChar;
    152 
    153  if ((pctCurChar + 2) >= mEndChar) {
    154    // string too short, can't be a valid pct-encoded char.
    155    return false;
    156  }
    157 
    158  // Any valid pct-encoding must follow the following format:
    159  // "% HEXDIG HEXDIG"
    160  if (PERCENT_SIGN != *pctCurChar || !isValidHexDig(*(pctCurChar + 1)) ||
    161      !isValidHexDig(*(pctCurChar + 2))) {
    162    return false;
    163  }
    164  return true;
    165 }
    166 
    167 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
    168 // http://tools.ietf.org/html/rfc3986#section-3.3
    169 bool nsCSPParser::atValidPathChar() {
    170  return (atValidUnreservedChar() || atValidSubDelimChar() ||
    171          atValidPctEncodedChar() || peek(COLON) || peek(ATSYMBOL));
    172 }
    173 
    174 void nsCSPParser::logWarningErrorToConsole(uint32_t aSeverityFlag,
    175                                           const char* aProperty,
    176                                           const nsTArray<nsString>& aParams) {
    177  CSPPARSERLOG(("nsCSPParser::logWarningErrorToConsole: %s", aProperty));
    178 
    179  if (mSuppressLogMessages) {
    180    return;
    181  }
    182 
    183  // send console messages off to the context and let the context
    184  // deal with it (potentially messages need to be queued up)
    185  mCSPContext->logToConsole(aProperty, aParams,
    186                            ""_ns,           // aSourceName
    187                            u""_ns,          // aSourceLine
    188                            0,               // aLineNumber
    189                            1,               // aColumnNumber
    190                            aSeverityFlag);  // aFlags
    191 }
    192 
    193 bool nsCSPParser::hostChar() {
    194  if (atEnd()) {
    195    return false;
    196  }
    197  return accept(isCharacterToken) || accept(isNumberToken) || accept(DASH);
    198 }
    199 
    200 // (ALPHA / DIGIT / "+" / "-" / "." )
    201 bool nsCSPParser::schemeChar() {
    202  if (atEnd()) {
    203    return false;
    204  }
    205  return accept(isCharacterToken) || accept(isNumberToken) || accept(PLUS) ||
    206         accept(DASH) || accept(DOT);
    207 }
    208 
    209 // port = ":" ( 1*DIGIT / "*" )
    210 bool nsCSPParser::port() {
    211  CSPPARSERLOG(("nsCSPParser::port, mCurToken: %s, mCurValue: %s",
    212                NS_ConvertUTF16toUTF8(mCurToken).get(),
    213                NS_ConvertUTF16toUTF8(mCurValue).get()));
    214 
    215  // Consume the COLON we just peeked at in houstSource
    216  accept(COLON);
    217 
    218  // Resetting current value since we start to parse a port now.
    219  // e.g; "http://www.example.com:8888" then we have already parsed
    220  // everything up to (including) ":";
    221  resetCurValue();
    222 
    223  // Port might be "*"
    224  if (accept(WILDCARD)) {
    225    return true;
    226  }
    227 
    228  // Port must start with a number
    229  if (!accept(isNumberToken)) {
    230    AutoTArray<nsString, 1> params = {mCurToken};
    231    logWarningErrorToConsole(nsIScriptError::warningFlag, "couldntParsePort",
    232                             params);
    233    return false;
    234  }
    235  // Consume more numbers and set parsed port to the nsCSPHost
    236  while (accept(isNumberToken)) { /* consume */
    237  }
    238  return true;
    239 }
    240 
    241 bool nsCSPParser::subPath(nsCSPHostSrc* aCspHost) {
    242  CSPPARSERLOG(("nsCSPParser::subPath, mCurToken: %s, mCurValue: %s",
    243                NS_ConvertUTF16toUTF8(mCurToken).get(),
    244                NS_ConvertUTF16toUTF8(mCurValue).get()));
    245 
    246  // Emergency exit to avoid endless loops in case a path in a CSP policy
    247  // is longer than 512 characters, or also to avoid endless loops
    248  // in case we are parsing unrecognized characters in the following loop.
    249  uint32_t charCounter = 0;
    250  nsString pctDecodedSubPath;
    251 
    252  while (!atEndOfPath()) {
    253    if (peek(SLASH)) {
    254      // before appendig any additional portion of a subpath we have to
    255      // pct-decode that portion of the subpath. atValidPathChar() already
    256      // verified a correct pct-encoding, now we can safely decode and append
    257      // the decoded-sub path.
    258      CSP_PercentDecodeStr(mCurValue, pctDecodedSubPath);
    259      aCspHost->appendPath(pctDecodedSubPath);
    260      // Resetting current value since we are appending parts of the path
    261      // to aCspHost, e.g; "http://www.example.com/path1/path2" then the
    262      // first part is "/path1", second part "/path2"
    263      resetCurValue();
    264    } else if (!atValidPathChar()) {
    265      AutoTArray<nsString, 1> params = {mCurToken};
    266      logWarningErrorToConsole(nsIScriptError::warningFlag,
    267                               "couldntParseInvalidSource", params);
    268      return false;
    269    }
    270    // potentially we have encountred a valid pct-encoded character in
    271    // atValidPathChar(); if so, we have to account for "% HEXDIG HEXDIG" and
    272    // advance the pointer past the pct-encoded char.
    273    if (peek(PERCENT_SIGN)) {
    274      advance();
    275      advance();
    276    }
    277    advance();
    278    if (++charCounter > kSubHostPathCharacterCutoff) {
    279      return false;
    280    }
    281  }
    282  // before appendig any additional portion of a subpath we have to pct-decode
    283  // that portion of the subpath. atValidPathChar() already verified a correct
    284  // pct-encoding, now we can safely decode and append the decoded-sub path.
    285  CSP_PercentDecodeStr(mCurValue, pctDecodedSubPath);
    286  aCspHost->appendPath(pctDecodedSubPath);
    287  resetCurValue();
    288  return true;
    289 }
    290 
    291 bool nsCSPParser::path(nsCSPHostSrc* aCspHost) {
    292  CSPPARSERLOG(("nsCSPParser::path, mCurToken: %s, mCurValue: %s",
    293                NS_ConvertUTF16toUTF8(mCurToken).get(),
    294                NS_ConvertUTF16toUTF8(mCurValue).get()));
    295 
    296  // Resetting current value and forgetting everything we have parsed so far
    297  // e.g. parsing "http://www.example.com/path1/path2", then
    298  // "http://www.example.com" has already been parsed so far
    299  // forget about it.
    300  resetCurValue();
    301 
    302  if (!accept(SLASH)) {
    303    AutoTArray<nsString, 1> params = {mCurToken};
    304    logWarningErrorToConsole(nsIScriptError::warningFlag,
    305                             "couldntParseInvalidSource", params);
    306    return false;
    307  }
    308  if (atEndOfPath()) {
    309    // one slash right after host [port] is also considered a path, e.g.
    310    // www.example.com/ should result in www.example.com/
    311    // please note that we do not have to perform any pct-decoding here
    312    // because we are just appending a '/' and not any actual chars.
    313    aCspHost->appendPath(u"/"_ns);
    314    return true;
    315  }
    316  // path can begin with "/" but not "//"
    317  // see http://tools.ietf.org/html/rfc3986#section-3.3
    318  if (peek(SLASH)) {
    319    AutoTArray<nsString, 1> params = {mCurToken};
    320    logWarningErrorToConsole(nsIScriptError::warningFlag,
    321                             "couldntParseInvalidSource", params);
    322    return false;
    323  }
    324  return subPath(aCspHost);
    325 }
    326 
    327 bool nsCSPParser::subHost() {
    328  CSPPARSERLOG(("nsCSPParser::subHost, mCurToken: %s, mCurValue: %s",
    329                NS_ConvertUTF16toUTF8(mCurToken).get(),
    330                NS_ConvertUTF16toUTF8(mCurValue).get()));
    331 
    332  // Emergency exit to avoid endless loops in case a host in a CSP policy
    333  // is longer than 512 characters, or also to avoid endless loops
    334  // in case we are parsing unrecognized characters in the following loop.
    335  uint32_t charCounter = 0;
    336 
    337  while (!atEndOfPath() && !peek(COLON) && !peek(SLASH)) {
    338    ++charCounter;
    339    while (hostChar()) {
    340      /* consume */
    341      ++charCounter;
    342    }
    343    if (accept(DOT) && !hostChar()) {
    344      return false;
    345    }
    346    if (charCounter > kSubHostPathCharacterCutoff) {
    347      return false;
    348    }
    349  }
    350  return true;
    351 }
    352 
    353 // host = "*" / [ "*." ] 1*host-char *( "." 1*host-char )
    354 nsCSPHostSrc* nsCSPParser::host() {
    355  CSPPARSERLOG(("nsCSPParser::host, mCurToken: %s, mCurValue: %s",
    356                NS_ConvertUTF16toUTF8(mCurToken).get(),
    357                NS_ConvertUTF16toUTF8(mCurValue).get()));
    358 
    359  // Check if the token starts with "*"; please remember that we handle
    360  // a single "*" as host in sourceExpression, but we still have to handle
    361  // the case where a scheme was defined, e.g., as:
    362  // "https://*", "*.example.com", "*:*", etc.
    363  if (accept(WILDCARD)) {
    364    // Might solely be the wildcard
    365    if (atEnd() || peek(COLON)) {
    366      return new nsCSPHostSrc(mCurValue);
    367    }
    368    // If the token is not only the "*", a "." must follow right after
    369    if (!accept(DOT)) {
    370      AutoTArray<nsString, 1> params = {mCurToken};
    371      logWarningErrorToConsole(nsIScriptError::warningFlag,
    372                               "couldntParseInvalidHost", params);
    373      return nullptr;
    374    }
    375  }
    376 
    377  // Expecting at least one host-char
    378  if (!hostChar()) {
    379    AutoTArray<nsString, 1> params = {mCurToken};
    380    logWarningErrorToConsole(nsIScriptError::warningFlag,
    381                             "couldntParseInvalidHost", params);
    382    return nullptr;
    383  }
    384 
    385  // There might be several sub hosts defined.
    386  if (!subHost()) {
    387    AutoTArray<nsString, 1> params = {mCurToken};
    388    logWarningErrorToConsole(nsIScriptError::warningFlag,
    389                             "couldntParseInvalidHost", params);
    390    return nullptr;
    391  }
    392 
    393  // HostName might match a keyword, log to the console.
    394  if (CSP_IsQuotelessKeyword(mCurValue)) {
    395    nsString keyword = mCurValue;
    396    ToLowerCase(keyword);
    397    AutoTArray<nsString, 2> params = {mCurToken, keyword};
    398    logWarningErrorToConsole(nsIScriptError::warningFlag,
    399                             "hostNameMightBeKeyword", params);
    400  }
    401 
    402  // Create a new nsCSPHostSrc with the parsed host.
    403  return new nsCSPHostSrc(mCurValue);
    404 }
    405 
    406 // keyword-source = "'self'" / "'unsafe-inline'" / "'unsafe-eval'" /
    407 // "'wasm-unsafe-eval'"
    408 nsCSPBaseSrc* nsCSPParser::keywordSource() {
    409  CSPPARSERLOG(("nsCSPParser::keywordSource, mCurToken: %s, mCurValue: %s",
    410                NS_ConvertUTF16toUTF8(mCurToken).get(),
    411                NS_ConvertUTF16toUTF8(mCurValue).get()));
    412 
    413  // Special case handling for 'self' which is not stored internally as a
    414  // keyword, but rather creates a nsCSPHostSrc using the selfURI
    415  if (CSP_IsKeyword(mCurToken, CSP_SELF)) {
    416    return CSP_CreateHostSrcFromSelfURI(mSelfURI);
    417  }
    418 
    419  if (CSP_IsKeyword(mCurToken, CSP_REPORT_SAMPLE)) {
    420    return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken));
    421  }
    422 
    423  if (CSP_IsKeyword(mCurToken, CSP_STRICT_DYNAMIC)) {
    424    if (!CSP_IsDirective(mCurDir[0],
    425                         nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE) &&
    426        !CSP_IsDirective(mCurDir[0],
    427                         nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) &&
    428        !CSP_IsDirective(mCurDir[0],
    429                         nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) &&
    430        !CSP_IsDirective(mCurDir[0],
    431                         nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE)) {
    432      AutoTArray<nsString, 1> params = {u"strict-dynamic"_ns};
    433      logWarningErrorToConsole(nsIScriptError::warningFlag,
    434                               "ignoringStrictDynamic", params);
    435    }
    436 
    437    mStrictDynamic = true;
    438    return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken));
    439  }
    440 
    441  if (CSP_IsKeyword(mCurToken, CSP_UNSAFE_INLINE)) {
    442    // make sure script-src only contains 'unsafe-inline' once;
    443    // ignore duplicates and log warning
    444    if (mUnsafeInlineKeywordSrc) {
    445      AutoTArray<nsString, 1> params = {mCurToken};
    446      logWarningErrorToConsole(nsIScriptError::warningFlag,
    447                               "ignoringDuplicateSrc", params);
    448      return nullptr;
    449    }
    450    // cache if we encounter 'unsafe-inline' so we can invalidate (ignore) it in
    451    // case that script-src directive also contains hash- or nonce-.
    452    mUnsafeInlineKeywordSrc =
    453        new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken));
    454    return mUnsafeInlineKeywordSrc;
    455  }
    456 
    457  if (CSP_IsKeyword(mCurToken, CSP_UNSAFE_EVAL)) {
    458    mHasAnyUnsafeEval = true;
    459    return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken));
    460  }
    461 
    462  if (CSP_IsKeyword(mCurToken, CSP_WASM_UNSAFE_EVAL)) {
    463    mHasAnyUnsafeEval = true;
    464    return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken));
    465  }
    466 
    467  if (CSP_IsKeyword(mCurToken, CSP_UNSAFE_HASHES)) {
    468    return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken));
    469  }
    470 
    471  if (StaticPrefs::dom_security_trusted_types_enabled() &&
    472      CSP_IsKeyword(mCurToken, CSP_TRUSTED_TYPES_EVAL)) {
    473    return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken));
    474  }
    475 
    476  return nullptr;
    477 }
    478 
    479 // host-source = [ scheme "://" ] host [ port ] [ path ]
    480 nsCSPHostSrc* nsCSPParser::hostSource() {
    481  CSPPARSERLOG(("nsCSPParser::hostSource, mCurToken: %s, mCurValue: %s",
    482                NS_ConvertUTF16toUTF8(mCurToken).get(),
    483                NS_ConvertUTF16toUTF8(mCurValue).get()));
    484 
    485  nsCSPHostSrc* cspHost = host();
    486  if (!cspHost) {
    487    // Error was reported in host()
    488    return nullptr;
    489  }
    490 
    491  // Calling port() to see if there is a port to parse, if an error
    492  // occurs, port() reports the error, if port() returns true;
    493  // we have a valid port, so we add it to cspHost.
    494  if (peek(COLON)) {
    495    if (!port()) {
    496      delete cspHost;
    497      return nullptr;
    498    }
    499    cspHost->setPort(mCurValue);
    500  }
    501 
    502  if (atEndOfPath()) {
    503    return cspHost;
    504  }
    505 
    506  // Calling path() to see if there is a path to parse, if an error
    507  // occurs, path() reports the error; handing cspHost as an argument
    508  // which simplifies parsing of several paths.
    509  if (!path(cspHost)) {
    510    // If the host [port] is followed by a path, it has to be a valid path,
    511    // otherwise we pass the nullptr, indicating an error, up the callstack.
    512    // see also http://www.w3.org/TR/CSP11/#source-list
    513    delete cspHost;
    514    return nullptr;
    515  }
    516  return cspHost;
    517 }
    518 
    519 // scheme-source = scheme ":"
    520 nsCSPSchemeSrc* nsCSPParser::schemeSource() {
    521  CSPPARSERLOG(("nsCSPParser::schemeSource, mCurToken: %s, mCurValue: %s",
    522                NS_ConvertUTF16toUTF8(mCurToken).get(),
    523                NS_ConvertUTF16toUTF8(mCurValue).get()));
    524 
    525  if (!accept(isCharacterToken)) {
    526    return nullptr;
    527  }
    528  while (schemeChar()) { /* consume */
    529  }
    530  nsString scheme = mCurValue;
    531 
    532  // If the potential scheme is not followed by ":" - it's not a scheme
    533  if (!accept(COLON)) {
    534    return nullptr;
    535  }
    536 
    537  // If the chraracter following the ":" is a number or the "*"
    538  // then we are not parsing a scheme; but rather a host;
    539  if (peek(isNumberToken) || peek(WILDCARD)) {
    540    return nullptr;
    541  }
    542 
    543  return new nsCSPSchemeSrc(scheme);
    544 }
    545 
    546 // nonce-source = "'nonce-" nonce-value "'"
    547 nsCSPNonceSrc* nsCSPParser::nonceSource() {
    548  CSPPARSERLOG(("nsCSPParser::nonceSource, mCurToken: %s, mCurValue: %s",
    549                NS_ConvertUTF16toUTF8(mCurToken).get(),
    550                NS_ConvertUTF16toUTF8(mCurValue).get()));
    551 
    552  // Check if mCurToken begins with "'nonce-" and ends with "'"
    553  if (!StringBeginsWith(mCurToken,
    554                        nsDependentString(CSP_EnumToUTF16Keyword(CSP_NONCE)),
    555                        nsASCIICaseInsensitiveStringComparator) ||
    556      mCurToken.Last() != SINGLEQUOTE) {
    557    return nullptr;
    558  }
    559 
    560  // Trim surrounding single quotes
    561  const nsAString& expr = Substring(mCurToken, 1, mCurToken.Length() - 2);
    562 
    563  int32_t dashIndex = expr.FindChar(DASH);
    564  if (dashIndex < 0) {
    565    return nullptr;
    566  }
    567  if (!isValidBase64Value(expr.BeginReading() + dashIndex + 1,
    568                          expr.EndReading())) {
    569    return nullptr;
    570  }
    571 
    572  // cache if encountering hash or nonce to invalidate unsafe-inline
    573  mHasHashOrNonce = true;
    574  return new nsCSPNonceSrc(
    575      Substring(expr, dashIndex + 1, expr.Length() - dashIndex + 1));
    576 }
    577 
    578 // hash-source = "'" hash-algo "-" base64-value "'"
    579 nsCSPHashSrc* nsCSPParser::hashSource() {
    580  CSPPARSERLOG(("nsCSPParser::hashSource, mCurToken: %s, mCurValue: %s",
    581                NS_ConvertUTF16toUTF8(mCurToken).get(),
    582                NS_ConvertUTF16toUTF8(mCurValue).get()));
    583 
    584  // Check if mCurToken starts and ends with "'"
    585  if (mCurToken.First() != SINGLEQUOTE || mCurToken.Last() != SINGLEQUOTE) {
    586    return nullptr;
    587  }
    588 
    589  // Trim surrounding single quotes
    590  const nsAString& expr = Substring(mCurToken, 1, mCurToken.Length() - 2);
    591 
    592  int32_t dashIndex = expr.FindChar(DASH);
    593  if (dashIndex < 0) {
    594    return nullptr;
    595  }
    596 
    597  if (!isValidBase64Value(expr.BeginReading() + dashIndex + 1,
    598                          expr.EndReading())) {
    599    return nullptr;
    600  }
    601 
    602  nsAutoString algo(Substring(expr, 0, dashIndex));
    603  nsAutoString hash(
    604      Substring(expr, dashIndex + 1, expr.Length() - dashIndex + 1));
    605 
    606  for (uint32_t i = 0; i < kHashSourceValidFnsLen; i++) {
    607    if (algo.LowerCaseEqualsASCII(kHashSourceValidFns[i])) {
    608      // cache if encountering hash or nonce to invalidate unsafe-inline
    609      mHasHashOrNonce = true;
    610      return new nsCSPHashSrc(algo, hash);
    611    }
    612  }
    613  return nullptr;
    614 }
    615 
    616 // source-expression = scheme-source / host-source / keyword-source
    617 //                     / nonce-source / hash-source
    618 nsCSPBaseSrc* nsCSPParser::sourceExpression() {
    619  CSPPARSERLOG(("nsCSPParser::sourceExpression, mCurToken: %s, mCurValue: %s",
    620                NS_ConvertUTF16toUTF8(mCurToken).get(),
    621                NS_ConvertUTF16toUTF8(mCurValue).get()));
    622 
    623  // Check if it is a keyword
    624  if (nsCSPBaseSrc* cspKeyword = keywordSource()) {
    625    return cspKeyword;
    626  }
    627 
    628  // Check if it is a nonce-source
    629  if (nsCSPNonceSrc* cspNonce = nonceSource()) {
    630    return cspNonce;
    631  }
    632 
    633  // Check if it is a hash-source
    634  if (nsCSPHashSrc* cspHash = hashSource()) {
    635    return cspHash;
    636  }
    637 
    638  // We handle a single "*" as host here, to avoid any confusion when applying
    639  // the default scheme. However, we still would need to apply the default
    640  // scheme in case we would parse "*:80".
    641  if (mCurToken.EqualsASCII("*")) {
    642    return new nsCSPHostSrc(u"*"_ns);
    643  }
    644 
    645  // Calling resetCurChar allows us to use mCurChar and mEndChar
    646  // to parse mCurToken; e.g. mCurToken = "http://www.example.com", then
    647  // mCurChar = 'h'
    648  // mEndChar = points just after the last 'm'
    649  // mCurValue = ""
    650  resetCurChar(mCurToken);
    651 
    652  // Check if mCurToken starts with a scheme
    653  nsAutoString parsedScheme;
    654  if (nsCSPSchemeSrc* cspScheme = schemeSource()) {
    655    // mCurToken might only enforce a specific scheme
    656    if (atEnd()) {
    657      return cspScheme;
    658    }
    659    // If something follows the scheme, we do not create
    660    // a nsCSPSchemeSrc, but rather a nsCSPHostSrc, which
    661    // needs to know the scheme to enforce; remember the
    662    // scheme and delete cspScheme;
    663    cspScheme->toString(parsedScheme);
    664    parsedScheme.Trim(":", false, true);
    665    delete cspScheme;
    666 
    667    // If mCurToken provides not only a scheme, but also a host, we have to
    668    // check if two slashes follow the scheme.
    669    if (!accept(SLASH) || !accept(SLASH)) {
    670      AutoTArray<nsString, 1> params = {mCurToken};
    671      logWarningErrorToConsole(nsIScriptError::warningFlag,
    672                               "failedToParseUnrecognizedSource", params);
    673      return nullptr;
    674    }
    675  }
    676 
    677  // Calling resetCurValue allows us to keep pointers for mCurChar and mEndChar
    678  // alive, but resets mCurValue; e.g. mCurToken = "http://www.example.com",
    679  // then mCurChar = 'w' mEndChar = 'm' mCurValue = ""
    680  resetCurValue();
    681 
    682  // If mCurToken does not provide a scheme (scheme-less source), we apply the
    683  // scheme from selfURI
    684  if (parsedScheme.IsEmpty()) {
    685    // Resetting internal helpers, because we might already have parsed some of
    686    // the host when trying to parse a scheme.
    687    resetCurChar(mCurToken);
    688    nsAutoCString selfScheme;
    689    mSelfURI->GetScheme(selfScheme);
    690    parsedScheme.AssignASCII(selfScheme.get());
    691  }
    692 
    693  // At this point we are expecting a host to be parsed.
    694  // Trying to create a new nsCSPHost.
    695  if (nsCSPHostSrc* cspHost = hostSource()) {
    696    // Do not forget to set the parsed scheme.
    697    cspHost->setScheme(parsedScheme);
    698    cspHost->setWithinFrameAncestorsDir(mParsingFrameAncestorsDir);
    699    return cspHost;
    700  }
    701  // Error was reported in hostSource()
    702  return nullptr;
    703 }
    704 
    705 void nsCSPParser::logWarningForIgnoringNoneKeywordToConsole() {
    706  AutoTArray<nsString, 1> params;
    707  params.AppendElement(CSP_EnumToUTF16Keyword(CSP_NONE));
    708  logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringUnknownOption",
    709                           params);
    710 }
    711 
    712 // source-list = *WSP [ source-expression *( 1*WSP source-expression ) *WSP ]
    713 //               / *WSP "'none'" *WSP
    714 void nsCSPParser::sourceList(nsTArray<nsCSPBaseSrc*>& outSrcs) {
    715  bool isNone = false;
    716 
    717  // remember, srcs start at index 1
    718  for (uint32_t i = 1; i < mCurDir.Length(); i++) {
    719    // mCurToken is only set here and remains the current token
    720    // to be processed, which avoid passing arguments between functions.
    721    mCurToken = mCurDir[i];
    722    resetCurValue();
    723 
    724    CSPPARSERLOG(("nsCSPParser::sourceList, mCurToken: %s, mCurValue: %s",
    725                  NS_ConvertUTF16toUTF8(mCurToken).get(),
    726                  NS_ConvertUTF16toUTF8(mCurValue).get()));
    727 
    728    // Special case handling for none:
    729    // Ignore 'none' if any other src is available.
    730    // (See http://www.w3.org/TR/CSP11/#parsing)
    731    if (CSP_IsKeyword(mCurToken, CSP_NONE)) {
    732      isNone = true;
    733      continue;
    734    }
    735    // Must be a regular source expression
    736    nsCSPBaseSrc* src = sourceExpression();
    737    if (src) {
    738      outSrcs.AppendElement(src);
    739    }
    740  }
    741 
    742  // Check if the directive contains a 'none'
    743  if (isNone) {
    744    // If the directive contains no other srcs, then we set the 'none'
    745    if (outSrcs.IsEmpty() ||
    746        (outSrcs.Length() == 1 && outSrcs[0]->isReportSample())) {
    747      nsCSPKeywordSrc* keyword = new nsCSPKeywordSrc(CSP_NONE);
    748      outSrcs.InsertElementAt(0, keyword);
    749    }
    750    // Otherwise, we ignore 'none' and report a warning
    751    else {
    752      logWarningForIgnoringNoneKeywordToConsole();
    753    }
    754  }
    755 }
    756 
    757 void nsCSPParser::reportURIList(nsCSPDirective* aDir) {
    758  CSPPARSERLOG(("nsCSPParser::reportURIList"));
    759 
    760  nsTArray<nsCSPBaseSrc*> srcs;
    761  nsCOMPtr<nsIURI> uri;
    762  nsresult rv;
    763 
    764  // remember, srcs start at index 1
    765  for (uint32_t i = 1; i < mCurDir.Length(); i++) {
    766    mCurToken = mCurDir[i];
    767 
    768    CSPPARSERLOG(("nsCSPParser::reportURIList, mCurToken: %s, mCurValue: %s",
    769                  NS_ConvertUTF16toUTF8(mCurToken).get(),
    770                  NS_ConvertUTF16toUTF8(mCurValue).get()));
    771 
    772    rv = NS_NewURI(getter_AddRefs(uri), mCurToken, "", mSelfURI);
    773 
    774    // If creating the URI casued an error, skip this URI
    775    if (NS_FAILED(rv)) {
    776      AutoTArray<nsString, 1> params = {mCurToken};
    777      logWarningErrorToConsole(nsIScriptError::warningFlag,
    778                               "couldNotParseReportURI", params);
    779      continue;
    780    }
    781 
    782    // Create new nsCSPReportURI and append to the list.
    783    nsCSPReportURI* reportURI = new nsCSPReportURI(uri);
    784    srcs.AppendElement(reportURI);
    785  }
    786 
    787  if (srcs.Length() == 0) {
    788    AutoTArray<nsString, 1> directiveName = {mCurToken};
    789    logWarningErrorToConsole(nsIScriptError::warningFlag,
    790                             "ignoringDirectiveWithNoValues", directiveName);
    791    delete aDir;
    792    return;
    793  }
    794 
    795  aDir->addSrcs(srcs);
    796  mPolicy->addDirective(aDir);
    797 }
    798 
    799 void nsCSPParser::reportGroup(nsCSPDirective* aDir) {
    800  CSPPARSERLOG(("nsCSPParser::reportGroup"));
    801 
    802  if (mCurDir.Length() < 2) {
    803    AutoTArray<nsString, 1> directiveName = {mCurToken};
    804    logWarningErrorToConsole(nsIScriptError::warningFlag,
    805                             "ignoringDirectiveWithNoValues", directiveName);
    806    delete aDir;
    807    return;
    808  }
    809 
    810  nsTArray<nsCSPBaseSrc*> srcs;
    811  mCurToken = mCurDir[1];
    812 
    813  CSPPARSERLOG(("nsCSPParser::reportGroup, mCurToken: %s, mCurValue: %s",
    814                NS_ConvertUTF16toUTF8(mCurToken).get(),
    815                NS_ConvertUTF16toUTF8(mCurValue).get()));
    816 
    817  resetCurChar(mCurToken);
    818  while (!atEnd()) {
    819    if (isGroupDelim(*mCurChar) ||
    820        nsContentUtils::IsHTMLWhitespace(*mCurChar)) {
    821      nsString badChar(mozilla::Span(mCurChar, 1));
    822      AutoTArray<nsString, 2> params = {mCurToken, badChar};
    823      logWarningErrorToConsole(nsIScriptError::warningFlag,
    824                               "ignoringInvalidGroupSyntax", params);
    825      delete aDir;
    826      return;
    827    }
    828    advance();
    829  }
    830 
    831  nsCSPGroup* group = new nsCSPGroup(mCurToken);
    832  srcs.AppendElement(group);
    833  aDir->addSrcs(srcs);
    834  mPolicy->addDirective(aDir);
    835  aDir = nullptr;
    836 };
    837 
    838 /* Helper function for parsing sandbox flags. This function solely concatenates
    839 * all the source list tokens (the sandbox flags) so the attribute parser
    840 * (nsContentUtils::ParseSandboxAttributeToFlags) can parse them.
    841 */
    842 void nsCSPParser::sandboxFlagList(nsCSPDirective* aDir) {
    843  CSPPARSERLOG(("nsCSPParser::sandboxFlagList"));
    844 
    845  nsAutoString flags;
    846 
    847  // remember, srcs start at index 1
    848  for (uint32_t i = 1; i < mCurDir.Length(); i++) {
    849    mCurToken = mCurDir[i];
    850 
    851    CSPPARSERLOG(("nsCSPParser::sandboxFlagList, mCurToken: %s, mCurValue: %s",
    852                  NS_ConvertUTF16toUTF8(mCurToken).get(),
    853                  NS_ConvertUTF16toUTF8(mCurValue).get()));
    854 
    855    if (!nsContentUtils::IsValidSandboxFlag(mCurToken)) {
    856      AutoTArray<nsString, 1> params = {mCurToken};
    857      logWarningErrorToConsole(nsIScriptError::warningFlag,
    858                               "couldntParseInvalidSandboxFlag", params);
    859      continue;
    860    }
    861 
    862    flags.Append(mCurToken);
    863    if (i != mCurDir.Length() - 1) {
    864      flags.AppendLiteral(" ");
    865    }
    866  }
    867 
    868  // Please note that the sandbox directive can exist
    869  // by itself (not containing any flags).
    870  nsTArray<nsCSPBaseSrc*> srcs;
    871  srcs.AppendElement(new nsCSPSandboxFlags(flags));
    872  aDir->addSrcs(srcs);
    873  mPolicy->addDirective(aDir);
    874 }
    875 
    876 static bool IsValidRequireTrustedTypesForDirectiveValue(
    877    const nsAString& aToken) {
    878  return aToken.Equals(kValidRequireTrustedTypesForDirectiveValue);
    879 }
    880 
    881 void nsCSPParser::handleRequireTrustedTypesForDirective(nsCSPDirective* aDir) {
    882  CSPPARSERLOG(("nsCSPParser::handleTrustedTypesDirective"));
    883 
    884  // "srcs" start at index 1. Here "srcs" should represent Trusted Types' sink
    885  // groups
    886  // (https://w3c.github.io/trusted-types/dist/spec/#require-trusted-types-for-csp-directive).
    887  // We align with other browsers and make the syntax forgiving i.e. invalid
    888  // trusted-types-sink-group-keyword are discarded without invalidating the
    889  // whole directive. However, if no valid trusted-types-sink-group-keyword is
    890  // found, the directive has no effect and can just be discarded completely.
    891  // See https://github.com/w3c/trusted-types/issues/580.
    892  if (mCurDir.Length() < 2) {
    893    nsString numberOfTokensStr;
    894 
    895    // Casting is required to avoid ambiguous function calls on some platforms.
    896    numberOfTokensStr.AppendInt(static_cast<uint64_t>(mCurDir.Length()));
    897 
    898    AutoTArray<nsString, 1> numberOfTokensArr = {std::move(numberOfTokensStr)};
    899    logWarningErrorToConsole(nsIScriptError::errorFlag,
    900                             "invalidNumberOfTrustedTypesForDirectiveValues",
    901                             numberOfTokensArr);
    902    return;
    903  }
    904 
    905  nsTArray<nsCSPBaseSrc*> trustedTypesSinkGroupKeywords;
    906  bool foundValidTrustedTypesSinkGroupKeyword = false;
    907  for (uint32_t i = 1; i < mCurDir.Length(); ++i) {
    908    mCurToken = mCurDir[i];
    909 
    910    CSPPARSERLOG(
    911        ("nsCSPParser::handleRequireTrustedTypesForDirective, mCurToken: %s",
    912         NS_ConvertUTF16toUTF8(mCurToken).get()));
    913 
    914    if (!IsValidRequireTrustedTypesForDirectiveValue(mCurToken)) {
    915      AutoTArray<nsString, 1> token = {mCurToken};
    916      logWarningErrorToConsole(nsIScriptError::warningFlag,
    917                               "invalidRequireTrustedTypesForDirectiveValue",
    918                               token);
    919    } else {
    920      foundValidTrustedTypesSinkGroupKeyword = true;
    921    }
    922    trustedTypesSinkGroupKeywords.AppendElement(
    923        new nsCSPRequireTrustedTypesForDirectiveValue(mCurToken));
    924  }
    925  if (!foundValidTrustedTypesSinkGroupKeyword) {
    926    for (auto* trustedTypesSinkGroupKeyword : trustedTypesSinkGroupKeywords) {
    927      delete trustedTypesSinkGroupKeyword;
    928    }
    929    return;
    930  }
    931  aDir->addSrcs(trustedTypesSinkGroupKeywords);
    932  mPolicy->addDirective(aDir);
    933 }
    934 
    935 // https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive
    936 static bool IsValidTrustedTypesWildcard(const nsAString& aToken) {
    937  // tt-wildcard = "*"
    938  return aToken.Length() == 1 && aToken.First() == WILDCARD;
    939 }
    940 
    941 static bool IsValidTrustedTypesPolicyNameChar(char16_t aChar) {
    942  // tt-policy-name = 1*( ALPHA / DIGIT / "-" / "#" / "=" / "_" / "/" / "@" /
    943  // "." / "%")
    944  return nsContentUtils::IsAlphanumeric(aChar) || aChar == DASH ||
    945         aChar == NUMBER_SIGN || aChar == EQUALS || aChar == UNDERLINE ||
    946         aChar == SLASH || aChar == ATSYMBOL || aChar == DOT ||
    947         aChar == PERCENT_SIGN;
    948 }
    949 
    950 // https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive
    951 static bool IsValidTrustedTypesPolicyName(const nsAString& aToken) {
    952  // tt-policy-name = 1*( ALPHA / DIGIT / "-" / "#" / "=" / "_" / "/" / "@" /
    953  // "." / "%")
    954 
    955  if (aToken.IsEmpty()) {
    956    return false;
    957  }
    958 
    959  for (uint32_t i = 0; i < aToken.Length(); ++i) {
    960    if (!IsValidTrustedTypesPolicyNameChar(aToken.CharAt(i))) {
    961      return false;
    962    }
    963  }
    964 
    965  return true;
    966 }
    967 
    968 void nsCSPParser::handleTrustedTypesDirective(nsCSPDirective* aDir) {
    969  CSPPARSERLOG(("nsCSPParser::handleTrustedTypesDirective"));
    970 
    971  nsTArray<nsCSPBaseSrc*> trustedTypesExpressions;
    972 
    973  bool containsKeywordNone = false;
    974 
    975  // "srcs" start and index 1. Here they should represent the tt-expressions
    976  // (https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive).
    977  for (uint32_t i = 1; i < mCurDir.Length(); ++i) {
    978    mCurToken = mCurDir[i];
    979 
    980    CSPPARSERLOG(("nsCSPParser::handleTrustedTypesDirective, mCurToken: %s",
    981                  NS_ConvertUTF16toUTF8(mCurToken).get()));
    982 
    983    // tt-expression = tt-policy-name  / tt-keyword / tt-wildcard
    984    if (IsValidTrustedTypesPolicyName(mCurToken)) {
    985      trustedTypesExpressions.AppendElement(
    986          new nsCSPTrustedTypesDirectivePolicyName(mCurToken));
    987    } else if (CSP_IsKeyword(mCurToken, CSP_NONE)) {
    988      containsKeywordNone = true;
    989    } else if (CSP_IsKeyword(mCurToken, CSP_ALLOW_DUPLICATES)) {
    990      trustedTypesExpressions.AppendElement(
    991          new nsCSPKeywordSrc(CSP_ALLOW_DUPLICATES));
    992    } else if (IsValidTrustedTypesWildcard(mCurToken)) {
    993      trustedTypesExpressions.AppendElement(
    994          new nsCSPTrustedTypesDirectivePolicyName(mCurToken));
    995    } else {
    996      AutoTArray<nsString, 1> token = {mCurToken};
    997      logWarningErrorToConsole(nsIScriptError::warningFlag,
    998                               "invalidTrustedTypesExpression", token);
    999      trustedTypesExpressions.AppendElement(
   1000          new nsCSPTrustedTypesDirectiveInvalidToken(mCurToken));
   1001    }
   1002  }
   1003 
   1004  if (trustedTypesExpressions.IsEmpty()) {
   1005    // No tt-expression is equivalent to 'none', see
   1006    // <https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive>.
   1007    trustedTypesExpressions.AppendElement(new nsCSPKeywordSrc(CSP_NONE));
   1008  } else if (containsKeywordNone) {
   1009    // See step 2.4's note at
   1010    // <https://w3c.github.io/trusted-types/dist/spec/#should-block-create-policy>.
   1011    logWarningForIgnoringNoneKeywordToConsole();
   1012  }
   1013 
   1014  aDir->addSrcs(trustedTypesExpressions);
   1015  mPolicy->addDirective(aDir);
   1016 }
   1017 
   1018 // directive-value = *( WSP / <VCHAR except ";" and ","> )
   1019 void nsCSPParser::directiveValue(nsTArray<nsCSPBaseSrc*>& outSrcs) {
   1020  CSPPARSERLOG(("nsCSPParser::directiveValue"));
   1021 
   1022  // Just forward to sourceList
   1023  sourceList(outSrcs);
   1024 }
   1025 
   1026 // directive-name = 1*( ALPHA / DIGIT / "-" )
   1027 nsCSPDirective* nsCSPParser::directiveName() {
   1028  CSPPARSERLOG(("nsCSPParser::directiveName, mCurToken: %s, mCurValue: %s",
   1029                NS_ConvertUTF16toUTF8(mCurToken).get(),
   1030                NS_ConvertUTF16toUTF8(mCurValue).get()));
   1031 
   1032  // Check if it is a valid directive
   1033  CSPDirective directive = CSP_StringToCSPDirective(mCurToken);
   1034  if (directive == nsIContentSecurityPolicy::NO_DIRECTIVE ||
   1035      (!StaticPrefs::dom_security_trusted_types_enabled() &&
   1036       (directive ==
   1037            nsIContentSecurityPolicy::REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE ||
   1038        directive == nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE))) {
   1039    AutoTArray<nsString, 1> params = {mCurToken};
   1040    logWarningErrorToConsole(nsIScriptError::warningFlag,
   1041                             "couldNotProcessUnknownDirective", params);
   1042    return nullptr;
   1043  }
   1044 
   1045  // The directive 'reflected-xss' is part of CSP 1.1, see:
   1046  // http://www.w3.org/TR/2014/WD-CSP11-20140211/#reflected-xss
   1047  // Currently we are not supporting that directive, hence we log a
   1048  // warning to the console and ignore the directive including its values.
   1049  if (directive == nsIContentSecurityPolicy::REFLECTED_XSS_DIRECTIVE) {
   1050    AutoTArray<nsString, 1> params = {mCurToken};
   1051    logWarningErrorToConsole(nsIScriptError::warningFlag,
   1052                             "notSupportingDirective", params);
   1053    return nullptr;
   1054  }
   1055 
   1056  // Make sure the directive does not already exist
   1057  // (see http://www.w3.org/TR/CSP11/#parsing)
   1058  if (mPolicy->hasDirective(directive)) {
   1059    AutoTArray<nsString, 1> params = {mCurToken};
   1060    logWarningErrorToConsole(nsIScriptError::warningFlag, "duplicateDirective",
   1061                             params);
   1062    return nullptr;
   1063  }
   1064 
   1065  // CSP delivered via meta tag should ignore the following directives:
   1066  // report-uri, frame-ancestors, and sandbox, see:
   1067  // http://www.w3.org/TR/CSP11/#delivery-html-meta-element
   1068  if (mDeliveredViaMetaTag &&
   1069      ((directive == nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE) ||
   1070       (directive == nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE) ||
   1071       (directive == nsIContentSecurityPolicy::SANDBOX_DIRECTIVE))) {
   1072    // log to the console to indicate that meta CSP is ignoring the directive
   1073    AutoTArray<nsString, 1> params = {mCurToken};
   1074    logWarningErrorToConsole(nsIScriptError::warningFlag,
   1075                             "ignoringSrcFromMetaCSP", params);
   1076    return nullptr;
   1077  }
   1078 
   1079  // special case handling for block-all-mixed-content
   1080  if (directive == nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT) {
   1081    // If mixed content upgrade is enabled for display content, then
   1082    // block-all-mixed-content is obsolete.
   1083    if (mozilla::StaticPrefs::
   1084            security_mixed_content_upgrade_display_content()) {
   1085      // log to the console that if mixed content display upgrading is enabled
   1086      // block-all-mixed-content is obsolete.
   1087      AutoTArray<nsString, 1> params = {mCurToken};
   1088      logWarningErrorToConsole(nsIScriptError::warningFlag,
   1089                               "obsoleteBlockAllMixedContent", params);
   1090    }
   1091    return new nsBlockAllMixedContentDirective(directive);
   1092  }
   1093 
   1094  // special case handling for upgrade-insecure-requests
   1095  if (directive == nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE) {
   1096    return new nsUpgradeInsecureDirective(directive);
   1097  }
   1098 
   1099  // if we have a child-src, cache it as a fallback for
   1100  //   * workers (if worker-src is not explicitly specified)
   1101  //   * frames  (if frame-src is not explicitly specified)
   1102  if (directive == nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE) {
   1103    mChildSrc = new nsCSPChildSrcDirective(directive);
   1104    return mChildSrc;
   1105  }
   1106 
   1107  // if we have a frame-src, cache it so we can discard child-src for frames
   1108  if (directive == nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE) {
   1109    mFrameSrc = new nsCSPDirective(directive);
   1110    return mFrameSrc;
   1111  }
   1112 
   1113  // if we have a worker-src, cache it so we can discard child-src for workers
   1114  if (directive == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) {
   1115    mWorkerSrc = new nsCSPDirective(directive);
   1116    return mWorkerSrc;
   1117  }
   1118 
   1119  // if we have a script-src, cache it as a fallback for worker-src
   1120  // in case child-src is not present. It is also used as a fallback for
   1121  // script-src-elem and script-src-attr.
   1122  if (directive == nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE) {
   1123    mScriptSrc = new nsCSPScriptSrcDirective(directive);
   1124    return mScriptSrc;
   1125  }
   1126 
   1127  // If we have a style-src, cache it as a fallback for style-src-elem and
   1128  // style-src-attr.
   1129  if (directive == nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE) {
   1130    mStyleSrc = new nsCSPStyleSrcDirective(directive);
   1131    return mStyleSrc;
   1132  }
   1133 
   1134  return new nsCSPDirective(directive);
   1135 }
   1136 
   1137 // directive = *WSP [ directive-name [ WSP directive-value ] ]
   1138 void nsCSPParser::directive() {
   1139  // Make sure that the directive-srcs-array contains at least
   1140  // one directive.
   1141  if (mCurDir.Length() == 0) {
   1142    AutoTArray<nsString, 1> params = {u"directive missing"_ns};
   1143    logWarningErrorToConsole(nsIScriptError::warningFlag,
   1144                             "failedToParseUnrecognizedSource", params);
   1145    return;
   1146  }
   1147 
   1148  // Set the directiveName to mCurToken
   1149  // Remember, the directive name is stored at index 0
   1150  mCurToken = mCurDir[0];
   1151 
   1152  CSPPARSERLOG(("nsCSPParser::directive, mCurToken: %s, mCurValue: %s",
   1153                NS_ConvertUTF16toUTF8(mCurToken).get(),
   1154                NS_ConvertUTF16toUTF8(mCurValue).get()));
   1155 
   1156  if (CSP_IsEmptyDirective(mCurValue, mCurToken)) {
   1157    return;
   1158  }
   1159 
   1160  // Try to create a new CSPDirective
   1161  nsCSPDirective* cspDir = directiveName();
   1162  if (!cspDir) {
   1163    // if we can not create a CSPDirective, we can skip parsing the srcs for
   1164    // that array
   1165    return;
   1166  }
   1167 
   1168  // special case handling for block-all-mixed-content, which is only specified
   1169  // by a directive name but does not include any srcs.
   1170  if (cspDir->equals(nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT)) {
   1171    if (mCurDir.Length() > 1) {
   1172      AutoTArray<nsString, 1> params = {u"block-all-mixed-content"_ns};
   1173      logWarningErrorToConsole(nsIScriptError::warningFlag,
   1174                               "ignoreSrcForDirective", params);
   1175    }
   1176    // add the directive and return
   1177    mPolicy->addDirective(cspDir);
   1178    return;
   1179  }
   1180 
   1181  // special case handling for upgrade-insecure-requests, which is only
   1182  // specified by a directive name but does not include any srcs.
   1183  if (cspDir->equals(nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)) {
   1184    if (mCurDir.Length() > 1) {
   1185      AutoTArray<nsString, 1> params = {u"upgrade-insecure-requests"_ns};
   1186      logWarningErrorToConsole(nsIScriptError::warningFlag,
   1187                               "ignoreSrcForDirective", params);
   1188    }
   1189    // add the directive and return
   1190    mPolicy->addUpgradeInsecDir(
   1191        static_cast<nsUpgradeInsecureDirective*>(cspDir));
   1192    return;
   1193  }
   1194 
   1195  // special case handling for report-uri directive (since it doesn't contain
   1196  // a valid source list but rather actual URIs)
   1197  if (CSP_IsDirective(mCurDir[0],
   1198                      nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
   1199    reportURIList(cspDir);
   1200    return;
   1201  }
   1202 
   1203  // special case handling for report-to directive (since it doesn't contain
   1204  // a valid source list but rather an endpoint group)
   1205  if (CSP_IsDirective(mCurDir[0],
   1206                      nsIContentSecurityPolicy::REPORT_TO_DIRECTIVE)) {
   1207    reportGroup(cspDir);
   1208    return;
   1209  }
   1210 
   1211  // special case handling for sandbox directive (since it doe4sn't contain
   1212  // a valid source list but rather special sandbox flags)
   1213  if (CSP_IsDirective(mCurDir[0],
   1214                      nsIContentSecurityPolicy::SANDBOX_DIRECTIVE)) {
   1215    sandboxFlagList(cspDir);
   1216    return;
   1217  }
   1218 
   1219  // Special case handling since these directives don't contain source lists.
   1220  if (CSP_IsDirective(
   1221          mCurDir[0],
   1222          nsIContentSecurityPolicy::REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE)) {
   1223    handleRequireTrustedTypesForDirective(cspDir);
   1224    return;
   1225  }
   1226 
   1227  if (cspDir->equals(nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE)) {
   1228    handleTrustedTypesDirective(cspDir);
   1229    return;
   1230  }
   1231 
   1232  // make sure to reset cache variables when trying to invalidate unsafe-inline;
   1233  // unsafe-inline might not only appear in script-src, but also in default-src
   1234  mHasHashOrNonce = false;
   1235  mHasAnyUnsafeEval = false;
   1236  mStrictDynamic = false;
   1237  mUnsafeInlineKeywordSrc = nullptr;
   1238 
   1239  mParsingFrameAncestorsDir = CSP_IsDirective(
   1240      mCurDir[0], nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE);
   1241 
   1242  // Try to parse all the srcs by handing the array off to directiveValue
   1243  nsTArray<nsCSPBaseSrc*> srcs;
   1244  directiveValue(srcs);
   1245 
   1246  // If we can not parse any srcs; we let the source expression be the empty set
   1247  // ('none') see, http://www.w3.org/TR/CSP11/#source-list-parsing
   1248  if (srcs.IsEmpty() || (srcs.Length() == 1 && srcs[0]->isReportSample())) {
   1249    nsCSPKeywordSrc* keyword = new nsCSPKeywordSrc(CSP_NONE);
   1250    srcs.InsertElementAt(0, keyword);
   1251  }
   1252 
   1253  MaybeWarnAboutIgnoredSources(srcs);
   1254  MaybeWarnAboutUnsafeInline(*cspDir);
   1255  MaybeWarnAboutUnsafeEval(*cspDir);
   1256 
   1257  // Add the newly created srcs to the directive and add the directive to the
   1258  // policy
   1259  cspDir->addSrcs(srcs);
   1260  mPolicy->addDirective(cspDir);
   1261 }
   1262 
   1263 void nsCSPParser::MaybeWarnAboutIgnoredSources(
   1264    const nsTArray<nsCSPBaseSrc*>& aSrcs) {
   1265  // If policy contains 'strict-dynamic' warn about ignored sources.
   1266  if (mStrictDynamic &&
   1267      !CSP_IsDirective(mCurDir[0],
   1268                       nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE)) {
   1269    for (uint32_t i = 0; i < aSrcs.Length(); i++) {
   1270      nsAutoString srcStr;
   1271      aSrcs[i]->toString(srcStr);
   1272      // Hashes and nonces continue to apply with 'strict-dynamic', as well as
   1273      // 'unsafe-eval', 'wasm-unsafe-eval', 'trusted-types-eval' and
   1274      // 'unsafe-hashes'.
   1275      if (!aSrcs[i]->isKeyword(CSP_STRICT_DYNAMIC) &&
   1276          !aSrcs[i]->isKeyword(CSP_TRUSTED_TYPES_EVAL) &&
   1277          !aSrcs[i]->isKeyword(CSP_UNSAFE_EVAL) &&
   1278          !aSrcs[i]->isKeyword(CSP_WASM_UNSAFE_EVAL) &&
   1279          !aSrcs[i]->isKeyword(CSP_UNSAFE_HASHES) && !aSrcs[i]->isNonce() &&
   1280          !aSrcs[i]->isHash()) {
   1281        AutoTArray<nsString, 2> params = {srcStr, mCurDir[0]};
   1282        logWarningErrorToConsole(nsIScriptError::warningFlag,
   1283                                 "ignoringScriptSrcForStrictDynamic", params);
   1284      }
   1285    }
   1286 
   1287    // Log a warning that all scripts might be blocked because the policy
   1288    // contains 'strict-dynamic' but no valid nonce or hash.
   1289    if (!mHasHashOrNonce) {
   1290      AutoTArray<nsString, 1> params = {mCurDir[0]};
   1291      logWarningErrorToConsole(nsIScriptError::warningFlag,
   1292                               "strictDynamicButNoHashOrNonce", params);
   1293    }
   1294  }
   1295 }
   1296 
   1297 void nsCSPParser::MaybeWarnAboutUnsafeInline(const nsCSPDirective& aDirective) {
   1298  // From https://w3c.github.io/webappsec-csp/#allow-all-inline
   1299  // follows that when either a hash or nonce is specified, 'unsafe-inline'
   1300  // should not apply.
   1301  if (mHasHashOrNonce && mUnsafeInlineKeywordSrc &&
   1302      (aDirective.isDefaultDirective() ||
   1303       aDirective.equals(nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE) ||
   1304       aDirective.equals(nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) ||
   1305       aDirective.equals(nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) ||
   1306       aDirective.equals(nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE) ||
   1307       aDirective.equals(nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE) ||
   1308       aDirective.equals(nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE))) {
   1309    // Log to the console that unsafe-inline will be ignored.
   1310    AutoTArray<nsString, 2> params = {u"'unsafe-inline'"_ns, mCurDir[0]};
   1311    logWarningErrorToConsole(nsIScriptError::warningFlag,
   1312                             "ignoringSrcWithinNonceOrHashDirective", params);
   1313  }
   1314 }
   1315 
   1316 void nsCSPParser::MaybeWarnAboutUnsafeEval(const nsCSPDirective& aDirective) {
   1317  if (mHasAnyUnsafeEval &&
   1318      (aDirective.equals(nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) ||
   1319       aDirective.equals(
   1320           nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE))) {
   1321    // Log to the console that (wasm-)unsafe-eval will be ignored.
   1322    AutoTArray<nsString, 1> params = {mCurDir[0]};
   1323    logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringUnsafeEval",
   1324                             params);
   1325  }
   1326 }
   1327 
   1328 // policy = [ directive *( ";" [ directive ] ) ]
   1329 nsCSPPolicy* nsCSPParser::policy() {
   1330  CSPPARSERLOG(("nsCSPParser::policy"));
   1331 
   1332  mPolicy = new nsCSPPolicy();
   1333  for (uint32_t i = 0; i < mTokens.Length(); i++) {
   1334    // https://w3c.github.io/webappsec-csp/#parse-serialized-policy
   1335    // Step 2.2. ..., or if token is not an ASCII string, continue.
   1336    // https://w3c.github.io/webappsec-csp/#grammardef-directive-value
   1337    // Also, if token contains characters outside 0x21-0x7E, continue.
   1338    //
   1339    // Note: In the spec the token isn't split by whitespace yet.
   1340    bool isValid = true;
   1341    for (const auto& token : mTokens[i]) {
   1342      if (CSP_IsInvalidDirectiveValue(token)) {
   1343        AutoTArray<nsString, 1> params = {mTokens[i][0], token};
   1344        logWarningErrorToConsole(nsIScriptError::warningFlag,
   1345                                 "ignoringInvalidToken", params);
   1346        isValid = false;
   1347        break;
   1348      }
   1349    }
   1350    if (!isValid) {
   1351      continue;
   1352    }
   1353 
   1354    // All input is already tokenized; set one tokenized array in the form of
   1355    // [ name, src, src, ... ]
   1356    // to mCurDir and call directive which processes the current directive.
   1357    mCurDir = mTokens[i].Clone();
   1358    directive();
   1359  }
   1360 
   1361  if (mChildSrc) {
   1362    if (!mFrameSrc) {
   1363      // if frame-src is specified explicitly for that policy than child-src
   1364      // should not restrict frames; if not, than child-src needs to restrict
   1365      // frames.
   1366      mChildSrc->setRestrictFrames();
   1367    }
   1368    if (!mWorkerSrc) {
   1369      // if worker-src is specified explicitly for that policy than child-src
   1370      // should not restrict workers; if not, than child-src needs to restrict
   1371      // workers.
   1372      mChildSrc->setRestrictWorkers();
   1373    }
   1374  }
   1375 
   1376  // if script-src is specified, but not worker-src and also no child-src, then
   1377  // script-src has to govern workers.
   1378  if (mScriptSrc && !mWorkerSrc && !mChildSrc) {
   1379    mScriptSrc->setRestrictWorkers();
   1380  }
   1381 
   1382  // If script-src is specified and script-src-elem is not specified, then
   1383  // script-src has to govern script requests and script blocks.
   1384  if (mScriptSrc && !mPolicy->hasDirective(
   1385                        nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE)) {
   1386    mScriptSrc->setRestrictScriptElem();
   1387  }
   1388 
   1389  // If script-src is specified and script-src-attr is not specified, then
   1390  // script-src has to govern script attr (event handlers).
   1391  if (mScriptSrc && !mPolicy->hasDirective(
   1392                        nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE)) {
   1393    mScriptSrc->setRestrictScriptAttr();
   1394  }
   1395 
   1396  // If style-src is specified and style-src-elem is not specified, then
   1397  // style-src serves as a fallback.
   1398  if (mStyleSrc && !mPolicy->hasDirective(
   1399                       nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE)) {
   1400    mStyleSrc->setRestrictStyleElem();
   1401  }
   1402 
   1403  // If style-src is specified and style-attr-elem is not specified, then
   1404  // style-src serves as a fallback.
   1405  if (mStyleSrc && !mPolicy->hasDirective(
   1406                       nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE)) {
   1407    mStyleSrc->setRestrictStyleAttr();
   1408  }
   1409 
   1410  return mPolicy;
   1411 }
   1412 
   1413 nsCSPPolicy* nsCSPParser::parseContentSecurityPolicy(
   1414    const nsAString& aPolicyString, nsIURI* aSelfURI, bool aReportOnly,
   1415    nsCSPContext* aCSPContext, bool aDeliveredViaMetaTag,
   1416    bool aSuppressLogMessages) {
   1417  if (CSPPARSERLOGENABLED()) {
   1418    CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, policy: %s",
   1419                  NS_ConvertUTF16toUTF8(aPolicyString).get()));
   1420    CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, selfURI: %s",
   1421                  aSelfURI->GetSpecOrDefault().get()));
   1422    CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, reportOnly: %s",
   1423                  (aReportOnly ? "true" : "false")));
   1424    CSPPARSERLOG(
   1425        ("nsCSPParser::parseContentSecurityPolicy, deliveredViaMetaTag: %s",
   1426         (aDeliveredViaMetaTag ? "true" : "false")));
   1427  }
   1428 
   1429  NS_ASSERTION(aSelfURI, "Can not parseContentSecurityPolicy without aSelfURI");
   1430 
   1431  // Separate all input into tokens and store them in the form of:
   1432  // [ [ name, src, src, ... ], [ name, src, src, ... ], ... ]
   1433  // The tokenizer itself can not fail; all eventual errors
   1434  // are detected in the parser itself.
   1435 
   1436  nsTArray<CopyableTArray<nsString> > tokens;
   1437  PolicyTokenizer::tokenizePolicy(aPolicyString, tokens);
   1438 
   1439  nsCSPParser parser(tokens, aSelfURI, aCSPContext, aDeliveredViaMetaTag,
   1440                     aSuppressLogMessages);
   1441 
   1442  // Start the parser to generate a new CSPPolicy using the generated tokens.
   1443  nsCSPPolicy* policy = parser.policy();
   1444 
   1445  // Check that report-only policies define a report-uri, otherwise log warning.
   1446  if (aReportOnly) {
   1447    policy->setReportOnlyFlag(true);
   1448    if (!policy->hasDirective(nsIContentSecurityPolicy::REPORT_TO_DIRECTIVE) &&
   1449        !policy->hasDirective(nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE) &&
   1450        !CSP_IsBrowserXHTML(aSelfURI)) {
   1451      nsAutoCString prePath;
   1452      nsresult rv = aSelfURI->GetPrePath(prePath);
   1453      NS_ENSURE_SUCCESS(rv, policy);
   1454      AutoTArray<nsString, 1> params;
   1455      CopyUTF8toUTF16(prePath, *params.AppendElement());
   1456      parser.logWarningErrorToConsole(
   1457          nsIScriptError::warningFlag,
   1458          "reportURINorReportToNotInReportOnlyHeader", params);
   1459    }
   1460  }
   1461 
   1462  policy->setDeliveredViaMetaTagFlag(aDeliveredViaMetaTag);
   1463 
   1464  if (policy->getNumDirectives() == 0) {
   1465    // Individual errors were already reported in the parser, but if
   1466    // we do not have an enforcable directive at all, we return null.
   1467    delete policy;
   1468    return nullptr;
   1469  }
   1470 
   1471  if (CSPPARSERLOGENABLED()) {
   1472    nsString parsedPolicy;
   1473    policy->toString(parsedPolicy);
   1474    CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, parsedPolicy: %s",
   1475                  NS_ConvertUTF16toUTF8(parsedPolicy).get()));
   1476  }
   1477 
   1478  return policy;
   1479 }