Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / core / loader / MixedContentChecker.cpp
blob5c26f56b6ecc54aa1b70739642c2ebc2ed64cfcd
1 /*
2 * Copyright (C) 2012 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * 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 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 #include "config.h"
30 #include "core/loader/MixedContentChecker.h"
32 #include "core/dom/Document.h"
33 #include "core/frame/LocalFrame.h"
34 #include "core/frame/Settings.h"
35 #include "core/frame/UseCounter.h"
36 #include "core/inspector/ConsoleMessage.h"
37 #include "core/loader/DocumentLoader.h"
38 #include "core/loader/FrameLoader.h"
39 #include "core/loader/FrameLoaderClient.h"
40 #include "platform/RuntimeEnabledFeatures.h"
41 #include "platform/weborigin/SchemeRegistry.h"
42 #include "platform/weborigin/SecurityOrigin.h"
43 #include "public/platform/Platform.h"
44 #include "wtf/text/StringBuilder.h"
46 namespace blink {
48 static void measureStricterVersionOfIsMixedContent(LocalFrame* frame, const KURL& url)
50 // We're currently only checking for mixed content in `https://*` contexts.
51 // What about other "secure" contexts the SchemeRegistry knows about? We'll
52 // use this method to measure the occurance of non-webby mixed content to
53 // make sure we're not breaking the world without realizing it.
54 SecurityOrigin* origin = frame->document()->securityOrigin();
55 if (MixedContentChecker::isMixedContent(origin, url)) {
56 if (frame->document()->securityOrigin()->protocol() != "https")
57 UseCounter::count(frame, UseCounter::MixedContentInNonHTTPSFrameThatRestrictsMixedContent);
58 } else if (!SecurityOrigin::isSecure(url) && SchemeRegistry::shouldTreatURLSchemeAsSecure(origin->protocol())) {
59 UseCounter::count(frame, UseCounter::MixedContentInSecureFrameThatDoesNotRestrictMixedContent);
63 // static
64 bool MixedContentChecker::isMixedContent(SecurityOrigin* securityOrigin, const KURL& url)
66 if (!SchemeRegistry::shouldTreatURLSchemeAsRestrictingMixedContent(securityOrigin->protocol()))
67 return false;
69 // We're in a secure context, so |url| is mixed content if it's insecure.
70 return !SecurityOrigin::isSecure(url);
73 // static
74 LocalFrame* MixedContentChecker::inWhichFrameIsContentMixed(LocalFrame* frame, WebURLRequest::FrameType frameType, const KURL& url)
76 // We only care about subresource loads; top-level navigations cannot be mixed content. Neither can frameless requests.
77 if (frameType == WebURLRequest::FrameTypeTopLevel || !frame)
78 return nullptr;
80 // Check the top frame first.
81 if (Frame* top = frame->tree().top()) {
82 // FIXME: We need a way to access the top-level frame's SecurityOrigin when that frame
83 // is in a different process from the current frame. Until that is done, we bail out.
84 if (!top->isLocalFrame())
85 return nullptr;
87 LocalFrame* localTop = toLocalFrame(top);
88 measureStricterVersionOfIsMixedContent(localTop, url);
89 if (isMixedContent(localTop->document()->securityOrigin(), url))
90 return localTop;
93 measureStricterVersionOfIsMixedContent(frame, url);
94 if (isMixedContent(frame->document()->securityOrigin(), url))
95 return frame;
97 // No mixed content, no problem.
98 return nullptr;
101 // static
102 MixedContentChecker::ContextType MixedContentChecker::contextTypeFromContext(WebURLRequest::RequestContext context, LocalFrame* frame)
104 switch (context) {
105 // "Optionally-blockable" mixed content
106 case WebURLRequest::RequestContextAudio:
107 case WebURLRequest::RequestContextFavicon:
108 case WebURLRequest::RequestContextImage:
109 case WebURLRequest::RequestContextVideo:
110 return ContextTypeOptionallyBlockable;
112 // Plugins! Oh how dearly we love plugin-loaded content!
113 case WebURLRequest::RequestContextPlugin: {
114 Settings* settings = frame->settings();
115 return settings && settings->strictMixedContentCheckingForPlugin() ? ContextTypeBlockable : ContextTypeOptionallyBlockable;
118 // "Blockable" mixed content
119 case WebURLRequest::RequestContextBeacon:
120 case WebURLRequest::RequestContextCSPReport:
121 case WebURLRequest::RequestContextEmbed:
122 case WebURLRequest::RequestContextEventSource:
123 case WebURLRequest::RequestContextFetch:
124 case WebURLRequest::RequestContextFont:
125 case WebURLRequest::RequestContextForm:
126 case WebURLRequest::RequestContextFrame:
127 case WebURLRequest::RequestContextHyperlink:
128 case WebURLRequest::RequestContextIframe:
129 case WebURLRequest::RequestContextImageSet:
130 case WebURLRequest::RequestContextImport:
131 case WebURLRequest::RequestContextLocation:
132 case WebURLRequest::RequestContextManifest:
133 case WebURLRequest::RequestContextObject:
134 case WebURLRequest::RequestContextPing:
135 case WebURLRequest::RequestContextScript:
136 case WebURLRequest::RequestContextServiceWorker:
137 case WebURLRequest::RequestContextSharedWorker:
138 case WebURLRequest::RequestContextStyle:
139 case WebURLRequest::RequestContextSubresource:
140 case WebURLRequest::RequestContextTrack:
141 case WebURLRequest::RequestContextWorker:
142 case WebURLRequest::RequestContextXMLHttpRequest:
143 case WebURLRequest::RequestContextXSLT:
144 return ContextTypeBlockable;
146 // FIXME: Contexts that we should block, but don't currently. https://crbug.com/388650
147 case WebURLRequest::RequestContextDownload:
148 case WebURLRequest::RequestContextInternal:
149 case WebURLRequest::RequestContextPrefetch:
150 return ContextTypeShouldBeBlockable;
152 case WebURLRequest::RequestContextUnspecified:
153 ASSERT_NOT_REACHED();
155 ASSERT_NOT_REACHED();
156 return ContextTypeBlockable;
159 // static
160 const char* MixedContentChecker::typeNameFromContext(WebURLRequest::RequestContext context)
162 switch (context) {
163 case WebURLRequest::RequestContextAudio:
164 return "audio file";
165 case WebURLRequest::RequestContextBeacon:
166 return "Beacon endpoint";
167 case WebURLRequest::RequestContextCSPReport:
168 return "Content Security Policy reporting endpoint";
169 case WebURLRequest::RequestContextDownload:
170 return "download";
171 case WebURLRequest::RequestContextEmbed:
172 return "plugin resource";
173 case WebURLRequest::RequestContextEventSource:
174 return "EventSource endpoint";
175 case WebURLRequest::RequestContextFavicon:
176 return "favicon";
177 case WebURLRequest::RequestContextFetch:
178 return "resource";
179 case WebURLRequest::RequestContextFont:
180 return "font";
181 case WebURLRequest::RequestContextForm:
182 return "form action";
183 case WebURLRequest::RequestContextFrame:
184 return "frame";
185 case WebURLRequest::RequestContextHyperlink:
186 return "resource";
187 case WebURLRequest::RequestContextIframe:
188 return "frame";
189 case WebURLRequest::RequestContextImage:
190 return "image";
191 case WebURLRequest::RequestContextImageSet:
192 return "image";
193 case WebURLRequest::RequestContextImport:
194 return "HTML Import";
195 case WebURLRequest::RequestContextInternal:
196 return "resource";
197 case WebURLRequest::RequestContextLocation:
198 return "resource";
199 case WebURLRequest::RequestContextManifest:
200 return "manifest";
201 case WebURLRequest::RequestContextObject:
202 return "plugin resource";
203 case WebURLRequest::RequestContextPing:
204 return "hyperlink auditing endpoint";
205 case WebURLRequest::RequestContextPlugin:
206 return "plugin data";
207 case WebURLRequest::RequestContextPrefetch:
208 return "prefetch resource";
209 case WebURLRequest::RequestContextScript:
210 return "script";
211 case WebURLRequest::RequestContextServiceWorker:
212 return "Service Worker script";
213 case WebURLRequest::RequestContextSharedWorker:
214 return "Shared Worker script";
215 case WebURLRequest::RequestContextStyle:
216 return "stylesheet";
217 case WebURLRequest::RequestContextSubresource:
218 return "resource";
219 case WebURLRequest::RequestContextTrack:
220 return "Text Track";
221 case WebURLRequest::RequestContextUnspecified:
222 return "resource";
223 case WebURLRequest::RequestContextVideo:
224 return "video";
225 case WebURLRequest::RequestContextWorker:
226 return "Worker script";
227 case WebURLRequest::RequestContextXMLHttpRequest:
228 return "XMLHttpRequest endpoint";
229 case WebURLRequest::RequestContextXSLT:
230 return "XSLT";
232 ASSERT_NOT_REACHED();
233 return "resource";
236 // static
237 void MixedContentChecker::logToConsoleAboutFetch(LocalFrame* frame, const KURL& url, WebURLRequest::RequestContext requestContext, bool allowed)
239 String message = String::format(
240 "Mixed Content: The page at '%s' was loaded over HTTPS, but requested an insecure %s '%s'. %s",
241 frame->document()->url().elidedString().utf8().data(), typeNameFromContext(requestContext), url.elidedString().utf8().data(),
242 allowed ? "This content should also be served over HTTPS." : "This request has been blocked; the content must be served over HTTPS.");
243 MessageLevel messageLevel = allowed ? WarningMessageLevel : ErrorMessageLevel;
244 frame->document()->addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, messageLevel, message));
247 // static
248 void MixedContentChecker::count(LocalFrame* frame, WebURLRequest::RequestContext requestContext)
250 UseCounter::count(frame, UseCounter::MixedContentPresent);
252 // Roll blockable content up into a single counter, count unblocked types individually so we
253 // can determine when they can be safely moved to the blockable category:
254 ContextType contextType = contextTypeFromContext(requestContext, frame);
255 if (contextType == ContextTypeBlockable) {
256 UseCounter::count(frame, UseCounter::MixedContentBlockable);
257 return;
260 UseCounter::Feature feature;
261 switch (requestContext) {
262 case WebURLRequest::RequestContextAudio:
263 feature = UseCounter::MixedContentAudio;
264 break;
265 case WebURLRequest::RequestContextDownload:
266 feature = UseCounter::MixedContentDownload;
267 break;
268 case WebURLRequest::RequestContextFavicon:
269 feature = UseCounter::MixedContentFavicon;
270 break;
271 case WebURLRequest::RequestContextImage:
272 feature = UseCounter::MixedContentImage;
273 break;
274 case WebURLRequest::RequestContextInternal:
275 feature = UseCounter::MixedContentInternal;
276 break;
277 case WebURLRequest::RequestContextPlugin:
278 feature = UseCounter::MixedContentPlugin;
279 break;
280 case WebURLRequest::RequestContextPrefetch:
281 feature = UseCounter::MixedContentPrefetch;
282 break;
283 case WebURLRequest::RequestContextVideo:
284 feature = UseCounter::MixedContentVideo;
285 break;
287 default:
288 ASSERT_NOT_REACHED();
289 return;
291 UseCounter::count(frame, feature);
294 // static
295 bool MixedContentChecker::shouldBlockFetch(LocalFrame* frame, WebURLRequest::RequestContext requestContext, WebURLRequest::FrameType frameType, const KURL& url, MixedContentChecker::ReportingStatus reportingStatus)
297 LocalFrame* mixedFrame = inWhichFrameIsContentMixed(frame, frameType, url);
298 if (!mixedFrame)
299 return false;
301 MixedContentChecker::count(mixedFrame, requestContext);
303 Settings* settings = mixedFrame->settings();
304 FrameLoaderClient* client = mixedFrame->loader().client();
305 SecurityOrigin* securityOrigin = mixedFrame->document()->securityOrigin();
306 bool allowed = false;
308 // If we're in strict mode, we'll automagically fail everything, and intentionally skip
309 // the client checks in order to prevent degrading the site's security UI.
310 bool strictMode = mixedFrame->document()->shouldEnforceStrictMixedContentChecking() || settings->strictMixedContentChecking();
312 ContextType contextType = contextTypeFromContext(requestContext, mixedFrame);
314 // If we're loading the main resource of a subframe, we need to take a close look at the loaded URL.
315 // If we're dealing with a CORS-enabled scheme, then block mixed frames as active content. Otherwise,
316 // treat frames as passive content.
318 // FIXME: Remove this temporary hack once we have a reasonable API for launching external applications
319 // via URLs. http://crbug.com/318788 and https://crbug.com/393481
320 if (frameType == WebURLRequest::FrameTypeNested && !SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(url.protocol()))
321 contextType = ContextTypeOptionallyBlockable;
323 switch (contextType) {
324 case ContextTypeOptionallyBlockable:
325 allowed = !strictMode && client->allowDisplayingInsecureContent(settings && settings->allowDisplayOfInsecureContent(), securityOrigin, url);
326 if (allowed)
327 client->didDisplayInsecureContent();
328 break;
330 case ContextTypeBlockable: {
331 bool shouldAskEmbedder = !strictMode && settings && (!settings->strictlyBlockBlockableMixedContent() || settings->allowRunningOfInsecureContent());
332 allowed = shouldAskEmbedder && client->allowRunningInsecureContent(settings && settings->allowRunningOfInsecureContent(), securityOrigin, url);
333 if (allowed) {
334 client->didRunInsecureContent(securityOrigin, url);
335 UseCounter::count(mixedFrame, UseCounter::MixedContentBlockableAllowed);
337 break;
340 case ContextTypeShouldBeBlockable:
341 allowed = !strictMode;
342 if (allowed)
343 client->didDisplayInsecureContent();
344 break;
345 case ContextTypeNotMixedContent:
346 ASSERT_NOT_REACHED();
347 break;
350 if (reportingStatus == SendReport)
351 logToConsoleAboutFetch(frame, url, requestContext, allowed);
352 return !allowed;
355 // static
356 void MixedContentChecker::logToConsoleAboutWebSocket(LocalFrame* frame, const KURL& url, bool allowed)
358 String message = String::format(
359 "Mixed Content: The page at '%s' was loaded over HTTPS, but attempted to connect to the insecure WebSocket endpoint '%s'. %s",
360 frame->document()->url().elidedString().utf8().data(), url.elidedString().utf8().data(),
361 allowed ? "This endpoint should be available via WSS. Insecure access is deprecated." : "This request has been blocked; this endpoint must be available over WSS.");
362 MessageLevel messageLevel = allowed ? WarningMessageLevel : ErrorMessageLevel;
363 frame->document()->addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, messageLevel, message));
366 // static
367 bool MixedContentChecker::shouldBlockWebSocket(LocalFrame* frame, const KURL& url, MixedContentChecker::ReportingStatus reportingStatus)
369 LocalFrame* mixedFrame = inWhichFrameIsContentMixed(frame, WebURLRequest::FrameTypeNone, url);
370 if (!mixedFrame)
371 return false;
373 UseCounter::count(mixedFrame, UseCounter::MixedContentPresent);
374 UseCounter::count(mixedFrame, UseCounter::MixedContentWebSocket);
376 Settings* settings = mixedFrame->settings();
377 FrameLoaderClient* client = mixedFrame->loader().client();
378 SecurityOrigin* securityOrigin = mixedFrame->document()->securityOrigin();
379 bool allowed = false;
381 // If we're in strict mode, we'll automagically fail everything, and intentionally skip
382 // the client checks in order to prevent degrading the site's security UI.
383 bool strictMode = mixedFrame->document()->shouldEnforceStrictMixedContentChecking() || settings->strictMixedContentChecking();
384 if (!strictMode) {
385 bool allowedPerSettings = settings && settings->allowRunningOfInsecureContent();
386 allowed = client->allowRunningInsecureContent(allowedPerSettings, securityOrigin, url);
389 if (allowed)
390 client->didRunInsecureContent(securityOrigin, url);
392 if (reportingStatus == SendReport)
393 logToConsoleAboutWebSocket(frame, url, allowed);
394 return !allowed;
397 bool MixedContentChecker::isMixedFormAction(LocalFrame* frame, const KURL& url, ReportingStatus reportingStatus)
399 // For whatever reason, some folks handle forms via JavaScript, and submit to `javascript:void(0)`
400 // rather than calling `preventDefault()`. We special-case `javascript:` URLs here, as they don't
401 // introduce MixedContent for form submissions.
402 if (url.protocolIs("javascript"))
403 return false;
405 LocalFrame* mixedFrame = inWhichFrameIsContentMixed(frame, WebURLRequest::FrameTypeNone, url);
406 if (!mixedFrame)
407 return false;
409 UseCounter::count(mixedFrame, UseCounter::MixedContentPresent);
411 mixedFrame->loader().client()->didDisplayInsecureContent();
413 if (reportingStatus == SendReport) {
414 String message = String::format(
415 "Mixed Content: The page at '%s' was loaded over a secure connection, but contains a form which targets an insecure endpoint '%s'. This endpoint should be made available over a secure connection.",
416 frame->document()->url().elidedString().utf8().data(), url.elidedString().utf8().data());
417 mixedFrame->document()->addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, WarningMessageLevel, message));
420 return true;
423 void MixedContentChecker::checkMixedPrivatePublic(LocalFrame* frame, const AtomicString& resourceIPAddress)
425 if (!frame || !frame->document() || !frame->document()->loader())
426 return;
428 // Just count these for the moment, don't block them.
429 if (Platform::current()->isReservedIPAddress(resourceIPAddress) && !frame->document()->isHostedInReservedIPRange())
430 UseCounter::count(frame->document(), UseCounter::MixedContentPrivateHostnameInPublicHostname);
433 LocalFrame* MixedContentChecker::effectiveFrameForFrameType(LocalFrame* frame, WebURLRequest::FrameType frameType)
435 // If we're loading the main resource of a subframe, ensure that we check
436 // against the parent of the active frame, rather than the frame itself.
437 LocalFrame* effectiveFrame = frame;
438 if (frameType == WebURLRequest::FrameTypeNested) {
439 // FIXME: Deal with RemoteFrames.
440 Frame* parentFrame = effectiveFrame->tree().parent();
441 ASSERT(parentFrame);
442 if (parentFrame->isLocalFrame())
443 effectiveFrame = toLocalFrame(parentFrame);
445 return effectiveFrame;
448 MixedContentChecker::ContextType MixedContentChecker::contextTypeForInspector(LocalFrame* frame, const ResourceRequest& request)
450 LocalFrame* effectiveFrame = effectiveFrameForFrameType(frame, request.frameType());
452 LocalFrame* mixedFrame = inWhichFrameIsContentMixed(effectiveFrame, request.frameType(), request.url());
453 if (!mixedFrame)
454 return ContextTypeNotMixedContent;
456 // See comment in shouldBlockFetch() about loading the main resource of a subframe.
457 if (request.frameType() == WebURLRequest::FrameTypeNested && !SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(request.url().protocol())) {
458 return ContextTypeOptionallyBlockable;
461 return contextTypeFromContext(request.requestContext(), mixedFrame);
464 } // namespace blink