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