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
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.
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"
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
);
64 bool MixedContentChecker::isMixedContent(SecurityOrigin
* securityOrigin
, const KURL
& url
)
66 if (!SchemeRegistry::shouldTreatURLSchemeAsRestrictingMixedContent(securityOrigin
->protocol()))
69 // We're in a secure context, so |url| is mixed content if it's insecure.
70 return !SecurityOrigin::isSecure(url
);
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
)
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())
87 LocalFrame
* localTop
= toLocalFrame(top
);
88 measureStricterVersionOfIsMixedContent(localTop
, url
);
89 if (isMixedContent(localTop
->document()->securityOrigin(), url
))
93 measureStricterVersionOfIsMixedContent(frame
, url
);
94 if (isMixedContent(frame
->document()->securityOrigin(), url
))
97 // No mixed content, no problem.
102 MixedContentChecker::ContextType
MixedContentChecker::contextTypeFromContext(WebURLRequest::RequestContext context
, LocalFrame
* frame
)
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
;
160 const char* MixedContentChecker::typeNameFromContext(WebURLRequest::RequestContext context
)
163 case WebURLRequest::RequestContextAudio
:
165 case WebURLRequest::RequestContextBeacon
:
166 return "Beacon endpoint";
167 case WebURLRequest::RequestContextCSPReport
:
168 return "Content Security Policy reporting endpoint";
169 case WebURLRequest::RequestContextDownload
:
171 case WebURLRequest::RequestContextEmbed
:
172 return "plugin resource";
173 case WebURLRequest::RequestContextEventSource
:
174 return "EventSource endpoint";
175 case WebURLRequest::RequestContextFavicon
:
177 case WebURLRequest::RequestContextFetch
:
179 case WebURLRequest::RequestContextFont
:
181 case WebURLRequest::RequestContextForm
:
182 return "form action";
183 case WebURLRequest::RequestContextFrame
:
185 case WebURLRequest::RequestContextHyperlink
:
187 case WebURLRequest::RequestContextIframe
:
189 case WebURLRequest::RequestContextImage
:
191 case WebURLRequest::RequestContextImageSet
:
193 case WebURLRequest::RequestContextImport
:
194 return "HTML Import";
195 case WebURLRequest::RequestContextInternal
:
197 case WebURLRequest::RequestContextLocation
:
199 case WebURLRequest::RequestContextManifest
:
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
:
211 case WebURLRequest::RequestContextServiceWorker
:
212 return "Service Worker script";
213 case WebURLRequest::RequestContextSharedWorker
:
214 return "Shared Worker script";
215 case WebURLRequest::RequestContextStyle
:
217 case WebURLRequest::RequestContextSubresource
:
219 case WebURLRequest::RequestContextTrack
:
221 case WebURLRequest::RequestContextUnspecified
:
223 case WebURLRequest::RequestContextVideo
:
225 case WebURLRequest::RequestContextWorker
:
226 return "Worker script";
227 case WebURLRequest::RequestContextXMLHttpRequest
:
228 return "XMLHttpRequest endpoint";
229 case WebURLRequest::RequestContextXSLT
:
232 ASSERT_NOT_REACHED();
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
));
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
);
260 UseCounter::Feature feature
;
261 switch (requestContext
) {
262 case WebURLRequest::RequestContextAudio
:
263 feature
= UseCounter::MixedContentAudio
;
265 case WebURLRequest::RequestContextDownload
:
266 feature
= UseCounter::MixedContentDownload
;
268 case WebURLRequest::RequestContextFavicon
:
269 feature
= UseCounter::MixedContentFavicon
;
271 case WebURLRequest::RequestContextImage
:
272 feature
= UseCounter::MixedContentImage
;
274 case WebURLRequest::RequestContextInternal
:
275 feature
= UseCounter::MixedContentInternal
;
277 case WebURLRequest::RequestContextPlugin
:
278 feature
= UseCounter::MixedContentPlugin
;
280 case WebURLRequest::RequestContextPrefetch
:
281 feature
= UseCounter::MixedContentPrefetch
;
283 case WebURLRequest::RequestContextVideo
:
284 feature
= UseCounter::MixedContentVideo
;
288 ASSERT_NOT_REACHED();
291 UseCounter::count(frame
, feature
);
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
);
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
);
327 client
->didDisplayInsecureContent();
330 case ContextTypeBlockable
: {
331 bool shouldAskEmbedder
= !strictMode
&& settings
&& (!settings
->strictlyBlockBlockableMixedContent() || settings
->allowRunningOfInsecureContent());
332 allowed
= shouldAskEmbedder
&& client
->allowRunningInsecureContent(settings
&& settings
->allowRunningOfInsecureContent(), securityOrigin
, url
);
334 client
->didRunInsecureContent(securityOrigin
, url
);
335 UseCounter::count(mixedFrame
, UseCounter::MixedContentBlockableAllowed
);
340 case ContextTypeShouldBeBlockable
:
341 allowed
= !strictMode
;
343 client
->didDisplayInsecureContent();
345 case ContextTypeNotMixedContent
:
346 ASSERT_NOT_REACHED();
350 if (reportingStatus
== SendReport
)
351 logToConsoleAboutFetch(frame
, url
, requestContext
, allowed
);
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
));
367 bool MixedContentChecker::shouldBlockWebSocket(LocalFrame
* frame
, const KURL
& url
, MixedContentChecker::ReportingStatus reportingStatus
)
369 LocalFrame
* mixedFrame
= inWhichFrameIsContentMixed(frame
, WebURLRequest::FrameTypeNone
, url
);
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();
385 bool allowedPerSettings
= settings
&& settings
->allowRunningOfInsecureContent();
386 allowed
= client
->allowRunningInsecureContent(allowedPerSettings
, securityOrigin
, url
);
390 client
->didRunInsecureContent(securityOrigin
, url
);
392 if (reportingStatus
== SendReport
)
393 logToConsoleAboutWebSocket(frame
, url
, 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"))
405 LocalFrame
* mixedFrame
= inWhichFrameIsContentMixed(frame
, WebURLRequest::FrameTypeNone
, url
);
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
));
423 void MixedContentChecker::checkMixedPrivatePublic(LocalFrame
* frame
, const AtomicString
& resourceIPAddress
)
425 if (!frame
|| !frame
->document() || !frame
->document()->loader())
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();
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());
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
);