1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "mozilla/ArrayUtils.h"
8 #include "mozilla/dom/Document.h"
9 #include "mozilla/Preferences.h"
10 #include "mozilla/StaticPrefs_security.h"
11 #include "nsCOMPtr.h"
12 #include "nsContentUtils.h"
13 #include "nsCSPParser.h"
14 #include "nsCSPUtils.h"
15 #include "nsIScriptError.h"
16 #include "nsNetUtil.h"
17 #include "nsReadableUtils.h"
18 #include "nsServiceManagerUtils.h"
19 #include "nsUnicharUtils.h"
20
21 using namespace mozilla;
22 using namespace mozilla::dom;
23
GetCspParserLog()24 static LogModule* GetCspParserLog() {
25 static LazyLogModule gCspParserPRLog("CSPParser");
26 return gCspParserPRLog;
27 }
28
29 #define CSPPARSERLOG(args) \
30 MOZ_LOG(GetCspParserLog(), mozilla::LogLevel::Debug, args)
31 #define CSPPARSERLOGENABLED() \
32 MOZ_LOG_TEST(GetCspParserLog(), mozilla::LogLevel::Debug)
33
34 static const uint32_t kSubHostPathCharacterCutoff = 512;
35
36 static const char* const kHashSourceValidFns[] = {"sha256", "sha384", "sha512"};
37 static const uint32_t kHashSourceValidFnsLen = 3;
38
39 /* ===== nsCSPParser ==================== */
40
nsCSPParser(policyTokens & aTokens,nsIURI * aSelfURI,nsCSPContext * aCSPContext,bool aDeliveredViaMetaTag)41 nsCSPParser::nsCSPParser(policyTokens& aTokens, nsIURI* aSelfURI,
42 nsCSPContext* aCSPContext, bool aDeliveredViaMetaTag)
43 : mCurChar(nullptr),
44 mEndChar(nullptr),
45 mHasHashOrNonce(false),
46 mStrictDynamic(false),
47 mUnsafeInlineKeywordSrc(nullptr),
48 mChildSrc(nullptr),
49 mFrameSrc(nullptr),
50 mWorkerSrc(nullptr),
51 mScriptSrc(nullptr),
52 mParsingFrameAncestorsDir(false),
53 mTokens(aTokens.Clone()),
54 mSelfURI(aSelfURI),
55 mPolicy(nullptr),
56 mCSPContext(aCSPContext),
57 mDeliveredViaMetaTag(aDeliveredViaMetaTag) {
58 CSPPARSERLOG(("nsCSPParser::nsCSPParser"));
59 }
60
~nsCSPParser()61 nsCSPParser::~nsCSPParser() { CSPPARSERLOG(("nsCSPParser::~nsCSPParser")); }
62
isCharacterToken(char16_t aSymbol)63 static bool isCharacterToken(char16_t aSymbol) {
64 return (aSymbol >= 'a' && aSymbol <= 'z') ||
65 (aSymbol >= 'A' && aSymbol <= 'Z');
66 }
67
isNumberToken(char16_t aSymbol)68 bool isNumberToken(char16_t aSymbol) {
69 return (aSymbol >= '0' && aSymbol <= '9');
70 }
71
isValidHexDig(char16_t aHexDig)72 bool isValidHexDig(char16_t aHexDig) {
73 return (isNumberToken(aHexDig) || (aHexDig >= 'A' && aHexDig <= 'F') ||
74 (aHexDig >= 'a' && aHexDig <= 'f'));
75 }
76
isValidBase64Value(const char16_t * cur,const char16_t * end)77 static bool isValidBase64Value(const char16_t* cur, const char16_t* end) {
78 // Using grammar at
79 // https://w3c.github.io/webappsec-csp/#grammardef-nonce-source
80
81 // May end with one or two =
82 if (end > cur && *(end - 1) == EQUALS) end--;
83 if (end > cur && *(end - 1) == EQUALS) end--;
84
85 // Must have at least one character aside from any =
86 if (end == cur) {
87 return false;
88 }
89
90 // Rest must all be A-Za-z0-9+/-_
91 for (; cur < end; ++cur) {
92 if (!(isCharacterToken(*cur) || isNumberToken(*cur) || *cur == PLUS ||
93 *cur == SLASH || *cur == DASH || *cur == UNDERLINE)) {
94 return false;
95 }
96 }
97
98 return true;
99 }
100
resetCurChar(const nsAString & aToken)101 void nsCSPParser::resetCurChar(const nsAString& aToken) {
102 mCurChar = aToken.BeginReading();
103 mEndChar = aToken.EndReading();
104 resetCurValue();
105 }
106
107 // The path is terminated by the first question mark ("?") or
108 // number sign ("#") character, or by the end of the URI.
109 // http://tools.ietf.org/html/rfc3986#section-3.3
atEndOfPath()110 bool nsCSPParser::atEndOfPath() {
111 return (atEnd() || peek(QUESTIONMARK) || peek(NUMBER_SIGN));
112 }
113
114 // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
atValidUnreservedChar()115 bool nsCSPParser::atValidUnreservedChar() {
116 return (peek(isCharacterToken) || peek(isNumberToken) || peek(DASH) ||
117 peek(DOT) || peek(UNDERLINE) || peek(TILDE));
118 }
119
120 // sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
121 // / "*" / "+" / "," / ";" / "="
122 // Please note that even though ',' and ';' appear to be
123 // valid sub-delims according to the RFC production of paths,
124 // both can not appear here by itself, they would need to be
125 // pct-encoded in order to be part of the path.
atValidSubDelimChar()126 bool nsCSPParser::atValidSubDelimChar() {
127 return (peek(EXCLAMATION) || peek(DOLLAR) || peek(AMPERSAND) ||
128 peek(SINGLEQUOTE) || peek(OPENBRACE) || peek(CLOSINGBRACE) ||
129 peek(WILDCARD) || peek(PLUS) || peek(EQUALS));
130 }
131
132 // pct-encoded = "%" HEXDIG HEXDIG
atValidPctEncodedChar()133 bool nsCSPParser::atValidPctEncodedChar() {
134 const char16_t* pctCurChar = mCurChar;
135
136 if ((pctCurChar + 2) >= mEndChar) {
137 // string too short, can't be a valid pct-encoded char.
138 return false;
139 }
140
141 // Any valid pct-encoding must follow the following format:
142 // "% HEXDIG HEXDIG"
143 if (PERCENT_SIGN != *pctCurChar || !isValidHexDig(*(pctCurChar + 1)) ||
144 !isValidHexDig(*(pctCurChar + 2))) {
145 return false;
146 }
147 return true;
148 }
149
150 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
151 // http://tools.ietf.org/html/rfc3986#section-3.3
atValidPathChar()152 bool nsCSPParser::atValidPathChar() {
153 return (atValidUnreservedChar() || atValidSubDelimChar() ||
154 atValidPctEncodedChar() || peek(COLON) || peek(ATSYMBOL));
155 }
156
logWarningErrorToConsole(uint32_t aSeverityFlag,const char * aProperty,const nsTArray<nsString> & aParams)157 void nsCSPParser::logWarningErrorToConsole(uint32_t aSeverityFlag,
158 const char* aProperty,
159 const nsTArray<nsString>& aParams) {
160 CSPPARSERLOG(("nsCSPParser::logWarningErrorToConsole: %s", aProperty));
161 // send console messages off to the context and let the context
162 // deal with it (potentially messages need to be queued up)
163 mCSPContext->logToConsole(aProperty, aParams,
164 u""_ns, // aSourceName
165 u""_ns, // aSourceLine
166 0, // aLineNumber
167 0, // aColumnNumber
168 aSeverityFlag); // aFlags
169 }
170
hostChar()171 bool nsCSPParser::hostChar() {
172 if (atEnd()) {
173 return false;
174 }
175 return accept(isCharacterToken) || accept(isNumberToken) || accept(DASH);
176 }
177
178 // (ALPHA / DIGIT / "+" / "-" / "." )
schemeChar()179 bool nsCSPParser::schemeChar() {
180 if (atEnd()) {
181 return false;
182 }
183 return accept(isCharacterToken) || accept(isNumberToken) || accept(PLUS) ||
184 accept(DASH) || accept(DOT);
185 }
186
187 // port = ":" ( 1*DIGIT / "*" )
port()188 bool nsCSPParser::port() {
189 CSPPARSERLOG(("nsCSPParser::port, mCurToken: %s, mCurValue: %s",
190 NS_ConvertUTF16toUTF8(mCurToken).get(),
191 NS_ConvertUTF16toUTF8(mCurValue).get()));
192
193 // Consume the COLON we just peeked at in houstSource
194 accept(COLON);
195
196 // Resetting current value since we start to parse a port now.
197 // e.g; "http://www.example.com:8888" then we have already parsed
198 // everything up to (including) ":";
199 resetCurValue();
200
201 // Port might be "*"
202 if (accept(WILDCARD)) {
203 return true;
204 }
205
206 // Port must start with a number
207 if (!accept(isNumberToken)) {
208 AutoTArray<nsString, 1> params = {mCurToken};
209 logWarningErrorToConsole(nsIScriptError::warningFlag, "couldntParsePort",
210 params);
211 return false;
212 }
213 // Consume more numbers and set parsed port to the nsCSPHost
214 while (accept(isNumberToken)) { /* consume */
215 }
216 return true;
217 }
218
subPath(nsCSPHostSrc * aCspHost)219 bool nsCSPParser::subPath(nsCSPHostSrc* aCspHost) {
220 CSPPARSERLOG(("nsCSPParser::subPath, mCurToken: %s, mCurValue: %s",
221 NS_ConvertUTF16toUTF8(mCurToken).get(),
222 NS_ConvertUTF16toUTF8(mCurValue).get()));
223
224 // Emergency exit to avoid endless loops in case a path in a CSP policy
225 // is longer than 512 characters, or also to avoid endless loops
226 // in case we are parsing unrecognized characters in the following loop.
227 uint32_t charCounter = 0;
228 nsString pctDecodedSubPath;
229
230 while (!atEndOfPath()) {
231 if (peek(SLASH)) {
232 // before appendig any additional portion of a subpath we have to
233 // pct-decode that portion of the subpath. atValidPathChar() already
234 // verified a correct pct-encoding, now we can safely decode and append
235 // the decoded-sub path.
236 CSP_PercentDecodeStr(mCurValue, pctDecodedSubPath);
237 aCspHost->appendPath(pctDecodedSubPath);
238 // Resetting current value since we are appending parts of the path
239 // to aCspHost, e.g; "http://www.example.com/path1/path2" then the
240 // first part is "/path1", second part "/path2"
241 resetCurValue();
242 } else if (!atValidPathChar()) {
243 AutoTArray<nsString, 1> params = {mCurToken};
244 logWarningErrorToConsole(nsIScriptError::warningFlag,
245 "couldntParseInvalidSource", params);
246 return false;
247 }
248 // potentially we have encountred a valid pct-encoded character in
249 // atValidPathChar(); if so, we have to account for "% HEXDIG HEXDIG" and
250 // advance the pointer past the pct-encoded char.
251 if (peek(PERCENT_SIGN)) {
252 advance();
253 advance();
254 }
255 advance();
256 if (++charCounter > kSubHostPathCharacterCutoff) {
257 return false;
258 }
259 }
260 // before appendig any additional portion of a subpath we have to pct-decode
261 // that portion of the subpath. atValidPathChar() already verified a correct
262 // pct-encoding, now we can safely decode and append the decoded-sub path.
263 CSP_PercentDecodeStr(mCurValue, pctDecodedSubPath);
264 aCspHost->appendPath(pctDecodedSubPath);
265 resetCurValue();
266 return true;
267 }
268
path(nsCSPHostSrc * aCspHost)269 bool nsCSPParser::path(nsCSPHostSrc* aCspHost) {
270 CSPPARSERLOG(("nsCSPParser::path, mCurToken: %s, mCurValue: %s",
271 NS_ConvertUTF16toUTF8(mCurToken).get(),
272 NS_ConvertUTF16toUTF8(mCurValue).get()));
273
274 // Resetting current value and forgetting everything we have parsed so far
275 // e.g. parsing "http://www.example.com/path1/path2", then
276 // "http://www.example.com" has already been parsed so far
277 // forget about it.
278 resetCurValue();
279
280 if (!accept(SLASH)) {
281 AutoTArray<nsString, 1> params = {mCurToken};
282 logWarningErrorToConsole(nsIScriptError::warningFlag,
283 "couldntParseInvalidSource", params);
284 return false;
285 }
286 if (atEndOfPath()) {
287 // one slash right after host [port] is also considered a path, e.g.
288 // www.example.com/ should result in www.example.com/
289 // please note that we do not have to perform any pct-decoding here
290 // because we are just appending a '/' and not any actual chars.
291 aCspHost->appendPath(u"/"_ns);
292 return true;
293 }
294 // path can begin with "/" but not "//"
295 // see http://tools.ietf.org/html/rfc3986#section-3.3
296 if (peek(SLASH)) {
297 AutoTArray<nsString, 1> params = {mCurToken};
298 logWarningErrorToConsole(nsIScriptError::warningFlag,
299 "couldntParseInvalidSource", params);
300 return false;
301 }
302 return subPath(aCspHost);
303 }
304
subHost()305 bool nsCSPParser::subHost() {
306 CSPPARSERLOG(("nsCSPParser::subHost, mCurToken: %s, mCurValue: %s",
307 NS_ConvertUTF16toUTF8(mCurToken).get(),
308 NS_ConvertUTF16toUTF8(mCurValue).get()));
309
310 // Emergency exit to avoid endless loops in case a host in a CSP policy
311 // is longer than 512 characters, or also to avoid endless loops
312 // in case we are parsing unrecognized characters in the following loop.
313 uint32_t charCounter = 0;
314
315 while (!atEndOfPath() && !peek(COLON) && !peek(SLASH)) {
316 ++charCounter;
317 while (hostChar()) {
318 /* consume */
319 ++charCounter;
320 }
321 if (accept(DOT) && !hostChar()) {
322 return false;
323 }
324 if (charCounter > kSubHostPathCharacterCutoff) {
325 return false;
326 }
327 }
328 return true;
329 }
330
331 // host = "*" / [ "*." ] 1*host-char *( "." 1*host-char )
host()332 nsCSPHostSrc* nsCSPParser::host() {
333 CSPPARSERLOG(("nsCSPParser::host, mCurToken: %s, mCurValue: %s",
334 NS_ConvertUTF16toUTF8(mCurToken).get(),
335 NS_ConvertUTF16toUTF8(mCurValue).get()));
336
337 // Check if the token starts with "*"; please remember that we handle
338 // a single "*" as host in sourceExpression, but we still have to handle
339 // the case where a scheme was defined, e.g., as:
340 // "https://*", "*.example.com", "*:*", etc.
341 if (accept(WILDCARD)) {
342 // Might solely be the wildcard
343 if (atEnd() || peek(COLON)) {
344 return new nsCSPHostSrc(mCurValue);
345 }
346 // If the token is not only the "*", a "." must follow right after
347 if (!accept(DOT)) {
348 AutoTArray<nsString, 1> params = {mCurToken};
349 logWarningErrorToConsole(nsIScriptError::warningFlag,
350 "couldntParseInvalidHost", params);
351 return nullptr;
352 }
353 }
354
355 // Expecting at least one host-char
356 if (!hostChar()) {
357 AutoTArray<nsString, 1> params = {mCurToken};
358 logWarningErrorToConsole(nsIScriptError::warningFlag,
359 "couldntParseInvalidHost", params);
360 return nullptr;
361 }
362
363 // There might be several sub hosts defined.
364 if (!subHost()) {
365 AutoTArray<nsString, 1> params = {mCurToken};
366 logWarningErrorToConsole(nsIScriptError::warningFlag,
367 "couldntParseInvalidHost", params);
368 return nullptr;
369 }
370
371 // HostName might match a keyword, log to the console.
372 if (CSP_IsQuotelessKeyword(mCurValue)) {
373 nsString keyword = mCurValue;
374 ToLowerCase(keyword);
375 AutoTArray<nsString, 2> params = {mCurToken, keyword};
376 logWarningErrorToConsole(nsIScriptError::warningFlag,
377 "hostNameMightBeKeyword", params);
378 }
379
380 // Create a new nsCSPHostSrc with the parsed host.
381 return new nsCSPHostSrc(mCurValue);
382 }
383
384 // keyword-source = "'self'" / "'unsafe-inline'" / "'unsafe-eval'"
keywordSource()385 nsCSPBaseSrc* nsCSPParser::keywordSource() {
386 CSPPARSERLOG(("nsCSPParser::keywordSource, mCurToken: %s, mCurValue: %s",
387 NS_ConvertUTF16toUTF8(mCurToken).get(),
388 NS_ConvertUTF16toUTF8(mCurValue).get()));
389
390 // Special case handling for 'self' which is not stored internally as a
391 // keyword, but rather creates a nsCSPHostSrc using the selfURI
392 if (CSP_IsKeyword(mCurToken, CSP_SELF)) {
393 return CSP_CreateHostSrcFromSelfURI(mSelfURI);
394 }
395
396 if (CSP_IsKeyword(mCurToken, CSP_REPORT_SAMPLE)) {
397 return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken));
398 }
399
400 if (CSP_IsKeyword(mCurToken, CSP_STRICT_DYNAMIC)) {
401 if (!CSP_IsDirective(mCurDir[0],
402 nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE)) {
403 // Todo: Enforce 'strict-dynamic' within default-src; see Bug 1313937
404 AutoTArray<nsString, 1> params = {u"strict-dynamic"_ns};
405 logWarningErrorToConsole(nsIScriptError::warningFlag,
406 "ignoringStrictDynamic", params);
407 return nullptr;
408 }
409 mStrictDynamic = true;
410 return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken));
411 }
412
413 if (CSP_IsKeyword(mCurToken, CSP_UNSAFE_INLINE)) {
414 nsWeakPtr ctx = mCSPContext->GetLoadingContext();
415 nsCOMPtr<Document> doc = do_QueryReferent(ctx);
416 if (doc) {
417 doc->SetHasUnsafeInlineCSP(true);
418 }
419 // make sure script-src only contains 'unsafe-inline' once;
420 // ignore duplicates and log warning
421 if (mUnsafeInlineKeywordSrc) {
422 AutoTArray<nsString, 1> params = {mCurToken};
423 logWarningErrorToConsole(nsIScriptError::warningFlag,
424 "ignoringDuplicateSrc", params);
425 return nullptr;
426 }
427 // cache if we encounter 'unsafe-inline' so we can invalidate (ignore) it in
428 // case that script-src directive also contains hash- or nonce-.
429 mUnsafeInlineKeywordSrc =
430 new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken));
431 return mUnsafeInlineKeywordSrc;
432 }
433
434 if (CSP_IsKeyword(mCurToken, CSP_UNSAFE_EVAL)) {
435 nsWeakPtr ctx = mCSPContext->GetLoadingContext();
436 nsCOMPtr<Document> doc = do_QueryReferent(ctx);
437 if (doc) {
438 doc->SetHasUnsafeEvalCSP(true);
439 }
440 return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken));
441 }
442
443 if (CSP_IsKeyword(mCurToken, CSP_UNSAFE_ALLOW_REDIRECTS)) {
444 if (!CSP_IsDirective(mCurDir[0],
445 nsIContentSecurityPolicy::NAVIGATE_TO_DIRECTIVE)) {
446 // Only allow 'unsafe-allow-redirects' within navigate-to.
447 AutoTArray<nsString, 2> params = {u"unsafe-allow-redirects"_ns,
448 u"navigate-to"_ns};
449 logWarningErrorToConsole(nsIScriptError::warningFlag,
450 "IgnoringSourceWithinDirective", params);
451 return nullptr;
452 }
453
454 return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken));
455 }
456
457 return nullptr;
458 }
459
460 // host-source = [ scheme "://" ] host [ port ] [ path ]
hostSource()461 nsCSPHostSrc* nsCSPParser::hostSource() {
462 CSPPARSERLOG(("nsCSPParser::hostSource, mCurToken: %s, mCurValue: %s",
463 NS_ConvertUTF16toUTF8(mCurToken).get(),
464 NS_ConvertUTF16toUTF8(mCurValue).get()));
465
466 nsCSPHostSrc* cspHost = host();
467 if (!cspHost) {
468 // Error was reported in host()
469 return nullptr;
470 }
471
472 // Calling port() to see if there is a port to parse, if an error
473 // occurs, port() reports the error, if port() returns true;
474 // we have a valid port, so we add it to cspHost.
475 if (peek(COLON)) {
476 if (!port()) {
477 delete cspHost;
478 return nullptr;
479 }
480 cspHost->setPort(mCurValue);
481 }
482
483 if (atEndOfPath()) {
484 return cspHost;
485 }
486
487 // Calling path() to see if there is a path to parse, if an error
488 // occurs, path() reports the error; handing cspHost as an argument
489 // which simplifies parsing of several paths.
490 if (!path(cspHost)) {
491 // If the host [port] is followed by a path, it has to be a valid path,
492 // otherwise we pass the nullptr, indicating an error, up the callstack.
493 // see also http://www.w3.org/TR/CSP11/#source-list
494 delete cspHost;
495 return nullptr;
496 }
497 return cspHost;
498 }
499
500 // scheme-source = scheme ":"
schemeSource()501 nsCSPSchemeSrc* nsCSPParser::schemeSource() {
502 CSPPARSERLOG(("nsCSPParser::schemeSource, mCurToken: %s, mCurValue: %s",
503 NS_ConvertUTF16toUTF8(mCurToken).get(),
504 NS_ConvertUTF16toUTF8(mCurValue).get()));
505
506 if (!accept(isCharacterToken)) {
507 return nullptr;
508 }
509 while (schemeChar()) { /* consume */
510 }
511 nsString scheme = mCurValue;
512
513 // If the potential scheme is not followed by ":" - it's not a scheme
514 if (!accept(COLON)) {
515 return nullptr;
516 }
517
518 // If the chraracter following the ":" is a number or the "*"
519 // then we are not parsing a scheme; but rather a host;
520 if (peek(isNumberToken) || peek(WILDCARD)) {
521 return nullptr;
522 }
523
524 return new nsCSPSchemeSrc(scheme);
525 }
526
527 // nonce-source = "'nonce-" nonce-value "'"
nonceSource()528 nsCSPNonceSrc* nsCSPParser::nonceSource() {
529 CSPPARSERLOG(("nsCSPParser::nonceSource, mCurToken: %s, mCurValue: %s",
530 NS_ConvertUTF16toUTF8(mCurToken).get(),
531 NS_ConvertUTF16toUTF8(mCurValue).get()));
532
533 // Check if mCurToken begins with "'nonce-" and ends with "'"
534 if (!StringBeginsWith(mCurToken,
535 nsDependentString(CSP_EnumToUTF16Keyword(CSP_NONCE)),
536 nsASCIICaseInsensitiveStringComparator) ||
537 mCurToken.Last() != SINGLEQUOTE) {
538 return nullptr;
539 }
540
541 // Trim surrounding single quotes
542 const nsAString& expr = Substring(mCurToken, 1, mCurToken.Length() - 2);
543
544 int32_t dashIndex = expr.FindChar(DASH);
545 if (dashIndex < 0) {
546 return nullptr;
547 }
548 if (!isValidBase64Value(expr.BeginReading() + dashIndex + 1,
549 expr.EndReading())) {
550 return nullptr;
551 }
552
553 // cache if encountering hash or nonce to invalidate unsafe-inline
554 mHasHashOrNonce = true;
555 return new nsCSPNonceSrc(
556 Substring(expr, dashIndex + 1, expr.Length() - dashIndex + 1));
557 }
558
559 // hash-source = "'" hash-algo "-" base64-value "'"
hashSource()560 nsCSPHashSrc* nsCSPParser::hashSource() {
561 CSPPARSERLOG(("nsCSPParser::hashSource, mCurToken: %s, mCurValue: %s",
562 NS_ConvertUTF16toUTF8(mCurToken).get(),
563 NS_ConvertUTF16toUTF8(mCurValue).get()));
564
565 // Check if mCurToken starts and ends with "'"
566 if (mCurToken.First() != SINGLEQUOTE || mCurToken.Last() != SINGLEQUOTE) {
567 return nullptr;
568 }
569
570 // Trim surrounding single quotes
571 const nsAString& expr = Substring(mCurToken, 1, mCurToken.Length() - 2);
572
573 int32_t dashIndex = expr.FindChar(DASH);
574 if (dashIndex < 0) {
575 return nullptr;
576 }
577
578 if (!isValidBase64Value(expr.BeginReading() + dashIndex + 1,
579 expr.EndReading())) {
580 return nullptr;
581 }
582
583 nsAutoString algo(Substring(expr, 0, dashIndex));
584 nsAutoString hash(
585 Substring(expr, dashIndex + 1, expr.Length() - dashIndex + 1));
586
587 for (uint32_t i = 0; i < kHashSourceValidFnsLen; i++) {
588 if (algo.LowerCaseEqualsASCII(kHashSourceValidFns[i])) {
589 // cache if encountering hash or nonce to invalidate unsafe-inline
590 mHasHashOrNonce = true;
591 return new nsCSPHashSrc(algo, hash);
592 }
593 }
594 return nullptr;
595 }
596
597 // source-expression = scheme-source / host-source / keyword-source
598 // / nonce-source / hash-source
sourceExpression()599 nsCSPBaseSrc* nsCSPParser::sourceExpression() {
600 CSPPARSERLOG(("nsCSPParser::sourceExpression, mCurToken: %s, mCurValue: %s",
601 NS_ConvertUTF16toUTF8(mCurToken).get(),
602 NS_ConvertUTF16toUTF8(mCurValue).get()));
603
604 // Check if it is a keyword
605 if (nsCSPBaseSrc* cspKeyword = keywordSource()) {
606 return cspKeyword;
607 }
608
609 // Check if it is a nonce-source
610 if (nsCSPNonceSrc* cspNonce = nonceSource()) {
611 return cspNonce;
612 }
613
614 // Check if it is a hash-source
615 if (nsCSPHashSrc* cspHash = hashSource()) {
616 return cspHash;
617 }
618
619 // We handle a single "*" as host here, to avoid any confusion when applying
620 // the default scheme. However, we still would need to apply the default
621 // scheme in case we would parse "*:80".
622 if (mCurToken.EqualsASCII("*")) {
623 return new nsCSPHostSrc(u"*"_ns);
624 }
625
626 // Calling resetCurChar allows us to use mCurChar and mEndChar
627 // to parse mCurToken; e.g. mCurToken = "http://www.example.com", then
628 // mCurChar = 'h'
629 // mEndChar = points just after the last 'm'
630 // mCurValue = ""
631 resetCurChar(mCurToken);
632
633 // Check if mCurToken starts with a scheme
634 nsAutoString parsedScheme;
635 if (nsCSPSchemeSrc* cspScheme = schemeSource()) {
636 // mCurToken might only enforce a specific scheme
637 if (atEnd()) {
638 return cspScheme;
639 }
640 // If something follows the scheme, we do not create
641 // a nsCSPSchemeSrc, but rather a nsCSPHostSrc, which
642 // needs to know the scheme to enforce; remember the
643 // scheme and delete cspScheme;
644 cspScheme->toString(parsedScheme);
645 parsedScheme.Trim(":", false, true);
646 delete cspScheme;
647
648 // If mCurToken provides not only a scheme, but also a host, we have to
649 // check if two slashes follow the scheme.
650 if (!accept(SLASH) || !accept(SLASH)) {
651 AutoTArray<nsString, 1> params = {mCurToken};
652 logWarningErrorToConsole(nsIScriptError::warningFlag,
653 "failedToParseUnrecognizedSource", params);
654 return nullptr;
655 }
656 }
657
658 // Calling resetCurValue allows us to keep pointers for mCurChar and mEndChar
659 // alive, but resets mCurValue; e.g. mCurToken = "http://www.example.com",
660 // then mCurChar = 'w' mEndChar = 'm' mCurValue = ""
661 resetCurValue();
662
663 // If mCurToken does not provide a scheme (scheme-less source), we apply the
664 // scheme from selfURI
665 if (parsedScheme.IsEmpty()) {
666 // Resetting internal helpers, because we might already have parsed some of
667 // the host when trying to parse a scheme.
668 resetCurChar(mCurToken);
669 nsAutoCString selfScheme;
670 mSelfURI->GetScheme(selfScheme);
671 parsedScheme.AssignASCII(selfScheme.get());
672 }
673
674 // At this point we are expecting a host to be parsed.
675 // Trying to create a new nsCSPHost.
676 if (nsCSPHostSrc* cspHost = hostSource()) {
677 // Do not forget to set the parsed scheme.
678 cspHost->setScheme(parsedScheme);
679 cspHost->setWithinFrameAncestorsDir(mParsingFrameAncestorsDir);
680 return cspHost;
681 }
682 // Error was reported in hostSource()
683 return nullptr;
684 }
685
686 // source-list = *WSP [ source-expression *( 1*WSP source-expression ) *WSP ]
687 // / *WSP "'none'" *WSP
sourceList(nsTArray<nsCSPBaseSrc * > & outSrcs)688 void nsCSPParser::sourceList(nsTArray<nsCSPBaseSrc*>& outSrcs) {
689 bool isNone = false;
690
691 // remember, srcs start at index 1
692 for (uint32_t i = 1; i < mCurDir.Length(); i++) {
693 // mCurToken is only set here and remains the current token
694 // to be processed, which avoid passing arguments between functions.
695 mCurToken = mCurDir[i];
696 resetCurValue();
697
698 CSPPARSERLOG(("nsCSPParser::sourceList, mCurToken: %s, mCurValue: %s",
699 NS_ConvertUTF16toUTF8(mCurToken).get(),
700 NS_ConvertUTF16toUTF8(mCurValue).get()));
701
702 // Special case handling for none:
703 // Ignore 'none' if any other src is available.
704 // (See http://www.w3.org/TR/CSP11/#parsing)
705 if (CSP_IsKeyword(mCurToken, CSP_NONE)) {
706 isNone = true;
707 continue;
708 }
709 // Must be a regular source expression
710 nsCSPBaseSrc* src = sourceExpression();
711 if (src) {
712 outSrcs.AppendElement(src);
713 }
714 }
715
716 // Check if the directive contains a 'none'
717 if (isNone) {
718 // If the directive contains no other srcs, then we set the 'none'
719 if (outSrcs.IsEmpty() ||
720 (outSrcs.Length() == 1 && outSrcs[0]->isReportSample())) {
721 nsCSPKeywordSrc* keyword = new nsCSPKeywordSrc(CSP_NONE);
722 outSrcs.InsertElementAt(0, keyword);
723 }
724 // Otherwise, we ignore 'none' and report a warning
725 else {
726 AutoTArray<nsString, 1> params;
727 params.AppendElement(CSP_EnumToUTF16Keyword(CSP_NONE));
728 logWarningErrorToConsole(nsIScriptError::warningFlag,
729 "ignoringUnknownOption", params);
730 }
731 }
732 }
733
reportURIList(nsCSPDirective * aDir)734 void nsCSPParser::reportURIList(nsCSPDirective* aDir) {
735 CSPPARSERLOG(("nsCSPParser::reportURIList"));
736
737 nsTArray<nsCSPBaseSrc*> srcs;
738 nsCOMPtr<nsIURI> uri;
739 nsresult rv;
740
741 // remember, srcs start at index 1
742 for (uint32_t i = 1; i < mCurDir.Length(); i++) {
743 mCurToken = mCurDir[i];
744
745 CSPPARSERLOG(("nsCSPParser::reportURIList, mCurToken: %s, mCurValue: %s",
746 NS_ConvertUTF16toUTF8(mCurToken).get(),
747 NS_ConvertUTF16toUTF8(mCurValue).get()));
748
749 rv = NS_NewURI(getter_AddRefs(uri), mCurToken, "", mSelfURI);
750
751 // If creating the URI casued an error, skip this URI
752 if (NS_FAILED(rv)) {
753 AutoTArray<nsString, 1> params = {mCurToken};
754 logWarningErrorToConsole(nsIScriptError::warningFlag,
755 "couldNotParseReportURI", params);
756 continue;
757 }
758
759 // Create new nsCSPReportURI and append to the list.
760 nsCSPReportURI* reportURI = new nsCSPReportURI(uri);
761 srcs.AppendElement(reportURI);
762 }
763
764 if (srcs.Length() == 0) {
765 AutoTArray<nsString, 1> directiveName = {mCurToken};
766 logWarningErrorToConsole(nsIScriptError::warningFlag,
767 "ignoringDirectiveWithNoValues", directiveName);
768 delete aDir;
769 return;
770 }
771
772 aDir->addSrcs(srcs);
773 mPolicy->addDirective(aDir);
774 }
775
776 /* Helper function for parsing sandbox flags. This function solely concatenates
777 * all the source list tokens (the sandbox flags) so the attribute parser
778 * (nsContentUtils::ParseSandboxAttributeToFlags) can parse them.
779 */
sandboxFlagList(nsCSPDirective * aDir)780 void nsCSPParser::sandboxFlagList(nsCSPDirective* aDir) {
781 CSPPARSERLOG(("nsCSPParser::sandboxFlagList"));
782
783 nsAutoString flags;
784
785 // remember, srcs start at index 1
786 for (uint32_t i = 1; i < mCurDir.Length(); i++) {
787 mCurToken = mCurDir[i];
788
789 CSPPARSERLOG(("nsCSPParser::sandboxFlagList, mCurToken: %s, mCurValue: %s",
790 NS_ConvertUTF16toUTF8(mCurToken).get(),
791 NS_ConvertUTF16toUTF8(mCurValue).get()));
792
793 if (!nsContentUtils::IsValidSandboxFlag(mCurToken)) {
794 AutoTArray<nsString, 1> params = {mCurToken};
795 logWarningErrorToConsole(nsIScriptError::warningFlag,
796 "couldntParseInvalidSandboxFlag", params);
797 continue;
798 }
799
800 flags.Append(mCurToken);
801 if (i != mCurDir.Length() - 1) {
802 flags.AppendLiteral(" ");
803 }
804 }
805
806 // Please note that the sandbox directive can exist
807 // by itself (not containing any flags).
808 nsTArray<nsCSPBaseSrc*> srcs;
809 srcs.AppendElement(new nsCSPSandboxFlags(flags));
810 aDir->addSrcs(srcs);
811 mPolicy->addDirective(aDir);
812 }
813
814 // directive-value = *( WSP / <VCHAR except ";" and ","> )
directiveValue(nsTArray<nsCSPBaseSrc * > & outSrcs)815 void nsCSPParser::directiveValue(nsTArray<nsCSPBaseSrc*>& outSrcs) {
816 CSPPARSERLOG(("nsCSPParser::directiveValue"));
817
818 // Just forward to sourceList
819 sourceList(outSrcs);
820 }
821
822 // directive-name = 1*( ALPHA / DIGIT / "-" )
directiveName()823 nsCSPDirective* nsCSPParser::directiveName() {
824 CSPPARSERLOG(("nsCSPParser::directiveName, mCurToken: %s, mCurValue: %s",
825 NS_ConvertUTF16toUTF8(mCurToken).get(),
826 NS_ConvertUTF16toUTF8(mCurValue).get()));
827
828 // Check if it is a valid directive
829 CSPDirective directive = CSP_StringToCSPDirective(mCurToken);
830 if (directive == nsIContentSecurityPolicy::NO_DIRECTIVE) {
831 AutoTArray<nsString, 1> params = {mCurToken};
832 logWarningErrorToConsole(nsIScriptError::warningFlag,
833 "couldNotProcessUnknownDirective", params);
834 return nullptr;
835 }
836
837 // The directive 'reflected-xss' is part of CSP 1.1, see:
838 // http://www.w3.org/TR/2014/WD-CSP11-20140211/#reflected-xss
839 // Currently we are not supporting that directive, hence we log a
840 // warning to the console and ignore the directive including its values.
841 if (directive == nsIContentSecurityPolicy::REFLECTED_XSS_DIRECTIVE) {
842 AutoTArray<nsString, 1> params = {mCurToken};
843 logWarningErrorToConsole(nsIScriptError::warningFlag,
844 "notSupportingDirective", params);
845 return nullptr;
846 }
847
848 // Bug 1529068: Implement navigate-to directive.
849 // Once all corner cases are resolved we can remove that special
850 // if-handling here and let the parser just fall through to
851 // return new nsCSPDirective.
852 if (directive == nsIContentSecurityPolicy::NAVIGATE_TO_DIRECTIVE &&
853 !StaticPrefs::security_csp_enableNavigateTo()) {
854 AutoTArray<nsString, 1> params = {mCurToken};
855 logWarningErrorToConsole(nsIScriptError::warningFlag,
856 "couldNotProcessUnknownDirective", params);
857 return nullptr;
858 }
859
860 // Make sure the directive does not already exist
861 // (see http://www.w3.org/TR/CSP11/#parsing)
862 if (mPolicy->hasDirective(directive)) {
863 AutoTArray<nsString, 1> params = {mCurToken};
864 logWarningErrorToConsole(nsIScriptError::warningFlag, "duplicateDirective",
865 params);
866 return nullptr;
867 }
868
869 // CSP delivered via meta tag should ignore the following directives:
870 // report-uri, frame-ancestors, and sandbox, see:
871 // http://www.w3.org/TR/CSP11/#delivery-html-meta-element
872 if (mDeliveredViaMetaTag &&
873 ((directive == nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE) ||
874 (directive == nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE) ||
875 (directive == nsIContentSecurityPolicy::SANDBOX_DIRECTIVE))) {
876 // log to the console to indicate that meta CSP is ignoring the directive
877 AutoTArray<nsString, 1> params = {mCurToken};
878 logWarningErrorToConsole(nsIScriptError::warningFlag,
879 "ignoringSrcFromMetaCSP", params);
880 return nullptr;
881 }
882
883 // special case handling for block-all-mixed-content
884 if (directive == nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT) {
885 return new nsBlockAllMixedContentDirective(directive);
886 }
887
888 // special case handling for upgrade-insecure-requests
889 if (directive == nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE) {
890 return new nsUpgradeInsecureDirective(directive);
891 }
892
893 // if we have a child-src, cache it as a fallback for
894 // * workers (if worker-src is not explicitly specified)
895 // * frames (if frame-src is not explicitly specified)
896 if (directive == nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE) {
897 mChildSrc = new nsCSPChildSrcDirective(directive);
898 return mChildSrc;
899 }
900
901 // if we have a frame-src, cache it so we can discard child-src for frames
902 if (directive == nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE) {
903 mFrameSrc = new nsCSPDirective(directive);
904 return mFrameSrc;
905 }
906
907 // if we have a worker-src, cache it so we can discard child-src for workers
908 if (directive == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) {
909 mWorkerSrc = new nsCSPDirective(directive);
910 return mWorkerSrc;
911 }
912
913 // if we have a script-src, cache it as a fallback for worker-src
914 // in case child-src is not present
915 if (directive == nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE) {
916 mScriptSrc = new nsCSPScriptSrcDirective(directive);
917 return mScriptSrc;
918 }
919
920 return new nsCSPDirective(directive);
921 }
922
923 // directive = *WSP [ directive-name [ WSP directive-value ] ]
directive()924 void nsCSPParser::directive() {
925 // Set the directiveName to mCurToken
926 // Remember, the directive name is stored at index 0
927 mCurToken = mCurDir[0];
928
929 CSPPARSERLOG(("nsCSPParser::directive, mCurToken: %s, mCurValue: %s",
930 NS_ConvertUTF16toUTF8(mCurToken).get(),
931 NS_ConvertUTF16toUTF8(mCurValue).get()));
932
933 // Make sure that the directive-srcs-array contains at least
934 // one directive and one src.
935 if (mCurDir.Length() < 1) {
936 AutoTArray<nsString, 1> params = {u"directive missing"_ns};
937 logWarningErrorToConsole(nsIScriptError::warningFlag,
938 "failedToParseUnrecognizedSource", params);
939 return;
940 }
941
942 if (CSP_IsEmptyDirective(mCurValue, mCurToken)) {
943 return;
944 }
945
946 // Try to create a new CSPDirective
947 nsCSPDirective* cspDir = directiveName();
948 if (!cspDir) {
949 // if we can not create a CSPDirective, we can skip parsing the srcs for
950 // that array
951 return;
952 }
953
954 // special case handling for block-all-mixed-content, which is only specified
955 // by a directive name but does not include any srcs.
956 if (cspDir->equals(nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT)) {
957 if (mCurDir.Length() > 1) {
958 AutoTArray<nsString, 1> params = {u"block-all-mixed-content"_ns};
959 logWarningErrorToConsole(nsIScriptError::warningFlag,
960 "ignoreSrcForDirective", params);
961 }
962 // add the directive and return
963 mPolicy->addDirective(cspDir);
964 return;
965 }
966
967 // special case handling for upgrade-insecure-requests, which is only
968 // specified by a directive name but does not include any srcs.
969 if (cspDir->equals(nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)) {
970 if (mCurDir.Length() > 1) {
971 AutoTArray<nsString, 1> params = {u"upgrade-insecure-requests"_ns};
972 logWarningErrorToConsole(nsIScriptError::warningFlag,
973 "ignoreSrcForDirective", params);
974 }
975 // add the directive and return
976 mPolicy->addUpgradeInsecDir(
977 static_cast<nsUpgradeInsecureDirective*>(cspDir));
978 return;
979 }
980
981 // special case handling for report-uri directive (since it doesn't contain
982 // a valid source list but rather actual URIs)
983 if (CSP_IsDirective(mCurDir[0],
984 nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
985 reportURIList(cspDir);
986 return;
987 }
988
989 // special case handling for sandbox directive (since it doe4sn't contain
990 // a valid source list but rather special sandbox flags)
991 if (CSP_IsDirective(mCurDir[0],
992 nsIContentSecurityPolicy::SANDBOX_DIRECTIVE)) {
993 sandboxFlagList(cspDir);
994 return;
995 }
996
997 // make sure to reset cache variables when trying to invalidate unsafe-inline;
998 // unsafe-inline might not only appear in script-src, but also in default-src
999 mHasHashOrNonce = false;
1000 mStrictDynamic = false;
1001 mUnsafeInlineKeywordSrc = nullptr;
1002
1003 mParsingFrameAncestorsDir = CSP_IsDirective(
1004 mCurDir[0], nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE);
1005
1006 // Try to parse all the srcs by handing the array off to directiveValue
1007 nsTArray<nsCSPBaseSrc*> srcs;
1008 directiveValue(srcs);
1009
1010 // If we can not parse any srcs; we let the source expression be the empty set
1011 // ('none') see, http://www.w3.org/TR/CSP11/#source-list-parsing
1012 if (srcs.IsEmpty() || (srcs.Length() == 1 && srcs[0]->isReportSample())) {
1013 nsCSPKeywordSrc* keyword = new nsCSPKeywordSrc(CSP_NONE);
1014 srcs.InsertElementAt(0, keyword);
1015 }
1016
1017 // If policy contains 'strict-dynamic' invalidate all srcs within script-src.
1018 if (mStrictDynamic) {
1019 MOZ_ASSERT(cspDir->equals(nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE),
1020 "strict-dynamic only allowed within script-src");
1021 for (uint32_t i = 0; i < srcs.Length(); i++) {
1022 // Please note that nsCSPNonceSrc as well as nsCSPHashSrc overwrite
1023 // invalidate(), so it's fine to just call invalidate() on all srcs.
1024 // Please also note that nsCSPKeywordSrc() can not be invalidated and
1025 // always returns false unless the keyword is 'strict-dynamic' in which
1026 // case we allow the load if the script is not parser created!
1027 srcs[i]->invalidate();
1028 // Log a message to the console that src will be ignored.
1029 nsAutoString srcStr;
1030 srcs[i]->toString(srcStr);
1031 // Even though we invalidate all of the srcs internally, we don't want to
1032 // log messages for the srcs: (1) strict-dynamic, (2) unsafe-inline, (3)
1033 // nonces, and (4) hashes
1034 if (!srcStr.EqualsASCII(CSP_EnumToUTF8Keyword(CSP_STRICT_DYNAMIC)) &&
1035 !srcStr.EqualsASCII(CSP_EnumToUTF8Keyword(CSP_UNSAFE_EVAL)) &&
1036 !StringBeginsWith(
1037 srcStr, nsDependentString(CSP_EnumToUTF16Keyword(CSP_NONCE))) &&
1038 !StringBeginsWith(srcStr, u"'sha"_ns)) {
1039 AutoTArray<nsString, 1> params = {srcStr};
1040 logWarningErrorToConsole(nsIScriptError::warningFlag,
1041 "ignoringSrcForStrictDynamic", params);
1042 }
1043 }
1044 // Log a warning that all scripts might be blocked because the policy
1045 // contains 'strict-dynamic' but no valid nonce or hash.
1046 if (!mHasHashOrNonce) {
1047 AutoTArray<nsString, 1> params = {mCurDir[0]};
1048 logWarningErrorToConsole(nsIScriptError::warningFlag,
1049 "strictDynamicButNoHashOrNonce", params);
1050 }
1051 } else if (mHasHashOrNonce && mUnsafeInlineKeywordSrc &&
1052 (cspDir->equals(nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE) ||
1053 cspDir->equals(nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE))) {
1054 mUnsafeInlineKeywordSrc->invalidate();
1055 // log to the console that unsafe-inline will be ignored
1056 AutoTArray<nsString, 1> params = {u"'unsafe-inline'"_ns};
1057 logWarningErrorToConsole(nsIScriptError::warningFlag,
1058 "ignoringSrcWithinScriptStyleSrc", params);
1059 }
1060
1061 // Add the newly created srcs to the directive and add the directive to the
1062 // policy
1063 cspDir->addSrcs(srcs);
1064 mPolicy->addDirective(cspDir);
1065 }
1066
1067 // policy = [ directive *( ";" [ directive ] ) ]
policy()1068 nsCSPPolicy* nsCSPParser::policy() {
1069 CSPPARSERLOG(("nsCSPParser::policy"));
1070
1071 mPolicy = new nsCSPPolicy();
1072 for (uint32_t i = 0; i < mTokens.Length(); i++) {
1073 // All input is already tokenized; set one tokenized array in the form of
1074 // [ name, src, src, ... ]
1075 // to mCurDir and call directive which processes the current directive.
1076 mCurDir = mTokens[i].Clone();
1077 directive();
1078 }
1079
1080 if (mChildSrc) {
1081 if (!mFrameSrc) {
1082 // if frame-src is specified explicitly for that policy than child-src
1083 // should not restrict frames; if not, than child-src needs to restrict
1084 // frames.
1085 mChildSrc->setRestrictFrames();
1086 }
1087 if (!mWorkerSrc) {
1088 // if worker-src is specified explicitly for that policy than child-src
1089 // should not restrict workers; if not, than child-src needs to restrict
1090 // workers.
1091 mChildSrc->setRestrictWorkers();
1092 }
1093 }
1094 // if script-src is specified, but not worker-src and also no child-src, then
1095 // script-src has to govern workers.
1096 if (mScriptSrc && !mWorkerSrc && !mChildSrc) {
1097 mScriptSrc->setRestrictWorkers();
1098 }
1099
1100 return mPolicy;
1101 }
1102
parseContentSecurityPolicy(const nsAString & aPolicyString,nsIURI * aSelfURI,bool aReportOnly,nsCSPContext * aCSPContext,bool aDeliveredViaMetaTag)1103 nsCSPPolicy* nsCSPParser::parseContentSecurityPolicy(
1104 const nsAString& aPolicyString, nsIURI* aSelfURI, bool aReportOnly,
1105 nsCSPContext* aCSPContext, bool aDeliveredViaMetaTag) {
1106 if (CSPPARSERLOGENABLED()) {
1107 CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, policy: %s",
1108 NS_ConvertUTF16toUTF8(aPolicyString).get()));
1109 CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, selfURI: %s",
1110 aSelfURI->GetSpecOrDefault().get()));
1111 CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, reportOnly: %s",
1112 (aReportOnly ? "true" : "false")));
1113 CSPPARSERLOG(
1114 ("nsCSPParser::parseContentSecurityPolicy, deliveredViaMetaTag: %s",
1115 (aDeliveredViaMetaTag ? "true" : "false")));
1116 }
1117
1118 NS_ASSERTION(aSelfURI, "Can not parseContentSecurityPolicy without aSelfURI");
1119
1120 // Separate all input into tokens and store them in the form of:
1121 // [ [ name, src, src, ... ], [ name, src, src, ... ], ... ]
1122 // The tokenizer itself can not fail; all eventual errors
1123 // are detected in the parser itself.
1124
1125 nsTArray<CopyableTArray<nsString> > tokens;
1126 PolicyTokenizer::tokenizePolicy(aPolicyString, tokens);
1127
1128 nsCSPParser parser(tokens, aSelfURI, aCSPContext, aDeliveredViaMetaTag);
1129
1130 // Start the parser to generate a new CSPPolicy using the generated tokens.
1131 nsCSPPolicy* policy = parser.policy();
1132
1133 // Check that report-only policies define a report-uri, otherwise log warning.
1134 if (aReportOnly) {
1135 policy->setReportOnlyFlag(true);
1136 if (!policy->hasDirective(nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
1137 nsAutoCString prePath;
1138 nsresult rv = aSelfURI->GetPrePath(prePath);
1139 NS_ENSURE_SUCCESS(rv, policy);
1140 AutoTArray<nsString, 1> params;
1141 CopyUTF8toUTF16(prePath, *params.AppendElement());
1142 parser.logWarningErrorToConsole(nsIScriptError::warningFlag,
1143 "reportURInotInReportOnlyHeader", params);
1144 }
1145 }
1146
1147 policy->setDeliveredViaMetaTagFlag(aDeliveredViaMetaTag);
1148
1149 if (policy->getNumDirectives() == 0) {
1150 // Individual errors were already reported in the parser, but if
1151 // we do not have an enforcable directive at all, we return null.
1152 delete policy;
1153 return nullptr;
1154 }
1155
1156 if (CSPPARSERLOGENABLED()) {
1157 nsString parsedPolicy;
1158 policy->toString(parsedPolicy);
1159 CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, parsedPolicy: %s",
1160 NS_ConvertUTF16toUTF8(parsedPolicy).get()));
1161 }
1162
1163 return policy;
1164 }
1165