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