Bug 1890277: part 2) Add `require-trusted-types-for` directive to CSP parser, guarded...
[gecko.git] / dom / security / nsCSPUtils.cpp
blob36deedd67d18dafc7dae3a587c61be6a208d168c
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsAttrValue.h"
8 #include "nsCharSeparatedTokenizer.h"
9 #include "nsContentUtils.h"
10 #include "nsCSPUtils.h"
11 #include "nsDebug.h"
12 #include "nsCSPParser.h"
13 #include "nsComponentManagerUtils.h"
14 #include "nsIConsoleService.h"
15 #include "nsIChannel.h"
16 #include "nsICryptoHash.h"
17 #include "nsIScriptError.h"
18 #include "nsIStringBundle.h"
19 #include "nsIURL.h"
20 #include "nsNetUtil.h"
21 #include "nsReadableUtils.h"
22 #include "nsSandboxFlags.h"
23 #include "nsServiceManagerUtils.h"
24 #include "nsWhitespaceTokenizer.h"
26 #include "mozilla/Components.h"
27 #include "mozilla/dom/CSPDictionariesBinding.h"
28 #include "mozilla/dom/Document.h"
29 #include "mozilla/dom/SRIMetadata.h"
30 #include "mozilla/StaticPrefs_security.h"
32 using namespace mozilla;
33 using mozilla::dom::SRIMetadata;
35 #define DEFAULT_PORT -1
37 static mozilla::LogModule* GetCspUtilsLog() {
38 static mozilla::LazyLogModule gCspUtilsPRLog("CSPUtils");
39 return gCspUtilsPRLog;
42 #define CSPUTILSLOG(args) \
43 MOZ_LOG(GetCspUtilsLog(), mozilla::LogLevel::Debug, args)
44 #define CSPUTILSLOGENABLED() \
45 MOZ_LOG_TEST(GetCspUtilsLog(), mozilla::LogLevel::Debug)
47 void CSP_PercentDecodeStr(const nsAString& aEncStr, nsAString& outDecStr) {
48 outDecStr.Truncate();
50 // helper function that should not be visible outside this methods scope
51 struct local {
52 static inline char16_t convertHexDig(char16_t aHexDig) {
53 if (isNumberToken(aHexDig)) {
54 return aHexDig - '0';
56 if (aHexDig >= 'A' && aHexDig <= 'F') {
57 return aHexDig - 'A' + 10;
59 // must be a lower case character
60 // (aHexDig >= 'a' && aHexDig <= 'f')
61 return aHexDig - 'a' + 10;
65 const char16_t *cur, *end, *hexDig1, *hexDig2;
66 cur = aEncStr.BeginReading();
67 end = aEncStr.EndReading();
69 while (cur != end) {
70 // if it's not a percent sign then there is
71 // nothing to do for that character
72 if (*cur != PERCENT_SIGN) {
73 outDecStr.Append(*cur);
74 cur++;
75 continue;
78 // get the two hexDigs following the '%'-sign
79 hexDig1 = cur + 1;
80 hexDig2 = cur + 2;
82 // if there are no hexdigs after the '%' then
83 // there is nothing to do for us.
84 if (hexDig1 == end || hexDig2 == end || !isValidHexDig(*hexDig1) ||
85 !isValidHexDig(*hexDig2)) {
86 outDecStr.Append(PERCENT_SIGN);
87 cur++;
88 continue;
91 // decode "% hexDig1 hexDig2" into a character.
92 char16_t decChar =
93 (local::convertHexDig(*hexDig1) << 4) + local::convertHexDig(*hexDig2);
94 outDecStr.Append(decChar);
96 // increment 'cur' to after the second hexDig
97 cur = ++hexDig2;
101 // The Content Security Policy should be inherited for
102 // local schemes like: "about", "blob", "data", or "filesystem".
103 // see: https://w3c.github.io/webappsec-csp/#initialize-document-csp
104 bool CSP_ShouldResponseInheritCSP(nsIChannel* aChannel) {
105 if (!aChannel) {
106 return false;
109 nsCOMPtr<nsIURI> uri;
110 nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
111 NS_ENSURE_SUCCESS(rv, false);
113 bool isAbout = uri->SchemeIs("about");
114 if (isAbout) {
115 nsAutoCString aboutSpec;
116 rv = uri->GetSpec(aboutSpec);
117 NS_ENSURE_SUCCESS(rv, false);
118 // also allow about:blank#foo
119 if (StringBeginsWith(aboutSpec, "about:blank"_ns) ||
120 StringBeginsWith(aboutSpec, "about:srcdoc"_ns)) {
121 return true;
125 return uri->SchemeIs("blob") || uri->SchemeIs("data") ||
126 uri->SchemeIs("filesystem") || uri->SchemeIs("javascript");
129 void CSP_ApplyMetaCSPToDoc(mozilla::dom::Document& aDoc,
130 const nsAString& aPolicyStr) {
131 if (aDoc.IsLoadedAsData()) {
132 return;
135 nsAutoString policyStr(
136 nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
137 aPolicyStr));
139 if (policyStr.IsEmpty()) {
140 return;
143 nsCOMPtr<nsIContentSecurityPolicy> csp = aDoc.GetCsp();
144 if (!csp) {
145 MOZ_ASSERT(false, "how come there is no CSP");
146 return;
149 // Multiple CSPs (delivered through either header of meta tag) need to
150 // be joined together, see:
151 // https://w3c.github.io/webappsec/specs/content-security-policy/#delivery-html-meta-element
152 nsresult rv =
153 csp->AppendPolicy(policyStr,
154 false, // csp via meta tag can not be report only
155 true); // delivered through the meta tag
156 NS_ENSURE_SUCCESS_VOID(rv);
157 if (nsPIDOMWindowInner* inner = aDoc.GetInnerWindow()) {
158 inner->SetCsp(csp);
160 aDoc.ApplySettingsFromCSP(false);
163 void CSP_GetLocalizedStr(const char* aName, const nsTArray<nsString>& aParams,
164 nsAString& outResult) {
165 nsCOMPtr<nsIStringBundle> keyStringBundle;
166 nsCOMPtr<nsIStringBundleService> stringBundleService =
167 mozilla::components::StringBundle::Service();
169 NS_ASSERTION(stringBundleService, "String bundle service must be present!");
170 stringBundleService->CreateBundle(
171 "chrome://global/locale/security/csp.properties",
172 getter_AddRefs(keyStringBundle));
174 NS_ASSERTION(keyStringBundle, "Key string bundle must be available!");
176 if (!keyStringBundle) {
177 return;
179 keyStringBundle->FormatStringFromName(aName, aParams, outResult);
182 void CSP_LogStrMessage(const nsAString& aMsg) {
183 nsCOMPtr<nsIConsoleService> console(
184 do_GetService("@mozilla.org/consoleservice;1"));
186 if (!console) {
187 return;
189 nsString msg(aMsg);
190 console->LogStringMessage(msg.get());
193 void CSP_LogMessage(const nsAString& aMessage, const nsAString& aSourceName,
194 const nsAString& aSourceLine, uint32_t aLineNumber,
195 uint32_t aColumnNumber, uint32_t aFlags,
196 const nsACString& aCategory, uint64_t aInnerWindowID,
197 bool aFromPrivateWindow) {
198 nsCOMPtr<nsIConsoleService> console(
199 do_GetService(NS_CONSOLESERVICE_CONTRACTID));
201 nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
203 if (!console || !error) {
204 return;
207 // Prepending CSP to the outgoing console message
208 nsString cspMsg;
209 CSP_GetLocalizedStr("CSPMessagePrefix",
210 AutoTArray<nsString, 1>{nsString(aMessage)}, cspMsg);
212 // Currently 'aSourceLine' is not logged to the console, because similar
213 // information is already included within the source link of the message.
214 // For inline violations however, the line and column number are 0 and
215 // information contained within 'aSourceLine' can be really useful for devs.
216 // E.g. 'aSourceLine' might be: 'onclick attribute on DIV element'.
217 // In such cases we append 'aSourceLine' directly to the error message.
218 if (!aSourceLine.IsEmpty() && aLineNumber == 0) {
219 cspMsg.AppendLiteral(u"\nSource: ");
220 cspMsg.Append(aSourceLine);
223 // Since we are leveraging csp errors as the category names which
224 // we pass to devtools, we should prepend them with "CSP_" to
225 // allow easy distincution in devtools code. e.g.
226 // upgradeInsecureRequest -> CSP_upgradeInsecureRequest
227 nsCString category("CSP_");
228 category.Append(aCategory);
230 nsresult rv;
231 if (aInnerWindowID > 0) {
232 rv = error->InitWithWindowID(cspMsg, aSourceName, aSourceLine, aLineNumber,
233 aColumnNumber, aFlags, category,
234 aInnerWindowID);
235 } else {
236 rv = error->Init(cspMsg, aSourceName, aSourceLine, aLineNumber,
237 aColumnNumber, aFlags, category, aFromPrivateWindow,
238 true /* from chrome context */);
240 if (NS_FAILED(rv)) {
241 return;
243 console->LogMessage(error);
246 CSPDirective CSP_StringToCSPDirective(const nsAString& aDir) {
247 nsString lowerDir = PromiseFlatString(aDir);
248 ToLowerCase(lowerDir);
250 uint32_t numDirs = (sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0]));
252 for (uint32_t i = 1; i < numDirs; i++) {
253 if (lowerDir.EqualsASCII(CSPStrDirectives[i])) {
254 return static_cast<CSPDirective>(i);
257 return nsIContentSecurityPolicy::NO_DIRECTIVE;
261 * Combines CSP_LogMessage and CSP_GetLocalizedStr into one call.
263 void CSP_LogLocalizedStr(const char* aName, const nsTArray<nsString>& aParams,
264 const nsAString& aSourceName,
265 const nsAString& aSourceLine, uint32_t aLineNumber,
266 uint32_t aColumnNumber, uint32_t aFlags,
267 const nsACString& aCategory, uint64_t aInnerWindowID,
268 bool aFromPrivateWindow) {
269 nsAutoString logMsg;
270 CSP_GetLocalizedStr(aName, aParams, logMsg);
271 CSP_LogMessage(logMsg, aSourceName, aSourceLine, aLineNumber, aColumnNumber,
272 aFlags, aCategory, aInnerWindowID, aFromPrivateWindow);
275 /* ===== Helpers ============================ */
276 // This implements
277 // https://w3c.github.io/webappsec-csp/#effective-directive-for-a-request.
278 // However the spec doesn't currently cover all request destinations, which
279 // we roughly represent using nsContentPolicyType.
280 CSPDirective CSP_ContentTypeToDirective(nsContentPolicyType aType) {
281 switch (aType) {
282 case nsIContentPolicy::TYPE_IMAGE:
283 case nsIContentPolicy::TYPE_IMAGESET:
284 case nsIContentPolicy::TYPE_INTERNAL_IMAGE:
285 case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD:
286 case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON:
287 return nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE;
289 // BLock XSLT as script, see bug 910139
290 case nsIContentPolicy::TYPE_XSLT:
291 case nsIContentPolicy::TYPE_SCRIPT:
292 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
293 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
294 case nsIContentPolicy::TYPE_INTERNAL_MODULE:
295 case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD:
296 case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS:
297 case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET:
298 case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET:
299 case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT:
300 case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT:
301 // (https://github.com/w3c/webappsec-csp/issues/554)
302 // Some of these types are not explicitly defined in the spec.
304 // Chrome seems to use script-src-elem for worklet!
305 return nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE;
307 case nsIContentPolicy::TYPE_STYLESHEET:
308 case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET:
309 case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD:
310 return nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE;
312 case nsIContentPolicy::TYPE_FONT:
313 case nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD:
314 return nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE;
316 case nsIContentPolicy::TYPE_MEDIA:
317 case nsIContentPolicy::TYPE_INTERNAL_AUDIO:
318 case nsIContentPolicy::TYPE_INTERNAL_VIDEO:
319 case nsIContentPolicy::TYPE_INTERNAL_TRACK:
320 return nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE;
322 case nsIContentPolicy::TYPE_WEB_MANIFEST:
323 return nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE;
325 case nsIContentPolicy::TYPE_INTERNAL_WORKER:
326 case nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE:
327 case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
328 case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
329 return nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE;
331 case nsIContentPolicy::TYPE_SUBDOCUMENT:
332 case nsIContentPolicy::TYPE_INTERNAL_FRAME:
333 case nsIContentPolicy::TYPE_INTERNAL_IFRAME:
334 return nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE;
336 case nsIContentPolicy::TYPE_WEBSOCKET:
337 case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
338 case nsIContentPolicy::TYPE_BEACON:
339 case nsIContentPolicy::TYPE_PING:
340 case nsIContentPolicy::TYPE_FETCH:
341 case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST:
342 case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE:
343 case nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD:
344 case nsIContentPolicy::TYPE_WEB_IDENTITY:
345 case nsIContentPolicy::TYPE_WEB_TRANSPORT:
346 return nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE;
348 case nsIContentPolicy::TYPE_OBJECT:
349 case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST:
350 case nsIContentPolicy::TYPE_INTERNAL_EMBED:
351 case nsIContentPolicy::TYPE_INTERNAL_OBJECT:
352 return nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE;
354 case nsIContentPolicy::TYPE_DTD:
355 case nsIContentPolicy::TYPE_OTHER:
356 case nsIContentPolicy::TYPE_SPECULATIVE:
357 case nsIContentPolicy::TYPE_INTERNAL_DTD:
358 case nsIContentPolicy::TYPE_INTERNAL_FORCE_ALLOWED_DTD:
359 return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
361 // CSP does not apply to webrtc connections
362 case nsIContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA:
363 // csp shold not block top level loads, e.g. in case
364 // of a redirect.
365 case nsIContentPolicy::TYPE_DOCUMENT:
366 // CSP can not block csp reports
367 case nsIContentPolicy::TYPE_CSP_REPORT:
368 return nsIContentSecurityPolicy::NO_DIRECTIVE;
370 case nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD:
371 case nsIContentPolicy::TYPE_UA_FONT:
372 return nsIContentSecurityPolicy::NO_DIRECTIVE;
374 // Fall through to error for all other directives
375 case nsIContentPolicy::TYPE_INVALID:
376 case nsIContentPolicy::TYPE_END:
377 MOZ_ASSERT(false, "Can not map nsContentPolicyType to CSPDirective");
378 // Do not add default: so that compilers can catch the missing case.
380 return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
383 nsCSPHostSrc* CSP_CreateHostSrcFromSelfURI(nsIURI* aSelfURI) {
384 // Create the host first
385 nsCString host;
386 aSelfURI->GetAsciiHost(host);
387 nsCSPHostSrc* hostsrc = new nsCSPHostSrc(NS_ConvertUTF8toUTF16(host));
388 hostsrc->setGeneratedFromSelfKeyword();
390 // Add the scheme.
391 nsCString scheme;
392 aSelfURI->GetScheme(scheme);
393 hostsrc->setScheme(NS_ConvertUTF8toUTF16(scheme));
395 // An empty host (e.g. for data:) indicates it's effectively a unique origin.
396 // Please note that we still need to set the scheme on hostsrc (see above),
397 // because it's used for reporting.
398 if (host.EqualsLiteral("")) {
399 hostsrc->setIsUniqueOrigin();
400 // no need to query the port in that case.
401 return hostsrc;
404 int32_t port;
405 aSelfURI->GetPort(&port);
406 // Only add port if it's not default port.
407 if (port > 0) {
408 nsAutoString portStr;
409 portStr.AppendInt(port);
410 hostsrc->setPort(portStr);
412 return hostsrc;
415 bool CSP_IsEmptyDirective(const nsAString& aValue, const nsAString& aDir) {
416 return (aDir.Length() == 0 && aValue.Length() == 0);
418 bool CSP_IsDirective(const nsAString& aValue, CSPDirective aDir) {
419 return aValue.LowerCaseEqualsASCII(CSP_CSPDirectiveToString(aDir));
422 bool CSP_IsKeyword(const nsAString& aValue, enum CSPKeyword aKey) {
423 return aValue.LowerCaseEqualsASCII(CSP_EnumToUTF8Keyword(aKey));
426 bool CSP_IsQuotelessKeyword(const nsAString& aKey) {
427 nsString lowerKey;
428 ToLowerCase(aKey, lowerKey);
430 nsAutoString keyword;
431 for (uint32_t i = 0; i < CSP_LAST_KEYWORD_VALUE; i++) {
432 // skipping the leading ' and trimming the trailing '
433 keyword.AssignASCII(gCSPUTF8Keywords[i] + 1);
434 keyword.Trim("'", false, true);
435 if (lowerKey.Equals(keyword)) {
436 return true;
439 return false;
443 * Checks whether the current directive permits a specific
444 * scheme. This function is called from nsCSPSchemeSrc() and
445 * also nsCSPHostSrc.
446 * @param aEnforcementScheme
447 * The scheme that this directive allows
448 * @param aUri
449 * The uri of the subresource load.
450 * @param aReportOnly
451 * Whether the enforced policy is report only or not.
452 * @param aUpgradeInsecure
453 * Whether the policy makes use of the directive
454 * 'upgrade-insecure-requests'.
455 * @param aFromSelfURI
456 * Whether a scheme was generated from the keyword 'self'
457 * which then allows schemeless sources to match ws and wss.
460 bool permitsScheme(const nsAString& aEnforcementScheme, nsIURI* aUri,
461 bool aReportOnly, bool aUpgradeInsecure, bool aFromSelfURI) {
462 nsAutoCString scheme;
463 nsresult rv = aUri->GetScheme(scheme);
464 NS_ENSURE_SUCCESS(rv, false);
466 // no scheme to enforce, let's allow the load (e.g. script-src *)
467 if (aEnforcementScheme.IsEmpty()) {
468 return true;
471 // if the scheme matches, all good - allow the load
472 if (aEnforcementScheme.EqualsASCII(scheme.get())) {
473 return true;
476 // allow scheme-less sources where the protected resource is http
477 // and the load is https, see:
478 // http://www.w3.org/TR/CSP2/#match-source-expression
479 if (aEnforcementScheme.EqualsASCII("http")) {
480 if (scheme.EqualsASCII("https")) {
481 return true;
483 if ((scheme.EqualsASCII("ws") || scheme.EqualsASCII("wss")) &&
484 aFromSelfURI) {
485 return true;
488 if (aEnforcementScheme.EqualsASCII("https")) {
489 if (scheme.EqualsLiteral("wss") && aFromSelfURI) {
490 return true;
493 if (aEnforcementScheme.EqualsASCII("ws") && scheme.EqualsASCII("wss")) {
494 return true;
497 // Allow the load when enforcing upgrade-insecure-requests with the
498 // promise the request gets upgraded from http to https and ws to wss.
499 // See nsHttpChannel::Connect() and also WebSocket.cpp. Please note,
500 // the report only policies should not allow the load and report
501 // the error back to the page.
502 return (
503 (aUpgradeInsecure && !aReportOnly) &&
504 ((scheme.EqualsASCII("http") &&
505 aEnforcementScheme.EqualsASCII("https")) ||
506 (scheme.EqualsASCII("ws") && aEnforcementScheme.EqualsASCII("wss"))));
510 * A helper function for appending a CSP header to an existing CSP
511 * policy.
513 * @param aCsp the CSP policy
514 * @param aHeaderValue the header
515 * @param aReportOnly is this a report-only header?
518 nsresult CSP_AppendCSPFromHeader(nsIContentSecurityPolicy* aCsp,
519 const nsAString& aHeaderValue,
520 bool aReportOnly) {
521 NS_ENSURE_ARG(aCsp);
523 // Need to tokenize the header value since multiple headers could be
524 // concatenated into one comma-separated list of policies.
525 // See RFC2616 section 4.2 (last paragraph)
526 nsresult rv = NS_OK;
527 for (const nsAString& policy :
528 nsCharSeparatedTokenizer(aHeaderValue, ',').ToRange()) {
529 rv = aCsp->AppendPolicy(policy, aReportOnly, false);
530 NS_ENSURE_SUCCESS(rv, rv);
532 CSPUTILSLOG(("CSP refined with policy: \"%s\"",
533 NS_ConvertUTF16toUTF8(policy).get()));
536 return NS_OK;
539 /* ===== nsCSPSrc ============================ */
541 nsCSPBaseSrc::nsCSPBaseSrc() {}
543 nsCSPBaseSrc::~nsCSPBaseSrc() = default;
545 // ::permits is only called for external load requests, therefore:
546 // nsCSPKeywordSrc and nsCSPHashSource fall back to this base class
547 // implementation which will never allow the load.
548 bool nsCSPBaseSrc::permits(nsIURI* aUri, bool aWasRedirected, bool aReportOnly,
549 bool aUpgradeInsecure) const {
550 if (CSPUTILSLOGENABLED()) {
551 CSPUTILSLOG(
552 ("nsCSPBaseSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
554 return false;
557 // ::allows is only called for inlined loads, therefore:
558 // nsCSPSchemeSrc, nsCSPHostSrc fall back
559 // to this base class implementation which will never allow the load.
560 bool nsCSPBaseSrc::allows(enum CSPKeyword aKeyword,
561 const nsAString& aHashOrNonce) const {
562 CSPUTILSLOG(("nsCSPBaseSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
563 aKeyword == CSP_HASH ? "hash" : CSP_EnumToUTF8Keyword(aKeyword),
564 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
565 return false;
568 /* ====== nsCSPSchemeSrc ===================== */
570 nsCSPSchemeSrc::nsCSPSchemeSrc(const nsAString& aScheme) : mScheme(aScheme) {
571 ToLowerCase(mScheme);
574 nsCSPSchemeSrc::~nsCSPSchemeSrc() = default;
576 bool nsCSPSchemeSrc::permits(nsIURI* aUri, bool aWasRedirected,
577 bool aReportOnly, bool aUpgradeInsecure) const {
578 if (CSPUTILSLOGENABLED()) {
579 CSPUTILSLOG(
580 ("nsCSPSchemeSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
582 MOZ_ASSERT((!mScheme.EqualsASCII("")), "scheme can not be the empty string");
583 return permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure, false);
586 bool nsCSPSchemeSrc::visit(nsCSPSrcVisitor* aVisitor) const {
587 return aVisitor->visitSchemeSrc(*this);
590 void nsCSPSchemeSrc::toString(nsAString& outStr) const {
591 outStr.Append(mScheme);
592 outStr.AppendLiteral(":");
595 /* ===== nsCSPHostSrc ======================== */
597 nsCSPHostSrc::nsCSPHostSrc(const nsAString& aHost)
598 : mHost(aHost),
599 mGeneratedFromSelfKeyword(false),
600 mIsUniqueOrigin(false),
601 mWithinFrameAncstorsDir(false) {
602 ToLowerCase(mHost);
605 nsCSPHostSrc::~nsCSPHostSrc() = default;
608 * Checks whether the current directive permits a specific port.
609 * @param aEnforcementScheme
610 * The scheme that this directive allows
611 * (used to query the default port for that scheme)
612 * @param aEnforcementPort
613 * The port that this directive allows
614 * @param aResourceURI
615 * The uri of the subresource load
617 bool permitsPort(const nsAString& aEnforcementScheme,
618 const nsAString& aEnforcementPort, nsIURI* aResourceURI) {
619 // If enforcement port is the wildcard, don't block the load.
620 if (aEnforcementPort.EqualsASCII("*")) {
621 return true;
624 int32_t resourcePort;
625 nsresult rv = aResourceURI->GetPort(&resourcePort);
626 if (NS_FAILED(rv) && aEnforcementPort.IsEmpty()) {
627 // If we cannot get a Port (e.g. because of an Custom Protocol handler)
628 // We need to check if a default port is associated with the Scheme
629 if (aEnforcementScheme.IsEmpty()) {
630 return false;
632 int defaultPortforScheme =
633 NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme).get());
635 // If there is no default port associated with the Scheme (
636 // defaultPortforScheme == -1) or it is an externally handled protocol (
637 // defaultPortforScheme == 0 ) and the csp does not enforce a port - we can
638 // allow not having a port
639 return (defaultPortforScheme == -1 || defaultPortforScheme == -0);
641 // Avoid unnecessary string creation/manipulation and don't block the
642 // load if the resource to be loaded uses the default port for that
643 // scheme and there is no port to be enforced.
644 // Note, this optimization relies on scheme checks within permitsScheme().
645 if (resourcePort == DEFAULT_PORT && aEnforcementPort.IsEmpty()) {
646 return true;
649 // By now we know at that either the resourcePort does not use the default
650 // port or there is a port restriction to be enforced. A port value of -1
651 // corresponds to the protocol's default port (eg. -1 implies port 80 for
652 // http URIs), in such a case we have to query the default port of the
653 // resource to be loaded.
654 if (resourcePort == DEFAULT_PORT) {
655 nsAutoCString resourceScheme;
656 rv = aResourceURI->GetScheme(resourceScheme);
657 NS_ENSURE_SUCCESS(rv, false);
658 resourcePort = NS_GetDefaultPort(resourceScheme.get());
661 // If there is a port to be enforced and the ports match, then
662 // don't block the load.
663 nsString resourcePortStr;
664 resourcePortStr.AppendInt(resourcePort);
665 if (aEnforcementPort.Equals(resourcePortStr)) {
666 return true;
669 // If there is no port to be enforced, query the default port for the load.
670 nsString enforcementPort(aEnforcementPort);
671 if (enforcementPort.IsEmpty()) {
672 // For scheme less sources, our parser always generates a scheme
673 // which is the scheme of the protected resource.
674 MOZ_ASSERT(!aEnforcementScheme.IsEmpty(),
675 "need a scheme to query default port");
676 int32_t defaultEnforcementPort =
677 NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme).get());
678 enforcementPort.Truncate();
679 enforcementPort.AppendInt(defaultEnforcementPort);
682 // If default ports match, don't block the load
683 if (enforcementPort.Equals(resourcePortStr)) {
684 return true;
687 // Additional port matching where the regular URL matching algorithm
688 // treats insecure ports as matching their secure variants.
689 // default port for http is :80
690 // default port for https is :443
691 if (enforcementPort.EqualsLiteral("80") &&
692 resourcePortStr.EqualsLiteral("443")) {
693 return true;
696 // ports do not match, block the load.
697 return false;
700 bool nsCSPHostSrc::permits(nsIURI* aUri, bool aWasRedirected, bool aReportOnly,
701 bool aUpgradeInsecure) const {
702 if (CSPUTILSLOGENABLED()) {
703 CSPUTILSLOG(
704 ("nsCSPHostSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
707 if (mIsUniqueOrigin) {
708 return false;
711 // we are following the enforcement rules from the spec, see:
712 // http://www.w3.org/TR/CSP11/#match-source-expression
714 // 4.3) scheme matching: Check if the scheme matches.
715 if (!permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure,
716 mGeneratedFromSelfKeyword)) {
717 return false;
720 // The host in nsCSpHostSrc should never be empty. In case we are enforcing
721 // just a specific scheme, the parser should generate a nsCSPSchemeSource.
722 NS_ASSERTION((!mHost.IsEmpty()), "host can not be the empty string");
724 // Before we can check if the host matches, we have to
725 // extract the host part from aUri.
726 nsAutoCString uriHost;
727 nsresult rv = aUri->GetAsciiHost(uriHost);
728 NS_ENSURE_SUCCESS(rv, false);
730 nsString decodedUriHost;
731 CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriHost), decodedUriHost);
733 // 2) host matching: Enforce a single *
734 if (mHost.EqualsASCII("*")) {
735 // The single ASTERISK character (*) does not match a URI's scheme of a type
736 // designating a globally unique identifier (such as blob:, data:, or
737 // filesystem:) At the moment firefox does not support filesystem; but for
738 // future compatibility we support it in CSP according to the spec,
739 // see: 4.2.2 Matching Source Expressions Note, that allowlisting any of
740 // these schemes would call nsCSPSchemeSrc::permits().
741 if (aUri->SchemeIs("blob") || aUri->SchemeIs("data") ||
742 aUri->SchemeIs("filesystem")) {
743 return false;
746 // If no scheme is present there also wont be a port and folder to check
747 // which means we can return early
748 if (mScheme.IsEmpty()) {
749 return true;
752 // 4.5) host matching: Check if the allowed host starts with a wilcard.
753 else if (mHost.First() == '*') {
754 NS_ASSERTION(
755 mHost[1] == '.',
756 "Second character needs to be '.' whenever host starts with '*'");
758 // Eliminate leading "*", but keeping the FULL STOP (.) thereafter before
759 // checking if the remaining characters match
760 nsString wildCardHost = mHost;
761 wildCardHost = Substring(wildCardHost, 1, wildCardHost.Length() - 1);
762 if (!StringEndsWith(decodedUriHost, wildCardHost)) {
763 return false;
766 // 4.6) host matching: Check if hosts match.
767 else if (!mHost.Equals(decodedUriHost)) {
768 return false;
771 // Port matching: Check if the ports match.
772 if (!permitsPort(mScheme, mPort, aUri)) {
773 return false;
776 // 4.9) Path matching: If there is a path, we have to enforce
777 // path-level matching, unless the channel got redirected, see:
778 // http://www.w3.org/TR/CSP11/#source-list-paths-and-redirects
779 if (!aWasRedirected && !mPath.IsEmpty()) {
780 // converting aUri into nsIURL so we can strip query and ref
781 // example.com/test#foo -> example.com/test
782 // example.com/test?val=foo -> example.com/test
783 nsCOMPtr<nsIURL> url = do_QueryInterface(aUri);
784 if (!url) {
785 NS_ASSERTION(false, "can't QI into nsIURI");
786 return false;
788 nsAutoCString uriPath;
789 rv = url->GetFilePath(uriPath);
790 NS_ENSURE_SUCCESS(rv, false);
792 if (mWithinFrameAncstorsDir) {
793 // no path matching for frame-ancestors to not leak any path information.
794 return true;
797 nsString decodedUriPath;
798 CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriPath), decodedUriPath);
800 // check if the last character of mPath is '/'; if so
801 // we just have to check loading resource is within
802 // the allowed path.
803 if (mPath.Last() == '/') {
804 if (!StringBeginsWith(decodedUriPath, mPath)) {
805 return false;
808 // otherwise mPath refers to a specific file, and we have to
809 // check if the loading resource matches the file.
810 else {
811 if (!mPath.Equals(decodedUriPath)) {
812 return false;
817 // At the end: scheme, host, port and path match -> allow the load.
818 return true;
821 bool nsCSPHostSrc::visit(nsCSPSrcVisitor* aVisitor) const {
822 return aVisitor->visitHostSrc(*this);
825 void nsCSPHostSrc::toString(nsAString& outStr) const {
826 if (mGeneratedFromSelfKeyword) {
827 outStr.AppendLiteral("'self'");
828 return;
831 // If mHost is a single "*", we append the wildcard and return.
832 if (mHost.EqualsASCII("*") && mScheme.IsEmpty() && mPort.IsEmpty()) {
833 outStr.Append(mHost);
834 return;
837 // append scheme
838 outStr.Append(mScheme);
840 // append host
841 outStr.AppendLiteral("://");
842 outStr.Append(mHost);
844 // append port
845 if (!mPort.IsEmpty()) {
846 outStr.AppendLiteral(":");
847 outStr.Append(mPort);
850 // append path
851 outStr.Append(mPath);
854 void nsCSPHostSrc::setScheme(const nsAString& aScheme) {
855 mScheme = aScheme;
856 ToLowerCase(mScheme);
859 void nsCSPHostSrc::setPort(const nsAString& aPort) { mPort = aPort; }
861 void nsCSPHostSrc::appendPath(const nsAString& aPath) { mPath.Append(aPath); }
863 /* ===== nsCSPKeywordSrc ===================== */
865 nsCSPKeywordSrc::nsCSPKeywordSrc(enum CSPKeyword aKeyword)
866 : mKeyword(aKeyword) {
867 NS_ASSERTION((aKeyword != CSP_SELF),
868 "'self' should have been replaced in the parser");
871 nsCSPKeywordSrc::~nsCSPKeywordSrc() = default;
873 bool nsCSPKeywordSrc::allows(enum CSPKeyword aKeyword,
874 const nsAString& aHashOrNonce) const {
875 CSPUTILSLOG(("nsCSPKeywordSrc::allows, aKeyWord: %s, aHashOrNonce: %s",
876 CSP_EnumToUTF8Keyword(aKeyword),
877 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
878 return mKeyword == aKeyword;
881 bool nsCSPKeywordSrc::visit(nsCSPSrcVisitor* aVisitor) const {
882 return aVisitor->visitKeywordSrc(*this);
885 void nsCSPKeywordSrc::toString(nsAString& outStr) const {
886 outStr.Append(CSP_EnumToUTF16Keyword(mKeyword));
889 /* ===== nsCSPNonceSrc ==================== */
891 nsCSPNonceSrc::nsCSPNonceSrc(const nsAString& aNonce) : mNonce(aNonce) {}
893 nsCSPNonceSrc::~nsCSPNonceSrc() = default;
895 bool nsCSPNonceSrc::allows(enum CSPKeyword aKeyword,
896 const nsAString& aHashOrNonce) const {
897 CSPUTILSLOG(("nsCSPNonceSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
898 CSP_EnumToUTF8Keyword(aKeyword),
899 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
901 if (aKeyword != CSP_NONCE) {
902 return false;
904 // nonces can not be invalidated by strict-dynamic
905 return mNonce.Equals(aHashOrNonce);
908 bool nsCSPNonceSrc::visit(nsCSPSrcVisitor* aVisitor) const {
909 return aVisitor->visitNonceSrc(*this);
912 void nsCSPNonceSrc::toString(nsAString& outStr) const {
913 outStr.Append(CSP_EnumToUTF16Keyword(CSP_NONCE));
914 outStr.Append(mNonce);
915 outStr.AppendLiteral("'");
918 /* ===== nsCSPHashSrc ===================== */
920 nsCSPHashSrc::nsCSPHashSrc(const nsAString& aAlgo, const nsAString& aHash)
921 : mAlgorithm(aAlgo), mHash(aHash) {
922 // Only the algo should be rewritten to lowercase, the hash must remain the
923 // same.
924 ToLowerCase(mAlgorithm);
925 // Normalize the base64url encoding to base64 encoding:
926 char16_t* cur = mHash.BeginWriting();
927 char16_t* end = mHash.EndWriting();
929 for (; cur < end; ++cur) {
930 if (char16_t('-') == *cur) {
931 *cur = char16_t('+');
933 if (char16_t('_') == *cur) {
934 *cur = char16_t('/');
939 nsCSPHashSrc::~nsCSPHashSrc() = default;
941 bool nsCSPHashSrc::allows(enum CSPKeyword aKeyword,
942 const nsAString& aHashOrNonce) const {
943 CSPUTILSLOG(("nsCSPHashSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
944 CSP_EnumToUTF8Keyword(aKeyword),
945 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
947 if (aKeyword != CSP_HASH) {
948 return false;
951 // hashes can not be invalidated by strict-dynamic
953 // Convert aHashOrNonce to UTF-8
954 NS_ConvertUTF16toUTF8 utf8_hash(aHashOrNonce);
956 nsCOMPtr<nsICryptoHash> hasher;
957 nsresult rv = NS_NewCryptoHash(NS_ConvertUTF16toUTF8(mAlgorithm),
958 getter_AddRefs(hasher));
959 NS_ENSURE_SUCCESS(rv, false);
961 rv = hasher->Update((uint8_t*)utf8_hash.get(), utf8_hash.Length());
962 NS_ENSURE_SUCCESS(rv, false);
964 nsAutoCString hash;
965 rv = hasher->Finish(true, hash);
966 NS_ENSURE_SUCCESS(rv, false);
968 return NS_ConvertUTF16toUTF8(mHash).Equals(hash);
971 bool nsCSPHashSrc::visit(nsCSPSrcVisitor* aVisitor) const {
972 return aVisitor->visitHashSrc(*this);
975 void nsCSPHashSrc::toString(nsAString& outStr) const {
976 outStr.AppendLiteral("'");
977 outStr.Append(mAlgorithm);
978 outStr.AppendLiteral("-");
979 outStr.Append(mHash);
980 outStr.AppendLiteral("'");
983 /* ===== nsCSPReportURI ===================== */
985 nsCSPReportURI::nsCSPReportURI(nsIURI* aURI) : mReportURI(aURI) {}
987 nsCSPReportURI::~nsCSPReportURI() = default;
989 bool nsCSPReportURI::visit(nsCSPSrcVisitor* aVisitor) const { return false; }
991 void nsCSPReportURI::toString(nsAString& outStr) const {
992 nsAutoCString spec;
993 nsresult rv = mReportURI->GetSpec(spec);
994 if (NS_FAILED(rv)) {
995 return;
997 outStr.AppendASCII(spec.get());
1000 /* ===== nsCSPSandboxFlags ===================== */
1002 nsCSPSandboxFlags::nsCSPSandboxFlags(const nsAString& aFlags) : mFlags(aFlags) {
1003 ToLowerCase(mFlags);
1006 nsCSPSandboxFlags::~nsCSPSandboxFlags() = default;
1008 bool nsCSPSandboxFlags::visit(nsCSPSrcVisitor* aVisitor) const { return false; }
1010 void nsCSPSandboxFlags::toString(nsAString& outStr) const {
1011 outStr.Append(mFlags);
1014 /* ===== nsCSPRequireTrustedTypesForDirectiveValue ===================== */
1016 nsCSPRequireTrustedTypesForDirectiveValue::
1017 nsCSPRequireTrustedTypesForDirectiveValue(const nsAString& aValue)
1018 : mValue{aValue} {}
1020 bool nsCSPRequireTrustedTypesForDirectiveValue::visit(
1021 nsCSPSrcVisitor* aVisitor) const {
1022 MOZ_ASSERT_UNREACHABLE(
1023 "This method should only be called for other overloads of this method.");
1024 return false;
1027 void nsCSPRequireTrustedTypesForDirectiveValue::toString(
1028 nsAString& aOutStr) const {
1029 aOutStr.Append(mValue);
1032 /* ===== nsCSPDirective ====================== */
1034 nsCSPDirective::nsCSPDirective(CSPDirective aDirective) {
1035 mDirective = aDirective;
1038 nsCSPDirective::~nsCSPDirective() {
1039 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1040 delete mSrcs[i];
1044 // https://w3c.github.io/webappsec-csp/#match-nonce-to-source-list
1045 static bool DoesNonceMatchSourceList(nsILoadInfo* aLoadInfo,
1046 const nsTArray<nsCSPBaseSrc*>& aSrcs) {
1047 // Step 1. Assert: source list is not null. (implicit)
1049 // Note: For code-reuse we do "request’s cryptographic nonce metadata" here
1050 // instead of the caller.
1051 nsAutoString nonce;
1052 MOZ_ALWAYS_SUCCEEDS(aLoadInfo->GetCspNonce(nonce));
1054 // Step 2. If nonce is the empty string, return "Does Not Match".
1055 if (nonce.IsEmpty()) {
1056 return false;
1059 // Step 3. For each expression of source list:
1060 for (nsCSPBaseSrc* src : aSrcs) {
1061 // Step 3.1. If expression matches the nonce-source grammar, and nonce is
1062 // identical to expression’s base64-value part, return "Matches".
1063 if (src->isNonce()) {
1064 nsAutoString srcNonce;
1065 static_cast<nsCSPNonceSrc*>(src)->getNonce(srcNonce);
1066 if (srcNonce == nonce) {
1067 return true;
1072 // Step 4. Return "Does Not Match".
1073 return false;
1076 // https://www.w3.org/TR/SRI/#parse-metadata
1077 // This function is similar to SRICheck::IntegrityMetadata, but also keeps
1078 // SRI metadata with weaker hashes.
1079 // CSP treats "no metadata" and empty results the same way.
1080 static nsTArray<SRIMetadata> ParseSRIMetadata(const nsAString& aMetadata) {
1081 // Step 1. Let result be the empty set.
1082 // Step 2. Let empty be equal to true.
1083 nsTArray<SRIMetadata> result;
1085 NS_ConvertUTF16toUTF8 metadataList(aMetadata);
1086 nsAutoCString token;
1088 // Step 3. For each token returned by splitting metadata on spaces:
1089 nsCWhitespaceTokenizer tokenizer(metadataList);
1090 while (tokenizer.hasMoreTokens()) {
1091 token = tokenizer.nextToken();
1092 // Step 3.1. Set empty to false.
1093 // Step 3.3. Parse token per the grammar in integrity metadata.
1094 SRIMetadata metadata(token);
1095 // Step 3.2. If token is not a valid metadata, skip the remaining steps, and
1096 // proceed to the next token.
1097 if (metadata.IsMalformed()) {
1098 continue;
1101 // Step 3.4. Let algorithm be the alg component of token.
1102 // Step 3.5. If algorithm is a hash function recognized by the user agent,
1103 // add the
1104 // parsed token to result.
1105 if (metadata.IsAlgorithmSupported()) {
1106 result.AppendElement(metadata);
1110 // Step 4. Return no metadata if empty is true, otherwise return result.
1111 return result;
1114 bool nsCSPDirective::permits(CSPDirective aDirective, nsILoadInfo* aLoadInfo,
1115 nsIURI* aUri, bool aWasRedirected,
1116 bool aReportOnly, bool aUpgradeInsecure) const {
1117 MOZ_ASSERT(equals(aDirective) || isDefaultDirective());
1119 if (CSPUTILSLOGENABLED()) {
1120 CSPUTILSLOG(("nsCSPDirective::permits, aUri: %s, aDirective: %s",
1121 aUri->GetSpecOrDefault().get(),
1122 CSP_CSPDirectiveToString(aDirective)));
1125 if (aLoadInfo) {
1126 // https://w3c.github.io/webappsec-csp/#style-src-elem-pre-request
1127 if (aDirective == CSPDirective::STYLE_SRC_ELEM_DIRECTIVE) {
1128 // Step 3. If the result of executing §6.7.2.3 Does nonce match source
1129 // list? on request’s cryptographic nonce metadata and this directive’s
1130 // value is "Matches", return "Allowed".
1131 if (DoesNonceMatchSourceList(aLoadInfo, mSrcs)) {
1132 CSPUTILSLOG((" Allowed by matching nonce (style)"));
1133 return true;
1137 // https://w3c.github.io/webappsec-csp/#script-pre-request
1138 // Step 1. If request’s destination is script-like:
1139 else if (aDirective == CSPDirective::SCRIPT_SRC_ELEM_DIRECTIVE ||
1140 aDirective == CSPDirective::WORKER_SRC_DIRECTIVE) {
1141 // Step 1.1. If the result of executing §6.7.2.3 Does nonce match source
1142 // list? on request’s cryptographic nonce metadata and this directive’s
1143 // value is "Matches", return "Allowed".
1144 if (DoesNonceMatchSourceList(aLoadInfo, mSrcs)) {
1145 CSPUTILSLOG((" Allowed by matching nonce (script-like)"));
1146 return true;
1149 // Step 1.2. Let integrity expressions be the set of source expressions in
1150 // directive’s value that match the hash-source grammar.
1151 nsTArray<nsCSPHashSrc*> integrityExpressions;
1152 bool hasStrictDynamicKeyword =
1153 false; // Optimization to reduce number of iterations.
1154 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1155 if (mSrcs[i]->isHash()) {
1156 integrityExpressions.AppendElement(
1157 static_cast<nsCSPHashSrc*>(mSrcs[i]));
1158 } else if (mSrcs[i]->isKeyword(CSP_STRICT_DYNAMIC)) {
1159 hasStrictDynamicKeyword = true;
1163 // Step 1.3. If integrity expressions is not empty:
1164 if (!integrityExpressions.IsEmpty()) {
1165 // Step 1.3.1. Let integrity sources be the result of executing the
1166 // algorithm defined in [SRI 3.3.3 Parse metadata] on request’s
1167 // integrity metadata.
1168 nsAutoString integrityMetadata;
1169 aLoadInfo->GetIntegrityMetadata(integrityMetadata);
1171 nsTArray<SRIMetadata> integritySources =
1172 ParseSRIMetadata(integrityMetadata);
1173 MOZ_ASSERT(
1174 integritySources.IsEmpty() == integrityMetadata.IsEmpty(),
1175 "The integrity metadata should be only be empty, "
1176 "when the parsed string was completely empty, otherwise it should "
1177 "include at least one valid hash");
1179 // Step 1.3.2. If integrity sources is "no metadata" or an empty set,
1180 // skip the remaining substeps.
1181 if (!integritySources.IsEmpty()) {
1182 // Step 1.3.3. Let bypass due to integrity match be true.
1183 bool bypass = true;
1185 nsAutoCString sourceAlgorithmUTF8;
1186 nsAutoCString sourceHashUTF8;
1187 nsAutoString sourceAlgorithm;
1188 nsAutoString sourceHash;
1189 nsAutoString algorithm;
1190 nsAutoString hash;
1192 // Step 1.3.4. For each source of integrity sources:
1193 for (const SRIMetadata& source : integritySources) {
1194 source.GetAlgorithm(&sourceAlgorithmUTF8);
1195 sourceAlgorithm = NS_ConvertUTF8toUTF16(sourceAlgorithmUTF8);
1196 source.GetHash(0, &sourceHashUTF8);
1197 sourceHash = NS_ConvertUTF8toUTF16(sourceHashUTF8);
1199 // Step 1.3.4.1 If directive’s value does not contain a source
1200 // expression whose hash-algorithm is an ASCII case-insensitive
1201 // match for source’s hash-algorithm, and whose base64-value is
1202 // identical to source’s base64-value, then set bypass due to
1203 // integrity match to false.
1204 bool found = false;
1205 for (const nsCSPHashSrc* hashSrc : integrityExpressions) {
1206 hashSrc->getAlgorithm(algorithm);
1207 hashSrc->getHash(hash);
1209 // The nsCSPHashSrc constructor lowercases algorithm, so this
1210 // is case-insensitive.
1211 if (sourceAlgorithm == algorithm && sourceHash == hash) {
1212 found = true;
1213 break;
1217 if (!found) {
1218 bypass = false;
1219 break;
1223 // Step 1.3.5. If bypass due to integrity match is true, return
1224 // "Allowed".
1225 if (bypass) {
1226 CSPUTILSLOG(
1227 (" Allowed by matching integrity metadata (script-like)"));
1228 return true;
1233 // Step 1.4. If directive’s value contains a source expression that is an
1234 // ASCII case-insensitive match for the "'strict-dynamic'" keyword-source:
1236 // XXX I don't think we should apply strict-dynamic to XSLT.
1237 if (hasStrictDynamicKeyword && aLoadInfo->InternalContentPolicyType() !=
1238 nsIContentPolicy::TYPE_XSLT) {
1239 // Step 1.4.1 If the request’s parser metadata is "parser-inserted",
1240 // return "Blocked". Otherwise, return "Allowed".
1241 if (aLoadInfo->GetParserCreatedScript()) {
1242 CSPUTILSLOG(
1243 (" Blocked by 'strict-dynamic' because parser-inserted"));
1244 return false;
1247 CSPUTILSLOG(
1248 (" Allowed by 'strict-dynamic' because not-parser-inserted"));
1249 return true;
1254 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1255 if (mSrcs[i]->permits(aUri, aWasRedirected, aReportOnly,
1256 aUpgradeInsecure)) {
1257 return true;
1260 return false;
1263 bool nsCSPDirective::allows(enum CSPKeyword aKeyword,
1264 const nsAString& aHashOrNonce) const {
1265 CSPUTILSLOG(("nsCSPDirective::allows, aKeyWord: %s, aHashOrNonce: %s",
1266 CSP_EnumToUTF8Keyword(aKeyword),
1267 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
1269 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1270 if (mSrcs[i]->allows(aKeyword, aHashOrNonce)) {
1271 return true;
1274 return false;
1277 // https://w3c.github.io/webappsec-csp/#allow-all-inline
1278 bool nsCSPDirective::allowsAllInlineBehavior(CSPDirective aDir) const {
1279 // Step 1. Let allow all inline be false.
1280 bool allowAll = false;
1282 // Step 2. For each expression of list:
1283 for (nsCSPBaseSrc* src : mSrcs) {
1284 // Step 2.1. If expression matches the nonce-source or hash-source grammar,
1285 // return "Does Not Allow".
1286 if (src->isNonce() || src->isHash()) {
1287 return false;
1290 // Step 2.2. If type is "script", "script attribute" or "navigation" and
1291 // expression matches the keyword-source "'strict-dynamic'", return "Does
1292 // Not Allow".
1293 if ((aDir == nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE ||
1294 aDir == nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) &&
1295 src->isKeyword(CSP_STRICT_DYNAMIC)) {
1296 return false;
1299 // Step 2.3. If expression is an ASCII case-insensitive match for the
1300 // keyword-source "'unsafe-inline'", set allow all inline to true.
1301 if (src->isKeyword(CSP_UNSAFE_INLINE)) {
1302 allowAll = true;
1306 // Step 3. If allow all inline is true, return "Allows". Otherwise, return
1307 // "Does Not Allow".
1308 return allowAll;
1311 void nsCSPDirective::toString(nsAString& outStr) const {
1312 // Append directive name
1313 outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective));
1314 outStr.AppendLiteral(" ");
1316 // Append srcs
1317 StringJoinAppend(outStr, u" "_ns, mSrcs,
1318 [](nsAString& dest, nsCSPBaseSrc* cspBaseSrc) {
1319 cspBaseSrc->toString(dest);
1323 void nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP& outCSP) const {
1324 mozilla::dom::Sequence<nsString> srcs;
1325 nsString src;
1326 if (NS_WARN_IF(!srcs.SetCapacity(mSrcs.Length(), mozilla::fallible))) {
1327 MOZ_ASSERT(false,
1328 "Not enough memory for 'sources' sequence in "
1329 "nsCSPDirective::toDomCSPStruct().");
1330 return;
1332 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1333 src.Truncate();
1334 mSrcs[i]->toString(src);
1335 if (!srcs.AppendElement(src, mozilla::fallible)) {
1336 MOZ_ASSERT(false,
1337 "Failed to append to 'sources' sequence in "
1338 "nsCSPDirective::toDomCSPStruct().");
1342 switch (mDirective) {
1343 case nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE:
1344 outCSP.mDefault_src.Construct();
1345 outCSP.mDefault_src.Value() = std::move(srcs);
1346 return;
1348 case nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE:
1349 outCSP.mScript_src.Construct();
1350 outCSP.mScript_src.Value() = std::move(srcs);
1351 return;
1353 case nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE:
1354 outCSP.mObject_src.Construct();
1355 outCSP.mObject_src.Value() = std::move(srcs);
1356 return;
1358 case nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE:
1359 outCSP.mStyle_src.Construct();
1360 outCSP.mStyle_src.Value() = std::move(srcs);
1361 return;
1363 case nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE:
1364 outCSP.mImg_src.Construct();
1365 outCSP.mImg_src.Value() = std::move(srcs);
1366 return;
1368 case nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE:
1369 outCSP.mMedia_src.Construct();
1370 outCSP.mMedia_src.Value() = std::move(srcs);
1371 return;
1373 case nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE:
1374 outCSP.mFrame_src.Construct();
1375 outCSP.mFrame_src.Value() = std::move(srcs);
1376 return;
1378 case nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE:
1379 outCSP.mFont_src.Construct();
1380 outCSP.mFont_src.Value() = std::move(srcs);
1381 return;
1383 case nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE:
1384 outCSP.mConnect_src.Construct();
1385 outCSP.mConnect_src.Value() = std::move(srcs);
1386 return;
1388 case nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE:
1389 outCSP.mReport_uri.Construct();
1390 outCSP.mReport_uri.Value() = std::move(srcs);
1391 return;
1393 case nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE:
1394 outCSP.mFrame_ancestors.Construct();
1395 outCSP.mFrame_ancestors.Value() = std::move(srcs);
1396 return;
1398 case nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE:
1399 outCSP.mManifest_src.Construct();
1400 outCSP.mManifest_src.Value() = std::move(srcs);
1401 return;
1402 // not supporting REFLECTED_XSS_DIRECTIVE
1404 case nsIContentSecurityPolicy::BASE_URI_DIRECTIVE:
1405 outCSP.mBase_uri.Construct();
1406 outCSP.mBase_uri.Value() = std::move(srcs);
1407 return;
1409 case nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE:
1410 outCSP.mForm_action.Construct();
1411 outCSP.mForm_action.Value() = std::move(srcs);
1412 return;
1414 case nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT:
1415 outCSP.mBlock_all_mixed_content.Construct();
1416 // does not have any srcs
1417 return;
1419 case nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE:
1420 outCSP.mUpgrade_insecure_requests.Construct();
1421 // does not have any srcs
1422 return;
1424 case nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE:
1425 outCSP.mChild_src.Construct();
1426 outCSP.mChild_src.Value() = std::move(srcs);
1427 return;
1429 case nsIContentSecurityPolicy::SANDBOX_DIRECTIVE:
1430 outCSP.mSandbox.Construct();
1431 outCSP.mSandbox.Value() = std::move(srcs);
1432 return;
1434 case nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE:
1435 outCSP.mWorker_src.Construct();
1436 outCSP.mWorker_src.Value() = std::move(srcs);
1437 return;
1439 case nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE:
1440 outCSP.mScript_src_elem.Construct();
1441 outCSP.mScript_src_elem.Value() = std::move(srcs);
1442 return;
1444 case nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE:
1445 outCSP.mScript_src_attr.Construct();
1446 outCSP.mScript_src_attr.Value() = std::move(srcs);
1447 return;
1449 case nsIContentSecurityPolicy::REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE:
1450 outCSP.mRequire_trusted_types_for.Construct();
1452 // Here, the srcs represent the sink group
1453 // (https://w3c.github.io/trusted-types/dist/spec/#integration-with-content-security-policy).
1454 outCSP.mRequire_trusted_types_for.Value() = std::move(srcs);
1455 return;
1457 default:
1458 NS_ASSERTION(false, "cannot find directive to convert CSP to JSON");
1462 void nsCSPDirective::getReportURIs(nsTArray<nsString>& outReportURIs) const {
1463 NS_ASSERTION((mDirective == nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE),
1464 "not a report-uri directive");
1466 // append uris
1467 nsString tmpReportURI;
1468 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1469 tmpReportURI.Truncate();
1470 mSrcs[i]->toString(tmpReportURI);
1471 outReportURIs.AppendElement(tmpReportURI);
1475 bool nsCSPDirective::visitSrcs(nsCSPSrcVisitor* aVisitor) const {
1476 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1477 if (!mSrcs[i]->visit(aVisitor)) {
1478 return false;
1481 return true;
1484 bool nsCSPDirective::equals(CSPDirective aDirective) const {
1485 return (mDirective == aDirective);
1488 void nsCSPDirective::getDirName(nsAString& outStr) const {
1489 outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective));
1492 bool nsCSPDirective::hasReportSampleKeyword() const {
1493 for (nsCSPBaseSrc* src : mSrcs) {
1494 if (src->isReportSample()) {
1495 return true;
1499 return false;
1502 /* =============== nsCSPChildSrcDirective ============= */
1504 nsCSPChildSrcDirective::nsCSPChildSrcDirective(CSPDirective aDirective)
1505 : nsCSPDirective(aDirective),
1506 mRestrictFrames(false),
1507 mRestrictWorkers(false) {}
1509 nsCSPChildSrcDirective::~nsCSPChildSrcDirective() = default;
1511 bool nsCSPChildSrcDirective::equals(CSPDirective aDirective) const {
1512 if (aDirective == nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE) {
1513 return mRestrictFrames;
1515 if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) {
1516 return mRestrictWorkers;
1518 return (mDirective == aDirective);
1521 /* =============== nsCSPScriptSrcDirective ============= */
1523 nsCSPScriptSrcDirective::nsCSPScriptSrcDirective(CSPDirective aDirective)
1524 : nsCSPDirective(aDirective) {}
1526 nsCSPScriptSrcDirective::~nsCSPScriptSrcDirective() = default;
1528 bool nsCSPScriptSrcDirective::equals(CSPDirective aDirective) const {
1529 if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) {
1530 return mRestrictWorkers;
1532 if (aDirective == nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) {
1533 return mRestrictScriptElem;
1535 if (aDirective == nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) {
1536 return mRestrictScriptAttr;
1538 return mDirective == aDirective;
1541 /* =============== nsCSPStyleSrcDirective ============= */
1543 nsCSPStyleSrcDirective::nsCSPStyleSrcDirective(CSPDirective aDirective)
1544 : nsCSPDirective(aDirective) {}
1546 nsCSPStyleSrcDirective::~nsCSPStyleSrcDirective() = default;
1548 bool nsCSPStyleSrcDirective::equals(CSPDirective aDirective) const {
1549 if (aDirective == nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE) {
1550 return mRestrictStyleElem;
1552 if (aDirective == nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE) {
1553 return mRestrictStyleAttr;
1555 return mDirective == aDirective;
1558 /* =============== nsBlockAllMixedContentDirective ============= */
1560 nsBlockAllMixedContentDirective::nsBlockAllMixedContentDirective(
1561 CSPDirective aDirective)
1562 : nsCSPDirective(aDirective) {}
1564 nsBlockAllMixedContentDirective::~nsBlockAllMixedContentDirective() = default;
1566 void nsBlockAllMixedContentDirective::toString(nsAString& outStr) const {
1567 outStr.AppendASCII(CSP_CSPDirectiveToString(
1568 nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT));
1571 void nsBlockAllMixedContentDirective::getDirName(nsAString& outStr) const {
1572 outStr.AppendASCII(CSP_CSPDirectiveToString(
1573 nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT));
1576 /* =============== nsUpgradeInsecureDirective ============= */
1578 nsUpgradeInsecureDirective::nsUpgradeInsecureDirective(CSPDirective aDirective)
1579 : nsCSPDirective(aDirective) {}
1581 nsUpgradeInsecureDirective::~nsUpgradeInsecureDirective() = default;
1583 void nsUpgradeInsecureDirective::toString(nsAString& outStr) const {
1584 outStr.AppendASCII(CSP_CSPDirectiveToString(
1585 nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE));
1588 void nsUpgradeInsecureDirective::getDirName(nsAString& outStr) const {
1589 outStr.AppendASCII(CSP_CSPDirectiveToString(
1590 nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE));
1593 /* ===== nsCSPPolicy ========================= */
1595 nsCSPPolicy::nsCSPPolicy()
1596 : mUpgradeInsecDir(nullptr),
1597 mReportOnly(false),
1598 mDeliveredViaMetaTag(false) {
1599 CSPUTILSLOG(("nsCSPPolicy::nsCSPPolicy"));
1602 nsCSPPolicy::~nsCSPPolicy() {
1603 CSPUTILSLOG(("nsCSPPolicy::~nsCSPPolicy"));
1605 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1606 delete mDirectives[i];
1610 bool nsCSPPolicy::permits(CSPDirective aDir, nsILoadInfo* aLoadInfo,
1611 nsIURI* aUri, bool aWasRedirected, bool aSpecific,
1612 nsAString& outViolatedDirective,
1613 nsAString& outViolatedDirectiveString) const {
1614 if (CSPUTILSLOGENABLED()) {
1615 CSPUTILSLOG(("nsCSPPolicy::permits, aUri: %s, aDir: %s, aSpecific: %s",
1616 aUri->GetSpecOrDefault().get(), CSP_CSPDirectiveToString(aDir),
1617 aSpecific ? "true" : "false"));
1620 NS_ASSERTION(aUri, "permits needs an uri to perform the check!");
1621 outViolatedDirective.Truncate();
1622 outViolatedDirectiveString.Truncate();
1624 nsCSPDirective* defaultDir = nullptr;
1626 // Try to find a relevant directive
1627 // These directive arrays are short (1-5 elements), not worth using a
1628 // hashtable.
1629 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1630 if (mDirectives[i]->equals(aDir)) {
1631 if (!mDirectives[i]->permits(aDir, aLoadInfo, aUri, aWasRedirected,
1632 mReportOnly, mUpgradeInsecDir)) {
1633 mDirectives[i]->getDirName(outViolatedDirective);
1634 mDirectives[i]->toString(outViolatedDirectiveString);
1635 return false;
1637 return true;
1639 if (mDirectives[i]->isDefaultDirective()) {
1640 defaultDir = mDirectives[i];
1644 // If the above loop runs through, we haven't found a matching directive.
1645 // Avoid relooping, just store the result of default-src while looping.
1646 if (!aSpecific && defaultDir) {
1647 if (!defaultDir->permits(aDir, aLoadInfo, aUri, aWasRedirected, mReportOnly,
1648 mUpgradeInsecDir)) {
1649 defaultDir->getDirName(outViolatedDirective);
1650 defaultDir->toString(outViolatedDirectiveString);
1651 return false;
1653 return true;
1656 // Nothing restricts this, so we're allowing the load
1657 // See bug 764937
1658 return true;
1661 bool nsCSPPolicy::allows(CSPDirective aDirective, enum CSPKeyword aKeyword,
1662 const nsAString& aHashOrNonce) const {
1663 CSPUTILSLOG(("nsCSPPolicy::allows, aKeyWord: %s, a HashOrNonce: %s",
1664 CSP_EnumToUTF8Keyword(aKeyword),
1665 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
1667 if (nsCSPDirective* directive = matchingOrDefaultDirective(aDirective)) {
1668 return directive->allows(aKeyword, aHashOrNonce);
1671 // No matching directive or default directive as fallback found, thus
1672 // allowing the load; see Bug 885433
1673 // a) inline scripts (also unsafe eval) should only be blocked
1674 // if there is a [script-src] or [default-src]
1675 // b) inline styles should only be blocked
1676 // if there is a [style-src] or [default-src]
1677 return true;
1680 nsCSPDirective* nsCSPPolicy::matchingOrDefaultDirective(
1681 CSPDirective aDirective) const {
1682 nsCSPDirective* defaultDir = nullptr;
1684 // Try to find a matching directive
1685 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1686 if (mDirectives[i]->isDefaultDirective()) {
1687 defaultDir = mDirectives[i];
1688 continue;
1690 if (mDirectives[i]->equals(aDirective)) {
1691 return mDirectives[i];
1695 return defaultDir;
1698 void nsCSPPolicy::toString(nsAString& outStr) const {
1699 StringJoinAppend(outStr, u"; "_ns, mDirectives,
1700 [](nsAString& dest, nsCSPDirective* cspDirective) {
1701 cspDirective->toString(dest);
1705 void nsCSPPolicy::toDomCSPStruct(mozilla::dom::CSP& outCSP) const {
1706 outCSP.mReport_only = mReportOnly;
1708 for (uint32_t i = 0; i < mDirectives.Length(); ++i) {
1709 mDirectives[i]->toDomCSPStruct(outCSP);
1713 bool nsCSPPolicy::hasDirective(CSPDirective aDir) const {
1714 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1715 if (mDirectives[i]->equals(aDir)) {
1716 return true;
1719 return false;
1722 bool nsCSPPolicy::allowsAllInlineBehavior(CSPDirective aDir) const {
1723 nsCSPDirective* directive = matchingOrDefaultDirective(aDir);
1724 if (!directive) {
1725 // No matching or default directive found thus allow the all inline
1726 // scripts or styles. (See nsCSPPolicy::allows)
1727 return true;
1730 return directive->allowsAllInlineBehavior(aDir);
1734 * Use this function only after ::allows() returned 'false'. Most and
1735 * foremost it's used to get the violated directive before sending reports.
1736 * The parameter outDirective is the equivalent of 'outViolatedDirective'
1737 * for the ::permits() function family.
1739 void nsCSPPolicy::getViolatedDirectiveInformation(CSPDirective aDirective,
1740 nsAString& outDirective,
1741 nsAString& outDirectiveString,
1742 bool* aReportSample) const {
1743 *aReportSample = false;
1744 nsCSPDirective* directive = matchingOrDefaultDirective(aDirective);
1745 if (!directive) {
1746 MOZ_ASSERT_UNREACHABLE("Can not query violated directive");
1747 outDirective.AppendLiteral("couldNotQueryViolatedDirective");
1748 outDirective.Truncate();
1749 return;
1752 directive->getDirName(outDirective);
1753 directive->toString(outDirectiveString);
1754 *aReportSample = directive->hasReportSampleKeyword();
1758 * Helper function that returns the underlying bit representation of sandbox
1759 * flags. The function returns SANDBOXED_NONE if there are no sandbox
1760 * directives.
1762 uint32_t nsCSPPolicy::getSandboxFlags() const {
1763 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1764 if (mDirectives[i]->equals(nsIContentSecurityPolicy::SANDBOX_DIRECTIVE)) {
1765 nsAutoString flags;
1766 mDirectives[i]->toString(flags);
1768 if (flags.IsEmpty()) {
1769 return SANDBOX_ALL_FLAGS;
1772 nsAttrValue attr;
1773 attr.ParseAtomArray(flags);
1775 return nsContentUtils::ParseSandboxAttributeToFlags(&attr);
1779 return SANDBOXED_NONE;
1782 void nsCSPPolicy::getReportURIs(nsTArray<nsString>& outReportURIs) const {
1783 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1784 if (mDirectives[i]->equals(
1785 nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
1786 mDirectives[i]->getReportURIs(outReportURIs);
1787 return;
1792 bool nsCSPPolicy::visitDirectiveSrcs(CSPDirective aDir,
1793 nsCSPSrcVisitor* aVisitor) const {
1794 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1795 if (mDirectives[i]->equals(aDir)) {
1796 return mDirectives[i]->visitSrcs(aVisitor);
1799 return false;