Home | History | Annotate | Download | only in loader
      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 
     51 MixedContentChecker::MixedContentChecker(LocalFrame* frame)
     52     : m_frame(frame)
     53 {
     54 }
     55 
     56 FrameLoaderClient* MixedContentChecker::client() const
     57 {
     58     return m_frame->loader().client();
     59 }
     60 
     61 // static
     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
     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
    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
    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
    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 
    278 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 
    309 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 
    341 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 
    353 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 
    360 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 
    374 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 
    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 
    410 void MixedContentChecker::trace(Visitor* visitor)
    411 {
    412     visitor->trace(m_frame);
    413 }
    414 
    415 } // namespace blink
    416