• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
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.
16  *
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.
27  */
28 
29 #include "config.h"
30 #include "core/loader/MixedContentChecker.h"
31 
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"
45 
46 namespace blink {
47 
48 namespace {
49 } // namespace
50 
MixedContentChecker(LocalFrame * frame)51 MixedContentChecker::MixedContentChecker(LocalFrame* frame)
52     : m_frame(frame)
53 {
54 }
55 
client() const56 FrameLoaderClient* MixedContentChecker::client() const
57 {
58     return m_frame->loader().client();
59 }
60 
61 // static
isMixedContent(SecurityOrigin * securityOrigin,const KURL & url)62 bool MixedContentChecker::isMixedContent(SecurityOrigin* securityOrigin, const KURL& url)
63 {
64     if (securityOrigin->protocol() != "https")
65         return false; // We only care about HTTPS security origins.
66 
67     // We're in a secure context, so |url| is mixed content if it's insecure.
68     return !SecurityOrigin::isSecure(url);
69 }
70 
71 // static
contextTypeFromContext(WebURLRequest::RequestContext context)72 MixedContentChecker::ContextType MixedContentChecker::contextTypeFromContext(WebURLRequest::RequestContext context)
73 {
74     switch (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;
81 
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;
107 
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;
112 
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;
119 
120     case WebURLRequest::RequestContextUnspecified:
121         ASSERT_NOT_REACHED();
122     }
123     ASSERT_NOT_REACHED();
124     return ContextTypeBlockable;
125 }
126 
127 // static
typeNameFromContext(WebURLRequest::RequestContext context)128 const char* MixedContentChecker::typeNameFromContext(WebURLRequest::RequestContext context)
129 {
130     switch (context) {
131     case WebURLRequest::RequestContextAudio:
132         return "audio file";
133     case WebURLRequest::RequestContextBeacon:
134         return "Beacon endpoint";
135     case WebURLRequest::RequestContextCSPReport:
136         return "Content Security Policy reporting endpoint";
137     case WebURLRequest::RequestContextDownload:
138         return "download";
139     case WebURLRequest::RequestContextEmbed:
140         return "plugin resource";
141     case WebURLRequest::RequestContextEventSource:
142         return "EventSource endpoint";
143     case WebURLRequest::RequestContextFavicon:
144         return "favicon";
145     case WebURLRequest::RequestContextFetch:
146         return "resource";
147     case WebURLRequest::RequestContextFont:
148         return "font";
149     case WebURLRequest::RequestContextForm:
150         return "form action";
151     case WebURLRequest::RequestContextFrame:
152         return "frame";
153     case WebURLRequest::RequestContextHyperlink:
154         return "resource";
155     case WebURLRequest::RequestContextIframe:
156         return "frame";
157     case WebURLRequest::RequestContextImage:
158         return "image";
159     case WebURLRequest::RequestContextImageSet:
160         return "image";
161     case WebURLRequest::RequestContextImport:
162         return "HTML Import";
163     case WebURLRequest::RequestContextInternal:
164         return "resource";
165     case WebURLRequest::RequestContextLocation:
166         return "resource";
167     case WebURLRequest::RequestContextManifest:
168         return "manifest";
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:
178         return "script";
179     case WebURLRequest::RequestContextServiceWorker:
180         return "Service Worker script";
181     case WebURLRequest::RequestContextSharedWorker:
182         return "Shared Worker script";
183     case WebURLRequest::RequestContextStyle:
184         return "stylesheet";
185     case WebURLRequest::RequestContextSubresource:
186         return "resource";
187     case WebURLRequest::RequestContextTrack:
188         return "Text Track";
189     case WebURLRequest::RequestContextUnspecified:
190         return "resource";
191     case WebURLRequest::RequestContextVideo:
192         return "video";
193     case WebURLRequest::RequestContextWorker:
194         return "Worker script";
195     case WebURLRequest::RequestContextXMLHttpRequest:
196         return "XMLHttpRequest endpoint";
197     case WebURLRequest::RequestContextXSLT:
198         return "XSLT";
199     }
200     ASSERT_NOT_REACHED();
201     return "resource";
202 }
203 
204 // static
logToConsole(LocalFrame * frame,const KURL & url,WebURLRequest::RequestContext requestContext,bool allowed)205 void MixedContentChecker::logToConsole(LocalFrame* frame, const KURL& url, WebURLRequest::RequestContext requestContext, bool allowed)
206 {
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));
213 }
214 
215 // static
shouldBlockFetch(LocalFrame * frame,const ResourceRequest & resourceRequest,const KURL & url)216 bool MixedContentChecker::shouldBlockFetch(LocalFrame* frame, const ResourceRequest& resourceRequest, const KURL& url)
217 {
218     // No frame, no mixed content:
219     if (!frame)
220         return false;
221 
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())
228             return false;
229 
230         LocalFrame* localTop = toLocalFrame(top);
231         if (frame != localTop && shouldBlockFetch(localTop, resourceRequest, url))
232             return true;
233     }
234 
235     // We only care about subresource loads; top-level navigations cannot be mixed content.
236     if (resourceRequest.frameType() == WebURLRequest::FrameTypeTopLevel)
237         return false;
238 
239     // No mixed content, no problem.
240     if (!isMixedContent(frame->document()->securityOrigin(), url))
241         return false;
242 
243     Settings* settings = frame->settings();
244     FrameLoaderClient* client = frame->loader().client();
245     SecurityOrigin* securityOrigin = frame->document()->securityOrigin();
246     bool allowed = false;
247 
248     ContextType contextType = contextTypeFromContext(resourceRequest.requestContext());
249     if (contextType == ContextTypeBlockableUnlessLax)
250         contextType = RuntimeEnabledFeatures::laxMixedContentCheckingEnabled() ? ContextTypeOptionallyBlockable : ContextTypeBlockable;
251 
252     switch (contextType) {
253     case ContextTypeOptionallyBlockable:
254         allowed = client->allowDisplayingInsecureContent(settings && settings->allowDisplayOfInsecureContent(), securityOrigin, url);
255         if (allowed)
256             client->didDisplayInsecureContent();
257         break;
258 
259     case ContextTypeBlockable:
260         allowed = client->allowRunningInsecureContent(settings && settings->allowRunningOfInsecureContent(), securityOrigin, url);
261         if (allowed)
262             client->didRunInsecureContent(securityOrigin, url);
263         break;
264 
265     case ContextTypeShouldBeBlockable:
266         return false;
267 
268     case ContextTypeBlockableUnlessLax:
269         // We map this to either OptionallyBlockable or Blockable above.
270         ASSERT_NOT_REACHED();
271         return true;
272     };
273 
274     logToConsole(frame, url, resourceRequest.requestContext(), allowed);
275     return !allowed;
276 }
277 
canDisplayInsecureContentInternal(SecurityOrigin * securityOrigin,const KURL & url,const MixedContentType type) const278 bool MixedContentChecker::canDisplayInsecureContentInternal(SecurityOrigin* securityOrigin, const KURL& url, const MixedContentType type) const
279 {
280     // Check the top frame if it differs from MixedContentChecker's m_frame.
281     if (!m_frame->tree().top()->isLocalFrame()) {
282         // FIXME: We need a way to access the top-level frame's MixedContentChecker when that frame
283         // is in a different process from the current frame. Until that is done, we always allow
284         // loads in remote frames.
285         return false;
286     }
287     Frame* top = m_frame->tree().top();
288     if (top != m_frame && !toLocalFrame(top)->loader().mixedContentChecker()->canDisplayInsecureContent(toLocalFrame(top)->document()->securityOrigin(), url))
289         return false;
290 
291     // Just count these for the moment, don't block them.
292     if (Platform::current()->isReservedIPAddress(url) && !Platform::current()->isReservedIPAddress(KURL(ParsedURLString, securityOrigin->toString())))
293         UseCounter::count(m_frame->document(), UseCounter::MixedContentPrivateIPInPublicWebsitePassive);
294 
295     // Then check the current frame:
296     if (!isMixedContent(securityOrigin, url))
297         return true;
298 
299     Settings* settings = m_frame->settings();
300     bool allowed = client()->allowDisplayingInsecureContent(settings && settings->allowDisplayOfInsecureContent(), securityOrigin, url);
301     logWarning(allowed, url, type);
302 
303     if (allowed)
304         client()->didDisplayInsecureContent();
305 
306     return allowed;
307 }
308 
canRunInsecureContentInternal(SecurityOrigin * securityOrigin,const KURL & url,const MixedContentType type) const309 bool MixedContentChecker::canRunInsecureContentInternal(SecurityOrigin* securityOrigin, const KURL& url, const MixedContentType type) const
310 {
311     // Check the top frame if it differs from MixedContentChecker's m_frame.
312     if (!m_frame->tree().top()->isLocalFrame()) {
313         // FIXME: We need a way to access the top-level frame's MixedContentChecker when that frame
314         // is in a different process from the current frame. Until that is done, we always allow
315         // loads in remote frames.
316         return true;
317     }
318     Frame* top = m_frame->tree().top();
319     if (top != m_frame && !toLocalFrame(top)->loader().mixedContentChecker()->canRunInsecureContent(toLocalFrame(top)->document()->securityOrigin(), url))
320         return false;
321 
322     // Just count these for the moment, don't block them.
323     if (Platform::current()->isReservedIPAddress(url) && !Platform::current()->isReservedIPAddress(KURL(ParsedURLString, securityOrigin->toString())))
324         UseCounter::count(m_frame->document(), UseCounter::MixedContentPrivateIPInPublicWebsiteActive);
325 
326     // Then check the current frame:
327     if (!isMixedContent(securityOrigin, url))
328         return true;
329 
330     Settings* settings = m_frame->settings();
331     bool allowedPerSettings = settings && (settings->allowRunningOfInsecureContent() || ((type == WebSocket) && settings->allowConnectingInsecureWebSocket()));
332     bool allowed = client()->allowRunningInsecureContent(allowedPerSettings, securityOrigin, url);
333     logWarning(allowed, url, type);
334 
335     if (allowed)
336         client()->didRunInsecureContent(securityOrigin, url);
337 
338     return allowed;
339 }
340 
canFrameInsecureContent(SecurityOrigin * securityOrigin,const KURL & url) const341 bool MixedContentChecker::canFrameInsecureContent(SecurityOrigin* securityOrigin, const KURL& url) const
342 {
343     // If we're dealing with a CORS-enabled scheme, then block mixed frames as active content. Otherwise,
344     // treat frames as passive content.
345     //
346     // FIXME: Remove this temporary hack once we have a reasonable API for launching external applications
347     // via URLs. http://crbug.com/318788 and https://crbug.com/393481
348     if (SchemeRegistry::shouldTreatURLSchemeAsCORSEnabled(url.protocol()))
349         return canRunInsecureContentInternal(securityOrigin, url, MixedContentChecker::Execution);
350     return canDisplayInsecureContentInternal(securityOrigin, url, MixedContentChecker::Display);
351 }
352 
canConnectInsecureWebSocket(SecurityOrigin * securityOrigin,const KURL & url) const353 bool MixedContentChecker::canConnectInsecureWebSocket(SecurityOrigin* securityOrigin, const KURL& url) const
354 {
355     if (RuntimeEnabledFeatures::laxMixedContentCheckingEnabled())
356         return canDisplayInsecureContentInternal(securityOrigin, url, MixedContentChecker::WebSocket);
357     return canRunInsecureContentInternal(securityOrigin, url, MixedContentChecker::WebSocket);
358 }
359 
canSubmitToInsecureForm(SecurityOrigin * securityOrigin,const KURL & url) const360 bool MixedContentChecker::canSubmitToInsecureForm(SecurityOrigin* securityOrigin, const KURL& url) const
361 {
362     // For whatever reason, some folks handle forms via JavaScript, and submit to `javascript:void(0)`
363     // rather than calling `preventDefault()`. We special-case `javascript:` URLs here, as they don't
364     // introduce MixedContent for form submissions.
365     if (url.protocolIs("javascript"))
366         return true;
367 
368     // If lax mixed content checking is enabled (noooo!), skip this check entirely.
369     if (RuntimeEnabledFeatures::laxMixedContentCheckingEnabled())
370         return true;
371     return canDisplayInsecureContentInternal(securityOrigin, url, MixedContentChecker::Submission);
372 }
373 
logWarning(bool allowed,const KURL & target,const MixedContentType type) const374 void MixedContentChecker::logWarning(bool allowed, const KURL& target, const MixedContentType type) const
375 {
376     StringBuilder message;
377     message.append((allowed ? "" : "[blocked] "));
378     message.append("The page at '" + m_frame->document()->url().elidedString() + "' was loaded over HTTPS, but ");
379     switch (type) {
380     case Display:
381         message.append("displayed insecure content from '" + target.elidedString() + "': this content should also be loaded over HTTPS.\n");
382         break;
383     case Execution:
384     case WebSocket:
385         message.append("ran insecure content from '" + target.elidedString() + "': this content should also be loaded over HTTPS.\n");
386         break;
387     case Submission:
388         message.append("is submitting data to an insecure location at '" + target.elidedString() + "': this content should also be submitted over HTTPS.\n");
389         break;
390     }
391     MessageLevel messageLevel = allowed ? WarningMessageLevel : ErrorMessageLevel;
392     m_frame->document()->addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, messageLevel, message.toString()));
393 }
394 
checkMixedPrivatePublic(LocalFrame * frame,const AtomicString & resourceIPAddress)395 void MixedContentChecker::checkMixedPrivatePublic(LocalFrame* frame, const AtomicString& resourceIPAddress)
396 {
397     if (!frame || !frame->document() || !frame->document()->loader())
398         return;
399 
400     KURL documentIP(ParsedURLString, "http://" + frame->document()->loader()->response().remoteIPAddress());
401     KURL resourceIP(ParsedURLString, "http://" + resourceIPAddress);
402 
403     // Just count these for the moment, don't block them.
404     //
405     // FIXME: Once we know how we want to check this, adjust the platform APIs to avoid the KURL construction.
406     if (Platform::current()->isReservedIPAddress(resourceIP) && !Platform::current()->isReservedIPAddress(documentIP))
407         UseCounter::count(frame->document(), UseCounter::MixedContentPrivateHostnameInPublicHostname);
408 }
409 
trace(Visitor * visitor)410 void MixedContentChecker::trace(Visitor* visitor)
411 {
412     visitor->trace(m_frame);
413 }
414 
415 } // namespace blink
416