1 /* 2 Copyright (C) 1998 Lars Knoll (knoll (at) mpi-hd.mpg.de) 3 Copyright (C) 2001 Dirk Mueller (mueller (at) kde.org) 4 Copyright (C) 2002 Waldo Bastian (bastian (at) kde.org) 5 Copyright (C) 2004, 2005, 2006, 2008 Apple Inc. All rights reserved. 6 Copyright (C) 2009 Torch Mobile Inc. http://www.torchmobile.com/ 7 8 This library is free software; you can redistribute it and/or 9 modify it under the terms of the GNU Library General Public 10 License as published by the Free Software Foundation; either 11 version 2 of the License, or (at your option) any later version. 12 13 This library is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 Library General Public License for more details. 17 18 You should have received a copy of the GNU Library General Public License 19 along with this library; see the file COPYING.LIB. If not, write to 20 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 21 Boston, MA 02110-1301, USA. 22 23 This class provides all functionality needed for loading images, style sheets and html 24 pages from the web. It has a memory cache for these objects. 25 */ 26 27 #include "config.h" 28 #include "DocLoader.h" 29 30 #include "Cache.h" 31 #include "CachedCSSStyleSheet.h" 32 #include "CachedFont.h" 33 #include "CachedImage.h" 34 #include "CachedScript.h" 35 #include "CachedXSLStyleSheet.h" 36 #include "Console.h" 37 #include "CString.h" 38 #include "Document.h" 39 #include "DOMWindow.h" 40 #include "HTMLElement.h" 41 #include "Frame.h" 42 #include "FrameLoader.h" 43 #include "FrameLoaderClient.h" 44 #include "loader.h" 45 #include "SecurityOrigin.h" 46 #include "Settings.h" 47 48 #define PRELOAD_DEBUG 0 49 50 namespace WebCore { 51 52 DocLoader::DocLoader(Document* doc) 53 : m_cache(cache()) 54 , m_doc(doc) 55 , m_requestCount(0) 56 #ifdef ANDROID_BLOCK_NETWORK_IMAGE 57 , m_blockNetworkImage(false) 58 #endif 59 , m_autoLoadImages(true) 60 , m_loadInProgress(false) 61 , m_allowStaleResources(false) 62 { 63 m_cache->addDocLoader(this); 64 } 65 66 DocLoader::~DocLoader() 67 { 68 if (m_requestCount) 69 m_cache->loader()->cancelRequests(this); 70 71 clearPreloads(); 72 DocumentResourceMap::iterator end = m_documentResources.end(); 73 for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) 74 it->second->setDocLoader(0); 75 m_cache->removeDocLoader(this); 76 77 // Make sure no requests still point to this DocLoader 78 ASSERT(m_requestCount == 0); 79 } 80 81 Frame* DocLoader::frame() const 82 { 83 return m_doc->frame(); 84 } 85 86 void DocLoader::checkForReload(const KURL& fullURL) 87 { 88 if (m_allowStaleResources) 89 return; // Don't reload resources while pasting 90 91 if (fullURL.isEmpty()) 92 return; 93 94 if (m_reloadedURLs.contains(fullURL.string())) 95 return; 96 97 CachedResource* existing = cache()->resourceForURL(fullURL.string()); 98 if (!existing || existing->isPreloaded()) 99 return; 100 101 switch (cachePolicy()) { 102 case CachePolicyVerify: 103 if (!existing->mustRevalidate(CachePolicyVerify)) 104 return; 105 cache()->revalidateResource(existing, this); 106 break; 107 case CachePolicyCache: 108 if (!existing->mustRevalidate(CachePolicyCache)) 109 return; 110 cache()->revalidateResource(existing, this); 111 break; 112 case CachePolicyReload: 113 cache()->remove(existing); 114 break; 115 case CachePolicyRevalidate: 116 cache()->revalidateResource(existing, this); 117 break; 118 case CachePolicyAllowStale: 119 return; 120 } 121 122 m_reloadedURLs.add(fullURL.string()); 123 } 124 125 CachedImage* DocLoader::requestImage(const String& url) 126 { 127 if (Frame* f = frame()) { 128 Settings* settings = f->settings(); 129 if (!f->loader()->client()->allowImages(!settings || settings->areImagesEnabled())) 130 return 0; 131 } 132 CachedImage* resource = static_cast<CachedImage*>(requestResource(CachedResource::ImageResource, url, String())); 133 if (autoLoadImages() && resource && resource->stillNeedsLoad()) { 134 #ifdef ANDROID_BLOCK_NETWORK_IMAGE 135 if (shouldBlockNetworkImage(url)) { 136 return resource; 137 } 138 #endif 139 resource->setLoading(true); 140 cache()->loader()->load(this, resource, true); 141 } 142 return resource; 143 } 144 145 CachedFont* DocLoader::requestFont(const String& url) 146 { 147 return static_cast<CachedFont*>(requestResource(CachedResource::FontResource, url, String())); 148 } 149 150 CachedCSSStyleSheet* DocLoader::requestCSSStyleSheet(const String& url, const String& charset) 151 { 152 return static_cast<CachedCSSStyleSheet*>(requestResource(CachedResource::CSSStyleSheet, url, charset)); 153 } 154 155 CachedCSSStyleSheet* DocLoader::requestUserCSSStyleSheet(const String& url, const String& charset) 156 { 157 return cache()->requestUserCSSStyleSheet(this, url, charset); 158 } 159 160 CachedScript* DocLoader::requestScript(const String& url, const String& charset) 161 { 162 return static_cast<CachedScript*>(requestResource(CachedResource::Script, url, charset)); 163 } 164 165 #if ENABLE(XSLT) 166 CachedXSLStyleSheet* DocLoader::requestXSLStyleSheet(const String& url) 167 { 168 return static_cast<CachedXSLStyleSheet*>(requestResource(CachedResource::XSLStyleSheet, url, String())); 169 } 170 #endif 171 172 #if ENABLE(XBL) 173 CachedXBLDocument* DocLoader::requestXBLDocument(const String& url) 174 { 175 return static_cast<CachedXSLStyleSheet*>(requestResource(CachedResource::XBL, url, String())); 176 } 177 #endif 178 179 bool DocLoader::canRequest(CachedResource::Type type, const KURL& url) 180 { 181 // Some types of resources can be loaded only from the same origin. Other 182 // types of resources, like Images, Scripts, and CSS, can be loaded from 183 // any URL. 184 switch (type) { 185 case CachedResource::ImageResource: 186 case CachedResource::CSSStyleSheet: 187 case CachedResource::Script: 188 case CachedResource::FontResource: 189 // These types of resources can be loaded from any origin. 190 // FIXME: Are we sure about CachedResource::FontResource? 191 break; 192 #if ENABLE(XSLT) 193 case CachedResource::XSLStyleSheet: 194 #endif 195 #if ENABLE(XBL) 196 case CachedResource::XBL: 197 #endif 198 #if ENABLE(XSLT) || ENABLE(XBL) 199 if (!m_doc->securityOrigin()->canRequest(url)) { 200 printAccessDeniedMessage(url); 201 return false; 202 } 203 break; 204 #endif 205 default: 206 ASSERT_NOT_REACHED(); 207 break; 208 } 209 210 // Given that the load is allowed by the same-origin policy, we should 211 // check whether the load passes the mixed-content policy. 212 // 213 // Note: Currently, we always allow mixed content, but we generate a 214 // callback to the FrameLoaderClient in case the embedder wants to 215 // update any security indicators. 216 // 217 switch (type) { 218 case CachedResource::Script: 219 #if ENABLE(XSLT) 220 case CachedResource::XSLStyleSheet: 221 #endif 222 #if ENABLE(XBL) 223 case CachedResource::XBL: 224 #endif 225 // These resource can inject script into the current document. 226 if (Frame* f = frame()) 227 f->loader()->checkIfRunInsecureContent(m_doc->securityOrigin(), url); 228 break; 229 case CachedResource::ImageResource: 230 case CachedResource::CSSStyleSheet: 231 case CachedResource::FontResource: { 232 // These resources can corrupt only the frame's pixels. 233 if (Frame* f = frame()) { 234 Frame* top = f->tree()->top(); 235 top->loader()->checkIfDisplayInsecureContent(top->document()->securityOrigin(), url); 236 } 237 break; 238 } 239 default: 240 ASSERT_NOT_REACHED(); 241 break; 242 } 243 // FIXME: Consider letting the embedder block mixed content loads. 244 return true; 245 } 246 247 CachedResource* DocLoader::requestResource(CachedResource::Type type, const String& url, const String& charset, bool isPreload) 248 { 249 KURL fullURL = m_doc->completeURL(url); 250 251 if (!fullURL.isValid() || !canRequest(type, fullURL)) 252 return 0; 253 254 if (cache()->disabled()) { 255 DocumentResourceMap::iterator it = m_documentResources.find(fullURL.string()); 256 257 if (it != m_documentResources.end()) { 258 it->second->setDocLoader(0); 259 m_documentResources.remove(it); 260 } 261 } 262 263 checkForReload(fullURL); 264 265 CachedResource* resource = cache()->requestResource(this, type, fullURL, charset, isPreload); 266 if (resource) { 267 // Check final URL of resource to catch redirects. 268 // See <https://bugs.webkit.org/show_bug.cgi?id=21963>. 269 if (fullURL != resource->url() && !canRequest(type, KURL(ParsedURLString, resource->url()))) 270 return 0; 271 272 m_documentResources.set(resource->url(), resource); 273 checkCacheObjectStatus(resource); 274 } 275 return resource; 276 } 277 278 void DocLoader::printAccessDeniedMessage(const KURL& url) const 279 { 280 if (url.isNull()) 281 return; 282 283 if (!frame()) 284 return; 285 286 Settings* settings = frame()->settings(); 287 if (!settings || settings->privateBrowsingEnabled()) 288 return; 289 290 String message = m_doc->url().isNull() ? 291 String::format("Unsafe attempt to load URL %s.", 292 url.string().utf8().data()) : 293 String::format("Unsafe attempt to load URL %s from frame with URL %s. " 294 "Domains, protocols and ports must match.\n", 295 url.string().utf8().data(), 296 m_doc->url().string().utf8().data()); 297 298 // FIXME: provide a real line number and source URL. 299 frame()->domWindow()->console()->addMessage(OtherMessageSource, LogMessageType, ErrorMessageLevel, message, 1, String()); 300 } 301 302 void DocLoader::setAutoLoadImages(bool enable) 303 { 304 if (enable == m_autoLoadImages) 305 return; 306 307 m_autoLoadImages = enable; 308 309 if (!m_autoLoadImages) 310 return; 311 312 DocumentResourceMap::iterator end = m_documentResources.end(); 313 for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) { 314 CachedResource* resource = it->second.get(); 315 if (resource->type() == CachedResource::ImageResource) { 316 CachedImage* image = const_cast<CachedImage*>(static_cast<const CachedImage*>(resource)); 317 #ifdef ANDROID_BLOCK_NETWORK_IMAGE 318 if (shouldBlockNetworkImage(image->url())) 319 continue; 320 #endif 321 322 if (image->stillNeedsLoad()) 323 cache()->loader()->load(this, image, true); 324 } 325 } 326 } 327 328 #ifdef ANDROID_BLOCK_NETWORK_IMAGE 329 bool DocLoader::shouldBlockNetworkImage(const String& url) const 330 { 331 if (!m_blockNetworkImage) 332 return false; 333 334 KURL kurl = m_doc->completeURL(url); 335 if (kurl.protocolIs("http") || kurl.protocolIs("https")) 336 return true; 337 338 return false; 339 } 340 341 void DocLoader::setBlockNetworkImage(bool block) 342 { 343 if (block == m_blockNetworkImage) 344 return; 345 346 m_blockNetworkImage = block; 347 348 if (!m_autoLoadImages || m_blockNetworkImage) 349 return; 350 351 DocumentResourceMap::iterator end = m_documentResources.end(); 352 for (DocumentResourceMap::iterator it = m_documentResources.begin(); it != end; ++it) { 353 CachedResource* resource = it->second.get(); 354 if (resource->type() == CachedResource::ImageResource) { 355 CachedImage* image = const_cast<CachedImage*>(static_cast<const CachedImage*>(resource)); 356 if (image->stillNeedsLoad()) 357 cache()->loader()->load(this, image, true); 358 } 359 } 360 } 361 #endif 362 363 CachePolicy DocLoader::cachePolicy() const 364 { 365 return frame() ? frame()->loader()->subresourceCachePolicy() : CachePolicyVerify; 366 } 367 368 void DocLoader::removeCachedResource(CachedResource* resource) const 369 { 370 #ifndef NDEBUG 371 DocumentResourceMap::iterator it = m_documentResources.find(resource->url()); 372 if (it != m_documentResources.end()) 373 ASSERT(it->second.get() == resource); 374 #endif 375 m_documentResources.remove(resource->url()); 376 } 377 378 void DocLoader::setLoadInProgress(bool load) 379 { 380 m_loadInProgress = load; 381 if (!load && frame()) 382 frame()->loader()->loadDone(); 383 } 384 385 void DocLoader::checkCacheObjectStatus(CachedResource* resource) 386 { 387 // Return from the function for objects that we didn't load from the cache or if we don't have a frame. 388 if (!resource || !frame()) 389 return; 390 391 switch (resource->status()) { 392 case CachedResource::Cached: 393 break; 394 case CachedResource::NotCached: 395 case CachedResource::Unknown: 396 case CachedResource::New: 397 case CachedResource::Pending: 398 return; 399 } 400 401 // FIXME: If the WebKit client changes or cancels the request, WebCore does not respect this and continues the load. 402 frame()->loader()->loadedResourceFromMemoryCache(resource); 403 } 404 405 void DocLoader::incrementRequestCount() 406 { 407 ++m_requestCount; 408 } 409 410 void DocLoader::decrementRequestCount() 411 { 412 --m_requestCount; 413 ASSERT(m_requestCount > -1); 414 } 415 416 int DocLoader::requestCount() 417 { 418 if (loadInProgress()) 419 return m_requestCount + 1; 420 return m_requestCount; 421 } 422 423 void DocLoader::preload(CachedResource::Type type, const String& url, const String& charset, bool referencedFromBody) 424 { 425 bool hasRendering = m_doc->body() && m_doc->body()->renderer(); 426 if (!hasRendering && (referencedFromBody || type == CachedResource::ImageResource)) { 427 // Don't preload images or body resources before we have something to draw. This prevents 428 // preloads from body delaying first display when bandwidth is limited. 429 PendingPreload pendingPreload = { type, url, charset }; 430 m_pendingPreloads.append(pendingPreload); 431 return; 432 } 433 requestPreload(type, url, charset); 434 } 435 436 void DocLoader::checkForPendingPreloads() 437 { 438 unsigned count = m_pendingPreloads.size(); 439 if (!count || !m_doc->body() || !m_doc->body()->renderer()) 440 return; 441 for (unsigned i = 0; i < count; ++i) { 442 PendingPreload& preload = m_pendingPreloads[i]; 443 // Don't request preload if the resource already loaded normally (this will result in double load if the page is being reloaded with cached results ignored). 444 if (!cachedResource(m_doc->completeURL(preload.m_url))) 445 requestPreload(preload.m_type, preload.m_url, preload.m_charset); 446 } 447 m_pendingPreloads.clear(); 448 } 449 450 void DocLoader::requestPreload(CachedResource::Type type, const String& url, const String& charset) 451 { 452 String encoding; 453 if (type == CachedResource::Script || type == CachedResource::CSSStyleSheet) 454 encoding = charset.isEmpty() ? m_doc->frame()->loader()->encoding() : charset; 455 456 CachedResource* resource = requestResource(type, url, encoding, true); 457 if (!resource || m_preloads.contains(resource)) 458 return; 459 resource->increasePreloadCount(); 460 m_preloads.add(resource); 461 #if PRELOAD_DEBUG 462 printf("PRELOADING %s\n", resource->url().latin1().data()); 463 #endif 464 } 465 466 void DocLoader::clearPreloads() 467 { 468 #if PRELOAD_DEBUG 469 printPreloadStats(); 470 #endif 471 ListHashSet<CachedResource*>::iterator end = m_preloads.end(); 472 for (ListHashSet<CachedResource*>::iterator it = m_preloads.begin(); it != end; ++it) { 473 CachedResource* res = *it; 474 res->decreasePreloadCount(); 475 if (res->canDelete() && !res->inCache()) 476 delete res; 477 else if (res->preloadResult() == CachedResource::PreloadNotReferenced) 478 cache()->remove(res); 479 } 480 m_preloads.clear(); 481 } 482 483 void DocLoader::clearPendingPreloads() 484 { 485 m_pendingPreloads.clear(); 486 } 487 488 #if PRELOAD_DEBUG 489 void DocLoader::printPreloadStats() 490 { 491 unsigned scripts = 0; 492 unsigned scriptMisses = 0; 493 unsigned stylesheets = 0; 494 unsigned stylesheetMisses = 0; 495 unsigned images = 0; 496 unsigned imageMisses = 0; 497 ListHashSet<CachedResource*>::iterator end = m_preloads.end(); 498 for (ListHashSet<CachedResource*>::iterator it = m_preloads.begin(); it != end; ++it) { 499 CachedResource* res = *it; 500 if (res->preloadResult() == CachedResource::PreloadNotReferenced) 501 printf("!! UNREFERENCED PRELOAD %s\n", res->url().latin1().data()); 502 else if (res->preloadResult() == CachedResource::PreloadReferencedWhileComplete) 503 printf("HIT COMPLETE PRELOAD %s\n", res->url().latin1().data()); 504 else if (res->preloadResult() == CachedResource::PreloadReferencedWhileLoading) 505 printf("HIT LOADING PRELOAD %s\n", res->url().latin1().data()); 506 507 if (res->type() == CachedResource::Script) { 508 scripts++; 509 if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading) 510 scriptMisses++; 511 } else if (res->type() == CachedResource::CSSStyleSheet) { 512 stylesheets++; 513 if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading) 514 stylesheetMisses++; 515 } else { 516 images++; 517 if (res->preloadResult() < CachedResource::PreloadReferencedWhileLoading) 518 imageMisses++; 519 } 520 521 if (res->errorOccurred()) 522 cache()->remove(res); 523 524 res->decreasePreloadCount(); 525 } 526 m_preloads.clear(); 527 528 if (scripts) 529 printf("SCRIPTS: %d (%d hits, hit rate %d%%)\n", scripts, scripts - scriptMisses, (scripts - scriptMisses) * 100 / scripts); 530 if (stylesheets) 531 printf("STYLESHEETS: %d (%d hits, hit rate %d%%)\n", stylesheets, stylesheets - stylesheetMisses, (stylesheets - stylesheetMisses) * 100 / stylesheets); 532 if (images) 533 printf("IMAGES: %d (%d hits, hit rate %d%%)\n", images, images - imageMisses, (images - imageMisses) * 100 / images); 534 } 535 #endif 536 537 } 538