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