tor-browser

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

nsCSPUtils.cpp (68251B)


      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 "nsCSPUtils.h"
      8 
      9 #include "mozilla/Assertions.h"
     10 #include "mozilla/Components.h"
     11 #include "mozilla/StaticPrefs_security.h"
     12 #include "mozilla/dom/CSPDictionariesBinding.h"
     13 #include "mozilla/dom/Document.h"
     14 #include "mozilla/dom/PolicyContainer.h"
     15 #include "mozilla/dom/SRIMetadata.h"
     16 #include "mozilla/dom/TrustedTypesConstants.h"
     17 #include "nsAboutProtocolUtils.h"
     18 #include "nsAttrValue.h"
     19 #include "nsCSPParser.h"
     20 #include "nsCharSeparatedTokenizer.h"
     21 #include "nsComponentManagerUtils.h"
     22 #include "nsContentUtils.h"
     23 #include "nsDebug.h"
     24 #include "nsIChannel.h"
     25 #include "nsIConsoleService.h"
     26 #include "nsIContentSecurityPolicy.h"
     27 #include "nsICryptoHash.h"
     28 #include "nsIScriptError.h"
     29 #include "nsIStringBundle.h"
     30 #include "nsIURL.h"
     31 #include "nsNetUtil.h"
     32 #include "nsReadableUtils.h"
     33 #include "nsSandboxFlags.h"
     34 #include "nsServiceManagerUtils.h"
     35 #include "nsWhitespaceTokenizer.h"
     36 
     37 using namespace mozilla;
     38 using mozilla::dom::SRIMetadata;
     39 
     40 #define DEFAULT_PORT -1
     41 
     42 static mozilla::LogModule* GetCspUtilsLog() {
     43  static mozilla::LazyLogModule gCspUtilsPRLog("CSPUtils");
     44  return gCspUtilsPRLog;
     45 }
     46 
     47 #define CSPUTILSLOG(args) \
     48  MOZ_LOG(GetCspUtilsLog(), mozilla::LogLevel::Debug, args)
     49 #define CSPUTILSLOGENABLED() \
     50  MOZ_LOG_TEST(GetCspUtilsLog(), mozilla::LogLevel::Debug)
     51 
     52 void CSP_PercentDecodeStr(const nsAString& aEncStr, nsAString& outDecStr) {
     53  outDecStr.Truncate();
     54 
     55  // helper function that should not be visible outside this methods scope
     56  struct local {
     57    static inline char16_t convertHexDig(char16_t aHexDig) {
     58      if (isNumberToken(aHexDig)) {
     59        return aHexDig - '0';
     60      }
     61      if (aHexDig >= 'A' && aHexDig <= 'F') {
     62        return aHexDig - 'A' + 10;
     63      }
     64      // must be a lower case character
     65      // (aHexDig >= 'a' && aHexDig <= 'f')
     66      return aHexDig - 'a' + 10;
     67    }
     68  };
     69 
     70  const char16_t *cur, *end, *hexDig1, *hexDig2;
     71  cur = aEncStr.BeginReading();
     72  end = aEncStr.EndReading();
     73 
     74  while (cur != end) {
     75    // if it's not a percent sign then there is
     76    // nothing to do for that character
     77    if (*cur != PERCENT_SIGN) {
     78      outDecStr.Append(*cur);
     79      cur++;
     80      continue;
     81    }
     82 
     83    // get the two hexDigs following the '%'-sign
     84    hexDig1 = cur + 1;
     85    hexDig2 = cur + 2;
     86 
     87    // if there are no hexdigs after the '%' then
     88    // there is nothing to do for us.
     89    if (hexDig1 == end || hexDig2 == end || !isValidHexDig(*hexDig1) ||
     90        !isValidHexDig(*hexDig2)) {
     91      outDecStr.Append(PERCENT_SIGN);
     92      cur++;
     93      continue;
     94    }
     95 
     96    // decode "% hexDig1 hexDig2" into a character.
     97    char16_t decChar =
     98        (local::convertHexDig(*hexDig1) << 4) + local::convertHexDig(*hexDig2);
     99    outDecStr.Append(decChar);
    100 
    101    // increment 'cur' to after the second hexDig
    102    cur = ++hexDig2;
    103  }
    104 }
    105 
    106 // The Content Security Policy should be inherited for
    107 // local schemes like: "about", "blob", "data", or "filesystem".
    108 // see: https://w3c.github.io/webappsec-csp/#initialize-document-csp
    109 bool CSP_ShouldResponseInheritCSP(nsIChannel* aChannel) {
    110  if (!aChannel) {
    111    return false;
    112  }
    113 
    114  nsCOMPtr<nsIURI> uri;
    115  nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
    116  NS_ENSURE_SUCCESS(rv, false);
    117 
    118  return CSP_ShouldURIInheritCSP(uri);
    119 }
    120 
    121 bool CSP_ShouldURIInheritCSP(nsIURI* aURI) {
    122  if (!aURI) {
    123    return false;
    124  }
    125  // about:blank and about:srcdoc
    126  if ((aURI->SchemeIs("about")) && (NS_IsContentAccessibleAboutURI(aURI))) {
    127    return true;
    128  }
    129  return aURI->SchemeIs("blob") || aURI->SchemeIs("data") ||
    130         aURI->SchemeIs("filesystem") || aURI->SchemeIs("javascript");
    131 }
    132 
    133 void CSP_ApplyMetaCSPToDoc(mozilla::dom::Document& aDoc,
    134                           const nsAString& aPolicyStr) {
    135  if (aDoc.IsLoadedAsData()) {
    136    return;
    137  }
    138 
    139  nsAutoString policyStr(
    140      nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
    141          aPolicyStr));
    142 
    143  if (policyStr.IsEmpty()) {
    144    return;
    145  }
    146 
    147  nsCOMPtr<nsIContentSecurityPolicy> csp =
    148      PolicyContainer::GetCSP(aDoc.GetPolicyContainer());
    149  if (!csp) {
    150    MOZ_ASSERT(false, "how come there is no CSP");
    151    return;
    152  }
    153 
    154  if (nsIURI* uri = aDoc.GetDocumentURI(); CSP_IsBrowserXHTML(uri)) {
    155    // Make the <meta> policy in browser.xhtml toggleable.
    156    if (!StaticPrefs::security_browser_xhtml_csp_enabled()) {
    157      return;
    158    }
    159  }
    160 
    161  // Multiple CSPs (delivered through either header of meta tag) need to
    162  // be joined together, see:
    163  // https://w3c.github.io/webappsec/specs/content-security-policy/#delivery-html-meta-element
    164  nsresult rv = csp->AppendPolicy(
    165      policyStr,
    166      false,  // CSPs delivered via a <meta> tag can not be report-only.
    167      true);  // delivered through the meta tag
    168  NS_ENSURE_SUCCESS_VOID(rv);
    169  if (nsPIDOMWindowInner* inner = aDoc.GetInnerWindow()) {
    170    if (nsIPolicyContainer* policyContainer = inner->GetPolicyContainer()) {
    171      inner->SetPolicyContainer(policyContainer);
    172    } else {
    173      RefPtr<PolicyContainer> newPolicyContainer = new PolicyContainer();
    174      inner->SetPolicyContainer(newPolicyContainer);
    175    }
    176  }
    177  aDoc.ApplySettingsFromCSP(false);
    178 }
    179 
    180 bool CSP_IsBrowserXHTML(nsIURI* aURI) {
    181  if (!aURI->SchemeIs("chrome")) {
    182    return false;
    183  }
    184 
    185  nsAutoCString spec;
    186  aURI->GetSpec(spec);
    187  return spec.EqualsLiteral("chrome://browser/content/browser.xhtml");
    188 }
    189 
    190 void CSP_GetLocalizedStr(const char* aName, const nsTArray<nsString>& aParams,
    191                         nsAString& outResult) {
    192  nsCOMPtr<nsIStringBundle> keyStringBundle;
    193  nsCOMPtr<nsIStringBundleService> stringBundleService =
    194      mozilla::components::StringBundle::Service();
    195 
    196  NS_ASSERTION(stringBundleService, "String bundle service must be present!");
    197  stringBundleService->CreateBundle(
    198      "chrome://global/locale/security/csp.properties",
    199      getter_AddRefs(keyStringBundle));
    200 
    201  NS_ASSERTION(keyStringBundle, "Key string bundle must be available!");
    202 
    203  if (!keyStringBundle) {
    204    return;
    205  }
    206 
    207  if (aParams.IsEmpty()) {
    208    keyStringBundle->GetStringFromName(aName, outResult);
    209  } else {
    210    keyStringBundle->FormatStringFromName(aName, aParams, outResult);
    211  }
    212 }
    213 
    214 void CSP_LogStrMessage(const nsAString& aMsg) {
    215  nsCOMPtr<nsIConsoleService> console(
    216      do_GetService("@mozilla.org/consoleservice;1"));
    217 
    218  if (!console) {
    219    return;
    220  }
    221  nsString msg(aMsg);
    222  console->LogStringMessage(msg.get());
    223 }
    224 
    225 void CSP_LogMessage(const nsAString& aMessage, const nsACString& aSourceName,
    226                    const nsAString& aSourceLine, uint32_t aLineNumber,
    227                    uint32_t aColumnNumber, uint32_t aFlags,
    228                    const nsACString& aCategory, uint64_t aInnerWindowID,
    229                    bool aFromPrivateWindow) {
    230  nsCOMPtr<nsIConsoleService> console(
    231      do_GetService(NS_CONSOLESERVICE_CONTRACTID));
    232 
    233  nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
    234 
    235  if (!console || !error) {
    236    return;
    237  }
    238 
    239  // Prepending CSP to the outgoing console message
    240  nsString cspMsg;
    241  CSP_GetLocalizedStr("CSPMessagePrefix",
    242                      AutoTArray<nsString, 1>{nsString(aMessage)}, cspMsg);
    243 
    244  // Currently 'aSourceLine' is not logged to the console, because similar
    245  // information is already included within the source link of the message.
    246  // For inline violations however, the line and column number are 0 and
    247  // information contained within 'aSourceLine' can be really useful for devs.
    248  // E.g. 'aSourceLine' might be: 'onclick attribute on DIV element'.
    249  // In such cases we append 'aSourceLine' directly to the error message.
    250  if (!aSourceLine.IsEmpty() && aLineNumber == 0) {
    251    cspMsg.AppendLiteral(u"\nSource: ");
    252    cspMsg.Append(aSourceLine);
    253  }
    254 
    255  // Since we are leveraging csp errors as the category names which
    256  // we pass to devtools, we should prepend them with "CSP_" to
    257  // allow easy distincution in devtools code. e.g.
    258  // upgradeInsecureRequest -> CSP_upgradeInsecureRequest
    259  nsCString category("CSP_");
    260  category.Append(aCategory);
    261 
    262  nsresult rv;
    263  if (aInnerWindowID > 0) {
    264    rv =
    265        error->InitWithWindowID(cspMsg, aSourceName, aLineNumber, aColumnNumber,
    266                                aFlags, category, aInnerWindowID);
    267  } else {
    268    rv = error->Init(cspMsg, aSourceName, aLineNumber, aColumnNumber, aFlags,
    269                     category, aFromPrivateWindow,
    270                     true /* from chrome context */);
    271  }
    272  if (NS_FAILED(rv)) {
    273    return;
    274  }
    275  console->LogMessage(error);
    276 }
    277 
    278 CSPDirective CSP_StringToCSPDirective(const nsAString& aDir) {
    279  nsString lowerDir = PromiseFlatString(aDir);
    280  ToLowerCase(lowerDir);
    281 
    282  uint32_t numDirs = (sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0]));
    283 
    284  for (uint32_t i = 1; i < numDirs; i++) {
    285    if (lowerDir.EqualsASCII(CSPStrDirectives[i])) {
    286      return static_cast<CSPDirective>(i);
    287    }
    288  }
    289  return nsIContentSecurityPolicy::NO_DIRECTIVE;
    290 }
    291 
    292 /**
    293 * Combines CSP_LogMessage and CSP_GetLocalizedStr into one call.
    294 */
    295 void CSP_LogLocalizedStr(const char* aName, const nsTArray<nsString>& aParams,
    296                         const nsACString& aSourceName,
    297                         const nsAString& aSourceLine, uint32_t aLineNumber,
    298                         uint32_t aColumnNumber, uint32_t aFlags,
    299                         const nsACString& aCategory, uint64_t aInnerWindowID,
    300                         bool aFromPrivateWindow) {
    301  nsAutoString logMsg;
    302  CSP_GetLocalizedStr(aName, aParams, logMsg);
    303  CSP_LogMessage(logMsg, aSourceName, aSourceLine, aLineNumber, aColumnNumber,
    304                 aFlags, aCategory, aInnerWindowID, aFromPrivateWindow);
    305 }
    306 
    307 /* ===== Helpers ============================ */
    308 // This implements
    309 // https://w3c.github.io/webappsec-csp/#effective-directive-for-a-request.
    310 // However the spec doesn't currently cover all request destinations, which
    311 // we roughly represent using nsContentPolicyType.
    312 CSPDirective CSP_ContentTypeToDirective(nsContentPolicyType aType) {
    313  switch (aType) {
    314    case nsIContentPolicy::TYPE_IMAGE:
    315    case nsIContentPolicy::TYPE_IMAGESET:
    316    case nsIContentPolicy::TYPE_INTERNAL_IMAGE:
    317    case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD:
    318    case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON:
    319    case nsIContentPolicy::TYPE_INTERNAL_EXTERNAL_RESOURCE:
    320      return nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE;
    321 
    322    // BLock XSLT as script, see bug 910139
    323    case nsIContentPolicy::TYPE_XSLT:
    324    case nsIContentPolicy::TYPE_SCRIPT:
    325    case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
    326    case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
    327    case nsIContentPolicy::TYPE_INTERNAL_MODULE:
    328    case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD:
    329    case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS:
    330    case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET:
    331    case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET:
    332    case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT:
    333    case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT:
    334      // (https://github.com/w3c/webappsec-csp/issues/554)
    335      // Some of these types are not explicitly defined in the spec.
    336      //
    337      // Chrome seems to use script-src-elem for worklet!
    338      return nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE;
    339 
    340    case nsIContentPolicy::TYPE_STYLESHEET:
    341    case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET:
    342    case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD:
    343      return nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE;
    344 
    345    case nsIContentPolicy::TYPE_FONT:
    346    case nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD:
    347      return nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE;
    348 
    349    case nsIContentPolicy::TYPE_MEDIA:
    350    case nsIContentPolicy::TYPE_INTERNAL_AUDIO:
    351    case nsIContentPolicy::TYPE_INTERNAL_VIDEO:
    352    case nsIContentPolicy::TYPE_INTERNAL_TRACK:
    353      return nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE;
    354 
    355    case nsIContentPolicy::TYPE_WEB_MANIFEST:
    356      return nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE;
    357 
    358    case nsIContentPolicy::TYPE_INTERNAL_WORKER:
    359    case nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE:
    360    case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
    361    case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
    362      return nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE;
    363 
    364    case nsIContentPolicy::TYPE_SUBDOCUMENT:
    365    case nsIContentPolicy::TYPE_INTERNAL_FRAME:
    366    case nsIContentPolicy::TYPE_INTERNAL_IFRAME:
    367      return nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE;
    368 
    369    case nsIContentPolicy::TYPE_WEBSOCKET:
    370    case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
    371    case nsIContentPolicy::TYPE_BEACON:
    372    case nsIContentPolicy::TYPE_PING:
    373    case nsIContentPolicy::TYPE_FETCH:
    374    case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST_ASYNC:
    375    case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST_SYNC:
    376    case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE:
    377    case nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD:
    378    case nsIContentPolicy::TYPE_WEB_IDENTITY:
    379    case nsIContentPolicy::TYPE_WEB_TRANSPORT:
    380    case nsIContentPolicy::TYPE_JSON:
    381    case nsIContentPolicy::TYPE_INTERNAL_JSON_PRELOAD:
    382      return nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE;
    383 
    384    case nsIContentPolicy::TYPE_OBJECT:
    385    case nsIContentPolicy::TYPE_INTERNAL_EMBED:
    386    case nsIContentPolicy::TYPE_INTERNAL_OBJECT:
    387      return nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE;
    388 
    389    case nsIContentPolicy::TYPE_DTD:
    390    case nsIContentPolicy::TYPE_OTHER:
    391    case nsIContentPolicy::TYPE_SPECULATIVE:
    392    case nsIContentPolicy::TYPE_INTERNAL_DTD:
    393    case nsIContentPolicy::TYPE_INTERNAL_FORCE_ALLOWED_DTD:
    394      return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
    395 
    396    // CSP does not apply to webrtc connections
    397    case nsIContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA:
    398    // csp shold not block top level loads, e.g. in case
    399    // of a redirect.
    400    case nsIContentPolicy::TYPE_DOCUMENT:
    401    // CSP can not block csp reports
    402    case nsIContentPolicy::TYPE_CSP_REPORT:
    403      return nsIContentSecurityPolicy::NO_DIRECTIVE;
    404 
    405    case nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD:
    406    case nsIContentPolicy::TYPE_UA_FONT:
    407      return nsIContentSecurityPolicy::NO_DIRECTIVE;
    408 
    409    // Fall through to error for all other directives
    410    case nsIContentPolicy::TYPE_INVALID:
    411    case nsIContentPolicy::TYPE_END:
    412      MOZ_ASSERT(false, "Can not map nsContentPolicyType to CSPDirective");
    413      // Do not add default: so that compilers can catch the missing case.
    414  }
    415  return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
    416 }
    417 
    418 already_AddRefed<nsIContentSecurityPolicy> CSP_CreateFromHeader(
    419    const nsAString& aHeaderValue, nsIURI* aSelfURI,
    420    nsIPrincipal* aLoadingPrincipal, ErrorResult& aRv) {
    421  RefPtr<nsCSPContext> csp = new nsCSPContext();
    422  // Hard code some default values until we have a use case where we can provide
    423  // something else.
    424  // When inheriting from this CSP, these values will be overwritten anyway.
    425  aRv = csp->SetRequestContextWithPrincipal(aLoadingPrincipal, aSelfURI,
    426                                            /* aReferrer */ ""_ns,
    427                                            /* aInnerWindowId */ 0);
    428  if (aRv.Failed()) {
    429    return nullptr;
    430  }
    431 
    432  aRv = CSP_AppendCSPFromHeader(csp, aHeaderValue, /* aReportOnly */ false);
    433  if (aRv.Failed()) {
    434    return nullptr;
    435  }
    436 
    437  return csp.forget();
    438 }
    439 
    440 nsCSPHostSrc* CSP_CreateHostSrcFromSelfURI(nsIURI* aSelfURI) {
    441  // Create the host first
    442  nsCString host;
    443  aSelfURI->GetAsciiHost(host);
    444  nsCSPHostSrc* hostsrc = new nsCSPHostSrc(NS_ConvertUTF8toUTF16(host));
    445  hostsrc->setGeneratedFromSelfKeyword();
    446 
    447  // Add the scheme.
    448  nsCString scheme;
    449  aSelfURI->GetScheme(scheme);
    450  hostsrc->setScheme(NS_ConvertUTF8toUTF16(scheme));
    451 
    452  // An empty host (e.g. for data:) indicates it's effectively a unique origin.
    453  // Please note that we still need to set the scheme on hostsrc (see above),
    454  // because it's used for reporting.
    455  if (host.EqualsLiteral("")) {
    456    hostsrc->setIsUniqueOrigin();
    457    // no need to query the port in that case.
    458    return hostsrc;
    459  }
    460 
    461  int32_t port;
    462  aSelfURI->GetPort(&port);
    463  // Only add port if it's not default port.
    464  if (port > 0) {
    465    nsAutoString portStr;
    466    portStr.AppendInt(port);
    467    hostsrc->setPort(portStr);
    468  }
    469  return hostsrc;
    470 }
    471 
    472 bool CSP_IsEmptyDirective(const nsAString& aValue, const nsAString& aDir) {
    473  return (aDir.Length() == 0 && aValue.Length() == 0);
    474 }
    475 
    476 bool CSP_IsInvalidDirectiveValue(mozilla::Span<const char16_t> aValue) {
    477  for (char16_t c : aValue) {
    478    if (!(c >= 0x21 && c <= 0x7E)) {
    479      return true;
    480    }
    481  }
    482  return false;
    483 }
    484 
    485 bool CSP_IsDirective(const nsAString& aValue, CSPDirective aDir) {
    486  return aValue.LowerCaseEqualsASCII(CSP_CSPDirectiveToString(aDir));
    487 }
    488 
    489 bool CSP_IsKeyword(const nsAString& aValue, enum CSPKeyword aKey) {
    490  return aValue.LowerCaseEqualsASCII(CSP_EnumToUTF8Keyword(aKey));
    491 }
    492 
    493 bool CSP_IsQuotelessKeyword(const nsAString& aKey) {
    494  nsString lowerKey;
    495  ToLowerCase(aKey, lowerKey);
    496 
    497  nsAutoString keyword;
    498  for (uint32_t i = 0; i < CSP_LAST_KEYWORD_VALUE; i++) {
    499    // skipping the leading ' and trimming the trailing '
    500    keyword.AssignASCII(gCSPUTF8Keywords[i] + 1);
    501    keyword.Trim("'", false, true);
    502    if (lowerKey.Equals(keyword)) {
    503      return true;
    504    }
    505  }
    506  return false;
    507 }
    508 
    509 /*
    510 * Checks whether the current directive permits a specific
    511 * scheme. This function is called from nsCSPSchemeSrc() and
    512 * also nsCSPHostSrc.
    513 * @param aEnforcementScheme
    514 *        The scheme that this directive allows
    515 * @param aUri
    516 *        The uri of the subresource load.
    517 * @param aReportOnly
    518 *        Whether the enforced policy is report only or not.
    519 * @param aUpgradeInsecure
    520 *        Whether the policy makes use of the directive
    521 *        'upgrade-insecure-requests'.
    522 * @param aFromSelfURI
    523 *        Whether a scheme was generated from the keyword 'self'
    524 *        which then allows schemeless sources to match ws and wss.
    525 */
    526 
    527 bool permitsScheme(const nsAString& aEnforcementScheme, nsIURI* aUri,
    528                   bool aReportOnly, bool aUpgradeInsecure, bool aFromSelfURI) {
    529  nsAutoCString scheme;
    530  nsresult rv = aUri->GetScheme(scheme);
    531  NS_ENSURE_SUCCESS(rv, false);
    532 
    533  // no scheme to enforce, let's allow the load (e.g. script-src *)
    534  if (aEnforcementScheme.IsEmpty()) {
    535    return true;
    536  }
    537 
    538  // if the scheme matches, all good - allow the load
    539  if (aEnforcementScheme.EqualsASCII(scheme.get())) {
    540    return true;
    541  }
    542 
    543  // allow scheme-less sources where the protected resource is http
    544  // and the load is https, see:
    545  // http://www.w3.org/TR/CSP2/#match-source-expression
    546  if (aEnforcementScheme.EqualsASCII("http")) {
    547    if (scheme.EqualsASCII("https")) {
    548      return true;
    549    }
    550    if ((scheme.EqualsASCII("ws") || scheme.EqualsASCII("wss")) &&
    551        aFromSelfURI) {
    552      return true;
    553    }
    554  }
    555  if (aEnforcementScheme.EqualsASCII("https")) {
    556    if (scheme.EqualsLiteral("wss") && aFromSelfURI) {
    557      return true;
    558    }
    559  }
    560  if (aEnforcementScheme.EqualsASCII("ws") && scheme.EqualsASCII("wss")) {
    561    return true;
    562  }
    563 
    564  // Allow the load when enforcing upgrade-insecure-requests with the
    565  // promise the request gets upgraded from http to https and ws to wss.
    566  // See nsHttpChannel::Connect() and also WebSocket.cpp. Please note,
    567  // the report only policies should not allow the load and report
    568  // the error back to the page.
    569  return (
    570      (aUpgradeInsecure && !aReportOnly) &&
    571      ((scheme.EqualsASCII("http") &&
    572        aEnforcementScheme.EqualsASCII("https")) ||
    573       (scheme.EqualsASCII("ws") && aEnforcementScheme.EqualsASCII("wss"))));
    574 }
    575 
    576 /*
    577 * A helper function for appending a CSP header to an existing CSP
    578 * policy.
    579 *
    580 * @param aCsp           the CSP policy
    581 * @param aHeaderValue   the header
    582 * @param aReportOnly    is this a report-only header?
    583 */
    584 
    585 nsresult CSP_AppendCSPFromHeader(nsIContentSecurityPolicy* aCsp,
    586                                 const nsAString& aHeaderValue,
    587                                 bool aReportOnly) {
    588  NS_ENSURE_ARG(aCsp);
    589 
    590  // Need to tokenize the header value since multiple headers could be
    591  // concatenated into one comma-separated list of policies.
    592  // See RFC2616 section 4.2 (last paragraph)
    593  nsresult rv = NS_OK;
    594  for (const nsAString& policy :
    595       nsCharSeparatedTokenizer(aHeaderValue, ',').ToRange()) {
    596    rv = aCsp->AppendPolicy(policy, aReportOnly, false);
    597    NS_ENSURE_SUCCESS(rv, rv);
    598    {
    599      CSPUTILSLOG(("CSP refined with policy: \"%s\"",
    600                   NS_ConvertUTF16toUTF8(policy).get()));
    601    }
    602  }
    603  return NS_OK;
    604 }
    605 
    606 /* ===== nsCSPSrc ============================ */
    607 
    608 nsCSPBaseSrc::nsCSPBaseSrc() {}
    609 
    610 nsCSPBaseSrc::~nsCSPBaseSrc() = default;
    611 
    612 // ::permits is only called for external load requests, therefore:
    613 // nsCSPKeywordSrc and nsCSPHashSource fall back to this base class
    614 // implementation which will never allow the load.
    615 bool nsCSPBaseSrc::permits(nsIURI* aUri, bool aWasRedirected, bool aReportOnly,
    616                           bool aUpgradeInsecure) const {
    617  if (CSPUTILSLOGENABLED()) {
    618    CSPUTILSLOG(
    619        ("nsCSPBaseSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
    620  }
    621  return false;
    622 }
    623 
    624 // ::allows is only called for inlined loads, therefore:
    625 // nsCSPSchemeSrc, nsCSPHostSrc fall back
    626 // to this base class implementation which will never allow the load.
    627 bool nsCSPBaseSrc::allows(enum CSPKeyword aKeyword,
    628                          const nsAString& aHashOrNonce) const {
    629  CSPUTILSLOG(("nsCSPBaseSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
    630               aKeyword == CSP_HASH ? "hash" : CSP_EnumToUTF8Keyword(aKeyword),
    631               NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
    632  return false;
    633 }
    634 
    635 /* ====== nsCSPSchemeSrc ===================== */
    636 
    637 nsCSPSchemeSrc::nsCSPSchemeSrc(const nsAString& aScheme) : mScheme(aScheme) {
    638  ToLowerCase(mScheme);
    639 }
    640 
    641 nsCSPSchemeSrc::~nsCSPSchemeSrc() = default;
    642 
    643 bool nsCSPSchemeSrc::permits(nsIURI* aUri, bool aWasRedirected,
    644                             bool aReportOnly, bool aUpgradeInsecure) const {
    645  if (CSPUTILSLOGENABLED()) {
    646    CSPUTILSLOG(
    647        ("nsCSPSchemeSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
    648  }
    649  MOZ_ASSERT((!mScheme.EqualsASCII("")), "scheme can not be the empty string");
    650  return permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure, false);
    651 }
    652 
    653 bool nsCSPSchemeSrc::visit(nsCSPSrcVisitor* aVisitor) const {
    654  return aVisitor->visitSchemeSrc(*this);
    655 }
    656 
    657 void nsCSPSchemeSrc::toString(nsAString& outStr) const {
    658  outStr.Append(mScheme);
    659  outStr.AppendLiteral(":");
    660 }
    661 
    662 /* ===== nsCSPHostSrc ======================== */
    663 
    664 nsCSPHostSrc::nsCSPHostSrc(const nsAString& aHost)
    665    : mHost(aHost),
    666      mGeneratedFromSelfKeyword(false),
    667      mIsUniqueOrigin(false),
    668      mWithinFrameAncstorsDir(false) {
    669  ToLowerCase(mHost);
    670 }
    671 
    672 nsCSPHostSrc::~nsCSPHostSrc() = default;
    673 
    674 /*
    675 * Checks whether the current directive permits a specific port.
    676 * @param aEnforcementScheme
    677 *        The scheme that this directive allows
    678 *        (used to query the default port for that scheme)
    679 * @param aEnforcementPort
    680 *        The port that this directive allows
    681 * @param aResourceURI
    682 *        The uri of the subresource load
    683 */
    684 bool permitsPort(const nsAString& aEnforcementScheme,
    685                 const nsAString& aEnforcementPort, nsIURI* aResourceURI) {
    686  // If enforcement port is the wildcard, don't block the load.
    687  if (aEnforcementPort.EqualsASCII("*")) {
    688    return true;
    689  }
    690 
    691  int32_t resourcePort;
    692  nsresult rv = aResourceURI->GetPort(&resourcePort);
    693  if (NS_FAILED(rv) && aEnforcementPort.IsEmpty()) {
    694    // If we cannot get a Port (e.g. because of an Custom Protocol handler)
    695    // We need to check if a default port is associated with the Scheme
    696    if (aEnforcementScheme.IsEmpty()) {
    697      return false;
    698    }
    699    int defaultPortforScheme =
    700        NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme).get());
    701 
    702    // If there is no default port associated with the Scheme (
    703    // defaultPortforScheme == -1) or it is an externally handled protocol (
    704    // defaultPortforScheme == 0 ) and the csp does not enforce a port - we can
    705    // allow not having a port
    706    return (defaultPortforScheme == -1 || defaultPortforScheme == -0);
    707  }
    708  // Avoid unnecessary string creation/manipulation and don't block the
    709  // load if the resource to be loaded uses the default port for that
    710  // scheme and there is no port to be enforced.
    711  // Note, this optimization relies on scheme checks within permitsScheme().
    712  if (resourcePort == DEFAULT_PORT && aEnforcementPort.IsEmpty()) {
    713    return true;
    714  }
    715 
    716  // By now we know at that either the resourcePort does not use the default
    717  // port or there is a port restriction to be enforced. A port value of -1
    718  // corresponds to the protocol's default port (eg. -1 implies port 80 for
    719  // http URIs), in such a case we have to query the default port of the
    720  // resource to be loaded.
    721  if (resourcePort == DEFAULT_PORT) {
    722    nsAutoCString resourceScheme;
    723    rv = aResourceURI->GetScheme(resourceScheme);
    724    NS_ENSURE_SUCCESS(rv, false);
    725    resourcePort = NS_GetDefaultPort(resourceScheme.get());
    726  }
    727 
    728  // If there is a port to be enforced and the ports match, then
    729  // don't block the load.
    730  nsString resourcePortStr;
    731  resourcePortStr.AppendInt(resourcePort);
    732  if (aEnforcementPort.Equals(resourcePortStr)) {
    733    return true;
    734  }
    735 
    736  // If there is no port to be enforced, query the default port for the load.
    737  nsString enforcementPort(aEnforcementPort);
    738  if (enforcementPort.IsEmpty()) {
    739    // For scheme less sources, our parser always generates a scheme
    740    // which is the scheme of the protected resource.
    741    MOZ_ASSERT(!aEnforcementScheme.IsEmpty(),
    742               "need a scheme to query default port");
    743    int32_t defaultEnforcementPort =
    744        NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme).get());
    745    enforcementPort.Truncate();
    746    enforcementPort.AppendInt(defaultEnforcementPort);
    747  }
    748 
    749  // If default ports match, don't block the load
    750  if (enforcementPort.Equals(resourcePortStr)) {
    751    return true;
    752  }
    753 
    754  // Additional port matching where the regular URL matching algorithm
    755  // treats insecure ports as matching their secure variants.
    756  // default port for http is  :80
    757  // default port for https is :443
    758  if (enforcementPort.EqualsLiteral("80") &&
    759      resourcePortStr.EqualsLiteral("443")) {
    760    return true;
    761  }
    762 
    763  // ports do not match, block the load.
    764  return false;
    765 }
    766 
    767 bool nsCSPHostSrc::permits(nsIURI* aUri, bool aWasRedirected, bool aReportOnly,
    768                           bool aUpgradeInsecure) const {
    769  if (CSPUTILSLOGENABLED()) {
    770    CSPUTILSLOG(
    771        ("nsCSPHostSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
    772  }
    773 
    774  if (mIsUniqueOrigin) {
    775    return false;
    776  }
    777 
    778  // we are following the enforcement rules from the spec, see:
    779  // http://www.w3.org/TR/CSP11/#match-source-expression
    780 
    781  // 4.3) scheme matching: Check if the scheme matches.
    782  if (!permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure,
    783                     mGeneratedFromSelfKeyword)) {
    784    return false;
    785  }
    786 
    787  // The host in nsCSpHostSrc should never be empty. In case we are enforcing
    788  // just a specific scheme, the parser should generate a nsCSPSchemeSource.
    789  NS_ASSERTION((!mHost.IsEmpty()), "host can not be the empty string");
    790 
    791  // Before we can check if the host matches, we have to
    792  // extract the host part from aUri.
    793  nsAutoCString uriHost;
    794  nsresult rv = aUri->GetAsciiHost(uriHost);
    795  NS_ENSURE_SUCCESS(rv, false);
    796 
    797  nsString decodedUriHost;
    798  CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriHost), decodedUriHost);
    799 
    800  // 2) host matching: Enforce a single *
    801  if (mHost.EqualsASCII("*")) {
    802    // The single ASTERISK character (*) does not match a URI's scheme of a type
    803    // designating a globally unique identifier (such as blob:, data:, or
    804    // filesystem:) At the moment firefox does not support filesystem; but for
    805    // future compatibility we support it in CSP according to the spec,
    806    // see: 4.2.2 Matching Source Expressions Note, that allowlisting any of
    807    // these schemes would call nsCSPSchemeSrc::permits().
    808    if (aUri->SchemeIs("blob") || aUri->SchemeIs("data") ||
    809        aUri->SchemeIs("filesystem")) {
    810      return false;
    811    }
    812 
    813    // If no scheme is present there also wont be a port and folder to check
    814    // which means we can return early
    815    if (mScheme.IsEmpty()) {
    816      return true;
    817    }
    818  }
    819  // 4.5) host matching: Check if the allowed host starts with a wilcard.
    820  else if (mHost.First() == '*') {
    821    NS_ASSERTION(
    822        mHost[1] == '.',
    823        "Second character needs to be '.' whenever host starts with '*'");
    824 
    825    // Eliminate leading "*", but keeping the FULL STOP (.) thereafter before
    826    // checking if the remaining characters match
    827    nsString wildCardHost = mHost;
    828    wildCardHost = Substring(wildCardHost, 1, wildCardHost.Length() - 1);
    829    if (!StringEndsWith(decodedUriHost, wildCardHost)) {
    830      return false;
    831    }
    832  }
    833  // 4.6) host matching: Check if hosts match.
    834  else if (!mHost.Equals(decodedUriHost)) {
    835    return false;
    836  }
    837 
    838  // Port matching: Check if the ports match.
    839  if (!permitsPort(mScheme, mPort, aUri)) {
    840    return false;
    841  }
    842 
    843  // 4.9) Path matching: If there is a path, we have to enforce
    844  // path-level matching, unless the channel got redirected, see:
    845  // http://www.w3.org/TR/CSP11/#source-list-paths-and-redirects
    846  if (!aWasRedirected && !mPath.IsEmpty()) {
    847    // converting aUri into nsIURL so we can strip query and ref
    848    // example.com/test#foo     -> example.com/test
    849    // example.com/test?val=foo -> example.com/test
    850    nsCOMPtr<nsIURL> url = do_QueryInterface(aUri);
    851    if (!url) {
    852      NS_ASSERTION(false, "can't QI into nsIURI");
    853      return false;
    854    }
    855    nsAutoCString uriPath;
    856    rv = url->GetFilePath(uriPath);
    857    NS_ENSURE_SUCCESS(rv, false);
    858 
    859    if (mWithinFrameAncstorsDir) {
    860      // no path matching for frame-ancestors to not leak any path information.
    861      return true;
    862    }
    863 
    864    nsString decodedUriPath;
    865    CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriPath), decodedUriPath);
    866 
    867    // check if the last character of mPath is '/'; if so
    868    // we just have to check loading resource is within
    869    // the allowed path.
    870    if (mPath.Last() == '/') {
    871      if (!StringBeginsWith(decodedUriPath, mPath)) {
    872        return false;
    873      }
    874    }
    875    // otherwise mPath refers to a specific file, and we have to
    876    // check if the loading resource matches the file.
    877    else {
    878      if (!mPath.Equals(decodedUriPath)) {
    879        return false;
    880      }
    881    }
    882  }
    883 
    884  // At the end: scheme, host, port and path match -> allow the load.
    885  return true;
    886 }
    887 
    888 bool nsCSPHostSrc::visit(nsCSPSrcVisitor* aVisitor) const {
    889  return aVisitor->visitHostSrc(*this);
    890 }
    891 
    892 void nsCSPHostSrc::toString(nsAString& outStr) const {
    893  if (mGeneratedFromSelfKeyword) {
    894    outStr.AppendLiteral("'self'");
    895    return;
    896  }
    897 
    898  // If mHost is a single "*", we append the wildcard and return.
    899  if (mHost.EqualsASCII("*") && mScheme.IsEmpty() && mPort.IsEmpty()) {
    900    outStr.Append(mHost);
    901    return;
    902  }
    903 
    904  // append scheme
    905  outStr.Append(mScheme);
    906 
    907  // append host
    908  outStr.AppendLiteral("://");
    909  outStr.Append(mHost);
    910 
    911  // append port
    912  if (!mPort.IsEmpty()) {
    913    outStr.AppendLiteral(":");
    914    outStr.Append(mPort);
    915  }
    916 
    917  // append path
    918  outStr.Append(mPath);
    919 }
    920 
    921 void nsCSPHostSrc::setScheme(const nsAString& aScheme) {
    922  mScheme = aScheme;
    923  ToLowerCase(mScheme);
    924 }
    925 
    926 void nsCSPHostSrc::setPort(const nsAString& aPort) { mPort = aPort; }
    927 
    928 void nsCSPHostSrc::appendPath(const nsAString& aPath) { mPath.Append(aPath); }
    929 
    930 /* ===== nsCSPKeywordSrc ===================== */
    931 
    932 nsCSPKeywordSrc::nsCSPKeywordSrc(enum CSPKeyword aKeyword)
    933    : mKeyword(aKeyword) {
    934  NS_ASSERTION((aKeyword != CSP_SELF),
    935               "'self' should have been replaced in the parser");
    936 }
    937 
    938 nsCSPKeywordSrc::~nsCSPKeywordSrc() = default;
    939 
    940 bool nsCSPKeywordSrc::allows(enum CSPKeyword aKeyword,
    941                             const nsAString& aHashOrNonce) const {
    942  CSPUTILSLOG(("nsCSPKeywordSrc::allows, aKeyWord: %s, aHashOrNonce: %s",
    943               CSP_EnumToUTF8Keyword(aKeyword),
    944               NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
    945  return mKeyword == aKeyword;
    946 }
    947 
    948 bool nsCSPKeywordSrc::visit(nsCSPSrcVisitor* aVisitor) const {
    949  return aVisitor->visitKeywordSrc(*this);
    950 }
    951 
    952 void nsCSPKeywordSrc::toString(nsAString& outStr) const {
    953  outStr.Append(CSP_EnumToUTF16Keyword(mKeyword));
    954 }
    955 
    956 /* ===== nsCSPNonceSrc ==================== */
    957 
    958 nsCSPNonceSrc::nsCSPNonceSrc(const nsAString& aNonce) : mNonce(aNonce) {}
    959 
    960 nsCSPNonceSrc::~nsCSPNonceSrc() = default;
    961 
    962 bool nsCSPNonceSrc::allows(enum CSPKeyword aKeyword,
    963                           const nsAString& aHashOrNonce) const {
    964  CSPUTILSLOG(("nsCSPNonceSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
    965               CSP_EnumToUTF8Keyword(aKeyword),
    966               NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
    967 
    968  if (aKeyword != CSP_NONCE) {
    969    return false;
    970  }
    971  // nonces can not be invalidated by strict-dynamic
    972  return mNonce.Equals(aHashOrNonce);
    973 }
    974 
    975 bool nsCSPNonceSrc::visit(nsCSPSrcVisitor* aVisitor) const {
    976  return aVisitor->visitNonceSrc(*this);
    977 }
    978 
    979 void nsCSPNonceSrc::toString(nsAString& outStr) const {
    980  outStr.Append(CSP_EnumToUTF16Keyword(CSP_NONCE));
    981  outStr.Append(mNonce);
    982  outStr.AppendLiteral("'");
    983 }
    984 
    985 /* ===== nsCSPHashSrc ===================== */
    986 
    987 nsCSPHashSrc::nsCSPHashSrc(const nsAString& aAlgo, const nsAString& aHash)
    988    : mAlgorithm(aAlgo), mHash(aHash) {
    989  // Only the algo should be rewritten to lowercase, the hash must remain the
    990  // same.
    991  ToLowerCase(mAlgorithm);
    992  // Normalize the base64url encoding to base64 encoding:
    993  char16_t* cur = mHash.BeginWriting();
    994  char16_t* end = mHash.EndWriting();
    995 
    996  for (; cur < end; ++cur) {
    997    if (char16_t('-') == *cur) {
    998      *cur = char16_t('+');
    999    }
   1000    if (char16_t('_') == *cur) {
   1001      *cur = char16_t('/');
   1002    }
   1003  }
   1004 }
   1005 
   1006 nsCSPHashSrc::~nsCSPHashSrc() = default;
   1007 
   1008 bool nsCSPHashSrc::allows(enum CSPKeyword aKeyword,
   1009                          const nsAString& aHashOrNonce) const {
   1010  CSPUTILSLOG(("nsCSPHashSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
   1011               CSP_EnumToUTF8Keyword(aKeyword),
   1012               NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
   1013 
   1014  if (aKeyword != CSP_HASH) {
   1015    return false;
   1016  }
   1017 
   1018  // hashes can not be invalidated by strict-dynamic
   1019 
   1020  // Convert aHashOrNonce to UTF-8
   1021  NS_ConvertUTF16toUTF8 utf8_hash(aHashOrNonce);
   1022 
   1023  nsCOMPtr<nsICryptoHash> hasher;
   1024  nsresult rv = NS_NewCryptoHash(NS_ConvertUTF16toUTF8(mAlgorithm),
   1025                                 getter_AddRefs(hasher));
   1026  NS_ENSURE_SUCCESS(rv, false);
   1027 
   1028  rv = hasher->Update((uint8_t*)utf8_hash.get(), utf8_hash.Length());
   1029  NS_ENSURE_SUCCESS(rv, false);
   1030 
   1031  nsAutoCString hash;
   1032  rv = hasher->Finish(true, hash);
   1033  NS_ENSURE_SUCCESS(rv, false);
   1034 
   1035  return NS_ConvertUTF16toUTF8(mHash).Equals(hash);
   1036 }
   1037 
   1038 bool nsCSPHashSrc::visit(nsCSPSrcVisitor* aVisitor) const {
   1039  return aVisitor->visitHashSrc(*this);
   1040 }
   1041 
   1042 void nsCSPHashSrc::toString(nsAString& outStr) const {
   1043  outStr.AppendLiteral("'");
   1044  outStr.Append(mAlgorithm);
   1045  outStr.AppendLiteral("-");
   1046  outStr.Append(mHash);
   1047  outStr.AppendLiteral("'");
   1048 }
   1049 
   1050 /* ===== nsCSPReportURI ===================== */
   1051 
   1052 nsCSPReportURI::nsCSPReportURI(nsIURI* aURI) : mReportURI(aURI) {}
   1053 
   1054 nsCSPReportURI::~nsCSPReportURI() = default;
   1055 
   1056 bool nsCSPReportURI::visit(nsCSPSrcVisitor* aVisitor) const { return false; }
   1057 
   1058 void nsCSPReportURI::toString(nsAString& outStr) const {
   1059  nsAutoCString spec;
   1060  nsresult rv = mReportURI->GetSpec(spec);
   1061  if (NS_FAILED(rv)) {
   1062    return;
   1063  }
   1064  outStr.AppendASCII(spec.get());
   1065 }
   1066 
   1067 /* ===== nsCSPReportGroup ===================== */
   1068 
   1069 nsCSPGroup::nsCSPGroup(const nsAString& aGroup) : mGroup(aGroup) {}
   1070 
   1071 nsCSPGroup::~nsCSPGroup() = default;
   1072 
   1073 bool nsCSPGroup::visit(nsCSPSrcVisitor* aVisitor) const { return false; }
   1074 
   1075 void nsCSPGroup::toString(nsAString& aOutStr) const { aOutStr.Append(mGroup); }
   1076 
   1077 /* ===== nsCSPSandboxFlags ===================== */
   1078 
   1079 nsCSPSandboxFlags::nsCSPSandboxFlags(const nsAString& aFlags) : mFlags(aFlags) {
   1080  ToLowerCase(mFlags);
   1081 }
   1082 
   1083 nsCSPSandboxFlags::~nsCSPSandboxFlags() = default;
   1084 
   1085 bool nsCSPSandboxFlags::visit(nsCSPSrcVisitor* aVisitor) const { return false; }
   1086 
   1087 void nsCSPSandboxFlags::toString(nsAString& outStr) const {
   1088  outStr.Append(mFlags);
   1089 }
   1090 
   1091 /* ===== nsCSPRequireTrustedTypesForDirectiveValue ===================== */
   1092 
   1093 nsCSPRequireTrustedTypesForDirectiveValue::
   1094    nsCSPRequireTrustedTypesForDirectiveValue(const nsAString& aValue)
   1095    : mValue{aValue} {}
   1096 
   1097 bool nsCSPRequireTrustedTypesForDirectiveValue::visit(
   1098    nsCSPSrcVisitor* aVisitor) const {
   1099  MOZ_ASSERT_UNREACHABLE(
   1100      "This method should only be called for other overloads of this method.");
   1101  return false;
   1102 }
   1103 
   1104 void nsCSPRequireTrustedTypesForDirectiveValue::toString(
   1105    nsAString& aOutStr) const {
   1106  aOutStr.Append(mValue);
   1107 }
   1108 
   1109 /* =============== nsCSPTrustedTypesDirectivePolicyName =============== */
   1110 
   1111 nsCSPTrustedTypesDirectivePolicyName::nsCSPTrustedTypesDirectivePolicyName(
   1112    const nsAString& aName)
   1113    : mName{aName} {}
   1114 
   1115 bool nsCSPTrustedTypesDirectivePolicyName::visit(
   1116    nsCSPSrcVisitor* aVisitor) const {
   1117  MOZ_ASSERT_UNREACHABLE(
   1118      "Should only be called for other overloads of this method.");
   1119  return false;
   1120 }
   1121 
   1122 void nsCSPTrustedTypesDirectivePolicyName::toString(nsAString& aOutStr) const {
   1123  aOutStr.Append(mName);
   1124 }
   1125 
   1126 /* =============== nsCSPTrustedTypesDirectiveInvalidToken =============== */
   1127 
   1128 nsCSPTrustedTypesDirectiveInvalidToken::nsCSPTrustedTypesDirectiveInvalidToken(
   1129    const nsAString& aInvalidToken)
   1130    : mInvalidToken{aInvalidToken} {}
   1131 
   1132 bool nsCSPTrustedTypesDirectiveInvalidToken::visit(
   1133    nsCSPSrcVisitor* aVisitor) const {
   1134  MOZ_ASSERT_UNREACHABLE(
   1135      "Should only be called for other overloads of this method.");
   1136  return false;
   1137 }
   1138 
   1139 void nsCSPTrustedTypesDirectiveInvalidToken::toString(
   1140    nsAString& aOutStr) const {
   1141  aOutStr.Append(mInvalidToken);
   1142 }
   1143 
   1144 /* ===== nsCSPDirective ====================== */
   1145 
   1146 nsCSPDirective::nsCSPDirective(CSPDirective aDirective) {
   1147  mDirective = aDirective;
   1148 }
   1149 
   1150 nsCSPDirective::~nsCSPDirective() {
   1151  for (uint32_t i = 0; i < mSrcs.Length(); i++) {
   1152    delete mSrcs[i];
   1153  }
   1154 }
   1155 
   1156 // https://w3c.github.io/webappsec-csp/#match-nonce-to-source-list
   1157 static bool DoesNonceMatchSourceList(nsILoadInfo* aLoadInfo,
   1158                                     const nsTArray<nsCSPBaseSrc*>& aSrcs) {
   1159  // Step 1. Assert: source list is not null. (implicit)
   1160 
   1161  // Note: For code-reuse we do "request’s cryptographic nonce metadata" here
   1162  // instead of the caller.
   1163  nsAutoString nonce;
   1164  MOZ_ALWAYS_SUCCEEDS(aLoadInfo->GetCspNonce(nonce));
   1165 
   1166  // Step 2. If nonce is the empty string, return "Does Not Match".
   1167  if (nonce.IsEmpty()) {
   1168    return false;
   1169  }
   1170 
   1171  // Step 3. For each expression of source list:
   1172  for (nsCSPBaseSrc* src : aSrcs) {
   1173    // Step 3.1. If expression matches the nonce-source grammar, and nonce is
   1174    // identical to expression’s base64-value part, return "Matches".
   1175    if (src->isNonce()) {
   1176      nsAutoString srcNonce;
   1177      static_cast<nsCSPNonceSrc*>(src)->getNonce(srcNonce);
   1178      if (srcNonce == nonce) {
   1179        return true;
   1180      }
   1181    }
   1182  }
   1183 
   1184  // Step 4. Return "Does Not Match".
   1185  return false;
   1186 }
   1187 
   1188 // https://www.w3.org/TR/SRI/#parse-metadata
   1189 // This function is similar to SRICheck::IntegrityMetadata, but also keeps
   1190 // SRI metadata with weaker hashes.
   1191 // CSP treats "no metadata" and empty results the same way.
   1192 static nsTArray<SRIMetadata> ParseSRIMetadata(const nsAString& aMetadata) {
   1193  // Step 1. Let result be the empty set.
   1194  // Step 2. Let empty be equal to true.
   1195  nsTArray<SRIMetadata> result;
   1196 
   1197  NS_ConvertUTF16toUTF8 metadataList(aMetadata);
   1198  nsAutoCString token;
   1199 
   1200  // Step 3. For each token returned by splitting metadata on spaces:
   1201  nsCWhitespaceTokenizer tokenizer(metadataList);
   1202  while (tokenizer.hasMoreTokens()) {
   1203    token = tokenizer.nextToken();
   1204    // Step 3.1. Set empty to false.
   1205    // Step 3.3. Parse token per the grammar in integrity metadata.
   1206    SRIMetadata metadata(token);
   1207    // Step 3.2. If token is not a valid metadata, skip the remaining steps, and
   1208    // proceed to the next token.
   1209    if (metadata.IsMalformed()) {
   1210      continue;
   1211    }
   1212 
   1213    // Step 3.4. Let algorithm be the alg component of token.
   1214    // Step 3.5. If algorithm is a hash function recognized by the user agent,
   1215    // add the
   1216    //  parsed token to result.
   1217    if (metadata.IsAlgorithmSupported()) {
   1218      result.AppendElement(metadata);
   1219    }
   1220  }
   1221 
   1222  // Step 4. Return no metadata if empty is true, otherwise return result.
   1223  return result;
   1224 }
   1225 
   1226 bool nsCSPDirective::permits(CSPDirective aDirective, nsILoadInfo* aLoadInfo,
   1227                             nsIURI* aUri, bool aWasRedirected,
   1228                             bool aReportOnly, bool aUpgradeInsecure) const {
   1229  MOZ_ASSERT(equals(aDirective) || isDefaultDirective());
   1230 
   1231  if (CSPUTILSLOGENABLED()) {
   1232    CSPUTILSLOG(("nsCSPDirective::permits, aUri: %s, aDirective: %s",
   1233                 aUri->GetSpecOrDefault().get(),
   1234                 CSP_CSPDirectiveToString(aDirective)));
   1235  }
   1236 
   1237  if (aLoadInfo) {
   1238    // https://w3c.github.io/webappsec-csp/#style-src-elem-pre-request
   1239    if (aDirective == CSPDirective::STYLE_SRC_ELEM_DIRECTIVE) {
   1240      // Step 3. If the result of executing §6.7.2.3 Does nonce match source
   1241      // list? on request’s cryptographic nonce metadata and this directive’s
   1242      // value is "Matches", return "Allowed".
   1243      if (DoesNonceMatchSourceList(aLoadInfo, mSrcs)) {
   1244        CSPUTILSLOG(("  Allowed by matching nonce (style)"));
   1245        return true;
   1246      }
   1247    }
   1248 
   1249    // https://w3c.github.io/webappsec-csp/#script-pre-request
   1250    // Step 1. If request’s destination is script-like:
   1251    else if (aDirective == CSPDirective::SCRIPT_SRC_ELEM_DIRECTIVE ||
   1252             aDirective == CSPDirective::WORKER_SRC_DIRECTIVE) {
   1253      // Step 1.1. If the result of executing §6.7.2.3 Does nonce match source
   1254      // list? on request’s cryptographic nonce metadata and this directive’s
   1255      // value is "Matches", return "Allowed".
   1256      if (DoesNonceMatchSourceList(aLoadInfo, mSrcs)) {
   1257        CSPUTILSLOG(("  Allowed by matching nonce (script-like)"));
   1258        return true;
   1259      }
   1260 
   1261      // Step 1.2. Let integrity expressions be the set of source expressions in
   1262      // directive’s value that match the hash-source grammar.
   1263      nsTArray<nsCSPHashSrc*> integrityExpressions;
   1264      bool hasStrictDynamicKeyword =
   1265          false;  // Optimization to reduce number of iterations.
   1266      for (uint32_t i = 0; i < mSrcs.Length(); i++) {
   1267        if (mSrcs[i]->isHash()) {
   1268          integrityExpressions.AppendElement(
   1269              static_cast<nsCSPHashSrc*>(mSrcs[i]));
   1270        } else if (mSrcs[i]->isKeyword(CSP_STRICT_DYNAMIC)) {
   1271          hasStrictDynamicKeyword = true;
   1272        }
   1273      }
   1274 
   1275      // Step 1.3. If integrity expressions is not empty:
   1276      if (!integrityExpressions.IsEmpty()) {
   1277        // Step 1.3.1. Let integrity sources be the result of executing the
   1278        // algorithm defined in [SRI 3.3.3 Parse metadata] on request’s
   1279        // integrity metadata.
   1280        nsAutoString integrityMetadata;
   1281        aLoadInfo->GetIntegrityMetadata(integrityMetadata);
   1282 
   1283        nsTArray<SRIMetadata> integritySources =
   1284            ParseSRIMetadata(integrityMetadata);
   1285 
   1286        // Step 1.3.2. If integrity sources is "no metadata" or an empty set,
   1287        // skip the remaining substeps.
   1288        if (!integritySources.IsEmpty()) {
   1289          // Step 1.3.3. Let bypass due to integrity match be true.
   1290          bool bypass = true;
   1291 
   1292          nsAutoCString sourceAlgorithmUTF8;
   1293          nsAutoCString sourceHashUTF8;
   1294          nsAutoString sourceAlgorithm;
   1295          nsAutoString sourceHash;
   1296          nsAutoString algorithm;
   1297          nsAutoString hash;
   1298 
   1299          // Step 1.3.4. For each source of integrity sources:
   1300          for (const SRIMetadata& source : integritySources) {
   1301            source.GetAlgorithm(&sourceAlgorithmUTF8);
   1302            sourceAlgorithm = NS_ConvertUTF8toUTF16(sourceAlgorithmUTF8);
   1303            source.GetHash(0, &sourceHashUTF8);
   1304            sourceHash = NS_ConvertUTF8toUTF16(sourceHashUTF8);
   1305 
   1306            // Step 1.3.4.1 If directive’s value does not contain a source
   1307            // expression whose hash-algorithm is an ASCII case-insensitive
   1308            // match for source’s hash-algorithm, and whose base64-value is
   1309            // identical to source’s base64-value, then set bypass due to
   1310            // integrity match to false.
   1311            bool found = false;
   1312            for (const nsCSPHashSrc* hashSrc : integrityExpressions) {
   1313              hashSrc->getAlgorithm(algorithm);
   1314              hashSrc->getHash(hash);
   1315 
   1316              // The nsCSPHashSrc constructor lowercases algorithm, so this
   1317              // is case-insensitive.
   1318              if (sourceAlgorithm == algorithm && sourceHash == hash) {
   1319                found = true;
   1320                break;
   1321              }
   1322            }
   1323 
   1324            if (!found) {
   1325              bypass = false;
   1326              break;
   1327            }
   1328          }
   1329 
   1330          // Step 1.3.5. If bypass due to integrity match is true, return
   1331          // "Allowed".
   1332          if (bypass) {
   1333            CSPUTILSLOG(
   1334                ("  Allowed by matching integrity metadata (script-like)"));
   1335            return true;
   1336          }
   1337        }
   1338      }
   1339 
   1340      // Step 1.4. If directive’s value contains a source expression that is an
   1341      // ASCII case-insensitive match for the "'strict-dynamic'" keyword-source:
   1342 
   1343      // XXX I don't think we should apply strict-dynamic to XSLT.
   1344      if (hasStrictDynamicKeyword && aLoadInfo->InternalContentPolicyType() !=
   1345                                         nsIContentPolicy::TYPE_XSLT) {
   1346        // Step 1.4.1  If the request’s parser metadata is "parser-inserted",
   1347        // return "Blocked". Otherwise, return "Allowed".
   1348        if (aLoadInfo->GetParserCreatedScript()) {
   1349          CSPUTILSLOG(
   1350              ("  Blocked by 'strict-dynamic' because parser-inserted"));
   1351          return false;
   1352        }
   1353 
   1354        CSPUTILSLOG(
   1355            ("  Allowed by 'strict-dynamic' because not-parser-inserted"));
   1356        return true;
   1357      }
   1358    }
   1359  }
   1360 
   1361  for (uint32_t i = 0; i < mSrcs.Length(); i++) {
   1362    if (mSrcs[i]->permits(aUri, aWasRedirected, aReportOnly,
   1363                          aUpgradeInsecure)) {
   1364      return true;
   1365    }
   1366  }
   1367  return false;
   1368 }
   1369 
   1370 bool nsCSPDirective::allows(enum CSPKeyword aKeyword,
   1371                            const nsAString& aHashOrNonce) const {
   1372  CSPUTILSLOG(("nsCSPDirective::allows, aKeyWord: %s, aHashOrNonce: %s",
   1373               CSP_EnumToUTF8Keyword(aKeyword),
   1374               NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
   1375 
   1376  for (uint32_t i = 0; i < mSrcs.Length(); i++) {
   1377    if (mSrcs[i]->allows(aKeyword, aHashOrNonce)) {
   1378      return true;
   1379    }
   1380  }
   1381  return false;
   1382 }
   1383 
   1384 // https://w3c.github.io/webappsec-csp/#allow-all-inline
   1385 bool nsCSPDirective::allowsAllInlineBehavior(CSPDirective aDir) const {
   1386  // Step 1. Let allow all inline be false.
   1387  bool allowAll = false;
   1388 
   1389  // Step 2. For each expression of list:
   1390  for (nsCSPBaseSrc* src : mSrcs) {
   1391    // Step 2.1. If expression matches the nonce-source or hash-source grammar,
   1392    // return "Does Not Allow".
   1393    if (src->isNonce() || src->isHash()) {
   1394      return false;
   1395    }
   1396 
   1397    // Step 2.2. If type is "script", "script attribute" or "navigation" and
   1398    // expression matches the keyword-source "'strict-dynamic'", return "Does
   1399    // Not Allow".
   1400    if ((aDir == nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE ||
   1401         aDir == nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) &&
   1402        src->isKeyword(CSP_STRICT_DYNAMIC)) {
   1403      return false;
   1404    }
   1405 
   1406    // Step 2.3. If expression is an ASCII case-insensitive match for the
   1407    // keyword-source "'unsafe-inline'", set allow all inline to true.
   1408    if (src->isKeyword(CSP_UNSAFE_INLINE)) {
   1409      allowAll = true;
   1410    }
   1411  }
   1412 
   1413  // Step 3. If allow all inline is true, return "Allows". Otherwise, return
   1414  // "Does Not Allow".
   1415  return allowAll;
   1416 }
   1417 
   1418 static constexpr auto kWildcard = u"*"_ns;
   1419 
   1420 bool nsCSPDirective::ShouldCreateViolationForNewTrustedTypesPolicy(
   1421    const nsAString& aPolicyName,
   1422    const nsTArray<nsString>& aCreatedPolicyNames) const {
   1423  MOZ_ASSERT(mDirective == nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE);
   1424 
   1425  if (mDirective == nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE) {
   1426    if (allows(CSP_NONE, EmptyString())) {
   1427      // Step 2.4: if directive’s value only contains a tt-keyword which is a
   1428      // match for a value 'none', set createViolation to true.
   1429      // `nsCSPParser` ignores the 'none' keyword if other keywords or policy
   1430      // names are present. Hence no additional checks required here.
   1431      return true;
   1432    }
   1433 
   1434    if (aCreatedPolicyNames.Contains(aPolicyName) &&
   1435        !allows(CSP_ALLOW_DUPLICATES, EmptyString())) {
   1436      // Step 2.5: if createdPolicyNames contains policyName and directive’s
   1437      // value does not contain a tt-keyword which is a match for a value
   1438      // 'allow-duplicates', set createViolation to true.
   1439      return true;
   1440    }
   1441 
   1442    if (!ContainsTrustedTypesDirectivePolicyName(aPolicyName) &&
   1443        !ContainsTrustedTypesDirectivePolicyName(kWildcard)) {
   1444      // Step 2.6: if directive’s value does not contain a tt-policy-name, which
   1445      // value is policyName, and directive’s value does not contain a
   1446      // tt-wildcard, set createViolation to true.
   1447      return true;
   1448    }
   1449  }
   1450 
   1451  return false;
   1452 }
   1453 
   1454 void nsCSPDirective::toString(nsAString& outStr) const {
   1455  // Append directive name
   1456  outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective));
   1457 
   1458  MOZ_ASSERT(!mSrcs.IsEmpty());
   1459 
   1460  outStr.AppendLiteral(" ");
   1461 
   1462  // Append srcs
   1463  StringJoinAppend(outStr, u" "_ns, mSrcs,
   1464                   [](nsAString& dest, nsCSPBaseSrc* cspBaseSrc) {
   1465                     cspBaseSrc->toString(dest);
   1466                   });
   1467 }
   1468 
   1469 void nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP& outCSP) const {
   1470  mozilla::dom::Sequence<nsString> srcs;
   1471  nsString src;
   1472  if (NS_WARN_IF(!srcs.SetCapacity(mSrcs.Length(), mozilla::fallible))) {
   1473    MOZ_ASSERT(false,
   1474               "Not enough memory for 'sources' sequence in "
   1475               "nsCSPDirective::toDomCSPStruct().");
   1476    return;
   1477  }
   1478  for (uint32_t i = 0; i < mSrcs.Length(); i++) {
   1479    src.Truncate();
   1480    mSrcs[i]->toString(src);
   1481    if (!srcs.AppendElement(src, mozilla::fallible)) {
   1482      MOZ_ASSERT(false,
   1483                 "Failed to append to 'sources' sequence in "
   1484                 "nsCSPDirective::toDomCSPStruct().");
   1485    }
   1486  }
   1487 
   1488  switch (mDirective) {
   1489    case nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE:
   1490      outCSP.mDefault_src.Construct();
   1491      outCSP.mDefault_src.Value() = std::move(srcs);
   1492      return;
   1493 
   1494    case nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE:
   1495      outCSP.mScript_src.Construct();
   1496      outCSP.mScript_src.Value() = std::move(srcs);
   1497      return;
   1498 
   1499    case nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE:
   1500      outCSP.mObject_src.Construct();
   1501      outCSP.mObject_src.Value() = std::move(srcs);
   1502      return;
   1503 
   1504    case nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE:
   1505      outCSP.mStyle_src.Construct();
   1506      outCSP.mStyle_src.Value() = std::move(srcs);
   1507      return;
   1508 
   1509    case nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE:
   1510      outCSP.mImg_src.Construct();
   1511      outCSP.mImg_src.Value() = std::move(srcs);
   1512      return;
   1513 
   1514    case nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE:
   1515      outCSP.mMedia_src.Construct();
   1516      outCSP.mMedia_src.Value() = std::move(srcs);
   1517      return;
   1518 
   1519    case nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE:
   1520      outCSP.mFrame_src.Construct();
   1521      outCSP.mFrame_src.Value() = std::move(srcs);
   1522      return;
   1523 
   1524    case nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE:
   1525      outCSP.mFont_src.Construct();
   1526      outCSP.mFont_src.Value() = std::move(srcs);
   1527      return;
   1528 
   1529    case nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE:
   1530      outCSP.mConnect_src.Construct();
   1531      outCSP.mConnect_src.Value() = std::move(srcs);
   1532      return;
   1533 
   1534    case nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE:
   1535      outCSP.mReport_uri.Construct();
   1536      outCSP.mReport_uri.Value() = std::move(srcs);
   1537      return;
   1538 
   1539    case nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE:
   1540      outCSP.mFrame_ancestors.Construct();
   1541      outCSP.mFrame_ancestors.Value() = std::move(srcs);
   1542      return;
   1543 
   1544    case nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE:
   1545      outCSP.mManifest_src.Construct();
   1546      outCSP.mManifest_src.Value() = std::move(srcs);
   1547      return;
   1548      // not supporting REFLECTED_XSS_DIRECTIVE
   1549 
   1550    case nsIContentSecurityPolicy::BASE_URI_DIRECTIVE:
   1551      outCSP.mBase_uri.Construct();
   1552      outCSP.mBase_uri.Value() = std::move(srcs);
   1553      return;
   1554 
   1555    case nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE:
   1556      outCSP.mForm_action.Construct();
   1557      outCSP.mForm_action.Value() = std::move(srcs);
   1558      return;
   1559 
   1560    case nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT:
   1561      outCSP.mBlock_all_mixed_content.Construct();
   1562      // does not have any srcs
   1563      return;
   1564 
   1565    case nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE:
   1566      outCSP.mUpgrade_insecure_requests.Construct();
   1567      // does not have any srcs
   1568      return;
   1569 
   1570    case nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE:
   1571      outCSP.mChild_src.Construct();
   1572      outCSP.mChild_src.Value() = std::move(srcs);
   1573      return;
   1574 
   1575    case nsIContentSecurityPolicy::SANDBOX_DIRECTIVE:
   1576      outCSP.mSandbox.Construct();
   1577      outCSP.mSandbox.Value() = std::move(srcs);
   1578      return;
   1579 
   1580    case nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE:
   1581      outCSP.mWorker_src.Construct();
   1582      outCSP.mWorker_src.Value() = std::move(srcs);
   1583      return;
   1584 
   1585    case nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE:
   1586      outCSP.mScript_src_elem.Construct();
   1587      outCSP.mScript_src_elem.Value() = std::move(srcs);
   1588      return;
   1589 
   1590    case nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE:
   1591      outCSP.mScript_src_attr.Construct();
   1592      outCSP.mScript_src_attr.Value() = std::move(srcs);
   1593      return;
   1594 
   1595    case nsIContentSecurityPolicy::REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE:
   1596      outCSP.mRequire_trusted_types_for.Construct();
   1597 
   1598      // Here, the srcs represent the sink group
   1599      // (https://w3c.github.io/trusted-types/dist/spec/#integration-with-content-security-policy).
   1600      outCSP.mRequire_trusted_types_for.Value() = std::move(srcs);
   1601      return;
   1602 
   1603    case nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE:
   1604      outCSP.mTrusted_types.Construct();
   1605      // Here, "srcs" represents tt-expressions
   1606      // (https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive).
   1607      outCSP.mTrusted_types.Value() = std::move(srcs);
   1608      return;
   1609 
   1610    case nsIContentSecurityPolicy::REPORT_TO_DIRECTIVE:
   1611      outCSP.mReport_to.Construct();
   1612      outCSP.mReport_to.Value() = std::move(srcs);
   1613      return;
   1614 
   1615    default:
   1616      NS_ASSERTION(false, "cannot find directive to convert CSP to JSON");
   1617  }
   1618 }
   1619 
   1620 bool nsCSPDirective::isDefaultDirective() const {
   1621  return mDirective == nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
   1622 }
   1623 
   1624 void nsCSPDirective::getReportURIs(nsTArray<nsString>& outReportURIs) const {
   1625  NS_ASSERTION((mDirective == nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE),
   1626               "not a report-uri directive");
   1627 
   1628  // append uris
   1629  nsString tmpReportURI;
   1630  for (uint32_t i = 0; i < mSrcs.Length(); i++) {
   1631    tmpReportURI.Truncate();
   1632    mSrcs[i]->toString(tmpReportURI);
   1633    outReportURIs.AppendElement(tmpReportURI);
   1634  }
   1635 }
   1636 
   1637 void nsCSPDirective::getReportGroup(nsAString& outReportGroup) const {
   1638  NS_ASSERTION((mDirective == nsIContentSecurityPolicy::REPORT_TO_DIRECTIVE),
   1639               "not a report-to directive");
   1640 
   1641  MOZ_ASSERT(mSrcs.Length() <= 1);
   1642  mSrcs[0]->toString(outReportGroup);
   1643 }
   1644 
   1645 bool nsCSPDirective::visitSrcs(nsCSPSrcVisitor* aVisitor) const {
   1646  for (uint32_t i = 0; i < mSrcs.Length(); i++) {
   1647    if (!mSrcs[i]->visit(aVisitor)) {
   1648      return false;
   1649    }
   1650  }
   1651  return true;
   1652 }
   1653 
   1654 bool nsCSPDirective::equals(CSPDirective aDirective) const {
   1655  return (mDirective == aDirective);
   1656 }
   1657 
   1658 void nsCSPDirective::getDirName(nsAString& outStr) const {
   1659  outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective));
   1660 }
   1661 
   1662 bool nsCSPDirective::hasReportSampleKeyword() const {
   1663  for (nsCSPBaseSrc* src : mSrcs) {
   1664    if (src->isReportSample()) {
   1665      return true;
   1666    }
   1667  }
   1668 
   1669  return false;
   1670 }
   1671 
   1672 bool nsCSPDirective::ContainsTrustedTypesDirectivePolicyName(
   1673    const nsAString& aPolicyName) const {
   1674  MOZ_ASSERT(mDirective == nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE);
   1675 
   1676  if (mDirective == nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE) {
   1677    for (const auto* src : mSrcs) {
   1678      if (src->isTrustedTypesDirectivePolicyName()) {
   1679        const auto& name =
   1680            static_cast<const nsCSPTrustedTypesDirectivePolicyName*>(src)
   1681                ->GetName();
   1682        if (name.Equals(aPolicyName)) {
   1683          return true;
   1684        }
   1685      }
   1686    }
   1687  }
   1688 
   1689  return false;
   1690 }
   1691 
   1692 /* =============== nsCSPChildSrcDirective ============= */
   1693 
   1694 nsCSPChildSrcDirective::nsCSPChildSrcDirective(CSPDirective aDirective)
   1695    : nsCSPDirective(aDirective),
   1696      mRestrictFrames(false),
   1697      mRestrictWorkers(false) {}
   1698 
   1699 nsCSPChildSrcDirective::~nsCSPChildSrcDirective() = default;
   1700 
   1701 bool nsCSPChildSrcDirective::equals(CSPDirective aDirective) const {
   1702  if (aDirective == nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE) {
   1703    return mRestrictFrames;
   1704  }
   1705  if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) {
   1706    return mRestrictWorkers;
   1707  }
   1708  return (mDirective == aDirective);
   1709 }
   1710 
   1711 /* =============== nsCSPScriptSrcDirective ============= */
   1712 
   1713 nsCSPScriptSrcDirective::nsCSPScriptSrcDirective(CSPDirective aDirective)
   1714    : nsCSPDirective(aDirective) {}
   1715 
   1716 nsCSPScriptSrcDirective::~nsCSPScriptSrcDirective() = default;
   1717 
   1718 bool nsCSPScriptSrcDirective::equals(CSPDirective aDirective) const {
   1719  if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) {
   1720    return mRestrictWorkers;
   1721  }
   1722  if (aDirective == nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) {
   1723    return mRestrictScriptElem;
   1724  }
   1725  if (aDirective == nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) {
   1726    return mRestrictScriptAttr;
   1727  }
   1728  return mDirective == aDirective;
   1729 }
   1730 
   1731 /* =============== nsCSPStyleSrcDirective ============= */
   1732 
   1733 nsCSPStyleSrcDirective::nsCSPStyleSrcDirective(CSPDirective aDirective)
   1734    : nsCSPDirective(aDirective) {}
   1735 
   1736 nsCSPStyleSrcDirective::~nsCSPStyleSrcDirective() = default;
   1737 
   1738 bool nsCSPStyleSrcDirective::equals(CSPDirective aDirective) const {
   1739  if (aDirective == nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE) {
   1740    return mRestrictStyleElem;
   1741  }
   1742  if (aDirective == nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE) {
   1743    return mRestrictStyleAttr;
   1744  }
   1745  return mDirective == aDirective;
   1746 }
   1747 
   1748 /* =============== nsBlockAllMixedContentDirective ============= */
   1749 
   1750 nsBlockAllMixedContentDirective::nsBlockAllMixedContentDirective(
   1751    CSPDirective aDirective)
   1752    : nsCSPDirective(aDirective) {}
   1753 
   1754 nsBlockAllMixedContentDirective::~nsBlockAllMixedContentDirective() = default;
   1755 
   1756 void nsBlockAllMixedContentDirective::toString(nsAString& outStr) const {
   1757  outStr.AppendASCII(CSP_CSPDirectiveToString(
   1758      nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT));
   1759 }
   1760 
   1761 void nsBlockAllMixedContentDirective::getDirName(nsAString& outStr) const {
   1762  outStr.AppendASCII(CSP_CSPDirectiveToString(
   1763      nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT));
   1764 }
   1765 
   1766 /* =============== nsUpgradeInsecureDirective ============= */
   1767 
   1768 nsUpgradeInsecureDirective::nsUpgradeInsecureDirective(CSPDirective aDirective)
   1769    : nsCSPDirective(aDirective) {}
   1770 
   1771 nsUpgradeInsecureDirective::~nsUpgradeInsecureDirective() = default;
   1772 
   1773 void nsUpgradeInsecureDirective::toString(nsAString& outStr) const {
   1774  outStr.AppendASCII(CSP_CSPDirectiveToString(
   1775      nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE));
   1776 }
   1777 
   1778 void nsUpgradeInsecureDirective::getDirName(nsAString& outStr) const {
   1779  outStr.AppendASCII(CSP_CSPDirectiveToString(
   1780      nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE));
   1781 }
   1782 
   1783 /* ===== nsCSPPolicy ========================= */
   1784 
   1785 nsCSPPolicy::nsCSPPolicy()
   1786    : mUpgradeInsecDir(nullptr),
   1787      mReportOnly(false),
   1788      mDeliveredViaMetaTag(false) {
   1789  CSPUTILSLOG(("nsCSPPolicy::nsCSPPolicy"));
   1790 }
   1791 
   1792 nsCSPPolicy::~nsCSPPolicy() {
   1793  CSPUTILSLOG(("nsCSPPolicy::~nsCSPPolicy"));
   1794 
   1795  for (uint32_t i = 0; i < mDirectives.Length(); i++) {
   1796    delete mDirectives[i];
   1797  }
   1798 }
   1799 
   1800 bool nsCSPPolicy::permits(CSPDirective aDir, nsILoadInfo* aLoadInfo,
   1801                          nsIURI* aUri, bool aWasRedirected, bool aSpecific,
   1802                          nsAString& outViolatedDirective,
   1803                          nsAString& outViolatedDirectiveString) const {
   1804  if (CSPUTILSLOGENABLED()) {
   1805    CSPUTILSLOG(("nsCSPPolicy::permits, aUri: %s, aDir: %s, aSpecific: %s",
   1806                 aUri->GetSpecOrDefault().get(), CSP_CSPDirectiveToString(aDir),
   1807                 aSpecific ? "true" : "false"));
   1808  }
   1809 
   1810  NS_ASSERTION(aUri, "permits needs an uri to perform the check!");
   1811  outViolatedDirective.Truncate();
   1812  outViolatedDirectiveString.Truncate();
   1813 
   1814  nsCSPDirective* defaultDir = nullptr;
   1815 
   1816  // Try to find a relevant directive
   1817  // These directive arrays are short (1-5 elements), not worth using a
   1818  // hashtable.
   1819  for (uint32_t i = 0; i < mDirectives.Length(); i++) {
   1820    if (mDirectives[i]->equals(aDir)) {
   1821      if (!mDirectives[i]->permits(aDir, aLoadInfo, aUri, aWasRedirected,
   1822                                   mReportOnly, mUpgradeInsecDir)) {
   1823        mDirectives[i]->getDirName(outViolatedDirective);
   1824        mDirectives[i]->toString(outViolatedDirectiveString);
   1825        return false;
   1826      }
   1827      return true;
   1828    }
   1829    if (mDirectives[i]->isDefaultDirective()) {
   1830      defaultDir = mDirectives[i];
   1831    }
   1832  }
   1833 
   1834  // If the above loop runs through, we haven't found a matching directive.
   1835  // Avoid relooping, just store the result of default-src while looping.
   1836  if (!aSpecific && defaultDir) {
   1837    if (!defaultDir->permits(aDir, aLoadInfo, aUri, aWasRedirected, mReportOnly,
   1838                             mUpgradeInsecDir)) {
   1839      defaultDir->getDirName(outViolatedDirective);
   1840      defaultDir->toString(outViolatedDirectiveString);
   1841      return false;
   1842    }
   1843    return true;
   1844  }
   1845 
   1846  // Nothing restricts this, so we're allowing the load
   1847  // See bug 764937
   1848  return true;
   1849 }
   1850 
   1851 bool nsCSPPolicy::allows(CSPDirective aDirective, enum CSPKeyword aKeyword,
   1852                         const nsAString& aHashOrNonce) const {
   1853  CSPUTILSLOG(("nsCSPPolicy::allows, aKeyWord: %s, a HashOrNonce: %s",
   1854               CSP_EnumToUTF8Keyword(aKeyword),
   1855               NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
   1856 
   1857  if (nsCSPDirective* directive = matchingOrDefaultDirective(aDirective)) {
   1858    return directive->allows(aKeyword, aHashOrNonce);
   1859  }
   1860 
   1861  // No matching directive or default directive as fallback found, thus
   1862  // allowing the load; see Bug 885433
   1863  // a) inline scripts (also unsafe eval) should only be blocked
   1864  //    if there is a [script-src] or [default-src]
   1865  // b) inline styles should only be blocked
   1866  //    if there is a [style-src] or [default-src]
   1867  return true;
   1868 }
   1869 
   1870 nsCSPDirective* nsCSPPolicy::matchingOrDefaultDirective(
   1871    CSPDirective aDirective) const {
   1872  nsCSPDirective* defaultDir = nullptr;
   1873 
   1874  // Try to find a matching directive
   1875  for (uint32_t i = 0; i < mDirectives.Length(); i++) {
   1876    if (mDirectives[i]->isDefaultDirective()) {
   1877      defaultDir = mDirectives[i];
   1878      continue;
   1879    }
   1880    if (mDirectives[i]->equals(aDirective)) {
   1881      return mDirectives[i];
   1882    }
   1883  }
   1884 
   1885  return defaultDir;
   1886 }
   1887 
   1888 void nsCSPPolicy::toString(nsAString& outStr) const {
   1889  StringJoinAppend(outStr, u"; "_ns, mDirectives,
   1890                   [](nsAString& dest, nsCSPDirective* cspDirective) {
   1891                     cspDirective->toString(dest);
   1892                   });
   1893 }
   1894 
   1895 void nsCSPPolicy::toDomCSPStruct(mozilla::dom::CSP& outCSP) const {
   1896  outCSP.mReport_only = mReportOnly;
   1897 
   1898  for (uint32_t i = 0; i < mDirectives.Length(); ++i) {
   1899    mDirectives[i]->toDomCSPStruct(outCSP);
   1900  }
   1901 }
   1902 
   1903 bool nsCSPPolicy::hasDirective(CSPDirective aDir) const {
   1904  for (uint32_t i = 0; i < mDirectives.Length(); i++) {
   1905    if (mDirectives[i]->equals(aDir)) {
   1906      return true;
   1907    }
   1908  }
   1909  return false;
   1910 }
   1911 
   1912 bool nsCSPPolicy::allowsAllInlineBehavior(CSPDirective aDir) const {
   1913  nsCSPDirective* directive = matchingOrDefaultDirective(aDir);
   1914  if (!directive) {
   1915    // No matching or default directive found thus allow the all inline
   1916    // scripts or styles. (See nsCSPPolicy::allows)
   1917    return true;
   1918  }
   1919 
   1920  return directive->allowsAllInlineBehavior(aDir);
   1921 }
   1922 
   1923 bool nsCSPPolicy::ShouldCreateViolationForNewTrustedTypesPolicy(
   1924    const nsAString& aPolicyName,
   1925    const nsTArray<nsString>& aCreatedPolicyNames) const {
   1926  for (const auto* directive : mDirectives) {
   1927    if (directive->equals(nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE)) {
   1928      return directive->ShouldCreateViolationForNewTrustedTypesPolicy(
   1929          aPolicyName, aCreatedPolicyNames);
   1930    }
   1931  }
   1932 
   1933  return false;
   1934 }
   1935 
   1936 bool nsCSPPolicy::AreTrustedTypesForSinkGroupRequired(
   1937    const nsAString& aSinkGroup) const {
   1938  MOZ_ASSERT(aSinkGroup == dom::kTrustedTypesOnlySinkGroup);
   1939  return mHasRequireTrustedTypesForDirective;
   1940 }
   1941 
   1942 /*
   1943 * Use this function only after ::allows() returned 'false' or if ensured by
   1944 * other means that the directive is violated. First and foremost it's used to
   1945 * get the violated directive before sending reports. The parameter
   1946 * aDirectiveName is the equivalent of 'outViolatedDirective' for the
   1947 * ::permits() function family.
   1948 */
   1949 void nsCSPPolicy::getViolatedDirectiveInformation(
   1950    CSPDirective aDirective, nsAString& aDirectiveName,
   1951    nsAString& aDirectiveNameAndValue, bool* aReportSample) const {
   1952  *aReportSample = false;
   1953  nsCSPDirective* directive = matchingOrDefaultDirective(aDirective);
   1954  if (!directive) {
   1955    MOZ_ASSERT_UNREACHABLE("Can not query violated directive");
   1956    aDirectiveName.Truncate();
   1957    aDirectiveNameAndValue.Truncate();
   1958    return;
   1959  }
   1960 
   1961  directive->getDirName(aDirectiveName);
   1962  directive->toString(aDirectiveNameAndValue);
   1963  *aReportSample = directive->hasReportSampleKeyword();
   1964 }
   1965 
   1966 /*
   1967 * Helper function that returns the underlying bit representation of sandbox
   1968 * flags. The function returns SANDBOXED_NONE if there are no sandbox
   1969 * directives.
   1970 */
   1971 uint32_t nsCSPPolicy::getSandboxFlags() const {
   1972  for (uint32_t i = 0; i < mDirectives.Length(); i++) {
   1973    if (mDirectives[i]->equals(nsIContentSecurityPolicy::SANDBOX_DIRECTIVE)) {
   1974      nsAutoString flags;
   1975      mDirectives[i]->toString(flags);
   1976 
   1977      if (flags.IsEmpty()) {
   1978        return SANDBOX_ALL_FLAGS;
   1979      }
   1980 
   1981      nsAttrValue attr;
   1982      attr.ParseAtomArray(flags);
   1983 
   1984      return nsContentUtils::ParseSandboxAttributeToFlags(&attr);
   1985    }
   1986  }
   1987 
   1988  return SANDBOXED_NONE;
   1989 }
   1990 
   1991 void nsCSPPolicy::getReportURIs(nsTArray<nsString>& outReportURIs) const {
   1992  for (uint32_t i = 0; i < mDirectives.Length(); i++) {
   1993    if (mDirectives[i]->equals(
   1994            nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
   1995      mDirectives[i]->getReportURIs(outReportURIs);
   1996      return;
   1997    }
   1998  }
   1999 }
   2000 
   2001 void nsCSPPolicy::getReportGroup(nsAString& outReportGroup) const {
   2002  for (uint32_t i = 0; i < mDirectives.Length(); i++) {
   2003    if (mDirectives[i]->equals(nsIContentSecurityPolicy::REPORT_TO_DIRECTIVE)) {
   2004      mDirectives[i]->getReportGroup(outReportGroup);
   2005      return;
   2006    }
   2007  }
   2008 }
   2009 
   2010 void nsCSPPolicy::getDirectiveNames(nsTArray<nsString>& outDirectives) const {
   2011  for (uint32_t i = 0; i < mDirectives.Length(); i++) {
   2012    nsAutoString name;
   2013    mDirectives[i]->getDirName(name);
   2014    outDirectives.AppendElement(name);
   2015  }
   2016 }
   2017 
   2018 bool nsCSPPolicy::visitDirectiveSrcs(CSPDirective aDir,
   2019                                     nsCSPSrcVisitor* aVisitor) const {
   2020  for (uint32_t i = 0; i < mDirectives.Length(); i++) {
   2021    if (mDirectives[i]->equals(aDir)) {
   2022      return mDirectives[i]->visitSrcs(aVisitor);
   2023    }
   2024  }
   2025  return false;
   2026 }