• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2     Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
3     Copyright (C) 2001 Dirk Mueller (mueller@kde.org)
4     Copyright (C) 2002 Waldo Bastian (bastian@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 
DocLoader(Document * doc)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 
~DocLoader()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 
frame() const81 Frame* DocLoader::frame() const
82 {
83     return m_doc->frame();
84 }
85 
checkForReload(const KURL & fullURL)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 
requestImage(const String & url)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 
requestFont(const String & url)145 CachedFont* DocLoader::requestFont(const String& url)
146 {
147     return static_cast<CachedFont*>(requestResource(CachedResource::FontResource, url, String()));
148 }
149 
requestCSSStyleSheet(const String & url,const String & charset)150 CachedCSSStyleSheet* DocLoader::requestCSSStyleSheet(const String& url, const String& charset)
151 {
152     return static_cast<CachedCSSStyleSheet*>(requestResource(CachedResource::CSSStyleSheet, url, charset));
153 }
154 
requestUserCSSStyleSheet(const String & url,const String & charset)155 CachedCSSStyleSheet* DocLoader::requestUserCSSStyleSheet(const String& url, const String& charset)
156 {
157     return cache()->requestUserCSSStyleSheet(this, url, charset);
158 }
159 
requestScript(const String & url,const String & charset)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)
requestXSLStyleSheet(const String & url)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)
requestXBLDocument(const String & url)173 CachedXBLDocument* DocLoader::requestXBLDocument(const String& url)
174 {
175     return static_cast<CachedXSLStyleSheet*>(requestResource(CachedResource::XBL, url, String()));
176 }
177 #endif
178 
canRequest(CachedResource::Type type,const KURL & url)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 
requestResource(CachedResource::Type type,const String & url,const String & charset,bool isPreload)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 
printAccessDeniedMessage(const KURL & url) const278 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 
setAutoLoadImages(bool enable)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
shouldBlockNetworkImage(const String & url) const329 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 
setBlockNetworkImage(bool block)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 
cachePolicy() const363 CachePolicy DocLoader::cachePolicy() const
364 {
365     return frame() ? frame()->loader()->subresourceCachePolicy() : CachePolicyVerify;
366 }
367 
removeCachedResource(CachedResource * resource) const368 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 
setLoadInProgress(bool load)378 void DocLoader::setLoadInProgress(bool load)
379 {
380     m_loadInProgress = load;
381     if (!load && frame())
382         frame()->loader()->loadDone();
383 }
384 
checkCacheObjectStatus(CachedResource * resource)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 
incrementRequestCount()405 void DocLoader::incrementRequestCount()
406 {
407     ++m_requestCount;
408 }
409 
decrementRequestCount()410 void DocLoader::decrementRequestCount()
411 {
412     --m_requestCount;
413     ASSERT(m_requestCount > -1);
414 }
415 
requestCount()416 int DocLoader::requestCount()
417 {
418     if (loadInProgress())
419          return m_requestCount + 1;
420     return m_requestCount;
421 }
422 
preload(CachedResource::Type type,const String & url,const String & charset,bool referencedFromBody)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 
checkForPendingPreloads()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 
requestPreload(CachedResource::Type type,const String & url,const String & charset)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 
clearPreloads()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 
clearPendingPreloads()483 void DocLoader::clearPendingPreloads()
484 {
485     m_pendingPreloads.clear();
486 }
487 
488 #if PRELOAD_DEBUG
printPreloadStats()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