Bug 1826136 [wpt PR 39338] - Update wpt metadata, a=testonly
[gecko.git] / dom / security / nsCSPUtils.cpp
blob2bc6c66de3e470786d33cf46ae14f9e20465a9fd
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"
25 #include "mozilla/Components.h"
26 #include "mozilla/dom/CSPDictionariesBinding.h"
27 #include "mozilla/dom/Document.h"
28 #include "mozilla/StaticPrefs_security.h"
30 #define DEFAULT_PORT -1
32 static mozilla::LogModule* GetCspUtilsLog() {
33 static mozilla::LazyLogModule gCspUtilsPRLog("CSPUtils");
34 return gCspUtilsPRLog;
37 #define CSPUTILSLOG(args) \
38 MOZ_LOG(GetCspUtilsLog(), mozilla::LogLevel::Debug, args)
39 #define CSPUTILSLOGENABLED() \
40 MOZ_LOG_TEST(GetCspUtilsLog(), mozilla::LogLevel::Debug)
42 void CSP_PercentDecodeStr(const nsAString& aEncStr, nsAString& outDecStr) {
43 outDecStr.Truncate();
45 // helper function that should not be visible outside this methods scope
46 struct local {
47 static inline char16_t convertHexDig(char16_t aHexDig) {
48 if (isNumberToken(aHexDig)) {
49 return aHexDig - '0';
51 if (aHexDig >= 'A' && aHexDig <= 'F') {
52 return aHexDig - 'A' + 10;
54 // must be a lower case character
55 // (aHexDig >= 'a' && aHexDig <= 'f')
56 return aHexDig - 'a' + 10;
60 const char16_t *cur, *end, *hexDig1, *hexDig2;
61 cur = aEncStr.BeginReading();
62 end = aEncStr.EndReading();
64 while (cur != end) {
65 // if it's not a percent sign then there is
66 // nothing to do for that character
67 if (*cur != PERCENT_SIGN) {
68 outDecStr.Append(*cur);
69 cur++;
70 continue;
73 // get the two hexDigs following the '%'-sign
74 hexDig1 = cur + 1;
75 hexDig2 = cur + 2;
77 // if there are no hexdigs after the '%' then
78 // there is nothing to do for us.
79 if (hexDig1 == end || hexDig2 == end || !isValidHexDig(*hexDig1) ||
80 !isValidHexDig(*hexDig2)) {
81 outDecStr.Append(PERCENT_SIGN);
82 cur++;
83 continue;
86 // decode "% hexDig1 hexDig2" into a character.
87 char16_t decChar =
88 (local::convertHexDig(*hexDig1) << 4) + local::convertHexDig(*hexDig2);
89 outDecStr.Append(decChar);
91 // increment 'cur' to after the second hexDig
92 cur = ++hexDig2;
96 // The Content Security Policy should be inherited for
97 // local schemes like: "about", "blob", "data", or "filesystem".
98 // see: https://w3c.github.io/webappsec-csp/#initialize-document-csp
99 bool CSP_ShouldResponseInheritCSP(nsIChannel* aChannel) {
100 if (!aChannel) {
101 return false;
104 nsCOMPtr<nsIURI> uri;
105 nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
106 NS_ENSURE_SUCCESS(rv, false);
108 bool isAbout = uri->SchemeIs("about");
109 if (isAbout) {
110 nsAutoCString aboutSpec;
111 rv = uri->GetSpec(aboutSpec);
112 NS_ENSURE_SUCCESS(rv, false);
113 // also allow about:blank#foo
114 if (StringBeginsWith(aboutSpec, "about:blank"_ns) ||
115 StringBeginsWith(aboutSpec, "about:srcdoc"_ns)) {
116 return true;
120 return uri->SchemeIs("blob") || uri->SchemeIs("data") ||
121 uri->SchemeIs("filesystem") || uri->SchemeIs("javascript");
124 void CSP_ApplyMetaCSPToDoc(mozilla::dom::Document& aDoc,
125 const nsAString& aPolicyStr) {
126 if (aDoc.IsLoadedAsData()) {
127 return;
130 nsAutoString policyStr(
131 nsContentUtils::TrimWhitespace<nsContentUtils::IsHTMLWhitespace>(
132 aPolicyStr));
134 if (policyStr.IsEmpty()) {
135 return;
138 nsCOMPtr<nsIContentSecurityPolicy> csp = aDoc.GetCsp();
139 if (!csp) {
140 MOZ_ASSERT(false, "how come there is no CSP");
141 return;
144 // Multiple CSPs (delivered through either header of meta tag) need to
145 // be joined together, see:
146 // https://w3c.github.io/webappsec/specs/content-security-policy/#delivery-html-meta-element
147 nsresult rv =
148 csp->AppendPolicy(policyStr,
149 false, // csp via meta tag can not be report only
150 true); // delivered through the meta tag
151 NS_ENSURE_SUCCESS_VOID(rv);
152 if (nsPIDOMWindowInner* inner = aDoc.GetInnerWindow()) {
153 inner->SetCsp(csp);
155 aDoc.ApplySettingsFromCSP(false);
158 void CSP_GetLocalizedStr(const char* aName, const nsTArray<nsString>& aParams,
159 nsAString& outResult) {
160 nsCOMPtr<nsIStringBundle> keyStringBundle;
161 nsCOMPtr<nsIStringBundleService> stringBundleService =
162 mozilla::components::StringBundle::Service();
164 NS_ASSERTION(stringBundleService, "String bundle service must be present!");
165 stringBundleService->CreateBundle(
166 "chrome://global/locale/security/csp.properties",
167 getter_AddRefs(keyStringBundle));
169 NS_ASSERTION(keyStringBundle, "Key string bundle must be available!");
171 if (!keyStringBundle) {
172 return;
174 keyStringBundle->FormatStringFromName(aName, aParams, outResult);
177 void CSP_LogStrMessage(const nsAString& aMsg) {
178 nsCOMPtr<nsIConsoleService> console(
179 do_GetService("@mozilla.org/consoleservice;1"));
181 if (!console) {
182 return;
184 nsString msg(aMsg);
185 console->LogStringMessage(msg.get());
188 void CSP_LogMessage(const nsAString& aMessage, const nsAString& aSourceName,
189 const nsAString& aSourceLine, uint32_t aLineNumber,
190 uint32_t aColumnNumber, uint32_t aFlags,
191 const nsACString& aCategory, uint64_t aInnerWindowID,
192 bool aFromPrivateWindow) {
193 nsCOMPtr<nsIConsoleService> console(
194 do_GetService(NS_CONSOLESERVICE_CONTRACTID));
196 nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
198 if (!console || !error) {
199 return;
202 // Prepending CSP to the outgoing console message
203 nsString cspMsg;
204 cspMsg.AppendLiteral(u"Content Security Policy: ");
205 cspMsg.Append(aMessage);
207 // Currently 'aSourceLine' is not logged to the console, because similar
208 // information is already included within the source link of the message.
209 // For inline violations however, the line and column number are 0 and
210 // information contained within 'aSourceLine' can be really useful for devs.
211 // E.g. 'aSourceLine' might be: 'onclick attribute on DIV element'.
212 // In such cases we append 'aSourceLine' directly to the error message.
213 if (!aSourceLine.IsEmpty() && aLineNumber == 0) {
214 cspMsg.AppendLiteral(u"\nSource: ");
215 cspMsg.Append(aSourceLine);
218 // Since we are leveraging csp errors as the category names which
219 // we pass to devtools, we should prepend them with "CSP_" to
220 // allow easy distincution in devtools code. e.g.
221 // upgradeInsecureRequest -> CSP_upgradeInsecureRequest
222 nsCString category("CSP_");
223 category.Append(aCategory);
225 nsresult rv;
226 if (aInnerWindowID > 0) {
227 rv = error->InitWithWindowID(cspMsg, aSourceName, aSourceLine, aLineNumber,
228 aColumnNumber, aFlags, category,
229 aInnerWindowID);
230 } else {
231 rv = error->Init(cspMsg, aSourceName, aSourceLine, aLineNumber,
232 aColumnNumber, aFlags, category, aFromPrivateWindow,
233 true /* from chrome context */);
235 if (NS_FAILED(rv)) {
236 return;
238 console->LogMessage(error);
242 * Combines CSP_LogMessage and CSP_GetLocalizedStr into one call.
244 void CSP_LogLocalizedStr(const char* aName, const nsTArray<nsString>& aParams,
245 const nsAString& aSourceName,
246 const nsAString& aSourceLine, uint32_t aLineNumber,
247 uint32_t aColumnNumber, uint32_t aFlags,
248 const nsACString& aCategory, uint64_t aInnerWindowID,
249 bool aFromPrivateWindow) {
250 nsAutoString logMsg;
251 CSP_GetLocalizedStr(aName, aParams, logMsg);
252 CSP_LogMessage(logMsg, aSourceName, aSourceLine, aLineNumber, aColumnNumber,
253 aFlags, aCategory, aInnerWindowID, aFromPrivateWindow);
256 /* ===== Helpers ============================ */
257 // This implements
258 // https://w3c.github.io/webappsec-csp/#effective-directive-for-a-request.
259 // However the spec doesn't currently cover all request destinations, which
260 // we roughly represent using nsContentPolicyType.
261 CSPDirective CSP_ContentTypeToDirective(nsContentPolicyType aType) {
262 switch (aType) {
263 case nsIContentPolicy::TYPE_IMAGE:
264 case nsIContentPolicy::TYPE_IMAGESET:
265 case nsIContentPolicy::TYPE_INTERNAL_IMAGE:
266 case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD:
267 case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON:
268 return nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE;
270 // BLock XSLT as script, see bug 910139
271 case nsIContentPolicy::TYPE_XSLT:
272 case nsIContentPolicy::TYPE_SCRIPT:
273 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
274 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
275 case nsIContentPolicy::TYPE_INTERNAL_MODULE:
276 case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD:
277 case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS:
278 case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET:
279 case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET:
280 case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT:
281 case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT:
282 // (https://github.com/w3c/webappsec-csp/issues/554)
283 // Some of these types are not explicitly defined in the spec.
285 // Chrome seems to use script-src-elem for worklet!
286 return nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE;
288 case nsIContentPolicy::TYPE_STYLESHEET:
289 case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET:
290 case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD:
291 return nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE;
293 case nsIContentPolicy::TYPE_FONT:
294 case nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD:
295 return nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE;
297 case nsIContentPolicy::TYPE_MEDIA:
298 case nsIContentPolicy::TYPE_INTERNAL_AUDIO:
299 case nsIContentPolicy::TYPE_INTERNAL_VIDEO:
300 case nsIContentPolicy::TYPE_INTERNAL_TRACK:
301 return nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE;
303 case nsIContentPolicy::TYPE_WEB_MANIFEST:
304 return nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE;
306 case nsIContentPolicy::TYPE_INTERNAL_WORKER:
307 case nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE:
308 case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
309 case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
310 return nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE;
312 case nsIContentPolicy::TYPE_SUBDOCUMENT:
313 case nsIContentPolicy::TYPE_INTERNAL_FRAME:
314 case nsIContentPolicy::TYPE_INTERNAL_IFRAME:
315 return nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE;
317 case nsIContentPolicy::TYPE_WEBSOCKET:
318 case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
319 case nsIContentPolicy::TYPE_BEACON:
320 case nsIContentPolicy::TYPE_PING:
321 case nsIContentPolicy::TYPE_FETCH:
322 case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST:
323 case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE:
324 case nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD:
325 case nsIContentPolicy::TYPE_WEB_IDENTITY:
326 case nsIContentPolicy::TYPE_WEB_TRANSPORT:
327 return nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE;
329 case nsIContentPolicy::TYPE_OBJECT:
330 case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST:
331 case nsIContentPolicy::TYPE_INTERNAL_EMBED:
332 case nsIContentPolicy::TYPE_INTERNAL_OBJECT:
333 return nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE;
335 case nsIContentPolicy::TYPE_DTD:
336 case nsIContentPolicy::TYPE_OTHER:
337 case nsIContentPolicy::TYPE_SPECULATIVE:
338 case nsIContentPolicy::TYPE_INTERNAL_DTD:
339 case nsIContentPolicy::TYPE_INTERNAL_FORCE_ALLOWED_DTD:
340 return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
342 // CSP does not apply to webrtc connections
343 case nsIContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA:
344 // csp shold not block top level loads, e.g. in case
345 // of a redirect.
346 case nsIContentPolicy::TYPE_DOCUMENT:
347 // CSP can not block csp reports
348 case nsIContentPolicy::TYPE_CSP_REPORT:
349 return nsIContentSecurityPolicy::NO_DIRECTIVE;
351 case nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD:
352 case nsIContentPolicy::TYPE_UA_FONT:
353 return nsIContentSecurityPolicy::NO_DIRECTIVE;
355 // Fall through to error for all other directives
356 // Note that we should never end up here for navigate-to
357 case nsIContentPolicy::TYPE_INVALID:
358 case nsIContentPolicy::TYPE_END:
359 MOZ_ASSERT(false, "Can not map nsContentPolicyType to CSPDirective");
360 // Do not add default: so that compilers can catch the missing case.
362 return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
365 nsCSPHostSrc* CSP_CreateHostSrcFromSelfURI(nsIURI* aSelfURI) {
366 // Create the host first
367 nsCString host;
368 aSelfURI->GetAsciiHost(host);
369 nsCSPHostSrc* hostsrc = new nsCSPHostSrc(NS_ConvertUTF8toUTF16(host));
370 hostsrc->setGeneratedFromSelfKeyword();
372 // Add the scheme.
373 nsCString scheme;
374 aSelfURI->GetScheme(scheme);
375 hostsrc->setScheme(NS_ConvertUTF8toUTF16(scheme));
377 // An empty host (e.g. for data:) indicates it's effectively a unique origin.
378 // Please note that we still need to set the scheme on hostsrc (see above),
379 // because it's used for reporting.
380 if (host.EqualsLiteral("")) {
381 hostsrc->setIsUniqueOrigin();
382 // no need to query the port in that case.
383 return hostsrc;
386 int32_t port;
387 aSelfURI->GetPort(&port);
388 // Only add port if it's not default port.
389 if (port > 0) {
390 nsAutoString portStr;
391 portStr.AppendInt(port);
392 hostsrc->setPort(portStr);
394 return hostsrc;
397 bool CSP_IsEmptyDirective(const nsAString& aValue, const nsAString& aDir) {
398 return (aDir.Length() == 0 && aValue.Length() == 0);
400 bool CSP_IsDirective(const nsAString& aValue, CSPDirective aDir) {
401 return aValue.LowerCaseEqualsASCII(CSP_CSPDirectiveToString(aDir));
404 bool CSP_IsKeyword(const nsAString& aValue, enum CSPKeyword aKey) {
405 return aValue.LowerCaseEqualsASCII(CSP_EnumToUTF8Keyword(aKey));
408 bool CSP_IsQuotelessKeyword(const nsAString& aKey) {
409 nsString lowerKey;
410 ToLowerCase(aKey, lowerKey);
412 nsAutoString keyword;
413 for (uint32_t i = 0; i < CSP_LAST_KEYWORD_VALUE; i++) {
414 // skipping the leading ' and trimming the trailing '
415 keyword.AssignASCII(gCSPUTF8Keywords[i] + 1);
416 keyword.Trim("'", false, true);
417 if (lowerKey.Equals(keyword)) {
418 return true;
421 return false;
425 * Checks whether the current directive permits a specific
426 * scheme. This function is called from nsCSPSchemeSrc() and
427 * also nsCSPHostSrc.
428 * @param aEnforcementScheme
429 * The scheme that this directive allows
430 * @param aUri
431 * The uri of the subresource load.
432 * @param aReportOnly
433 * Whether the enforced policy is report only or not.
434 * @param aUpgradeInsecure
435 * Whether the policy makes use of the directive
436 * 'upgrade-insecure-requests'.
437 * @param aFromSelfURI
438 * Whether a scheme was generated from the keyword 'self'
439 * which then allows schemeless sources to match ws and wss.
442 bool permitsScheme(const nsAString& aEnforcementScheme, nsIURI* aUri,
443 bool aReportOnly, bool aUpgradeInsecure, bool aFromSelfURI) {
444 nsAutoCString scheme;
445 nsresult rv = aUri->GetScheme(scheme);
446 NS_ENSURE_SUCCESS(rv, false);
448 // no scheme to enforce, let's allow the load (e.g. script-src *)
449 if (aEnforcementScheme.IsEmpty()) {
450 return true;
453 // if the scheme matches, all good - allow the load
454 if (aEnforcementScheme.EqualsASCII(scheme.get())) {
455 return true;
458 // allow scheme-less sources where the protected resource is http
459 // and the load is https, see:
460 // http://www.w3.org/TR/CSP2/#match-source-expression
461 if (aEnforcementScheme.EqualsASCII("http")) {
462 if (scheme.EqualsASCII("https")) {
463 return true;
465 if ((scheme.EqualsASCII("ws") || scheme.EqualsASCII("wss")) &&
466 aFromSelfURI) {
467 return true;
470 if (aEnforcementScheme.EqualsASCII("https")) {
471 if (scheme.EqualsLiteral("wss") && aFromSelfURI) {
472 return true;
475 if (aEnforcementScheme.EqualsASCII("ws") && scheme.EqualsASCII("wss")) {
476 return true;
479 // Allow the load when enforcing upgrade-insecure-requests with the
480 // promise the request gets upgraded from http to https and ws to wss.
481 // See nsHttpChannel::Connect() and also WebSocket.cpp. Please note,
482 // the report only policies should not allow the load and report
483 // the error back to the page.
484 return (
485 (aUpgradeInsecure && !aReportOnly) &&
486 ((scheme.EqualsASCII("http") &&
487 aEnforcementScheme.EqualsASCII("https")) ||
488 (scheme.EqualsASCII("ws") && aEnforcementScheme.EqualsASCII("wss"))));
492 * A helper function for appending a CSP header to an existing CSP
493 * policy.
495 * @param aCsp the CSP policy
496 * @param aHeaderValue the header
497 * @param aReportOnly is this a report-only header?
500 nsresult CSP_AppendCSPFromHeader(nsIContentSecurityPolicy* aCsp,
501 const nsAString& aHeaderValue,
502 bool aReportOnly) {
503 NS_ENSURE_ARG(aCsp);
505 // Need to tokenize the header value since multiple headers could be
506 // concatenated into one comma-separated list of policies.
507 // See RFC2616 section 4.2 (last paragraph)
508 nsresult rv = NS_OK;
509 for (const nsAString& policy :
510 nsCharSeparatedTokenizer(aHeaderValue, ',').ToRange()) {
511 rv = aCsp->AppendPolicy(policy, aReportOnly, false);
512 NS_ENSURE_SUCCESS(rv, rv);
514 CSPUTILSLOG(("CSP refined with policy: \"%s\"",
515 NS_ConvertUTF16toUTF8(policy).get()));
518 return NS_OK;
521 /* ===== nsCSPSrc ============================ */
523 nsCSPBaseSrc::nsCSPBaseSrc() : mInvalidated(false) {}
525 nsCSPBaseSrc::~nsCSPBaseSrc() = default;
527 // ::permits is only called for external load requests, therefore:
528 // nsCSPKeywordSrc and nsCSPHashSource fall back to this base class
529 // implementation which will never allow the load.
530 bool nsCSPBaseSrc::permits(nsIURI* aUri, const nsAString& aNonce,
531 bool aWasRedirected, bool aReportOnly,
532 bool aUpgradeInsecure, bool aParserCreated) const {
533 if (CSPUTILSLOGENABLED()) {
534 CSPUTILSLOG(
535 ("nsCSPBaseSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
537 return false;
540 // ::allows is only called for inlined loads, therefore:
541 // nsCSPSchemeSrc, nsCSPHostSrc fall back
542 // to this base class implementation which will never allow the load.
543 bool nsCSPBaseSrc::allows(enum CSPKeyword aKeyword,
544 const nsAString& aHashOrNonce,
545 bool aParserCreated) const {
546 CSPUTILSLOG(("nsCSPBaseSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
547 aKeyword == CSP_HASH ? "hash" : CSP_EnumToUTF8Keyword(aKeyword),
548 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
549 return false;
552 /* ====== nsCSPSchemeSrc ===================== */
554 nsCSPSchemeSrc::nsCSPSchemeSrc(const nsAString& aScheme) : mScheme(aScheme) {
555 ToLowerCase(mScheme);
558 nsCSPSchemeSrc::~nsCSPSchemeSrc() = default;
560 bool nsCSPSchemeSrc::permits(nsIURI* aUri, const nsAString& aNonce,
561 bool aWasRedirected, bool aReportOnly,
562 bool aUpgradeInsecure, bool aParserCreated) const {
563 if (CSPUTILSLOGENABLED()) {
564 CSPUTILSLOG(
565 ("nsCSPSchemeSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
567 MOZ_ASSERT((!mScheme.EqualsASCII("")), "scheme can not be the empty string");
568 if (mInvalidated) {
569 return false;
571 return permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure, false);
574 bool nsCSPSchemeSrc::visit(nsCSPSrcVisitor* aVisitor) const {
575 return aVisitor->visitSchemeSrc(*this);
578 void nsCSPSchemeSrc::toString(nsAString& outStr) const {
579 outStr.Append(mScheme);
580 outStr.AppendLiteral(":");
583 /* ===== nsCSPHostSrc ======================== */
585 nsCSPHostSrc::nsCSPHostSrc(const nsAString& aHost)
586 : mHost(aHost),
587 mGeneratedFromSelfKeyword(false),
588 mIsUniqueOrigin(false),
589 mWithinFrameAncstorsDir(false) {
590 ToLowerCase(mHost);
593 nsCSPHostSrc::~nsCSPHostSrc() = default;
596 * Checks whether the current directive permits a specific port.
597 * @param aEnforcementScheme
598 * The scheme that this directive allows
599 * (used to query the default port for that scheme)
600 * @param aEnforcementPort
601 * The port that this directive allows
602 * @param aResourceURI
603 * The uri of the subresource load
605 bool permitsPort(const nsAString& aEnforcementScheme,
606 const nsAString& aEnforcementPort, nsIURI* aResourceURI) {
607 // If enforcement port is the wildcard, don't block the load.
608 if (aEnforcementPort.EqualsASCII("*")) {
609 return true;
612 int32_t resourcePort;
613 nsresult rv = aResourceURI->GetPort(&resourcePort);
614 if (NS_FAILED(rv) && aEnforcementPort.IsEmpty()) {
615 // If we cannot get a Port (e.g. because of an Custom Protocol handler)
616 // We need to check if a default port is associated with the Scheme
617 if (aEnforcementScheme.IsEmpty()) {
618 return false;
620 int defaultPortforScheme =
621 NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme).get());
623 // If there is no default port associated with the Scheme (
624 // defaultPortforScheme == -1) or it is an externally handled protocol (
625 // defaultPortforScheme == 0 ) and the csp does not enforce a port - we can
626 // allow not having a port
627 return (defaultPortforScheme == -1 || defaultPortforScheme == -0);
629 // Avoid unnecessary string creation/manipulation and don't block the
630 // load if the resource to be loaded uses the default port for that
631 // scheme and there is no port to be enforced.
632 // Note, this optimization relies on scheme checks within permitsScheme().
633 if (resourcePort == DEFAULT_PORT && aEnforcementPort.IsEmpty()) {
634 return true;
637 // By now we know at that either the resourcePort does not use the default
638 // port or there is a port restriction to be enforced. A port value of -1
639 // corresponds to the protocol's default port (eg. -1 implies port 80 for
640 // http URIs), in such a case we have to query the default port of the
641 // resource to be loaded.
642 if (resourcePort == DEFAULT_PORT) {
643 nsAutoCString resourceScheme;
644 rv = aResourceURI->GetScheme(resourceScheme);
645 NS_ENSURE_SUCCESS(rv, false);
646 resourcePort = NS_GetDefaultPort(resourceScheme.get());
649 // If there is a port to be enforced and the ports match, then
650 // don't block the load.
651 nsString resourcePortStr;
652 resourcePortStr.AppendInt(resourcePort);
653 if (aEnforcementPort.Equals(resourcePortStr)) {
654 return true;
657 // If there is no port to be enforced, query the default port for the load.
658 nsString enforcementPort(aEnforcementPort);
659 if (enforcementPort.IsEmpty()) {
660 // For scheme less sources, our parser always generates a scheme
661 // which is the scheme of the protected resource.
662 MOZ_ASSERT(!aEnforcementScheme.IsEmpty(),
663 "need a scheme to query default port");
664 int32_t defaultEnforcementPort =
665 NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme).get());
666 enforcementPort.Truncate();
667 enforcementPort.AppendInt(defaultEnforcementPort);
670 // If default ports match, don't block the load
671 if (enforcementPort.Equals(resourcePortStr)) {
672 return true;
675 // Additional port matching where the regular URL matching algorithm
676 // treats insecure ports as matching their secure variants.
677 // default port for http is :80
678 // default port for https is :443
679 if (enforcementPort.EqualsLiteral("80") &&
680 resourcePortStr.EqualsLiteral("443")) {
681 return true;
684 // ports do not match, block the load.
685 return false;
688 bool nsCSPHostSrc::permits(nsIURI* aUri, const nsAString& aNonce,
689 bool aWasRedirected, bool aReportOnly,
690 bool aUpgradeInsecure, bool aParserCreated) const {
691 if (CSPUTILSLOGENABLED()) {
692 CSPUTILSLOG(
693 ("nsCSPHostSrc::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
696 if (mInvalidated || mIsUniqueOrigin) {
697 return false;
700 // we are following the enforcement rules from the spec, see:
701 // http://www.w3.org/TR/CSP11/#match-source-expression
703 // 4.3) scheme matching: Check if the scheme matches.
704 if (!permitsScheme(mScheme, aUri, aReportOnly, aUpgradeInsecure,
705 mGeneratedFromSelfKeyword)) {
706 return false;
709 // The host in nsCSpHostSrc should never be empty. In case we are enforcing
710 // just a specific scheme, the parser should generate a nsCSPSchemeSource.
711 NS_ASSERTION((!mHost.IsEmpty()), "host can not be the empty string");
713 // Before we can check if the host matches, we have to
714 // extract the host part from aUri.
715 nsAutoCString uriHost;
716 nsresult rv = aUri->GetAsciiHost(uriHost);
717 NS_ENSURE_SUCCESS(rv, false);
719 nsString decodedUriHost;
720 CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriHost), decodedUriHost);
722 // 2) host matching: Enforce a single *
723 if (mHost.EqualsASCII("*")) {
724 // The single ASTERISK character (*) does not match a URI's scheme of a type
725 // designating a globally unique identifier (such as blob:, data:, or
726 // filesystem:) At the moment firefox does not support filesystem; but for
727 // future compatibility we support it in CSP according to the spec,
728 // see: 4.2.2 Matching Source Expressions Note, that allowlisting any of
729 // these schemes would call nsCSPSchemeSrc::permits().
730 if (aUri->SchemeIs("blob") || aUri->SchemeIs("data") ||
731 aUri->SchemeIs("filesystem")) {
732 return false;
735 // If no scheme is present there also wont be a port and folder to check
736 // which means we can return early
737 if (mScheme.IsEmpty()) {
738 return true;
741 // 4.5) host matching: Check if the allowed host starts with a wilcard.
742 else if (mHost.First() == '*') {
743 NS_ASSERTION(
744 mHost[1] == '.',
745 "Second character needs to be '.' whenever host starts with '*'");
747 // Eliminate leading "*", but keeping the FULL STOP (.) thereafter before
748 // checking if the remaining characters match
749 nsString wildCardHost = mHost;
750 wildCardHost = Substring(wildCardHost, 1, wildCardHost.Length() - 1);
751 if (!StringEndsWith(decodedUriHost, wildCardHost)) {
752 return false;
755 // 4.6) host matching: Check if hosts match.
756 else if (!mHost.Equals(decodedUriHost)) {
757 return false;
760 // Port matching: Check if the ports match.
761 if (!permitsPort(mScheme, mPort, aUri)) {
762 return false;
765 // 4.9) Path matching: If there is a path, we have to enforce
766 // path-level matching, unless the channel got redirected, see:
767 // http://www.w3.org/TR/CSP11/#source-list-paths-and-redirects
768 if (!aWasRedirected && !mPath.IsEmpty()) {
769 // converting aUri into nsIURL so we can strip query and ref
770 // example.com/test#foo -> example.com/test
771 // example.com/test?val=foo -> example.com/test
772 nsCOMPtr<nsIURL> url = do_QueryInterface(aUri);
773 if (!url) {
774 NS_ASSERTION(false, "can't QI into nsIURI");
775 return false;
777 nsAutoCString uriPath;
778 rv = url->GetFilePath(uriPath);
779 NS_ENSURE_SUCCESS(rv, false);
781 if (mWithinFrameAncstorsDir) {
782 // no path matching for frame-ancestors to not leak any path information.
783 return true;
786 nsString decodedUriPath;
787 CSP_PercentDecodeStr(NS_ConvertUTF8toUTF16(uriPath), decodedUriPath);
789 // check if the last character of mPath is '/'; if so
790 // we just have to check loading resource is within
791 // the allowed path.
792 if (mPath.Last() == '/') {
793 if (!StringBeginsWith(decodedUriPath, mPath)) {
794 return false;
797 // otherwise mPath refers to a specific file, and we have to
798 // check if the loading resource matches the file.
799 else {
800 if (!mPath.Equals(decodedUriPath)) {
801 return false;
806 // At the end: scheme, host, port and path match -> allow the load.
807 return true;
810 bool nsCSPHostSrc::visit(nsCSPSrcVisitor* aVisitor) const {
811 return aVisitor->visitHostSrc(*this);
814 void nsCSPHostSrc::toString(nsAString& outStr) const {
815 if (mGeneratedFromSelfKeyword) {
816 outStr.AppendLiteral("'self'");
817 return;
820 // If mHost is a single "*", we append the wildcard and return.
821 if (mHost.EqualsASCII("*") && mScheme.IsEmpty() && mPort.IsEmpty()) {
822 outStr.Append(mHost);
823 return;
826 // append scheme
827 outStr.Append(mScheme);
829 // append host
830 outStr.AppendLiteral("://");
831 outStr.Append(mHost);
833 // append port
834 if (!mPort.IsEmpty()) {
835 outStr.AppendLiteral(":");
836 outStr.Append(mPort);
839 // append path
840 outStr.Append(mPath);
843 void nsCSPHostSrc::setScheme(const nsAString& aScheme) {
844 mScheme = aScheme;
845 ToLowerCase(mScheme);
848 void nsCSPHostSrc::setPort(const nsAString& aPort) { mPort = aPort; }
850 void nsCSPHostSrc::appendPath(const nsAString& aPath) { mPath.Append(aPath); }
852 /* ===== nsCSPKeywordSrc ===================== */
854 nsCSPKeywordSrc::nsCSPKeywordSrc(enum CSPKeyword aKeyword)
855 : mKeyword(aKeyword) {
856 NS_ASSERTION((aKeyword != CSP_SELF),
857 "'self' should have been replaced in the parser");
860 nsCSPKeywordSrc::~nsCSPKeywordSrc() = default;
862 bool nsCSPKeywordSrc::permits(nsIURI* aUri, const nsAString& aNonce,
863 bool aWasRedirected, bool aReportOnly,
864 bool aUpgradeInsecure,
865 bool aParserCreated) const {
866 // no need to check for invalidated, this will always return false unless
867 // it is an nsCSPKeywordSrc for 'strict-dynamic', which should allow non
868 // parser created scripts.
869 return ((mKeyword == CSP_STRICT_DYNAMIC) && !aParserCreated);
872 bool nsCSPKeywordSrc::allows(enum CSPKeyword aKeyword,
873 const nsAString& aHashOrNonce,
874 bool aParserCreated) const {
875 CSPUTILSLOG(
876 ("nsCSPKeywordSrc::allows, aKeyWord: %s, aHashOrNonce: %s, mInvalidated: "
877 "%s",
878 CSP_EnumToUTF8Keyword(aKeyword),
879 NS_ConvertUTF16toUTF8(aHashOrNonce).get(),
880 mInvalidated ? "true" : "false"));
882 if (mInvalidated) {
883 // only 'self', 'report-sample' and 'unsafe-inline' are keywords that can be
884 // ignored. Please note that the parser already translates 'self' into a uri
885 // (see assertion in constructor).
886 MOZ_ASSERT(mKeyword == CSP_UNSAFE_INLINE || mKeyword == CSP_REPORT_SAMPLE,
887 "should only invalidate unsafe-inline");
888 return false;
890 // either the keyword allows the load or the policy contains 'strict-dynamic',
891 // in which case we have to make sure the script is not parser created before
892 // allowing the load and also eval & wasm-eval should be blocked even if
893 // 'strict-dynamic' is present. Should be allowed only if 'unsafe-eval' is
894 // present.
895 return ((mKeyword == aKeyword) ||
896 ((mKeyword == CSP_STRICT_DYNAMIC) && !aParserCreated &&
897 aKeyword != CSP_UNSAFE_EVAL && aKeyword != CSP_WASM_UNSAFE_EVAL));
900 bool nsCSPKeywordSrc::visit(nsCSPSrcVisitor* aVisitor) const {
901 return aVisitor->visitKeywordSrc(*this);
904 void nsCSPKeywordSrc::toString(nsAString& outStr) const {
905 outStr.Append(CSP_EnumToUTF16Keyword(mKeyword));
908 /* ===== nsCSPNonceSrc ==================== */
910 nsCSPNonceSrc::nsCSPNonceSrc(const nsAString& aNonce) : mNonce(aNonce) {}
912 nsCSPNonceSrc::~nsCSPNonceSrc() = default;
914 bool nsCSPNonceSrc::permits(nsIURI* aUri, const nsAString& aNonce,
915 bool aWasRedirected, bool aReportOnly,
916 bool aUpgradeInsecure, bool aParserCreated) const {
917 if (CSPUTILSLOGENABLED()) {
918 CSPUTILSLOG(("nsCSPNonceSrc::permits, aUri: %s, aNonce: %s",
919 aUri->GetSpecOrDefault().get(),
920 NS_ConvertUTF16toUTF8(aNonce).get()));
923 if (aReportOnly && aWasRedirected && aNonce.IsEmpty()) {
924 /* Fix for Bug 1505412
925 * If we land here, we're currently handling a script-preload which got
926 * redirected. Preloads do not have any info about the nonce assiociated.
927 * Because of Report-Only the preload passes the 1st CSP-check so the
928 * preload does not get retried with a nonce attached.
929 * Currently we're relying on the script-manager to
930 * provide a fake loadinfo to check the preloads against csp.
931 * So during HTTPChannel->OnRedirect we cant check csp for this case.
932 * But as the script-manager already checked the csp,
933 * a report would already have been send,
934 * if the nonce didnt match.
935 * So we can pass the check here for Report-Only Cases.
937 MOZ_ASSERT(aParserCreated == false,
938 "Skipping nonce-check is only allowed for Preloads");
939 return true;
942 // nonces can not be invalidated by strict-dynamic
943 return mNonce.Equals(aNonce);
946 bool nsCSPNonceSrc::allows(enum CSPKeyword aKeyword,
947 const nsAString& aHashOrNonce,
948 bool aParserCreated) const {
949 CSPUTILSLOG(("nsCSPNonceSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
950 CSP_EnumToUTF8Keyword(aKeyword),
951 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
953 if (aKeyword != CSP_NONCE) {
954 return false;
956 // nonces can not be invalidated by strict-dynamic
957 return mNonce.Equals(aHashOrNonce);
960 bool nsCSPNonceSrc::visit(nsCSPSrcVisitor* aVisitor) const {
961 return aVisitor->visitNonceSrc(*this);
964 void nsCSPNonceSrc::toString(nsAString& outStr) const {
965 outStr.Append(CSP_EnumToUTF16Keyword(CSP_NONCE));
966 outStr.Append(mNonce);
967 outStr.AppendLiteral("'");
970 /* ===== nsCSPHashSrc ===================== */
972 nsCSPHashSrc::nsCSPHashSrc(const nsAString& aAlgo, const nsAString& aHash)
973 : mAlgorithm(aAlgo), mHash(aHash) {
974 // Only the algo should be rewritten to lowercase, the hash must remain the
975 // same.
976 ToLowerCase(mAlgorithm);
977 // Normalize the base64url encoding to base64 encoding:
978 char16_t* cur = mHash.BeginWriting();
979 char16_t* end = mHash.EndWriting();
981 for (; cur < end; ++cur) {
982 if (char16_t('-') == *cur) {
983 *cur = char16_t('+');
985 if (char16_t('_') == *cur) {
986 *cur = char16_t('/');
991 nsCSPHashSrc::~nsCSPHashSrc() = default;
993 bool nsCSPHashSrc::allows(enum CSPKeyword aKeyword,
994 const nsAString& aHashOrNonce,
995 bool aParserCreated) const {
996 CSPUTILSLOG(("nsCSPHashSrc::allows, aKeyWord: %s, a HashOrNonce: %s",
997 CSP_EnumToUTF8Keyword(aKeyword),
998 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
1000 if (aKeyword != CSP_HASH) {
1001 return false;
1004 // hashes can not be invalidated by strict-dynamic
1006 // Convert aHashOrNonce to UTF-8
1007 NS_ConvertUTF16toUTF8 utf8_hash(aHashOrNonce);
1009 nsCOMPtr<nsICryptoHash> hasher;
1010 nsresult rv = NS_NewCryptoHash(NS_ConvertUTF16toUTF8(mAlgorithm),
1011 getter_AddRefs(hasher));
1012 NS_ENSURE_SUCCESS(rv, false);
1014 rv = hasher->Update((uint8_t*)utf8_hash.get(), utf8_hash.Length());
1015 NS_ENSURE_SUCCESS(rv, false);
1017 nsAutoCString hash;
1018 rv = hasher->Finish(true, hash);
1019 NS_ENSURE_SUCCESS(rv, false);
1021 return NS_ConvertUTF16toUTF8(mHash).Equals(hash);
1024 bool nsCSPHashSrc::visit(nsCSPSrcVisitor* aVisitor) const {
1025 return aVisitor->visitHashSrc(*this);
1028 void nsCSPHashSrc::toString(nsAString& outStr) const {
1029 outStr.AppendLiteral("'");
1030 outStr.Append(mAlgorithm);
1031 outStr.AppendLiteral("-");
1032 outStr.Append(mHash);
1033 outStr.AppendLiteral("'");
1036 /* ===== nsCSPReportURI ===================== */
1038 nsCSPReportURI::nsCSPReportURI(nsIURI* aURI) : mReportURI(aURI) {}
1040 nsCSPReportURI::~nsCSPReportURI() = default;
1042 bool nsCSPReportURI::visit(nsCSPSrcVisitor* aVisitor) const { return false; }
1044 void nsCSPReportURI::toString(nsAString& outStr) const {
1045 nsAutoCString spec;
1046 nsresult rv = mReportURI->GetSpec(spec);
1047 if (NS_FAILED(rv)) {
1048 return;
1050 outStr.AppendASCII(spec.get());
1053 /* ===== nsCSPSandboxFlags ===================== */
1055 nsCSPSandboxFlags::nsCSPSandboxFlags(const nsAString& aFlags) : mFlags(aFlags) {
1056 ToLowerCase(mFlags);
1059 nsCSPSandboxFlags::~nsCSPSandboxFlags() = default;
1061 bool nsCSPSandboxFlags::visit(nsCSPSrcVisitor* aVisitor) const { return false; }
1063 void nsCSPSandboxFlags::toString(nsAString& outStr) const {
1064 outStr.Append(mFlags);
1067 /* ===== nsCSPDirective ====================== */
1069 nsCSPDirective::nsCSPDirective(CSPDirective aDirective) {
1070 mDirective = aDirective;
1073 nsCSPDirective::~nsCSPDirective() {
1074 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1075 delete mSrcs[i];
1079 bool nsCSPDirective::permits(nsIURI* aUri, const nsAString& aNonce,
1080 bool aWasRedirected, bool aReportOnly,
1081 bool aUpgradeInsecure, bool aParserCreated) const {
1082 if (CSPUTILSLOGENABLED()) {
1083 CSPUTILSLOG(
1084 ("nsCSPDirective::permits, aUri: %s", aUri->GetSpecOrDefault().get()));
1087 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1088 if (mSrcs[i]->permits(aUri, aNonce, aWasRedirected, aReportOnly,
1089 aUpgradeInsecure, aParserCreated)) {
1090 return true;
1093 return false;
1096 bool nsCSPDirective::allows(enum CSPKeyword aKeyword,
1097 const nsAString& aHashOrNonce,
1098 bool aParserCreated) const {
1099 CSPUTILSLOG(("nsCSPDirective::allows, aKeyWord: %s, a HashOrNonce: %s",
1100 CSP_EnumToUTF8Keyword(aKeyword),
1101 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
1103 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1104 if (mSrcs[i]->allows(aKeyword, aHashOrNonce, aParserCreated)) {
1105 return true;
1108 return false;
1111 void nsCSPDirective::toString(nsAString& outStr) const {
1112 // Append directive name
1113 outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective));
1114 outStr.AppendLiteral(" ");
1116 // Append srcs
1117 StringJoinAppend(outStr, u" "_ns, mSrcs,
1118 [](nsAString& dest, nsCSPBaseSrc* cspBaseSrc) {
1119 cspBaseSrc->toString(dest);
1123 void nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP& outCSP) const {
1124 mozilla::dom::Sequence<nsString> srcs;
1125 nsString src;
1126 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1127 src.Truncate();
1128 mSrcs[i]->toString(src);
1129 if (!srcs.AppendElement(src, mozilla::fallible)) {
1130 // XXX(Bug 1632090) Instead of extending the array 1-by-1 (which might
1131 // involve multiple reallocations) and potentially crashing here,
1132 // SetCapacity could be called outside the loop once.
1133 mozalloc_handle_oom(0);
1137 switch (mDirective) {
1138 case nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE:
1139 outCSP.mDefault_src.Construct();
1140 outCSP.mDefault_src.Value() = std::move(srcs);
1141 return;
1143 case nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE:
1144 outCSP.mScript_src.Construct();
1145 outCSP.mScript_src.Value() = std::move(srcs);
1146 return;
1148 case nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE:
1149 outCSP.mObject_src.Construct();
1150 outCSP.mObject_src.Value() = std::move(srcs);
1151 return;
1153 case nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE:
1154 outCSP.mStyle_src.Construct();
1155 outCSP.mStyle_src.Value() = std::move(srcs);
1156 return;
1158 case nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE:
1159 outCSP.mImg_src.Construct();
1160 outCSP.mImg_src.Value() = std::move(srcs);
1161 return;
1163 case nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE:
1164 outCSP.mMedia_src.Construct();
1165 outCSP.mMedia_src.Value() = std::move(srcs);
1166 return;
1168 case nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE:
1169 outCSP.mFrame_src.Construct();
1170 outCSP.mFrame_src.Value() = std::move(srcs);
1171 return;
1173 case nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE:
1174 outCSP.mFont_src.Construct();
1175 outCSP.mFont_src.Value() = std::move(srcs);
1176 return;
1178 case nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE:
1179 outCSP.mConnect_src.Construct();
1180 outCSP.mConnect_src.Value() = std::move(srcs);
1181 return;
1183 case nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE:
1184 outCSP.mReport_uri.Construct();
1185 outCSP.mReport_uri.Value() = std::move(srcs);
1186 return;
1188 case nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE:
1189 outCSP.mFrame_ancestors.Construct();
1190 outCSP.mFrame_ancestors.Value() = std::move(srcs);
1191 return;
1193 case nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE:
1194 outCSP.mManifest_src.Construct();
1195 outCSP.mManifest_src.Value() = std::move(srcs);
1196 return;
1197 // not supporting REFLECTED_XSS_DIRECTIVE
1199 case nsIContentSecurityPolicy::BASE_URI_DIRECTIVE:
1200 outCSP.mBase_uri.Construct();
1201 outCSP.mBase_uri.Value() = std::move(srcs);
1202 return;
1204 case nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE:
1205 outCSP.mForm_action.Construct();
1206 outCSP.mForm_action.Value() = std::move(srcs);
1207 return;
1209 case nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT:
1210 outCSP.mBlock_all_mixed_content.Construct();
1211 // does not have any srcs
1212 return;
1214 case nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE:
1215 outCSP.mUpgrade_insecure_requests.Construct();
1216 // does not have any srcs
1217 return;
1219 case nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE:
1220 outCSP.mChild_src.Construct();
1221 outCSP.mChild_src.Value() = std::move(srcs);
1222 return;
1224 case nsIContentSecurityPolicy::SANDBOX_DIRECTIVE:
1225 outCSP.mSandbox.Construct();
1226 outCSP.mSandbox.Value() = std::move(srcs);
1227 return;
1229 case nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE:
1230 outCSP.mWorker_src.Construct();
1231 outCSP.mWorker_src.Value() = std::move(srcs);
1232 return;
1234 case nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE:
1235 outCSP.mScript_src_elem.Construct();
1236 outCSP.mScript_src_elem.Value() = std::move(srcs);
1237 return;
1239 case nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE:
1240 outCSP.mScript_src_attr.Construct();
1241 outCSP.mScript_src_attr.Value() = std::move(srcs);
1242 return;
1244 default:
1245 NS_ASSERTION(false, "cannot find directive to convert CSP to JSON");
1249 void nsCSPDirective::getReportURIs(nsTArray<nsString>& outReportURIs) const {
1250 NS_ASSERTION((mDirective == nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE),
1251 "not a report-uri directive");
1253 // append uris
1254 nsString tmpReportURI;
1255 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1256 tmpReportURI.Truncate();
1257 mSrcs[i]->toString(tmpReportURI);
1258 outReportURIs.AppendElement(tmpReportURI);
1262 bool nsCSPDirective::visitSrcs(nsCSPSrcVisitor* aVisitor) const {
1263 for (uint32_t i = 0; i < mSrcs.Length(); i++) {
1264 if (!mSrcs[i]->visit(aVisitor)) {
1265 return false;
1268 return true;
1271 bool nsCSPDirective::equals(CSPDirective aDirective) const {
1272 return (mDirective == aDirective);
1275 void nsCSPDirective::getDirName(nsAString& outStr) const {
1276 outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective));
1279 bool nsCSPDirective::hasReportSampleKeyword() const {
1280 for (nsCSPBaseSrc* src : mSrcs) {
1281 if (src->isReportSample()) {
1282 return true;
1286 return false;
1289 /* =============== nsCSPChildSrcDirective ============= */
1291 nsCSPChildSrcDirective::nsCSPChildSrcDirective(CSPDirective aDirective)
1292 : nsCSPDirective(aDirective),
1293 mRestrictFrames(false),
1294 mRestrictWorkers(false) {}
1296 nsCSPChildSrcDirective::~nsCSPChildSrcDirective() = default;
1298 bool nsCSPChildSrcDirective::equals(CSPDirective aDirective) const {
1299 if (aDirective == nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE) {
1300 return mRestrictFrames;
1302 if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) {
1303 return mRestrictWorkers;
1305 return (mDirective == aDirective);
1308 /* =============== nsCSPScriptSrcDirective ============= */
1310 nsCSPScriptSrcDirective::nsCSPScriptSrcDirective(CSPDirective aDirective)
1311 : nsCSPDirective(aDirective) {}
1313 nsCSPScriptSrcDirective::~nsCSPScriptSrcDirective() = default;
1315 bool nsCSPScriptSrcDirective::equals(CSPDirective aDirective) const {
1316 if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) {
1317 return mRestrictWorkers;
1319 if (aDirective == nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) {
1320 return mRestrictScriptElem;
1322 if (aDirective == nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) {
1323 return mRestrictScriptAttr;
1325 return mDirective == aDirective;
1328 /* =============== nsCSPStyleSrcDirective ============= */
1330 nsCSPStyleSrcDirective::nsCSPStyleSrcDirective(CSPDirective aDirective)
1331 : nsCSPDirective(aDirective) {}
1333 nsCSPStyleSrcDirective::~nsCSPStyleSrcDirective() = default;
1335 bool nsCSPStyleSrcDirective::equals(CSPDirective aDirective) const {
1336 if (aDirective == nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE) {
1337 return mRestrictStyleElem;
1339 if (aDirective == nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE) {
1340 return mRestrictStyleAttr;
1342 return mDirective == aDirective;
1345 /* =============== nsBlockAllMixedContentDirective ============= */
1347 nsBlockAllMixedContentDirective::nsBlockAllMixedContentDirective(
1348 CSPDirective aDirective)
1349 : nsCSPDirective(aDirective) {}
1351 nsBlockAllMixedContentDirective::~nsBlockAllMixedContentDirective() = default;
1353 void nsBlockAllMixedContentDirective::toString(nsAString& outStr) const {
1354 outStr.AppendASCII(CSP_CSPDirectiveToString(
1355 nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT));
1358 void nsBlockAllMixedContentDirective::getDirName(nsAString& outStr) const {
1359 outStr.AppendASCII(CSP_CSPDirectiveToString(
1360 nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT));
1363 /* =============== nsUpgradeInsecureDirective ============= */
1365 nsUpgradeInsecureDirective::nsUpgradeInsecureDirective(CSPDirective aDirective)
1366 : nsCSPDirective(aDirective) {}
1368 nsUpgradeInsecureDirective::~nsUpgradeInsecureDirective() = default;
1370 void nsUpgradeInsecureDirective::toString(nsAString& outStr) const {
1371 outStr.AppendASCII(CSP_CSPDirectiveToString(
1372 nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE));
1375 void nsUpgradeInsecureDirective::getDirName(nsAString& outStr) const {
1376 outStr.AppendASCII(CSP_CSPDirectiveToString(
1377 nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE));
1380 /* ===== nsCSPPolicy ========================= */
1382 nsCSPPolicy::nsCSPPolicy()
1383 : mUpgradeInsecDir(nullptr),
1384 mReportOnly(false),
1385 mDeliveredViaMetaTag(false) {
1386 CSPUTILSLOG(("nsCSPPolicy::nsCSPPolicy"));
1389 nsCSPPolicy::~nsCSPPolicy() {
1390 CSPUTILSLOG(("nsCSPPolicy::~nsCSPPolicy"));
1392 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1393 delete mDirectives[i];
1397 bool nsCSPPolicy::permits(CSPDirective aDir, nsIURI* aUri,
1398 const nsAString& aNonce, bool aWasRedirected,
1399 bool aSpecific, bool aParserCreated,
1400 nsAString& outViolatedDirective) const {
1401 if (CSPUTILSLOGENABLED()) {
1402 CSPUTILSLOG(("nsCSPPolicy::permits, aUri: %s, aDir: %d, aSpecific: %s",
1403 aUri->GetSpecOrDefault().get(), aDir,
1404 aSpecific ? "true" : "false"));
1407 NS_ASSERTION(aUri, "permits needs an uri to perform the check!");
1408 outViolatedDirective.Truncate();
1410 nsCSPDirective* defaultDir = nullptr;
1412 // Try to find a relevant directive
1413 // These directive arrays are short (1-5 elements), not worth using a
1414 // hashtable.
1415 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1416 if (mDirectives[i]->equals(aDir)) {
1417 if (!mDirectives[i]->permits(aUri, aNonce, aWasRedirected, mReportOnly,
1418 mUpgradeInsecDir, aParserCreated)) {
1419 mDirectives[i]->getDirName(outViolatedDirective);
1420 return false;
1422 return true;
1424 if (mDirectives[i]->isDefaultDirective()) {
1425 defaultDir = mDirectives[i];
1429 // If the above loop runs through, we haven't found a matching directive.
1430 // Avoid relooping, just store the result of default-src while looping.
1431 if (!aSpecific && defaultDir) {
1432 if (!defaultDir->permits(aUri, aNonce, aWasRedirected, mReportOnly,
1433 mUpgradeInsecDir, aParserCreated)) {
1434 defaultDir->getDirName(outViolatedDirective);
1435 return false;
1437 return true;
1440 // Nothing restricts this, so we're allowing the load
1441 // See bug 764937
1442 return true;
1445 bool nsCSPPolicy::allows(CSPDirective aDirective, enum CSPKeyword aKeyword,
1446 const nsAString& aHashOrNonce,
1447 bool aParserCreated) const {
1448 CSPUTILSLOG(("nsCSPPolicy::allows, aKeyWord: %s, a HashOrNonce: %s",
1449 CSP_EnumToUTF8Keyword(aKeyword),
1450 NS_ConvertUTF16toUTF8(aHashOrNonce).get()));
1452 nsCSPDirective* defaultDir = nullptr;
1454 // Try to find a matching directive
1455 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1456 if (mDirectives[i]->isDefaultDirective()) {
1457 defaultDir = mDirectives[i];
1458 continue;
1460 if (mDirectives[i]->equals(aDirective)) {
1461 if (mDirectives[i]->allows(aKeyword, aHashOrNonce, aParserCreated)) {
1462 return true;
1464 return false;
1468 // If the above loop runs through, we haven't found a matching directive.
1469 // Avoid relooping, just store the result of default-src while looping.
1470 if (defaultDir) {
1471 return defaultDir->allows(aKeyword, aHashOrNonce, aParserCreated);
1474 // Allowing the load; see Bug 885433
1475 // a) inline scripts (also unsafe eval) should only be blocked
1476 // if there is a [script-src] or [default-src]
1477 // b) inline styles should only be blocked
1478 // if there is a [style-src] or [default-src]
1479 return true;
1482 void nsCSPPolicy::toString(nsAString& outStr) const {
1483 StringJoinAppend(outStr, u"; "_ns, mDirectives,
1484 [](nsAString& dest, nsCSPDirective* cspDirective) {
1485 cspDirective->toString(dest);
1489 void nsCSPPolicy::toDomCSPStruct(mozilla::dom::CSP& outCSP) const {
1490 outCSP.mReport_only = mReportOnly;
1492 for (uint32_t i = 0; i < mDirectives.Length(); ++i) {
1493 mDirectives[i]->toDomCSPStruct(outCSP);
1497 bool nsCSPPolicy::hasDirective(CSPDirective aDir) const {
1498 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1499 if (mDirectives[i]->equals(aDir)) {
1500 return true;
1503 return false;
1506 bool nsCSPPolicy::allowsNavigateTo(nsIURI* aURI, bool aWasRedirected,
1507 bool aEnforceAllowlist) const {
1508 bool allowsNavigateTo = true;
1510 for (unsigned long i = 0; i < mDirectives.Length(); i++) {
1511 if (mDirectives[i]->equals(
1512 nsIContentSecurityPolicy::NAVIGATE_TO_DIRECTIVE)) {
1513 // Early return if we can skip the allowlist AND 'unsafe-allow-redirects'
1514 // is present.
1515 if (!aEnforceAllowlist &&
1516 mDirectives[i]->allows(CSP_UNSAFE_ALLOW_REDIRECTS, u""_ns, false)) {
1517 return true;
1519 // Otherwise, check against the allowlist.
1520 if (!mDirectives[i]->permits(aURI, u""_ns, aWasRedirected, false, false,
1521 false)) {
1522 allowsNavigateTo = false;
1527 return allowsNavigateTo;
1531 * Use this function only after ::allows() returned 'false'. Most and
1532 * foremost it's used to get the violated directive before sending reports.
1533 * The parameter outDirective is the equivalent of 'outViolatedDirective'
1534 * for the ::permits() function family.
1536 void nsCSPPolicy::getDirectiveStringAndReportSampleForContentType(
1537 CSPDirective aDirective, nsAString& outDirective,
1538 bool* aReportSample) const {
1539 MOZ_ASSERT(aReportSample);
1540 *aReportSample = false;
1542 nsCSPDirective* defaultDir = nullptr;
1543 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1544 if (mDirectives[i]->isDefaultDirective()) {
1545 defaultDir = mDirectives[i];
1546 continue;
1548 if (mDirectives[i]->equals(aDirective)) {
1549 mDirectives[i]->getDirName(outDirective);
1550 *aReportSample = mDirectives[i]->hasReportSampleKeyword();
1551 return;
1554 // if we haven't found a matching directive yet,
1555 // the contentType must be restricted by the default directive
1556 if (defaultDir) {
1557 defaultDir->getDirName(outDirective);
1558 *aReportSample = defaultDir->hasReportSampleKeyword();
1559 return;
1561 NS_ASSERTION(false, "Can not query directive string for contentType!");
1562 outDirective.AppendLiteral("couldNotQueryViolatedDirective");
1565 void nsCSPPolicy::getDirectiveAsString(CSPDirective aDir,
1566 nsAString& outDirective) const {
1567 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1568 if (mDirectives[i]->equals(aDir)) {
1569 mDirectives[i]->toString(outDirective);
1570 return;
1576 * Helper function that returns the underlying bit representation of sandbox
1577 * flags. The function returns SANDBOXED_NONE if there are no sandbox
1578 * directives.
1580 uint32_t nsCSPPolicy::getSandboxFlags() const {
1581 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1582 if (mDirectives[i]->equals(nsIContentSecurityPolicy::SANDBOX_DIRECTIVE)) {
1583 nsAutoString flags;
1584 mDirectives[i]->toString(flags);
1586 if (flags.IsEmpty()) {
1587 return SANDBOX_ALL_FLAGS;
1590 nsAttrValue attr;
1591 attr.ParseAtomArray(flags);
1593 return nsContentUtils::ParseSandboxAttributeToFlags(&attr);
1597 return SANDBOXED_NONE;
1600 void nsCSPPolicy::getReportURIs(nsTArray<nsString>& outReportURIs) const {
1601 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1602 if (mDirectives[i]->equals(
1603 nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
1604 mDirectives[i]->getReportURIs(outReportURIs);
1605 return;
1610 bool nsCSPPolicy::visitDirectiveSrcs(CSPDirective aDir,
1611 nsCSPSrcVisitor* aVisitor) const {
1612 for (uint32_t i = 0; i < mDirectives.Length(); i++) {
1613 if (mDirectives[i]->equals(aDir)) {
1614 return mDirectives[i]->visitSrcs(aVisitor);
1617 return false;