1/*
2 * Copyright (C) 2011 Google, Inc. All rights reserved.
3 * Copyright (C) 2013-2018 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "ContentSecurityPolicy.h"
29
30#include "ContentSecurityPolicyClient.h"
31#include "ContentSecurityPolicyDirective.h"
32#include "ContentSecurityPolicyDirectiveList.h"
33#include "ContentSecurityPolicyDirectiveNames.h"
34#include "ContentSecurityPolicyHash.h"
35#include "ContentSecurityPolicySource.h"
36#include "ContentSecurityPolicySourceList.h"
37#include "CustomHeaderFields.h"
38#include "DOMStringList.h"
39#include "Document.h"
40#include "DocumentLoader.h"
41#include "EventNames.h"
42#include "FormData.h"
43#include "Frame.h"
44#include "HTMLParserIdioms.h"
45#include "InspectorInstrumentation.h"
46#include "JSExecState.h"
47#include "JSWindowProxy.h"
48#include "ParsingUtilities.h"
49#include "PingLoader.h"
50#include "ResourceRequest.h"
51#include "RuntimeEnabledFeatures.h"
52#include "SchemeRegistry.h"
53#include "SecurityOrigin.h"
54#include "SecurityPolicyViolationEvent.h"
55#include "Settings.h"
56#include "TextEncoding.h"
57#include <JavaScriptCore/ScriptCallStack.h>
58#include <JavaScriptCore/ScriptCallStackFactory.h>
59#include <pal/crypto/CryptoDigest.h>
60#include <wtf/JSONValues.h>
61#include <wtf/SetForScope.h>
62#include <wtf/text/StringBuilder.h>
63#include <wtf/text/TextPosition.h>
64
65
66namespace WebCore {
67using namespace Inspector;
68
69static String consoleMessageForViolation(const char* effectiveViolatedDirective, const ContentSecurityPolicyDirective& violatedDirective, const URL& blockedURL, const char* prefix, const char* subject = "it")
70{
71 StringBuilder result;
72 if (violatedDirective.directiveList().isReportOnly())
73 result.appendLiteral("[Report Only] ");
74 result.append(prefix);
75 if (!blockedURL.isEmpty()) {
76 result.append(' ');
77 result.append(blockedURL.stringCenterEllipsizedToLength());
78 }
79 result.appendLiteral(" because ");
80 result.append(subject);
81 if (violatedDirective.isDefaultSrc()) {
82 result.appendLiteral(" appears in neither the ");
83 result.append(effectiveViolatedDirective);
84 result.appendLiteral(" directive nor the default-src directive of the Content Security Policy.");
85 } else {
86 result.appendLiteral(" does not appear in the ");
87 result.append(effectiveViolatedDirective);
88 result.appendLiteral(" directive of the Content Security Policy.");
89 }
90 return result.toString();
91}
92
93ContentSecurityPolicy::ContentSecurityPolicy(URL&& protectedURL, ContentSecurityPolicyClient* client)
94 : m_client { client }
95 , m_protectedURL { WTFMove(protectedURL) }
96{
97 updateSourceSelf(SecurityOrigin::create(m_protectedURL).get());
98}
99
100ContentSecurityPolicy::ContentSecurityPolicy(URL&& protectedURL, ScriptExecutionContext& scriptExecutionContext)
101 : m_scriptExecutionContext(&scriptExecutionContext)
102 , m_protectedURL { WTFMove(protectedURL) }
103{
104 ASSERT(scriptExecutionContext.securityOrigin());
105 updateSourceSelf(*scriptExecutionContext.securityOrigin());
106}
107
108ContentSecurityPolicy::~ContentSecurityPolicy() = default;
109
110void ContentSecurityPolicy::copyStateFrom(const ContentSecurityPolicy* other)
111{
112 if (m_hasAPIPolicy)
113 return;
114 ASSERT(m_policies.isEmpty());
115 for (auto& policy : other->m_policies)
116 didReceiveHeader(policy->header(), policy->headerType(), ContentSecurityPolicy::PolicyFrom::Inherited, String { });
117 m_referrer = other->m_referrer;
118 m_httpStatusCode = other->m_httpStatusCode;
119}
120
121void ContentSecurityPolicy::createPolicyForPluginDocumentFrom(const ContentSecurityPolicy& other)
122{
123 if (m_hasAPIPolicy)
124 return;
125 ASSERT(m_policies.isEmpty());
126 for (auto& policy : other.m_policies)
127 didReceiveHeader(policy->header(), policy->headerType(), ContentSecurityPolicy::PolicyFrom::InheritedForPluginDocument, String { });
128 m_referrer = other.m_referrer;
129 m_httpStatusCode = other.m_httpStatusCode;
130}
131
132void ContentSecurityPolicy::copyUpgradeInsecureRequestStateFrom(const ContentSecurityPolicy& other)
133{
134 m_upgradeInsecureRequests = other.m_upgradeInsecureRequests;
135 m_insecureNavigationRequestsToUpgrade.add(other.m_insecureNavigationRequestsToUpgrade.begin(), other.m_insecureNavigationRequestsToUpgrade.end());
136}
137
138bool ContentSecurityPolicy::allowRunningOrDisplayingInsecureContent(const URL& url)
139{
140 bool allow = true;
141 bool isReportOnly = false;
142 for (auto& policy : m_policies) {
143 if (!policy->hasBlockAllMixedContentDirective())
144 continue;
145
146 isReportOnly = policy->isReportOnly();
147
148 StringBuilder consoleMessage;
149 if (isReportOnly)
150 consoleMessage.appendLiteral("[Report Only] ");
151 consoleMessage.append("Blocked mixed content ");
152 consoleMessage.append(url.stringCenterEllipsizedToLength());
153 consoleMessage.appendLiteral(" because ");
154 consoleMessage.append("'block-all-mixed-content' appears in the Content Security Policy.");
155 reportViolation(ContentSecurityPolicyDirectiveNames::blockAllMixedContent, ContentSecurityPolicyDirectiveNames::blockAllMixedContent, *policy, url, consoleMessage.toString());
156
157 if (!isReportOnly)
158 allow = false;
159 }
160 return allow;
161}
162
163void ContentSecurityPolicy::didCreateWindowProxy(JSWindowProxy& windowProxy) const
164{
165 auto* window = windowProxy.window();
166 ASSERT(window);
167 ASSERT(window->scriptExecutionContext());
168 ASSERT(window->scriptExecutionContext()->contentSecurityPolicy() == this);
169 if (!windowProxy.world().isNormal()) {
170 window->setEvalEnabled(true);
171 return;
172 }
173 window->setEvalEnabled(m_lastPolicyEvalDisabledErrorMessage.isNull(), m_lastPolicyEvalDisabledErrorMessage);
174 window->setWebAssemblyEnabled(m_lastPolicyWebAssemblyDisabledErrorMessage.isNull(), m_lastPolicyWebAssemblyDisabledErrorMessage);
175}
176
177ContentSecurityPolicyResponseHeaders ContentSecurityPolicy::responseHeaders() const
178{
179 if (!m_cachedResponseHeaders) {
180 ContentSecurityPolicyResponseHeaders result;
181 result.m_headers.reserveInitialCapacity(m_policies.size());
182 for (auto& policy : m_policies)
183 result.m_headers.uncheckedAppend({ policy->header(), policy->headerType() });
184 result.m_httpStatusCode = m_httpStatusCode;
185 m_cachedResponseHeaders = WTFMove(result);
186 }
187 return *m_cachedResponseHeaders;
188}
189
190void ContentSecurityPolicy::didReceiveHeaders(const ContentSecurityPolicyResponseHeaders& headers, String&& referrer, ReportParsingErrors reportParsingErrors)
191{
192 SetForScope<bool> isReportingEnabled(m_isReportingEnabled, reportParsingErrors == ReportParsingErrors::Yes);
193 for (auto& header : headers.m_headers)
194 didReceiveHeader(header.first, header.second, ContentSecurityPolicy::PolicyFrom::HTTPHeader, String { });
195 m_referrer = WTFMove(referrer);
196 m_httpStatusCode = headers.m_httpStatusCode;
197}
198
199void ContentSecurityPolicy::didReceiveHeader(const String& header, ContentSecurityPolicyHeaderType type, ContentSecurityPolicy::PolicyFrom policyFrom, String&& referrer, int httpStatusCode)
200{
201 if (m_hasAPIPolicy)
202 return;
203
204 m_referrer = WTFMove(referrer);
205 m_httpStatusCode = httpStatusCode;
206
207 if (policyFrom == PolicyFrom::API) {
208 ASSERT(m_policies.isEmpty());
209 m_hasAPIPolicy = true;
210 }
211
212 m_cachedResponseHeaders = WTF::nullopt;
213
214 // RFC2616, section 4.2 specifies that headers appearing multiple times can
215 // be combined with a comma. Walk the header string, and parse each comma
216 // separated chunk as a separate header.
217 auto characters = StringView(header).upconvertedCharacters();
218 const UChar* begin = characters;
219 const UChar* position = begin;
220 const UChar* end = begin + header.length();
221 while (position < end) {
222 skipUntil<UChar>(position, end, ',');
223
224 // header1,header2 OR header1
225 // ^ ^
226 m_policies.append(ContentSecurityPolicyDirectiveList::create(*this, String(begin, position - begin), type, policyFrom));
227
228 // Skip the comma, and begin the next header from the current position.
229 ASSERT(position == end || *position == ',');
230 skipExactly<UChar>(position, end, ',');
231 begin = position;
232 }
233
234 if (m_scriptExecutionContext)
235 applyPolicyToScriptExecutionContext();
236}
237
238void ContentSecurityPolicy::updateSourceSelf(const SecurityOrigin& securityOrigin)
239{
240 m_selfSourceProtocol = securityOrigin.protocol();
241 m_selfSource = std::make_unique<ContentSecurityPolicySource>(*this, m_selfSourceProtocol, securityOrigin.host(), securityOrigin.port(), emptyString(), false, false);
242}
243
244void ContentSecurityPolicy::applyPolicyToScriptExecutionContext()
245{
246 ASSERT(m_scriptExecutionContext);
247
248 // Update source self as the security origin may have changed between the time we were created and now.
249 // For instance, we may have been initially created for an about:blank iframe that later inherited the
250 // security origin of its owner document.
251 ASSERT(m_scriptExecutionContext->securityOrigin());
252 updateSourceSelf(*m_scriptExecutionContext->securityOrigin());
253
254 bool enableStrictMixedContentMode = false;
255 for (auto& policy : m_policies) {
256 const ContentSecurityPolicyDirective* violatedDirective = policy->violatedDirectiveForUnsafeEval();
257 if (violatedDirective && !violatedDirective->directiveList().isReportOnly()) {
258 m_lastPolicyEvalDisabledErrorMessage = policy->evalDisabledErrorMessage();
259 m_lastPolicyWebAssemblyDisabledErrorMessage = policy->webAssemblyDisabledErrorMessage();
260 }
261 if (policy->hasBlockAllMixedContentDirective() && !policy->isReportOnly())
262 enableStrictMixedContentMode = true;
263 }
264
265 if (!m_lastPolicyEvalDisabledErrorMessage.isNull())
266 m_scriptExecutionContext->disableEval(m_lastPolicyEvalDisabledErrorMessage);
267 if (!m_lastPolicyWebAssemblyDisabledErrorMessage.isNull())
268 m_scriptExecutionContext->disableWebAssembly(m_lastPolicyWebAssemblyDisabledErrorMessage);
269 if (m_sandboxFlags != SandboxNone && is<Document>(m_scriptExecutionContext))
270 m_scriptExecutionContext->enforceSandboxFlags(m_sandboxFlags);
271 if (enableStrictMixedContentMode)
272 m_scriptExecutionContext->setStrictMixedContentMode(true);
273}
274
275void ContentSecurityPolicy::setOverrideAllowInlineStyle(bool value)
276{
277 m_overrideInlineStyleAllowed = value;
278}
279
280bool ContentSecurityPolicy::urlMatchesSelf(const URL& url) const
281{
282 return m_selfSource->matches(url);
283}
284
285bool ContentSecurityPolicy::allowContentSecurityPolicySourceStarToMatchAnyProtocol() const
286{
287 if (is<Document>(m_scriptExecutionContext))
288 return downcast<Document>(*m_scriptExecutionContext).settings().allowContentSecurityPolicySourceStarToMatchAnyProtocol();
289 return false;
290}
291
292bool ContentSecurityPolicy::protocolMatchesSelf(const URL& url) const
293{
294 if (equalLettersIgnoringASCIICase(m_selfSourceProtocol, "http"))
295 return url.protocolIsInHTTPFamily();
296 return equalIgnoringASCIICase(url.protocol(), m_selfSourceProtocol);
297}
298
299template<typename Predicate, typename... Args>
300typename std::enable_if<!std::is_convertible<Predicate, ContentSecurityPolicy::ViolatedDirectiveCallback>::value, bool>::type ContentSecurityPolicy::allPoliciesWithDispositionAllow(Disposition disposition, Predicate&& predicate, Args&&... args) const
301{
302 bool isReportOnly = disposition == ContentSecurityPolicy::Disposition::ReportOnly;
303 for (auto& policy : m_policies) {
304 if (policy->isReportOnly() != isReportOnly)
305 continue;
306 if ((policy.get()->*predicate)(std::forward<Args>(args)...))
307 return false;
308 }
309 return true;
310}
311
312template<typename Predicate, typename... Args>
313bool ContentSecurityPolicy::allPoliciesWithDispositionAllow(Disposition disposition, ViolatedDirectiveCallback&& callback, Predicate&& predicate, Args&&... args) const
314{
315 bool isReportOnly = disposition == ContentSecurityPolicy::Disposition::ReportOnly;
316 bool isAllowed = true;
317 for (auto& policy : m_policies) {
318 if (policy->isReportOnly() != isReportOnly)
319 continue;
320 if (const ContentSecurityPolicyDirective* violatedDirective = (policy.get()->*predicate)(std::forward<Args>(args)...)) {
321 isAllowed = false;
322 callback(*violatedDirective);
323 }
324 }
325 return isAllowed;
326}
327
328template<typename Predicate, typename... Args>
329bool ContentSecurityPolicy::allPoliciesAllow(ViolatedDirectiveCallback&& callback, Predicate&& predicate, Args&&... args) const
330{
331 bool isAllowed = true;
332 for (auto& policy : m_policies) {
333 if (const ContentSecurityPolicyDirective* violatedDirective = (policy.get()->*predicate)(std::forward<Args>(args)...)) {
334 if (!violatedDirective->directiveList().isReportOnly())
335 isAllowed = false;
336 callback(*violatedDirective);
337 }
338 }
339 return isAllowed;
340}
341
342template<typename Predicate>
343ContentSecurityPolicy::HashInEnforcedAndReportOnlyPoliciesPair ContentSecurityPolicy::findHashOfContentInPolicies(Predicate&& predicate, const String& content, OptionSet<ContentSecurityPolicyHashAlgorithm> algorithms) const
344{
345 if (algorithms.isEmpty() || content.isEmpty())
346 return { false, false };
347
348 // FIXME: We should compute the document encoding once and cache it instead of computing it on each invocation.
349 TextEncoding documentEncoding;
350 if (is<Document>(m_scriptExecutionContext))
351 documentEncoding = downcast<Document>(*m_scriptExecutionContext).textEncoding();
352 const TextEncoding& encodingToUse = documentEncoding.isValid() ? documentEncoding : UTF8Encoding();
353
354 // FIXME: Compute the digest with respect to the raw bytes received from the page.
355 // See <https://bugs.webkit.org/show_bug.cgi?id=155184>.
356 auto encodedContent = encodingToUse.encode(content, UnencodableHandling::Entities);
357 bool foundHashInEnforcedPolicies = false;
358 bool foundHashInReportOnlyPolicies = false;
359 for (auto algorithm : algorithms) {
360 ContentSecurityPolicyHash hash = cryptographicDigestForBytes(algorithm, encodedContent.data(), encodedContent.size());
361 if (!foundHashInEnforcedPolicies && allPoliciesWithDispositionAllow(ContentSecurityPolicy::Disposition::Enforce, std::forward<Predicate>(predicate), hash))
362 foundHashInEnforcedPolicies = true;
363 if (!foundHashInReportOnlyPolicies && allPoliciesWithDispositionAllow(ContentSecurityPolicy::Disposition::ReportOnly, std::forward<Predicate>(predicate), hash))
364 foundHashInReportOnlyPolicies = true;
365 if (foundHashInEnforcedPolicies && foundHashInReportOnlyPolicies)
366 break;
367 }
368 return { foundHashInEnforcedPolicies, foundHashInReportOnlyPolicies };
369}
370
371bool ContentSecurityPolicy::allowJavaScriptURLs(const String& contextURL, const WTF::OrdinalNumber& contextLine, bool overrideContentSecurityPolicy) const
372{
373 if (overrideContentSecurityPolicy)
374 return true;
375 bool didNotifyInspector = false;
376 auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
377 String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::scriptSrc, violatedDirective, URL(), "Refused to execute a script", "its hash, its nonce, or 'unsafe-inline'");
378 reportViolation(ContentSecurityPolicyDirectiveNames::scriptSrc, violatedDirective, URL(), consoleMessage, contextURL, TextPosition(contextLine, WTF::OrdinalNumber()));
379 if (!didNotifyInspector && violatedDirective.directiveList().isReportOnly()) {
380 reportBlockedScriptExecutionToInspector(violatedDirective.text());
381 didNotifyInspector = true;
382 }
383 };
384 return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeInlineScript);
385}
386
387bool ContentSecurityPolicy::allowInlineEventHandlers(const String& contextURL, const WTF::OrdinalNumber& contextLine, bool overrideContentSecurityPolicy) const
388{
389 if (overrideContentSecurityPolicy)
390 return true;
391 bool didNotifyInspector = false;
392 auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
393 String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::scriptSrc, violatedDirective, URL(), "Refused to execute a script for an inline event handler", "'unsafe-inline'");
394 reportViolation(ContentSecurityPolicyDirectiveNames::scriptSrc, violatedDirective, URL(), consoleMessage, contextURL, TextPosition(contextLine, WTF::OrdinalNumber()));
395 if (!didNotifyInspector && !violatedDirective.directiveList().isReportOnly()) {
396 reportBlockedScriptExecutionToInspector(violatedDirective.text());
397 didNotifyInspector = true;
398 }
399 };
400 return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeInlineScript);
401}
402
403bool ContentSecurityPolicy::allowScriptWithNonce(const String& nonce, bool overrideContentSecurityPolicy) const
404{
405 if (overrideContentSecurityPolicy)
406 return true;
407 String strippedNonce = stripLeadingAndTrailingHTMLSpaces(nonce);
408 if (strippedNonce.isEmpty())
409 return false;
410 // FIXME: We need to report violations in report-only policies. See <https://bugs.webkit.org/show_bug.cgi?id=159830>.
411 return allPoliciesWithDispositionAllow(ContentSecurityPolicy::Disposition::Enforce, &ContentSecurityPolicyDirectiveList::violatedDirectiveForScriptNonce, strippedNonce);
412}
413
414bool ContentSecurityPolicy::allowStyleWithNonce(const String& nonce, bool overrideContentSecurityPolicy) const
415{
416 if (overrideContentSecurityPolicy)
417 return true;
418 String strippedNonce = stripLeadingAndTrailingHTMLSpaces(nonce);
419 if (strippedNonce.isEmpty())
420 return false;
421 // FIXME: We need to report violations in report-only policies. See <https://bugs.webkit.org/show_bug.cgi?id=159830>.
422 return allPoliciesWithDispositionAllow(ContentSecurityPolicy::Disposition::Enforce, &ContentSecurityPolicyDirectiveList::violatedDirectiveForStyleNonce, strippedNonce);
423}
424
425bool ContentSecurityPolicy::allowInlineScript(const String& contextURL, const WTF::OrdinalNumber& contextLine, const String& scriptContent, bool overrideContentSecurityPolicy) const
426{
427 if (overrideContentSecurityPolicy)
428 return true;
429 bool didNotifyInspector = false;
430 bool foundHashInEnforcedPolicies;
431 bool foundHashInReportOnlyPolicies;
432 std::tie(foundHashInEnforcedPolicies, foundHashInReportOnlyPolicies) = findHashOfContentInPolicies(&ContentSecurityPolicyDirectiveList::violatedDirectiveForScriptHash, scriptContent, m_hashAlgorithmsForInlineScripts);
433 if (foundHashInEnforcedPolicies && foundHashInReportOnlyPolicies)
434 return true;
435 auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
436 String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::scriptSrc, violatedDirective, URL(), "Refused to execute a script", "its hash, its nonce, or 'unsafe-inline'");
437 reportViolation(ContentSecurityPolicyDirectiveNames::scriptSrc, violatedDirective, URL(), consoleMessage, contextURL, TextPosition(contextLine, WTF::OrdinalNumber()));
438 if (!didNotifyInspector && !violatedDirective.directiveList().isReportOnly()) {
439 reportBlockedScriptExecutionToInspector(violatedDirective.text());
440 didNotifyInspector = true;
441 }
442 };
443 // FIXME: We should not report that the inline script violated a policy when its hash matched a source
444 // expression in the policy and the page has more than one policy. See <https://bugs.webkit.org/show_bug.cgi?id=159832>.
445 if (!foundHashInReportOnlyPolicies)
446 allPoliciesWithDispositionAllow(ContentSecurityPolicy::Disposition::ReportOnly, handleViolatedDirective, &ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeInlineScript);
447 return foundHashInEnforcedPolicies || allPoliciesWithDispositionAllow(ContentSecurityPolicy::Disposition::Enforce, handleViolatedDirective, &ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeInlineScript);
448}
449
450bool ContentSecurityPolicy::allowInlineStyle(const String& contextURL, const WTF::OrdinalNumber& contextLine, const String& styleContent, bool overrideContentSecurityPolicy) const
451{
452 if (overrideContentSecurityPolicy)
453 return true;
454 if (m_overrideInlineStyleAllowed)
455 return true;
456 bool foundHashInEnforcedPolicies;
457 bool foundHashInReportOnlyPolicies;
458 std::tie(foundHashInEnforcedPolicies, foundHashInReportOnlyPolicies) = findHashOfContentInPolicies(&ContentSecurityPolicyDirectiveList::violatedDirectiveForStyleHash, styleContent, m_hashAlgorithmsForInlineStylesheets);
459 if (foundHashInEnforcedPolicies && foundHashInReportOnlyPolicies)
460 return true;
461 auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
462 String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::styleSrc, violatedDirective, URL(), "Refused to apply a stylesheet", "its hash, its nonce, or 'unsafe-inline'");
463 reportViolation(ContentSecurityPolicyDirectiveNames::styleSrc, violatedDirective, URL(), consoleMessage, contextURL, TextPosition(contextLine, WTF::OrdinalNumber()));
464 };
465 // FIXME: We should not report that the inline stylesheet violated a policy when its hash matched a source
466 // expression in the policy and the page has more than one policy. See <https://bugs.webkit.org/show_bug.cgi?id=159832>.
467 if (!foundHashInReportOnlyPolicies)
468 allPoliciesWithDispositionAllow(ContentSecurityPolicy::Disposition::ReportOnly, handleViolatedDirective, &ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeInlineStyle);
469 return foundHashInEnforcedPolicies || allPoliciesWithDispositionAllow(ContentSecurityPolicy::Disposition::Enforce, handleViolatedDirective, &ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeInlineStyle);
470}
471
472bool ContentSecurityPolicy::allowEval(JSC::ExecState* state, bool overrideContentSecurityPolicy) const
473{
474 if (overrideContentSecurityPolicy)
475 return true;
476 bool didNotifyInspector = false;
477 auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
478 String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::scriptSrc, violatedDirective, URL(), "Refused to execute a script", "'unsafe-eval'");
479 reportViolation(ContentSecurityPolicyDirectiveNames::scriptSrc, violatedDirective, URL(), consoleMessage, state);
480 if (!didNotifyInspector && !violatedDirective.directiveList().isReportOnly()) {
481 reportBlockedScriptExecutionToInspector(violatedDirective.text());
482 didNotifyInspector = true;
483 }
484 };
485 return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeEval);
486}
487
488bool ContentSecurityPolicy::allowFrameAncestors(const Frame& frame, const URL& url, bool overrideContentSecurityPolicy) const
489{
490 if (overrideContentSecurityPolicy)
491 return true;
492 Frame& topFrame = frame.tree().top();
493 if (&frame == &topFrame)
494 return true;
495 String sourceURL;
496 TextPosition sourcePosition(WTF::OrdinalNumber::beforeFirst(), WTF::OrdinalNumber());
497 auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
498 String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::frameAncestors, violatedDirective, url, "Refused to load");
499 reportViolation(ContentSecurityPolicyDirectiveNames::frameAncestors, violatedDirective, url, consoleMessage, sourceURL, sourcePosition);
500 };
501 return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForFrameAncestor, frame);
502}
503
504bool ContentSecurityPolicy::overridesXFrameOptions() const
505{
506 // If a resource is delivered with an policy that includes a directive named frame-ancestors and whose disposition
507 // is "enforce", then the X-Frame-Options header MUST be ignored.
508 // https://www.w3.org/TR/CSP3/#frame-ancestors-and-frame-options
509 for (auto& policy : m_policies) {
510 if (!policy->isReportOnly() && policy->hasFrameAncestorsDirective())
511 return true;
512 }
513 return false;
514}
515
516bool ContentSecurityPolicy::allowFrameAncestors(const Vector<RefPtr<SecurityOrigin>>& ancestorOrigins, const URL& url, bool overrideContentSecurityPolicy) const
517{
518 if (overrideContentSecurityPolicy)
519 return true;
520 bool isTopLevelFrame = ancestorOrigins.isEmpty();
521 if (isTopLevelFrame)
522 return true;
523 String sourceURL;
524 TextPosition sourcePosition(WTF::OrdinalNumber::beforeFirst(), WTF::OrdinalNumber());
525 auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
526 String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::frameAncestors, violatedDirective, url, "Refused to load");
527 reportViolation(ContentSecurityPolicyDirectiveNames::frameAncestors, violatedDirective, url, consoleMessage, sourceURL, sourcePosition);
528 };
529 return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForFrameAncestorOrigins, ancestorOrigins);
530}
531
532bool ContentSecurityPolicy::allowPluginType(const String& type, const String& typeAttribute, const URL& url, bool overrideContentSecurityPolicy) const
533{
534 if (overrideContentSecurityPolicy)
535 return true;
536 String sourceURL;
537 TextPosition sourcePosition(WTF::OrdinalNumber::beforeFirst(), WTF::OrdinalNumber());
538 auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
539 String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::pluginTypes, violatedDirective, url, "Refused to load", "its MIME type");
540 reportViolation(ContentSecurityPolicyDirectiveNames::pluginTypes, violatedDirective, url, consoleMessage, sourceURL, sourcePosition);
541 };
542 return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForPluginType, type, typeAttribute);
543}
544
545bool ContentSecurityPolicy::allowObjectFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const
546{
547 if (SchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol().toStringWithoutCopying()))
548 return true;
549 // As per section object-src of the Content Security Policy Level 3 spec., <http://w3c.github.io/webappsec-csp> (Editor's Draft, 29 February 2016),
550 // "If plugin content is loaded without an associated URL (perhaps an object element lacks a data attribute, but loads some default plugin based
551 // on the specified type), it MUST be blocked if object-src's value is 'none', but will otherwise be allowed".
552 String sourceURL;
553 TextPosition sourcePosition(WTF::OrdinalNumber::beforeFirst(), WTF::OrdinalNumber());
554 auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
555 String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::objectSrc, violatedDirective, url, "Refused to load");
556 reportViolation(ContentSecurityPolicyDirectiveNames::objectSrc, violatedDirective, url, consoleMessage, sourceURL, sourcePosition);
557 };
558 return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForObjectSource, url, redirectResponseReceived == RedirectResponseReceived::Yes, ContentSecurityPolicySourceListDirective::ShouldAllowEmptyURLIfSourceListIsNotNone::Yes);
559}
560
561bool ContentSecurityPolicy::allowChildFrameFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const
562{
563 if (SchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol().toStringWithoutCopying()))
564 return true;
565 String sourceURL;
566 TextPosition sourcePosition(WTF::OrdinalNumber::beforeFirst(), WTF::OrdinalNumber());
567 auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
568 const char* effectiveViolatedDirective = violatedDirective.name() == ContentSecurityPolicyDirectiveNames::frameSrc ? ContentSecurityPolicyDirectiveNames::frameSrc : ContentSecurityPolicyDirectiveNames::childSrc;
569 String consoleMessage = consoleMessageForViolation(effectiveViolatedDirective, violatedDirective, url, "Refused to load");
570 reportViolation(effectiveViolatedDirective, violatedDirective, url, consoleMessage, sourceURL, sourcePosition);
571 };
572 return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForFrame, url, redirectResponseReceived == RedirectResponseReceived::Yes);
573}
574
575bool ContentSecurityPolicy::allowResourceFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived, const char* name, ResourcePredicate resourcePredicate) const
576{
577 if (SchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol().toStringWithoutCopying()))
578 return true;
579 String sourceURL;
580 TextPosition sourcePosition(WTF::OrdinalNumber::beforeFirst(), WTF::OrdinalNumber());
581 auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
582 String consoleMessage = consoleMessageForViolation(name, violatedDirective, url, "Refused to load");
583 reportViolation(name, violatedDirective, url, consoleMessage, sourceURL, sourcePosition);
584 };
585 return allPoliciesAllow(WTFMove(handleViolatedDirective), resourcePredicate, url, redirectResponseReceived == RedirectResponseReceived::Yes);
586}
587
588bool ContentSecurityPolicy::allowChildContextFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const
589{
590 return allowResourceFromSource(url, redirectResponseReceived, ContentSecurityPolicyDirectiveNames::childSrc, &ContentSecurityPolicyDirectiveList::violatedDirectiveForChildContext);
591}
592
593bool ContentSecurityPolicy::allowScriptFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const
594{
595 return allowResourceFromSource(url, redirectResponseReceived, ContentSecurityPolicyDirectiveNames::scriptSrc, &ContentSecurityPolicyDirectiveList::violatedDirectiveForScript);
596}
597
598bool ContentSecurityPolicy::allowImageFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const
599{
600 return allowResourceFromSource(url, redirectResponseReceived, ContentSecurityPolicyDirectiveNames::imgSrc, &ContentSecurityPolicyDirectiveList::violatedDirectiveForImage);
601}
602
603bool ContentSecurityPolicy::allowStyleFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const
604{
605 return allowResourceFromSource(url, redirectResponseReceived, ContentSecurityPolicyDirectiveNames::styleSrc, &ContentSecurityPolicyDirectiveList::violatedDirectiveForStyle);
606}
607
608bool ContentSecurityPolicy::allowFontFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const
609{
610 return allowResourceFromSource(url, redirectResponseReceived, ContentSecurityPolicyDirectiveNames::fontSrc, &ContentSecurityPolicyDirectiveList::violatedDirectiveForFont);
611}
612
613#if ENABLE(APPLICATION_MANIFEST)
614bool ContentSecurityPolicy::allowManifestFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const
615{
616 return allowResourceFromSource(url, redirectResponseReceived, ContentSecurityPolicyDirectiveNames::manifestSrc, &ContentSecurityPolicyDirectiveList::violatedDirectiveForManifest);
617}
618#endif // ENABLE(APPLICATION_MANIFEST)
619
620bool ContentSecurityPolicy::allowMediaFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const
621{
622 return allowResourceFromSource(url, redirectResponseReceived, ContentSecurityPolicyDirectiveNames::mediaSrc, &ContentSecurityPolicyDirectiveList::violatedDirectiveForMedia);
623}
624
625bool ContentSecurityPolicy::allowConnectToSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const
626{
627 if (SchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol().toStringWithoutCopying()))
628 return true;
629 String sourceURL;
630 TextPosition sourcePosition(WTF::OrdinalNumber::beforeFirst(), WTF::OrdinalNumber());
631 auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
632 String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::connectSrc, violatedDirective, url, "Refused to connect to");
633 reportViolation(ContentSecurityPolicyDirectiveNames::connectSrc, violatedDirective, url, consoleMessage, sourceURL, sourcePosition);
634 };
635 return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForConnectSource, url, redirectResponseReceived == RedirectResponseReceived::Yes);
636}
637
638bool ContentSecurityPolicy::allowFormAction(const URL& url, RedirectResponseReceived redirectResponseReceived) const
639{
640 return allowResourceFromSource(url, redirectResponseReceived, ContentSecurityPolicyDirectiveNames::formAction, &ContentSecurityPolicyDirectiveList::violatedDirectiveForFormAction);
641}
642
643bool ContentSecurityPolicy::allowBaseURI(const URL& url, bool overrideContentSecurityPolicy) const
644{
645 if (overrideContentSecurityPolicy)
646 return true;
647 if (SchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol().toStringWithoutCopying()))
648 return true;
649 String sourceURL;
650 TextPosition sourcePosition(WTF::OrdinalNumber::beforeFirst(), WTF::OrdinalNumber());
651 auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
652 String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::baseURI, violatedDirective, url, "Refused to change the document base URL to");
653 reportViolation(ContentSecurityPolicyDirectiveNames::baseURI, violatedDirective, url, consoleMessage, sourceURL, sourcePosition);
654 };
655 return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForBaseURI, url);
656}
657
658String ContentSecurityPolicy::deprecatedURLForReporting(const URL& url) const
659{
660 if (!url.isValid())
661 return { };
662 if (!url.isHierarchical() || url.protocolIs("file"))
663 return url.protocol().toString();
664 return static_cast<SecurityOriginData>(*m_selfSource).securityOrigin()->canRequest(url) ? url.strippedForUseAsReferrer() : SecurityOrigin::create(url)->toString();
665}
666
667void ContentSecurityPolicy::reportViolation(const String& violatedDirective, const ContentSecurityPolicyDirective& effectiveViolatedDirective, const URL& blockedURL, const String& consoleMessage, JSC::ExecState* state) const
668{
669 // FIXME: Extract source file and source position from JSC::ExecState.
670 return reportViolation(violatedDirective, effectiveViolatedDirective.text(), effectiveViolatedDirective.directiveList(), blockedURL, consoleMessage, String(), TextPosition(WTF::OrdinalNumber::beforeFirst(), WTF::OrdinalNumber::beforeFirst()), state);
671}
672
673void ContentSecurityPolicy::reportViolation(const String& effectiveViolatedDirective, const String& violatedDirective, const ContentSecurityPolicyDirectiveList& violatedDirectiveList, const URL& blockedURL, const String& consoleMessage, JSC::ExecState* state) const
674{
675 // FIXME: Extract source file and source position from JSC::ExecState.
676 return reportViolation(effectiveViolatedDirective, violatedDirective, violatedDirectiveList, blockedURL, consoleMessage, String(), TextPosition(WTF::OrdinalNumber::beforeFirst(), WTF::OrdinalNumber::beforeFirst()), state);
677}
678
679void ContentSecurityPolicy::reportViolation(const String& effectiveViolatedDirective, const ContentSecurityPolicyDirective& violatedDirective, const URL& blockedURL, const String& consoleMessage, const String& sourceURL, const TextPosition& sourcePosition, JSC::ExecState* state) const
680{
681 return reportViolation(effectiveViolatedDirective, violatedDirective.text(), violatedDirective.directiveList(), blockedURL, consoleMessage, sourceURL, sourcePosition, state);
682}
683
684void ContentSecurityPolicy::reportViolation(const String& effectiveViolatedDirective, const String& violatedDirective, const ContentSecurityPolicyDirectiveList& violatedDirectiveList, const URL& blockedURL, const String& consoleMessage, const String& sourceURL, const TextPosition& sourcePosition, JSC::ExecState* state) const
685{
686 logToConsole(consoleMessage, sourceURL, sourcePosition.m_line, sourcePosition.m_column, state);
687
688 if (!m_isReportingEnabled)
689 return;
690
691 // FIXME: Support sending reports from worker.
692 CSPInfo info;
693 info.documentURI = blockedURL;
694 if (m_client)
695 m_client->willSendCSPViolationReport(info);
696 else {
697 if (!is<Document>(m_scriptExecutionContext))
698 return;
699
700 auto& document = downcast<Document>(*m_scriptExecutionContext);
701 auto* frame = document.frame();
702 if (!frame)
703 return;
704
705 info.documentURI = document.url().strippedForUseAsReferrer();
706
707 auto stack = createScriptCallStack(JSExecState::currentState(), 2);
708 auto* callFrame = stack->firstNonNativeCallFrame();
709 if (callFrame && callFrame->lineNumber()) {
710 info.sourceFile = deprecatedURLForReporting(URL { URL { }, callFrame->sourceURL() });
711 info.lineNumber = callFrame->lineNumber();
712 info.columnNumber = callFrame->columnNumber();
713 }
714 }
715 ASSERT(m_client || is<Document>(m_scriptExecutionContext));
716
717 String blockedURI = deprecatedURLForReporting(blockedURL);
718 // FIXME: Is it policy to not use the status code for HTTPS, or is that a bug?
719 unsigned short httpStatusCode = m_selfSourceProtocol == "http" ? m_httpStatusCode : 0;
720
721 // 1. Dispatch violation event.
722 SecurityPolicyViolationEvent::Init violationEventInit;
723 violationEventInit.documentURI = info.documentURI;
724 violationEventInit.referrer = m_referrer;
725 violationEventInit.blockedURI = blockedURI;
726 violationEventInit.violatedDirective = violatedDirective;
727 violationEventInit.effectiveDirective = effectiveViolatedDirective;
728 violationEventInit.originalPolicy = violatedDirectiveList.header();
729 violationEventInit.sourceFile = info.sourceFile;
730 violationEventInit.statusCode = httpStatusCode;
731 violationEventInit.lineNumber = info.lineNumber;
732 violationEventInit.columnNumber = info.columnNumber;
733 if (m_client)
734 m_client->enqueueSecurityPolicyViolationEvent(WTFMove(violationEventInit));
735 else
736 downcast<Document>(*m_scriptExecutionContext).enqueueSecurityPolicyViolationEvent(WTFMove(violationEventInit));
737
738 // 2. Send violation report (if applicable).
739 auto& reportURIs = violatedDirectiveList.reportURIs();
740 if (reportURIs.isEmpty())
741 return;
742
743 // We need to be careful here when deciding what information to send to the
744 // report-uri. Currently, we send only the current document's URL and the
745 // directive that was violated. The document's URL is safe to send because
746 // it's the document itself that's requesting that it be sent. You could
747 // make an argument that we shouldn't send HTTPS document URLs to HTTP
748 // report-uris (for the same reasons that we suppress the Referer in that
749 // case), but the Referer is sent implicitly whereas this request is only
750 // sent explicitly. As for which directive was violated, that's pretty
751 // harmless information.
752
753 auto cspReport = JSON::Object::create();
754 cspReport->setString("document-uri"_s, info.documentURI);
755 cspReport->setString("referrer"_s, m_referrer);
756 cspReport->setString("violated-directive"_s, violatedDirective);
757 cspReport->setString("effective-directive"_s, effectiveViolatedDirective);
758 cspReport->setString("original-policy"_s, violatedDirectiveList.header());
759 cspReport->setString("blocked-uri"_s, blockedURI);
760 cspReport->setInteger("status-code"_s, httpStatusCode);
761 if (!info.sourceFile.isNull()) {
762 cspReport->setString("source-file"_s, info.sourceFile);
763 cspReport->setInteger("line-number"_s, info.lineNumber);
764 cspReport->setInteger("column-number"_s, info.columnNumber);
765 }
766
767 auto reportObject = JSON::Object::create();
768 reportObject->setObject("csp-report"_s, WTFMove(cspReport));
769
770 auto report = FormData::create(reportObject->toJSONString().utf8());
771
772 if (m_client) {
773 for (const auto& url : reportURIs)
774 m_client->sendCSPViolationReport(URL { m_protectedURL, url }, report.copyRef());
775 } else {
776 auto& document = downcast<Document>(*m_scriptExecutionContext);
777 for (const auto& url : reportURIs)
778 PingLoader::sendViolationReport(*document.frame(), URL { m_protectedURL, url }, report.copyRef(), ViolationReportType::ContentSecurityPolicy);
779 }
780}
781
782void ContentSecurityPolicy::reportUnsupportedDirective(const String& name) const
783{
784 String message;
785 if (equalLettersIgnoringASCIICase(name, "allow"))
786 message = "The 'allow' directive has been replaced with 'default-src'. Please use that directive instead, as 'allow' has no effect."_s;
787 else if (equalLettersIgnoringASCIICase(name, "options"))
788 message = "The 'options' directive has been replaced with 'unsafe-inline' and 'unsafe-eval' source expressions for the 'script-src' and 'style-src' directives. Please use those directives instead, as 'options' has no effect."_s;
789 else if (equalLettersIgnoringASCIICase(name, "policy-uri"))
790 message = "The 'policy-uri' directive has been removed from the specification. Please specify a complete policy via the Content-Security-Policy header."_s;
791 else
792 message = makeString("Unrecognized Content-Security-Policy directive '", name, "'.\n"); // FIXME: Why does this include a newline?
793
794 logToConsole(message);
795}
796
797void ContentSecurityPolicy::reportDirectiveAsSourceExpression(const String& directiveName, const String& sourceExpression) const
798{
799 logToConsole("The Content Security Policy directive '" + directiveName + "' contains '" + sourceExpression + "' as a source expression. Did you mean '" + directiveName + " ...; " + sourceExpression + "...' (note the semicolon)?");
800}
801
802void ContentSecurityPolicy::reportDuplicateDirective(const String& name) const
803{
804 logToConsole(makeString("Ignoring duplicate Content-Security-Policy directive '", name, "'.\n"));
805}
806
807void ContentSecurityPolicy::reportInvalidPluginTypes(const String& pluginType) const
808{
809 String message;
810 if (pluginType.isNull())
811 message = "'plugin-types' Content Security Policy directive is empty; all plugins will be blocked.\n";
812 else
813 message = makeString("Invalid plugin type in 'plugin-types' Content Security Policy directive: '", pluginType, "'.\n");
814 logToConsole(message);
815}
816
817void ContentSecurityPolicy::reportInvalidSandboxFlags(const String& invalidFlags) const
818{
819 logToConsole("Error while parsing the 'sandbox' Content Security Policy directive: " + invalidFlags);
820}
821
822void ContentSecurityPolicy::reportInvalidDirectiveInReportOnlyMode(const String& directiveName) const
823{
824 logToConsole("The Content Security Policy directive '" + directiveName + "' is ignored when delivered in a report-only policy.");
825}
826
827void ContentSecurityPolicy::reportInvalidDirectiveInHTTPEquivMeta(const String& directiveName) const
828{
829 logToConsole("The Content Security Policy directive '" + directiveName + "' is ignored when delivered via an HTML meta element.");
830}
831
832void ContentSecurityPolicy::reportInvalidDirectiveValueCharacter(const String& directiveName, const String& value) const
833{
834 String message = makeString("The value for Content Security Policy directive '", directiveName, "' contains an invalid character: '", value, "'. Non-whitespace characters outside ASCII 0x21-0x7E must be percent-encoded, as described in RFC 3986, section 2.1: http://tools.ietf.org/html/rfc3986#section-2.1.");
835 logToConsole(message);
836}
837
838void ContentSecurityPolicy::reportInvalidPathCharacter(const String& directiveName, const String& value, const char invalidChar) const
839{
840 ASSERT(invalidChar == '#' || invalidChar == '?');
841
842 String ignoring;
843 if (invalidChar == '?')
844 ignoring = "The query component, including the '?', will be ignored.";
845 else
846 ignoring = "The fragment identifier, including the '#', will be ignored.";
847
848 String message = makeString("The source list for Content Security Policy directive '", directiveName, "' contains a source with an invalid path: '", value, "'. ", ignoring);
849 logToConsole(message);
850}
851
852void ContentSecurityPolicy::reportInvalidSourceExpression(const String& directiveName, const String& source) const
853{
854 String message = makeString("The source list for Content Security Policy directive '", directiveName, "' contains an invalid source: '", source, "'. It will be ignored.");
855 if (equalLettersIgnoringASCIICase(source, "'none'"))
856 message = makeString(message, " Note that 'none' has no effect unless it is the only expression in the source list.");
857 logToConsole(message);
858}
859
860void ContentSecurityPolicy::reportMissingReportURI(const String& policy) const
861{
862 logToConsole("The Content Security Policy '" + policy + "' was delivered in report-only mode, but does not specify a 'report-uri'; the policy will have no effect. Please either add a 'report-uri' directive, or deliver the policy via the 'Content-Security-Policy' header.");
863}
864
865void ContentSecurityPolicy::logToConsole(const String& message, const String& contextURL, const WTF::OrdinalNumber& contextLine, const WTF::OrdinalNumber& contextColumn, JSC::ExecState* state) const
866{
867 if (!m_isReportingEnabled)
868 return;
869
870 if (m_client)
871 m_client->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message, 0);
872 else if (m_scriptExecutionContext)
873 m_scriptExecutionContext->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message, contextURL, contextLine.oneBasedInt(), contextColumn.oneBasedInt(), state);
874}
875
876void ContentSecurityPolicy::reportBlockedScriptExecutionToInspector(const String& directiveText) const
877{
878 if (m_scriptExecutionContext)
879 InspectorInstrumentation::scriptExecutionBlockedByCSP(m_scriptExecutionContext, directiveText);
880}
881
882void ContentSecurityPolicy::upgradeInsecureRequestIfNeeded(ResourceRequest& request, InsecureRequestType requestType) const
883{
884 URL url = request.url();
885 upgradeInsecureRequestIfNeeded(url, requestType);
886 request.setURL(url);
887}
888
889void ContentSecurityPolicy::upgradeInsecureRequestIfNeeded(URL& url, InsecureRequestType requestType) const
890{
891 if (!url.protocolIs("http") && !url.protocolIs("ws"))
892 return;
893
894 bool upgradeRequest = m_insecureNavigationRequestsToUpgrade.contains(SecurityOriginData::fromURL(url));
895 if (requestType == InsecureRequestType::Load || requestType == InsecureRequestType::FormSubmission)
896 upgradeRequest |= m_upgradeInsecureRequests;
897
898 if (!upgradeRequest)
899 return;
900
901 if (url.protocolIs("http"))
902 url.setProtocol("https");
903 else {
904 ASSERT(url.protocolIs("ws"));
905 url.setProtocol("wss");
906 }
907
908 if (url.port() && url.port().value() == 80)
909 url.setPort(443);
910}
911
912void ContentSecurityPolicy::setUpgradeInsecureRequests(bool upgradeInsecureRequests)
913{
914 m_upgradeInsecureRequests = upgradeInsecureRequests;
915 if (!m_upgradeInsecureRequests)
916 return;
917
918 if (!m_scriptExecutionContext)
919 return;
920
921 // Store the upgrade domain as an 'insecure' protocol so we can quickly identify
922 // origins we should upgrade.
923 URL upgradeURL = m_scriptExecutionContext->url();
924 if (upgradeURL.protocolIs("https"))
925 upgradeURL.setProtocol("http");
926 else if (upgradeURL.protocolIs("wss"))
927 upgradeURL.setProtocol("ws");
928
929 m_insecureNavigationRequestsToUpgrade.add(SecurityOriginData::fromURL(upgradeURL));
930}
931
932void ContentSecurityPolicy::inheritInsecureNavigationRequestsToUpgradeFromOpener(const ContentSecurityPolicy& other)
933{
934 m_insecureNavigationRequestsToUpgrade.add(other.m_insecureNavigationRequestsToUpgrade.begin(), other.m_insecureNavigationRequestsToUpgrade.end());
935}
936
937HashSet<SecurityOriginData> ContentSecurityPolicy::takeNavigationRequestsToUpgrade()
938{
939 return WTFMove(m_insecureNavigationRequestsToUpgrade);
940}
941
942void ContentSecurityPolicy::setInsecureNavigationRequestsToUpgrade(HashSet<SecurityOriginData>&& insecureNavigationRequests)
943{
944 m_insecureNavigationRequestsToUpgrade = WTFMove(insecureNavigationRequests);
945}
946
947}
948