Coverage Report

Created: 2018-09-25 14:53

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