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