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"
51 MixedContentChecker::MixedContentChecker(LocalFrame* frame)
56 FrameLoaderClient* MixedContentChecker::client() const
58 return m_frame->loader().client();
62 bool MixedContentChecker::isMixedContent(SecurityOrigin* securityOrigin, const KURL& url)
64 if (securityOrigin->protocol() != "https")
65 return false; // We only care about HTTPS security origins.
67 // We're in a secure context, so |url| is mixed content if it's insecure.
68 return !SecurityOrigin::isSecure(url);
72 MixedContentChecker::ContextType MixedContentChecker::contextTypeFromContext(WebURLRequest::RequestContext context)
75 // "Optionally-blockable" mixed content
76 case WebURLRequest::RequestContextAudio:
77 case WebURLRequest::RequestContextFavicon:
78 case WebURLRequest::RequestContextImage:
79 case WebURLRequest::RequestContextVideo:
80 return ContextTypeOptionallyBlockable;
82 // "Blockable" mixed content
83 case WebURLRequest::RequestContextBeacon:
84 case WebURLRequest::RequestContextCSPReport:
85 case WebURLRequest::RequestContextEmbed:
86 case WebURLRequest::RequestContextFetch:
87 case WebURLRequest::RequestContextFont:
88 case WebURLRequest::RequestContextForm:
89 case WebURLRequest::RequestContextFrame:
90 case WebURLRequest::RequestContextHyperlink:
91 case WebURLRequest::RequestContextIframe:
92 case WebURLRequest::RequestContextImageSet:
93 case WebURLRequest::RequestContextImport:
94 case WebURLRequest::RequestContextLocation:
95 case WebURLRequest::RequestContextManifest:
96 case WebURLRequest::RequestContextObject:
97 case WebURLRequest::RequestContextPing:
98 case WebURLRequest::RequestContextScript:
99 case WebURLRequest::RequestContextServiceWorker:
100 case WebURLRequest::RequestContextSharedWorker:
101 case WebURLRequest::RequestContextStyle:
102 case WebURLRequest::RequestContextSubresource:
103 case WebURLRequest::RequestContextTrack:
104 case WebURLRequest::RequestContextWorker:
105 case WebURLRequest::RequestContextXSLT:
106 return ContextTypeBlockable;
108 // "Blockable" mixed content whose behavior changed recently, and which is thus guarded behind the "lax" flag
109 case WebURLRequest::RequestContextEventSource:
110 case WebURLRequest::RequestContextXMLHttpRequest:
111 return ContextTypeBlockableUnlessLax;
113 // FIXME: Contexts that we should block, but don't currently. https://crbug.com/388650
114 case WebURLRequest::RequestContextDownload:
115 case WebURLRequest::RequestContextInternal:
116 case WebURLRequest::RequestContextPlugin:
117 case WebURLRequest::RequestContextPrefetch:
118 return ContextTypeShouldBeBlockable;
120 case WebURLRequest::RequestContextUnspecified:
121 ASSERT_NOT_REACHED();
123 ASSERT_NOT_REACHED();
124 return ContextTypeBlockable;
128 const char* MixedContentChecker::typeNameFromContext(WebURLRequest::RequestContext context)
131 case WebURLRequest::RequestContextAudio:
133 case WebURLRequest::RequestContextBeacon:
134 return "Beacon endpoint";
135 case WebURLRequest::RequestContextCSPReport:
136 return "Content Security Policy reporting endpoint";
137 case WebURLRequest::RequestContextDownload:
139 case WebURLRequest::RequestContextEmbed:
140 return "plugin resource";
141 case WebURLRequest::RequestContextEventSource:
142 return "EventSource endpoint";
143 case WebURLRequest::RequestContextFavicon:
145 case WebURLRequest::RequestContextFetch:
147 case WebURLRequest::RequestContextFont:
149 case WebURLRequest::RequestContextForm:
150 return "form action";
151 case WebURLRequest::RequestContextFrame:
153 case WebURLRequest::RequestContextHyperlink:
155 case WebURLRequest::RequestContextIframe:
157 case WebURLRequest::RequestContextImage:
159 case WebURLRequest::RequestContextImageSet:
161 case WebURLRequest::RequestContextImport:
162 return "HTML Import";
163 case WebURLRequest::RequestContextInternal:
165 case WebURLRequest::RequestContextLocation:
167 case WebURLRequest::RequestContextManifest:
169 case WebURLRequest::RequestContextObject:
170 return "plugin resource";
171 case WebURLRequest::RequestContextPing:
172 return "hyperlink auditing endpoint";
173 case WebURLRequest::RequestContextPlugin:
174 return "plugin data";
175 case WebURLRequest::RequestContextPrefetch:
176 return "prefetch resource";
177 case WebURLRequest::RequestContextScript:
179 case WebURLRequest::RequestContextServiceWorker:
180 return "Service Worker script";
181 case WebURLRequest::RequestContextSharedWorker:
182 return "Shared Worker script";
183 case WebURLRequest::RequestContextStyle:
185 case WebURLRequest::RequestContextSubresource:
187 case WebURLRequest::RequestContextTrack:
189 case WebURLRequest::RequestContextUnspecified:
191 case WebURLRequest::RequestContextVideo:
193 case WebURLRequest::RequestContextWorker:
194 return "Worker script";
195 case WebURLRequest::RequestContextXMLHttpRequest:
196 return "XMLHttpRequest endpoint";
197 case WebURLRequest::RequestContextXSLT:
200 ASSERT_NOT_REACHED();
205 void MixedContentChecker::logToConsole(LocalFrame* frame, const KURL& url, WebURLRequest::RequestContext requestContext, bool allowed)
207 String message = String::format(
208 "Mixed Content: The page at '%s' was loaded over HTTPS, but requested an insecure %s '%s'. %s",
209 frame->document()->url().elidedString().utf8().data(), typeNameFromContext(requestContext), url.elidedString().utf8().data(),
210 allowed ? "This content should also be served over HTTPS." : "This request has been blocked; the content must be served over HTTPS.");
211 MessageLevel messageLevel = allowed ? WarningMessageLevel : ErrorMessageLevel;
212 frame->document()->addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, messageLevel, message));
216 bool MixedContentChecker::shouldBlockFetch(LocalFrame* frame, const ResourceRequest& resourceRequest, const KURL& url)
218 // No frame, no mixed content:
222 // Check the top frame first.
223 if (Frame* top = frame->tree().top()) {
224 // FIXME: We need a way to access the top-level frame's SecurityOrigin when that frame
225 // is in a different process from the current frame. Until that is done, we bail out
226 // early and allow the load.
227 if (!top->isLocalFrame())
230 LocalFrame* localTop = toLocalFrame(top);
231 if (frame != localTop && shouldBlockFetch(localTop, resourceRequest, url))
235 // We only care about subresource loads; top-level navigations cannot be mixed content.
236 if (resourceRequest.frameType() == WebURLRequest::FrameTypeTopLevel)
239 // No mixed content, no problem.
240 if (!isMixedContent(frame->document()->securityOrigin(), url))
243 Settings* settings = frame->settings();
244 FrameLoaderClient* client = frame->loader().client();
245 SecurityOrigin* securityOrigin = frame->document()->securityOrigin();
246 bool allowed = false;
248 ContextType contextType = contextTypeFromContext(resourceRequest.requestContext());
249 if (contextType == ContextTypeBlockableUnlessLax)
250 contextType = RuntimeEnabledFeatures::laxMixedContentCheckingEnabled() ? ContextTypeOptionallyBlockable : ContextTypeBlockable;
252 // If we're loading the main resource of a subframe, we need to take a close look at the loaded URL.
253 // If we're dealing with a CORS-enabled scheme, then block mixed frames as active content. Otherwise,
254 // treat frames as passive content.
256 // FIXME: Remove this temporary hack once we have a reasonable API for launching external applications
257 // via URLs. http://crbug.com/318788 and https://crbug.com/393481
258 if (resourceRequest.frameType() == WebURLRequest::FrameTypeNested && !SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(url.protocol()))
259 contextType = ContextTypeOptionallyBlockable;
261 switch (contextType) {
262 case ContextTypeOptionallyBlockable:
263 allowed = client->allowDisplayingInsecureContent(settings && settings->allowDisplayOfInsecureContent(), securityOrigin, url);
265 client->didDisplayInsecureContent();
268 case ContextTypeBlockable:
269 allowed = client->allowRunningInsecureContent(settings && settings->allowRunningOfInsecureContent(), securityOrigin, url);
271 client->didRunInsecureContent(securityOrigin, url);
274 case ContextTypeShouldBeBlockable:
277 case ContextTypeBlockableUnlessLax:
278 // We map this to either OptionallyBlockable or Blockable above.
279 ASSERT_NOT_REACHED();
283 logToConsole(frame, url, resourceRequest.requestContext(), allowed);
287 bool MixedContentChecker::canDisplayInsecureContentInternal(SecurityOrigin* securityOrigin, const KURL& url, const MixedContentType type) const
289 // Check the top frame if it differs from MixedContentChecker's m_frame.
290 if (!m_frame->tree().top()->isLocalFrame()) {
291 // FIXME: We need a way to access the top-level frame's MixedContentChecker when that frame
292 // is in a different process from the current frame. Until that is done, we always allow
293 // loads in remote frames.
296 Frame* top = m_frame->tree().top();
297 if (top != m_frame && !toLocalFrame(top)->loader().mixedContentChecker()->canDisplayInsecureContent(toLocalFrame(top)->document()->securityOrigin(), url))
300 // Just count these for the moment, don't block them.
301 if (Platform::current()->isReservedIPAddress(url) && !Platform::current()->isReservedIPAddress(KURL(ParsedURLString, securityOrigin->toString())))
302 UseCounter::count(m_frame->document(), UseCounter::MixedContentPrivateIPInPublicWebsitePassive);
304 // Then check the current frame:
305 if (!isMixedContent(securityOrigin, url))
308 Settings* settings = m_frame->settings();
309 bool allowed = client()->allowDisplayingInsecureContent(settings && settings->allowDisplayOfInsecureContent(), securityOrigin, url);
310 logWarning(allowed, url, type);
313 client()->didDisplayInsecureContent();
318 bool MixedContentChecker::canRunInsecureContentInternal(SecurityOrigin* securityOrigin, const KURL& url, const MixedContentType type) const
320 // Check the top frame if it differs from MixedContentChecker's m_frame.
321 if (!m_frame->tree().top()->isLocalFrame()) {
322 // FIXME: We need a way to access the top-level frame's MixedContentChecker when that frame
323 // is in a different process from the current frame. Until that is done, we always allow
324 // loads in remote frames.
327 Frame* top = m_frame->tree().top();
328 if (top != m_frame && !toLocalFrame(top)->loader().mixedContentChecker()->canRunInsecureContent(toLocalFrame(top)->document()->securityOrigin(), url))
331 // Just count these for the moment, don't block them.
332 if (Platform::current()->isReservedIPAddress(url) && !Platform::current()->isReservedIPAddress(KURL(ParsedURLString, securityOrigin->toString())))
333 UseCounter::count(m_frame->document(), UseCounter::MixedContentPrivateIPInPublicWebsiteActive);
335 // Then check the current frame:
336 if (!isMixedContent(securityOrigin, url))
339 Settings* settings = m_frame->settings();
340 bool allowedPerSettings = settings && (settings->allowRunningOfInsecureContent() || ((type == WebSocket) && settings->allowConnectingInsecureWebSocket()));
341 bool allowed = client()->allowRunningInsecureContent(allowedPerSettings, securityOrigin, url);
342 logWarning(allowed, url, type);
345 client()->didRunInsecureContent(securityOrigin, url);
350 bool MixedContentChecker::canFrameInsecureContent(SecurityOrigin* securityOrigin, const KURL& url) const
352 // If we're dealing with a CORS-enabled scheme, then block mixed frames as active content. Otherwise,
353 // treat frames as passive content.
355 // FIXME: Remove this temporary hack once we have a reasonable API for launching external applications
356 // via URLs. http://crbug.com/318788 and https://crbug.com/393481
357 if (SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(url.protocol()))
358 return canRunInsecureContentInternal(securityOrigin, url, MixedContentChecker::Execution);
359 return canDisplayInsecureContentInternal(securityOrigin, url, MixedContentChecker::Display);
362 bool MixedContentChecker::canConnectInsecureWebSocket(SecurityOrigin* securityOrigin, const KURL& url) const
364 if (RuntimeEnabledFeatures::laxMixedContentCheckingEnabled())
365 return canDisplayInsecureContentInternal(securityOrigin, url, MixedContentChecker::WebSocket);
366 return canRunInsecureContentInternal(securityOrigin, url, MixedContentChecker::WebSocket);
369 bool MixedContentChecker::canSubmitToInsecureForm(SecurityOrigin* securityOrigin, const KURL& url) const
371 // For whatever reason, some folks handle forms via JavaScript, and submit to `javascript:void(0)`
372 // rather than calling `preventDefault()`. We special-case `javascript:` URLs here, as they don't
373 // introduce MixedContent for form submissions.
374 if (url.protocolIs("javascript"))
377 // If lax mixed content checking is enabled (noooo!), skip this check entirely.
378 if (RuntimeEnabledFeatures::laxMixedContentCheckingEnabled())
380 return canDisplayInsecureContentInternal(securityOrigin, url, MixedContentChecker::Submission);
383 void MixedContentChecker::logWarning(bool allowed, const KURL& target, const MixedContentType type) const
385 StringBuilder message;
386 message.append((allowed ? "" : "[blocked] "));
387 message.append("The page at '" + m_frame->document()->url().elidedString() + "' was loaded over HTTPS, but ");
390 message.append("displayed insecure content from '" + target.elidedString() + "': this content should also be loaded over HTTPS.\n");
394 message.append("ran insecure content from '" + target.elidedString() + "': this content should also be loaded over HTTPS.\n");
397 message.append("is submitting data to an insecure location at '" + target.elidedString() + "': this content should also be submitted over HTTPS.\n");
400 MessageLevel messageLevel = allowed ? WarningMessageLevel : ErrorMessageLevel;
401 m_frame->document()->addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, messageLevel, message.toString()));
404 void MixedContentChecker::checkMixedPrivatePublic(LocalFrame* frame, const AtomicString& resourceIPAddress)
406 if (!frame || !frame->document() || !frame->document()->loader())
409 KURL documentIP(ParsedURLString, "http://" + frame->document()->loader()->response().remoteIPAddress());
410 KURL resourceIP(ParsedURLString, "http://" + resourceIPAddress);
412 // Just count these for the moment, don't block them.
414 // FIXME: Once we know how we want to check this, adjust the platform APIs to avoid the KURL construction.
415 if (Platform::current()->isReservedIPAddress(resourceIP) && !Platform::current()->isReservedIPAddress(documentIP))
416 UseCounter::count(frame->document(), UseCounter::MixedContentPrivateHostnameInPublicHostname);
419 void MixedContentChecker::trace(Visitor* visitor)
421 visitor->trace(m_frame);