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