1 /*
2     This file is part of the KDE libraries
3 
4     Copyright (C) 1998 Lars Knoll (knoll@mpi-hd.mpg.de)
5               (C) 2001-2003 Dirk Mueller (mueller@kde.org)
6               (C) 2002 Waldo Bastian (bastian@kde.org)
7               (C) 2003 Apple Computer, Inc.
8               (C) 2006-2010 Germain Garand (germain@ebooksfrance.org)
9 
10     This library is free software; you can redistribute it and/or
11     modify it under the terms of the GNU Library General Public
12     License as published by the Free Software Foundation; either
13     version 2 of the License, or (at your option) any later version.
14 
15     This library is distributed in the hope that it will be useful,
16     but WITHOUT ANY WARRANTY; without even the implied warranty of
17     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18     Library General Public License for more details.
19 
20     You should have received a copy of the GNU Library General Public License
21     along with this library; see the file COPYING.LIB.  If not, write to
22     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23     Boston, MA 02110-1301, USA.
24 
25     This class provides all functionality needed for loading images, style sheets and html
26     pages from the web. It has a memory cache for these objects.
27 
28     // regarding the LRU:
29     // http://www.is.kyusan-u.ac.jp/~chengk/pub/papers/compsac00_A07-07.pdf
30 */
31 
32 #undef CACHE_DEBUG
33 //#define CACHE_DEBUG
34 
35 #ifdef CACHE_DEBUG
36 #define CDEBUG qCDebug(KHTML_LOG)
37 #else
38 #define CDEBUG qCDebug(KHTML_LOG)
39 #endif
40 
41 #undef LOADER_DEBUG
42 //#define LOADER_DEBUG
43 
44 //#define PRELOAD_DEBUG
45 
46 #include "loader.h"
47 #include "seed.h"
48 #include "woff.h"
49 #include <imload/imagepainter.h>
50 #include <imload/imagemanager.h>
51 #include <KCompressionDevice>
52 
53 #include <assert.h>
54 
55 // default cache size
56 #define DEFCACHESIZE 2096*1024
57 
58 //#include <qasyncio.h>
59 //#include <qasyncimageio.h>
60 #include <QApplication>
61 #include <QDesktopWidget>
62 #include <QPainter>
63 #include <QBitmap>
64 #include <QMovie>
65 #include <QWidget>
66 #include <QDebug>
67 #include <kurlauthorized.h>
68 #include <kio/job.h>
69 #include <kio/jobuidelegate.h>
70 #include <kio/jobclasses.h>
71 #include <kio/scheduler.h>
72 #include <kcharsets.h>
73 #include <kiconloader.h>
74 #include "khtml_debug.h"
75 #include <kjobwidgets.h>
76 
77 #include <khtml_global.h>
78 #include <khtml_part.h>
79 
80 #ifdef IMAGE_TITLES
81 #include <qfile.h>
82 #include <kfilemetainfo.h>
83 #include <qtemporaryfile.h>
84 #endif
85 
86 #include "html/html_documentimpl.h"
87 #include "css/css_stylesheetimpl.h"
88 #include "xml/dom_docimpl.h"
89 
90 #include "blocked_icon.cpp"
91 
92 #include <QPaintEngine>
93 
94 using namespace khtml;
95 using namespace DOM;
96 using namespace khtmlImLoad;
97 
98 #define MAX_LRU_LISTS 20
99 struct LRUList {
100     CachedObject *m_head;
101     CachedObject *m_tail;
102 
LRUListLRUList103     LRUList() : m_head(nullptr), m_tail(nullptr) {}
104 };
105 
106 static LRUList m_LRULists[MAX_LRU_LISTS];
107 static LRUList *getLRUListFor(CachedObject *o);
108 
~CachedObjectClient()109 CachedObjectClient::~CachedObjectClient()
110 {
111 }
112 
~CachedObject()113 CachedObject::~CachedObject()
114 {
115     Cache::removeFromLRUList(this);
116 }
117 
finish()118 void CachedObject::finish()
119 {
120     m_status = Cached;
121 }
122 
isExpired() const123 bool CachedObject::isExpired() const
124 {
125     if (!m_expireDate.isValid()) {
126         return false;
127     }
128     QDateTime now = QDateTime::currentDateTime();
129     return (now >= m_expireDate);
130 }
131 
setRequest(Request * _request)132 void CachedObject::setRequest(Request *_request)
133 {
134     if (_request && !m_request) {
135         m_status = Pending;
136     }
137 
138     if (allowInLRUList()) {
139         Cache::removeFromLRUList(this);
140     }
141 
142     m_request = _request;
143 
144     if (allowInLRUList()) {
145         Cache::insertInLRUList(this);
146     }
147 }
148 
ref(CachedObjectClient * c)149 void CachedObject::ref(CachedObjectClient *c)
150 {
151     if (m_preloadResult == PreloadNotReferenced) {
152         if (isLoaded()) {
153             m_preloadResult = PreloadReferencedWhileComplete;
154         } else if (m_prospectiveRequest) {
155             m_preloadResult = PreloadReferencedWhileLoading;
156         } else {
157             m_preloadResult = PreloadReferenced;
158         }
159     }
160     // unfortunately we can be ref'ed multiple times from the
161     // same object,  because it uses e.g. the same foreground
162     // and the same background picture. so deal with it.
163     // Hence the use of a QHash rather than of a QSet.
164     m_clients.insertMulti(c, c);
165     Cache::removeFromLRUList(this);
166     m_accessCount++;
167 }
168 
deref(CachedObjectClient * c)169 void CachedObject::deref(CachedObjectClient *c)
170 {
171     assert(c);
172     assert(m_clients.count());
173     assert(!canDelete());
174     assert(m_clients.contains(c));
175 
176     Cache::flush();
177 
178     m_clients.take(c);
179 
180     if (allowInLRUList()) {
181         Cache::insertInLRUList(this);
182     }
183 }
184 
setSize(int size)185 void CachedObject::setSize(int size)
186 {
187     bool sizeChanged;
188 
189     if (!m_next && !m_prev && getLRUListFor(this)->m_head != this) {
190         sizeChanged = false;
191     } else {
192         sizeChanged = (size - m_size) != 0;
193     }
194 
195     // The object must now be moved to a different queue,
196     // since its size has been changed.
197     if (sizeChanged  && allowInLRUList()) {
198         Cache::removeFromLRUList(this);
199     }
200 
201     m_size = size;
202 
203     if (sizeChanged && allowInLRUList()) {
204         Cache::insertInLRUList(this);
205     }
206 }
207 
codecForBuffer(const QString & charset,const QByteArray & buffer) const208 QTextCodec *CachedObject::codecForBuffer(const QString &charset, const QByteArray &buffer) const
209 {
210     // we don't use heuristicContentMatch here since it is a) far too slow and
211     // b) having too much functionality for our case.
212 
213     uchar *d = (uchar *) buffer.data();
214     int s = buffer.size();
215 
216     // BOM
217     if (s >= 3 &&
218             d[0] == 0xef && d[1] == 0xbb && d[2] == 0xbf) {
219         return QTextCodec::codecForMib(106);    // UTF-8
220     }
221 
222     if (s >= 2 && ((d[0] == 0xff && d[1] == 0xfe) ||
223                    (d[0] == 0xfe && d[1] == 0xff))) {
224         return QTextCodec::codecForMib(1000);    // UCS-2
225     }
226 
227     // Link or @charset
228     if (!charset.isEmpty()) {
229         QTextCodec *c = KCharsets::charsets()->codecForName(charset);
230         if (c->mibEnum() == 11)  {
231             // iso8859-8 (visually ordered)
232             c = QTextCodec::codecForName("iso8859-8-i");
233         }
234         return c;
235     }
236 
237     // Default
238     return QTextCodec::codecForMib(4);   // latin 1
239 }
240 
241 // -------------------------------------------------------------------------------------------
242 
CachedCSSStyleSheet(DocLoader * dl,const DOMString & url,KIO::CacheControl _cachePolicy,const char * accept)243 CachedCSSStyleSheet::CachedCSSStyleSheet(DocLoader *dl, const DOMString &url, KIO::CacheControl _cachePolicy,
244         const char *accept)
245     : CachedObject(url, CSSStyleSheet, _cachePolicy, 0)
246 {
247     // Set the type we want (probably css or xml)
248     QString ah = QLatin1String(accept);
249     if (!ah.isEmpty()) {
250         ah += ',';
251     }
252     ah += "*/*;q=0.1";
253     setAccept(ah);
254     m_hadError = false;
255     m_wasBlocked = false;
256     m_err = 0;
257     // load the file.
258     // Style sheets block rendering, they are therefore the higher priority item.
259     // Do |not| touch the priority value unless you conducted thorough tests and
260     // can back your choice with meaningful data, testing page load time and
261     // time to first paint.
262     Cache::loader()->load(dl, this, false, -8);
263     m_loading = true;
264 }
265 
CachedCSSStyleSheet(const DOMString & url,const QString & stylesheet_data)266 CachedCSSStyleSheet::CachedCSSStyleSheet(const DOMString &url, const QString &stylesheet_data)
267     : CachedObject(url, CSSStyleSheet, KIO::CC_Verify, stylesheet_data.length())
268 {
269     m_loading = false;
270     m_status = Persistent;
271     m_sheet = DOMString(stylesheet_data);
272 }
273 
isAcceptableCSSMimetype(const DOM::DOMString & mimetype)274 bool khtml::isAcceptableCSSMimetype(const DOM::DOMString &mimetype)
275 {
276     // matches Mozilla's check (cf. nsCSSLoader.cpp)
277     return mimetype.isEmpty() || mimetype == "text/css" || mimetype == "application/x-unknown-content-type";
278 }
279 
ref(CachedObjectClient * c)280 void CachedCSSStyleSheet::ref(CachedObjectClient *c)
281 {
282     CachedObject::ref(c);
283 
284     if (!m_loading) {
285         if (m_hadError) {
286             c->error(m_err, m_errText);
287         } else {
288             c->setStyleSheet(m_url, m_sheet, m_charset, m_mimetype);
289         }
290     }
291 }
292 
data(QBuffer & buffer,bool eof)293 void CachedCSSStyleSheet::data(QBuffer &buffer, bool eof)
294 {
295     if (!eof) {
296         return;
297     }
298     buffer.close();
299     setSize(buffer.buffer().size());
300 
301     m_charset = checkCharset(buffer.buffer());
302     QTextCodec *c = nullptr;
303     if (!m_charset.isEmpty()) {
304         c = KCharsets::charsets()->codecForName(m_charset);
305         if (c->mibEnum() == 11) {
306             c = QTextCodec::codecForName("iso8859-8-i");
307         }
308     } else {
309         c = codecForBuffer(m_charsetHint, buffer.buffer());
310         m_charset = c->name();
311     }
312     QString data = c->toUnicode(buffer.buffer().data(), m_size);
313     // workaround Qt bugs
314     m_sheet = static_cast<QChar>(data[0]) == QChar::ByteOrderMark ? DOMString(data.mid(1)) : DOMString(data);
315     m_loading = false;
316 
317     checkNotify();
318 }
319 
checkNotify()320 void CachedCSSStyleSheet::checkNotify()
321 {
322     if (m_loading || m_hadError) {
323         return;
324     }
325 
326     CDEBUG << "finishedLoading" << m_url.string();
327 
328     for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
329         it.next().value()->setStyleSheet(m_url, m_sheet, m_charset, m_mimetype);
330     }
331 }
332 
error(int err,const char * text)333 void CachedCSSStyleSheet::error(int err, const char *text)
334 {
335     m_hadError = true;
336     m_err = err;
337     m_errText = text;
338     m_loading = false;
339 
340     for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
341         it.next().value()->error(m_err, m_errText);
342     }
343 }
344 
checkCharset(const QByteArray & buffer) const345 QString CachedCSSStyleSheet::checkCharset(const QByteArray &buffer) const
346 {
347     int s = buffer.size();
348     if (s <= 12) {
349         return m_charset;
350     }
351 
352     // @charset has to be first or directly after BOM.
353     // CSS 2.1 says @charset should win over BOM, but since more browsers support BOM
354     // than @charset, we default to that.
355     const char *d = buffer.data();
356     if (strncmp(d, "@charset \"", 10) == 0) {
357         // the string until "; is the charset name
358         const char *p = strchr(d + 10, '"');
359         if (p == nullptr) {
360             return m_charset;
361         }
362         QString charset = QString::fromLatin1(d + 10, p - (d + 10));
363         return charset;
364     }
365     return m_charset;
366 }
367 
368 // -------------------------------------------------------------------------------------------
369 
CachedScript(DocLoader * dl,const DOMString & url,KIO::CacheControl _cachePolicy,const char *)370 CachedScript::CachedScript(DocLoader *dl, const DOMString &url, KIO::CacheControl _cachePolicy, const char *)
371     : CachedObject(url, Script, _cachePolicy, 0)
372 {
373     // It's javascript we want.
374     // But some websites think their scripts are <some wrong mimetype here>
375     // and refuse to serve them if we only accept application/x-javascript.
376     setAccept(QLatin1String("*/*"));
377     // load the file.
378     // Scripts block document parsing. They are therefore second in our list of most
379     // desired resources.
380     Cache::loader()->load(dl, this, false/*incremental*/, -6);
381     m_loading = true;
382     m_hadError = false;
383 }
384 
CachedScript(const DOMString & url,const QString & script_data)385 CachedScript::CachedScript(const DOMString &url, const QString &script_data)
386     : CachedObject(url, Script, KIO::CC_Verify, script_data.length())
387 {
388     m_hadError = false;
389     m_loading = false;
390     m_status = Persistent;
391     m_script = DOMString(script_data);
392 }
393 
ref(CachedObjectClient * c)394 void CachedScript::ref(CachedObjectClient *c)
395 {
396     CachedObject::ref(c);
397 
398     if (!m_loading) {
399         c->notifyFinished(this);
400     }
401 }
402 
data(QBuffer & buffer,bool eof)403 void CachedScript::data(QBuffer &buffer, bool eof)
404 {
405     if (!eof) {
406         return;
407     }
408     buffer.close();
409     setSize(buffer.buffer().size());
410 
411     QTextCodec *c = codecForBuffer(m_charset, buffer.buffer());
412     QString data = c->toUnicode(buffer.buffer().data(), m_size);
413     m_script = static_cast<QChar>(data[0]) == QChar::ByteOrderMark ? DOMString(data.mid(1)) : DOMString(data);
414     m_loading = false;
415     checkNotify();
416 }
417 
checkNotify()418 void CachedScript::checkNotify()
419 {
420     if (m_loading) {
421         return;
422     }
423 
424     for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
425         it.next().value()->notifyFinished(this);
426     }
427 }
428 
error(int,const char *)429 void CachedScript::error(int /*err*/, const char * /*text*/)
430 {
431     m_hadError = true;
432     m_loading  = false;
433     checkNotify();
434 }
435 
436 // ------------------------------------------------------------------------------------------
437 
buildAcceptHeader()438 static QString buildAcceptHeader()
439 {
440     return "image/png, image/jpeg, video/x-mng, image/jp2, image/gif;q=0.5,*/*;q=0.1";
441 }
442 
443 // -------------------------------------------------------------------------------------
444 
CachedImage(DocLoader * dl,const DOMString & url,KIO::CacheControl _cachePolicy,const char *)445 CachedImage::CachedImage(DocLoader *dl, const DOMString &url, KIO::CacheControl _cachePolicy, const char *)
446     : QObject(), CachedObject(url, Image, _cachePolicy, 0)
447 {
448     i = new khtmlImLoad::Image(this);
449     //p = 0;
450     //pixPart = 0;
451     bg = nullptr;
452     scaled = nullptr;
453     bgColor = qRgba(0, 0, 0, 0);
454     m_status = Unknown;
455     setAccept(buildAcceptHeader());
456     i->setShowAnimations(dl->showAnimations());
457     m_loading = true;
458 
459     if (KHTMLGlobal::defaultHTMLSettings()->isAdFiltered(url.string())) {
460         m_wasBlocked = true;
461         CachedObject::finish();
462     }
463 }
464 
~CachedImage()465 CachedImage::~CachedImage()
466 {
467     clear();
468     delete i;
469 }
470 
ref(CachedObjectClient * c)471 void CachedImage::ref(CachedObjectClient *c)
472 {
473     CachedObject::ref(c);
474 
475 #ifdef LOADER_DEBUG
476     qCDebug(KHTML_LOG) << "image" << this << "ref'd by client" << c;
477 #endif
478 
479     // for mouseovers, dynamic changes
480     //### having both makes no sense
481     if (m_status >= Persistent && !pixmap_size().isNull()) {
482 #ifdef LOADER_DEBUG
483         qCDebug(KHTML_LOG) << "Notifying finished size:" << i->size().width() << "," << i->size().height();
484 #endif
485         c->updatePixmap(QRect(QPoint(0, 0), pixmap_size()), this);
486         c->notifyFinished(this);
487     }
488 }
489 
deref(CachedObjectClient * c)490 void CachedImage::deref(CachedObjectClient *c)
491 {
492     CachedObject::deref(c);
493     /*    if(m && m_clients.isEmpty() && m->running())
494             m->pause();*/
495 }
496 
497 #define BGMINWIDTH      32
498 #define BGMINHEIGHT     32
499 
tiled_pixmap(const QColor & newc,int xWidth,int xHeight)500 QPixmap CachedImage::tiled_pixmap(const QColor &newc, int xWidth, int xHeight)
501 {
502 
503     // no error indication for background images
504     if (m_hadError || m_wasBlocked) {
505         return *Cache::nullPixmap;
506     }
507 
508     // If we don't have size yet, nothing to draw yet
509     if (i->size().width() == 0 || i->size().height() == 0) {
510         return *Cache::nullPixmap;
511     }
512 
513 #ifdef __GNUC__
514 #warning "Needs some additional performance work"
515 #endif
516 
517     static QRgb bgTransparent = qRgba(0, 0, 0, 0);
518 
519     QSize s(pixmap_size());
520     int w = xWidth;
521     int h = xHeight;
522 
523     if (w == -1) {
524         xWidth = w = s.width();
525     }
526     if (h == -1) {
527         xHeight = h = s.height();
528     }
529 
530     if (((bgColor != bgTransparent) && (bgColor != newc.rgba())) ||
531             (bgSize != QSize(xWidth, xHeight))) {
532         delete bg; bg = nullptr;
533     }
534 
535     if (bg) {
536         return *bg;
537     }
538 
539     const QPixmap *src; //source for pretiling, if any
540 
541     const QPixmap &r = pixmap(); //this is expensive
542     if (r.isNull()) {
543         return r;
544     }
545 
546     //See whether we should scale
547     if (xWidth != s.width() || xHeight != s.height()) {
548         src = scaled_pixmap(xWidth, xHeight);
549     } else {
550         src = &r;
551     }
552 
553     bgSize = QSize(xWidth, xHeight);
554 
555     //See whether we can - and should - pre-blend
556     // ### this needs serious investigations. Not likely to help with transparent bgColor,
557     // won't work with CSS3 multiple backgrounds. Does it help at all in Qt4? (ref: #114938)
558     if (newc.isValid() && (r.hasAlpha() || r.hasAlphaChannel())) {
559         bg = new QPixmap(xWidth, xHeight);
560         bg->fill(newc);
561         QPainter p(bg);
562         p.drawPixmap(0, 0, *src);
563         bgColor = newc.rgba();
564         src     = bg;
565     } else {
566         bgColor = bgTransparent;
567     }
568 
569     //See whether to pre-tile.
570     if (w * h < 8192) {
571         if (r.width() < BGMINWIDTH) {
572             w = ((BGMINWIDTH - 1) / xWidth + 1) * xWidth;
573         }
574         if (r.height() < BGMINHEIGHT) {
575             h = ((BGMINHEIGHT - 1) / xHeight + 1) * xHeight;
576         }
577     }
578 
579     if (w != xWidth  || h != xHeight) {
580         // qCDebug(KHTML_LOG) << "pre-tiling " << s.width() << "," << s.height() << " to " << w << "," << h;
581         QPixmap *oldbg = bg;
582         bg = new QPixmap(w, h);
583         if (src->hasAlpha() || src->hasAlphaChannel()) {
584             if (newc.isValid() && (bgColor != bgTransparent)) {
585                 bg->fill(bgColor);
586             } else {
587                 bg->fill(Qt::transparent);
588             }
589         }
590 
591         QPainter p(bg);
592         p.drawTiledPixmap(0, 0, w, h, *src);
593         p.end();
594 
595         if (src == oldbg) {
596             delete oldbg;
597         }
598     } else if (src && !bg) {
599         // we were asked for the entire pixmap. Cache it.
600         // ### goes against imload stuff, but it's far too expensive
601         //     to recreate the full pixmap each time it's requested as
602         //     we don't know what portion of it will be used eventually
603         //     (by e.g. paintBackgroundExtended). It could be a few pixels of
604         //     a huge image. See #140248/#1 for an obvious example.
605         //     Imload probably needs to handle all painting in paintBackgroundExtended.
606         bg = new QPixmap(*src);
607     }
608 
609     if (bg) {
610         return *bg;
611     }
612 
613     return *src;
614 }
615 
scaled_pixmap(int xWidth,int xHeight)616 QPixmap *CachedImage::scaled_pixmap(int xWidth, int xHeight)
617 {
618     // no error indication for background images
619     if (m_hadError || m_wasBlocked) {
620         return Cache::nullPixmap;
621     }
622 
623     // If we don't have size yet, nothing to draw yet
624     if (i->size().width() == 0 || i->size().height() == 0) {
625         return Cache::nullPixmap;
626     }
627 
628     if (scaled) {
629         if (scaled->width() == xWidth && scaled->height() == xHeight) {
630             return scaled;
631         }
632         delete scaled;
633     }
634 
635     //### this is quite awful performance-wise. It should avoid
636     // alpha if not needed, and go to pixmap, etc.
637     QImage im(xWidth, xHeight, QImage::Format_ARGB32_Premultiplied);
638 
639     QPainter paint(&im);
640     paint.setCompositionMode(QPainter::CompositionMode_Source);
641     ImagePainter pi(i, QSize(xWidth, xHeight));
642     pi.paint(0, 0, &paint);
643     paint.end();
644 
645     scaled = new QPixmap(QPixmap::fromImage(im));
646 
647     return scaled;
648 }
649 
pixmap() const650 QPixmap CachedImage::pixmap() const
651 {
652     if (m_hadError) {
653         return *Cache::brokenPixmap;
654     }
655 
656     if (m_wasBlocked) {
657         return *Cache::blockedPixmap;
658     }
659 
660     int w = i->size().width();
661     int h = i->size().height();
662 
663     if (i->hasAlpha() && QApplication::desktop()->paintEngine() &&
664             !QApplication::desktop()->paintEngine()->hasFeature(QPaintEngine::PorterDuff)) {
665         QImage im(w, h, QImage::Format_ARGB32_Premultiplied);
666         QPainter paint(&im);
667         paint.setCompositionMode(QPainter::CompositionMode_Source);
668         ImagePainter pi(i);
669         pi.paint(0, 0, &paint);
670         paint.end();
671         return QPixmap::fromImage(im, Qt::NoOpaqueDetection);
672     } else {
673         QPixmap pm(w, h);
674         if (i->hasAlpha()) {
675             pm.fill(Qt::transparent);
676         }
677         QPainter paint(&pm);
678         paint.setCompositionMode(QPainter::CompositionMode_Source);
679         ImagePainter pi(i);
680         pi.paint(0, 0, &paint);
681         paint.end();
682         return pm;
683     }
684 }
685 
pixmap_size() const686 QSize CachedImage::pixmap_size() const
687 {
688     if (m_wasBlocked) {
689         return Cache::blockedPixmap->size();
690     }
691     if (m_hadError) {
692         return Cache::brokenPixmap->size();
693     }
694     if (i) {
695         return i->size();
696     }
697     return QSize();
698 }
699 
imageHasGeometry(khtmlImLoad::Image *,int width,int height)700 void CachedImage::imageHasGeometry(khtmlImLoad::Image * /*img*/, int width, int height)
701 {
702 #ifdef LOADER_DEBUG
703     qCDebug(KHTML_LOG) << this << "got geometry" << width << "x" << height;
704 #endif
705 
706     do_notify(QRect(0, 0, width, height));
707 }
708 
imageChange(khtmlImLoad::Image *,QRect region)709 void CachedImage::imageChange(khtmlImLoad::Image * /*img*/, QRect region)
710 {
711 #ifdef LOADER_DEBUG
712     qCDebug(KHTML_LOG) << "Image" << this << "change" <<
713              region.x() << "," << region.y() << ":" << region.width() << "x" << region.height();
714 #endif
715 
716     //### this is overly conservative -- I guess we need to also specify reason,
717     //e.g. repaint vs. changed !!!
718     delete bg;
719     bg = nullptr;
720 
721     do_notify(region);
722 }
723 
doNotifyFinished()724 void CachedImage::doNotifyFinished()
725 {
726     for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
727         it.next().value()->notifyFinished(this);
728     }
729 }
730 
imageError(khtmlImLoad::Image *)731 void CachedImage::imageError(khtmlImLoad::Image * /*img*/)
732 {
733     error(0, nullptr);
734 }
735 
imageDone(khtmlImLoad::Image *)736 void CachedImage::imageDone(khtmlImLoad::Image * /*img*/)
737 {
738 #ifdef LOADER_DEBUG
739     qCDebug(KHTML_LOG) << "Image is done:" << this;
740 #endif
741     m_status = Persistent;
742     m_loading = false;
743     doNotifyFinished();
744 }
745 
746 // QRect CachedImage::valid_rect() const
747 // {
748 //     if (m_wasBlocked) return Cache::blockedPixmap->rect();
749 //     return (m_hadError ? Cache::brokenPixmap->rect() : m ? m->frameRect() : ( p ? p->rect() : QRect()) );
750 // }
751 
do_notify(const QRect & r)752 void CachedImage::do_notify(const QRect &r)
753 {
754     for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
755 #ifdef LOADER_DEBUG
756         qCDebug(KHTML_LOG) << "image" << this << "notify of geom client" << it.peekNext().value();
757 #endif
758         it.next().value()->updatePixmap(r, this);
759     }
760 }
761 
setShowAnimations(KHTMLSettings::KAnimationAdvice showAnimations)762 void CachedImage::setShowAnimations(KHTMLSettings::KAnimationAdvice showAnimations)
763 {
764     if (i) {
765         i->setShowAnimations(showAnimations);
766     }
767 }
768 
clear()769 void CachedImage::clear()
770 {
771     delete i;   i = new khtmlImLoad::Image(this);
772     delete scaled;  scaled = nullptr;
773     bgColor = qRgba(0, 0, 0, 0xff);
774     delete bg;  bg = nullptr;
775     bgSize = QSize(-1, -1);
776 
777     setSize(0);
778 }
779 
data(QBuffer & _buffer,bool eof)780 void CachedImage::data(QBuffer &_buffer, bool eof)
781 {
782 #ifdef LOADER_DEBUG
783     qCDebug(KHTML_LOG) << this << "buffersize =" << _buffer.buffer().size() << ", eof =" << eof << ", pos :" << _buffer.pos();
784 #endif
785     i->processData((uchar *)_buffer.data().data(), _buffer.pos());
786 
787     _buffer.close();
788 
789     if (eof) {
790         i->processEOF();
791     }
792 }
793 
finish()794 void CachedImage::finish()
795 {
796     CachedObject::finish();
797     m_loading = false;
798     QSize s = pixmap_size();
799     setSize(s.width() * s.height() * 2);
800 }
801 
error(int,const char *)802 void CachedImage::error(int /*err*/, const char * /*text*/)
803 {
804     clear();
805     m_hadError = true;
806     m_loading = false;
807     do_notify(QRect(0, 0, 16, 16));
808     for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
809         it.next().value()->notifyFinished(this);
810     }
811 }
812 
813 // -------------------------------------------------------------------------------------------
814 
CachedSound(DocLoader * dl,const DOMString & url,KIO::CacheControl _cachePolicy,const char *)815 CachedSound::CachedSound(DocLoader *dl, const DOMString &url, KIO::CacheControl _cachePolicy, const char *)
816     : CachedObject(url, Sound, _cachePolicy, 0)
817 {
818     setAccept(QLatin1String("*/*"));   // should be whatever phonon would accept...
819     Cache::loader()->load(dl, this, false/*incremental*/, 2);
820     m_loading = true;
821 }
822 
ref(CachedObjectClient * c)823 void CachedSound::ref(CachedObjectClient *c)
824 {
825     CachedObject::ref(c);
826 
827     if (!m_loading) {
828         c->notifyFinished(this);
829     }
830 }
831 
data(QBuffer & buffer,bool eof)832 void CachedSound::data(QBuffer &buffer, bool eof)
833 {
834     if (!eof) {
835         return;
836     }
837     buffer.close();
838     setSize(buffer.buffer().size());
839 
840     m_sound = buffer.buffer();
841     m_loading = false;
842     checkNotify();
843 }
844 
checkNotify()845 void CachedSound::checkNotify()
846 {
847     if (m_loading) {
848         return;
849     }
850 
851     for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
852         it.next().value()->notifyFinished(this);
853     }
854 }
855 
error(int,const char *)856 void CachedSound::error(int /*err*/, const char * /*text*/)
857 {
858     m_loading = false;
859     checkNotify();
860 }
861 
862 // -------------------------------------------------------------------------------------------
863 
CachedFont(DocLoader * dl,const DOMString & url,KIO::CacheControl _cachePolicy,const char *)864 CachedFont::CachedFont(DocLoader *dl, const DOMString &url, KIO::CacheControl _cachePolicy, const char *)
865     : CachedObject(url, Font, _cachePolicy, 0)
866 {
867     setAccept(QLatin1String("*/*"));
868     // Fonts are desired early because their absence will lead to a page being rendered
869     // with a default replacement, then the text being re-rendered with the new font when it arrives.
870     // This can be fairly disturbing for the reader - more than missing images for instance.
871     Cache::loader()->load(dl, this, false /*incremental*/, -4);
872     m_loading = true;
873 }
874 
ref(CachedObjectClient * c)875 void CachedFont::ref(CachedObjectClient *c)
876 {
877     CachedObject::ref(c);
878 
879     if (!m_loading) {
880         c->notifyFinished(this);
881     }
882 }
883 
data(QBuffer & buffer,bool eof)884 void CachedFont::data(QBuffer &buffer, bool eof)
885 {
886     if (!eof) {
887         return;
888     }
889     buffer.close();
890     m_font = buffer.buffer();
891 
892     // some fonts are compressed.
893     {
894         KCompressionDevice::CompressionType compressionType = KCompressionDevice::compressionTypeForMimeType(mimetype());
895         QScopedPointer<KCompressionDevice> dev(new KCompressionDevice(&buffer, false /*autoDeleteInDevice*/, compressionType));
896         if (dev && dev->open(QIODevice::ReadOnly)) {
897             m_font = dev->readAll();
898         }
899     }
900 
901     // handle decoding of WOFF fonts
902     int woffStatus = eWOFF_ok;
903     if (int need = WOFF::getDecodedSize(m_font.constData(), m_font.size(), &woffStatus)) {
904         // qCDebug(KHTML_LOG) << "***************************** Got WOFF FoNT";
905         m_hadError = true;
906         do {
907             if (WOFF_FAILURE(woffStatus)) {
908                 break;
909             }
910             QByteArray wbuffer;
911             wbuffer.resize(need);
912             int len;
913             woffStatus = eWOFF_ok;
914             WOFF::decodeToBuffer(m_font.constData(), m_font.size(), wbuffer.data(), wbuffer.size(), &len, &woffStatus);
915             if (WOFF_FAILURE(woffStatus)) {
916                 break;
917             }
918             wbuffer.resize(len);
919             m_font = wbuffer;
920             m_hadError = false;
921         } while (false);
922     } else if (m_font.isEmpty()) {
923         m_hadError = true;
924     } else {
925         // qCDebug(KHTML_LOG) << "******** #################### ********************* NON WOFF font";
926     }
927     setSize(m_font.size());
928 
929     m_loading = false;
930     checkNotify();
931 }
932 
checkNotify()933 void CachedFont::checkNotify()
934 {
935     if (m_loading) {
936         return;
937     }
938 
939     for (QHashIterator<CachedObjectClient *, CachedObjectClient *> it(m_clients); it.hasNext();) {
940         it.next().value()->notifyFinished(this);
941     }
942 }
943 
error(int,const char *)944 void CachedFont::error(int /*err*/, const char * /*text*/)
945 {
946     m_loading = false;
947     m_hadError = true;
948     checkNotify();
949 }
950 
951 // ------------------------------------------------------------------------------------------
952 
Request(DocLoader * dl,CachedObject * _object,bool _incremental,int _priority)953 Request::Request(DocLoader *dl, CachedObject *_object, bool _incremental, int _priority)
954 {
955     object = _object;
956     object->setRequest(this);
957     incremental = _incremental;
958     priority = _priority;
959     m_docLoader = dl;
960 }
961 
~Request()962 Request::~Request()
963 {
964     object->setRequest(nullptr);
965 }
966 
967 // ------------------------------------------------------------------------------------------
968 
DocLoader(KHTMLPart * part,DocumentImpl * doc)969 DocLoader::DocLoader(KHTMLPart *part, DocumentImpl *doc)
970 {
971     m_cachePolicy = KIO::CC_Verify;
972     m_creationDate = QDateTime::currentDateTime();
973     m_bautoloadImages = true;
974     m_showAnimations = KHTMLSettings::KAnimationEnabled;
975     m_part = part;
976     m_doc = doc;
977 
978     Cache::docloader->append(this);
979 }
980 
~DocLoader()981 DocLoader::~DocLoader()
982 {
983     clearPreloads();
984     Cache::loader()->cancelRequests(this);
985     Cache::docloader->removeAll(this);
986 }
987 
setCacheCreationDate(const QDateTime & _creationDate)988 void DocLoader::setCacheCreationDate(const QDateTime &_creationDate)
989 {
990     if (_creationDate.isValid()) {
991         m_creationDate = _creationDate;
992     } else {
993         m_creationDate = QDateTime::currentDateTime();
994     }
995 }
996 
setExpireDate(const QDateTime & _expireDate)997 void DocLoader::setExpireDate(const QDateTime &_expireDate)
998 {
999     m_expireDate = _expireDate;
1000 
1001 #ifdef CACHE_DEBUG
1002     qCDebug(KHTML_LOG) << QDateTime::currentDateTime().secsTo(m_expireDate) << "seconds left until reload required.";
1003 #endif
1004 }
1005 
setRelativeExpireDate(qint64 seconds)1006 void DocLoader::setRelativeExpireDate(qint64 seconds)
1007 {
1008     m_expireDate = m_creationDate.addSecs(seconds);
1009 }
1010 
insertCachedObject(CachedObject * o) const1011 void DocLoader::insertCachedObject(CachedObject *o) const
1012 {
1013     m_docObjects.insert(o);
1014 }
1015 
needReload(CachedObject * existing,const QString & fullURL)1016 bool DocLoader::needReload(CachedObject *existing, const QString &fullURL)
1017 {
1018     bool reload = false;
1019     if (m_cachePolicy == KIO::CC_Verify) {
1020         if (!m_reloadedURLs.contains(fullURL)) {
1021             if (existing && existing->isExpired() && !existing->isPreloaded()) {
1022                 Cache::removeCacheEntry(existing);
1023                 m_reloadedURLs.append(fullURL);
1024                 reload = true;
1025             }
1026         }
1027     } else if ((m_cachePolicy == KIO::CC_Reload) || (m_cachePolicy == KIO::CC_Refresh)) {
1028         if (!m_reloadedURLs.contains(fullURL)) {
1029             if (existing && !existing->isPreloaded()) {
1030                 Cache::removeCacheEntry(existing);
1031             }
1032             if (!existing || !existing->isPreloaded()) {
1033                 m_reloadedURLs.append(fullURL);
1034                 reload = true;
1035             }
1036         }
1037     }
1038     return reload;
1039 }
1040 
registerPreload(CachedObject * resource)1041 void DocLoader::registerPreload(CachedObject *resource)
1042 {
1043     if (!resource || resource->isLoaded() || m_preloads.contains(resource)) {
1044         return;
1045     }
1046     resource->increasePreloadCount();
1047     m_preloads.insert(resource);
1048     resource->setProspectiveRequest();
1049 #ifdef PRELOAD_DEBUG
1050     fprintf(stderr, "PRELOADING %s\n", resource->url().string().toLatin1().data());
1051 #endif
1052 }
1053 
clearPreloads()1054 void DocLoader::clearPreloads()
1055 {
1056     printPreloadStats();
1057     QSet<CachedObject *>::iterator end = m_preloads.end();
1058     for (QSet<CachedObject *>::iterator it = m_preloads.begin(); it != end; ++it) {
1059         CachedObject *res = *it;
1060         res->decreasePreloadCount();
1061         if (res->preloadResult() == CachedObject::PreloadNotReferenced || res->hadError()) {
1062             Cache::removeCacheEntry(res);
1063         }
1064     }
1065     m_preloads.clear();
1066 }
1067 
printPreloadStats()1068 void DocLoader::printPreloadStats()
1069 {
1070 #ifdef PRELOAD_DEBUG
1071     unsigned scripts = 0;
1072     unsigned scriptMisses = 0;
1073     unsigned stylesheets = 0;
1074     unsigned stylesheetMisses = 0;
1075     unsigned images = 0;
1076     unsigned imageMisses = 0;
1077     QSet<CachedObject *>::iterator end = m_preloads.end();
1078     for (QSet<CachedObject *>::iterator it = m_preloads.begin(); it != end; ++it) {
1079         CachedObject *res = *it;
1080         if (res->preloadResult() == CachedObject::PreloadNotReferenced) {
1081             fprintf(stderr, "!! UNREFERENCED PRELOAD %s\n", res->url().string().toLatin1().data());
1082         } else if (res->preloadResult() == CachedObject::PreloadReferencedWhileComplete) {
1083             fprintf(stderr, "HIT COMPLETE PRELOAD %s\n", res->url().string().toLatin1().data());
1084         } else if (res->preloadResult() == CachedObject::PreloadReferencedWhileLoading) {
1085             fprintf(stderr, "HIT LOADING PRELOAD %s\n", res->url().string().toLatin1().data());
1086         }
1087 
1088         if (res->type() == CachedObject::Script) {
1089             scripts++;
1090             if (res->preloadResult() < CachedObject::PreloadReferencedWhileLoading) {
1091                 scriptMisses++;
1092             }
1093         } else if (res->type() == CachedObject::CSSStyleSheet) {
1094             stylesheets++;
1095             if (res->preloadResult() < CachedObject::PreloadReferencedWhileLoading) {
1096                 stylesheetMisses++;
1097             }
1098         } else {
1099             images++;
1100             if (res->preloadResult() < CachedObject::PreloadReferencedWhileLoading) {
1101                 imageMisses++;
1102             }
1103         }
1104     }
1105     if (scripts) {
1106         fprintf(stderr, "SCRIPTS: %d (%d hits, hit rate %d%%)\n", scripts, scripts - scriptMisses, (scripts - scriptMisses) * 100 / scripts);
1107     }
1108     if (stylesheets) {
1109         fprintf(stderr, "STYLESHEETS: %d (%d hits, hit rate %d%%)\n", stylesheets, stylesheets - stylesheetMisses, (stylesheets - stylesheetMisses) * 100 / stylesheets);
1110     }
1111     if (images) {
1112         fprintf(stderr, "IMAGES:  %d (%d hits, hit rate %d%%)\n", images, images - imageMisses, (images - imageMisses) * 100 / images);
1113     }
1114 #endif
1115 }
1116 
securityCheckUrl(const QUrl & fullURL,KHTMLPart * part,DOM::DocumentImpl * doc,bool doRedirectCheck,bool isImg)1117 static inline bool securityCheckUrl(const QUrl &fullURL, KHTMLPart *part, DOM::DocumentImpl *doc,
1118                                     bool doRedirectCheck, bool isImg)
1119 {
1120     if (!fullURL.isValid()) {
1121         return false;
1122     }
1123     if (part && part->onlyLocalReferences() && fullURL.scheme() != "file" && fullURL.scheme() != "data") {
1124         return false;
1125     }
1126     if (doRedirectCheck && doc) {
1127         if (isImg && part && part->forcePermitLocalImages() && fullURL.scheme() == "file") {
1128             return true;
1129         } else {
1130             return KUrlAuthorized::authorizeUrlAction("redirect", doc->URL(), fullURL);
1131         }
1132     }
1133 
1134     return true;
1135 }
1136 
1137 #define DOCLOADER_SECCHECK_IMP(doRedirectCheck,isImg,failValue) \
1138     QUrl fullURL(m_doc->completeURL(url.string())); \
1139     if (!securityCheckUrl(fullURL, m_part, m_doc, doRedirectCheck, isImg)) \
1140         return failValue;
1141 
1142 #define DOCLOADER_SECCHECK(doRedirectCheck)      DOCLOADER_SECCHECK_IMP(doRedirectCheck, false, nullptr)
1143 #define DOCLOADER_SECCHECK_BOOL(doRedirectCheck) DOCLOADER_SECCHECK_IMP(doRedirectCheck, false, false)
1144 #define DOCLOADER_SECCHECK_IMG(doRedirectCheck)  DOCLOADER_SECCHECK_IMP(doRedirectCheck, true,  nullptr)
1145 
willLoadMediaElement(const DOM::DOMString & url)1146 bool DocLoader::willLoadMediaElement(const DOM::DOMString &url)
1147 {
1148     DOCLOADER_SECCHECK_BOOL(true);
1149 
1150     return true;
1151 }
1152 
requestImage(const DOM::DOMString & url)1153 CachedImage *DocLoader::requestImage(const DOM::DOMString &url)
1154 {
1155     DOCLOADER_SECCHECK_IMG(true);
1156 
1157     CachedImage *i = Cache::requestObject<CachedImage, CachedObject::Image>(this, fullURL, nullptr);
1158 
1159     if (i && i->status() == CachedObject::Unknown && autoloadImages()) {
1160         Cache::loader()->load(this, i, true /*incremental*/);
1161     }
1162 
1163     return i;
1164 }
1165 
requestStyleSheet(const DOM::DOMString & url,const QString & charset,const char * accept,bool userSheet)1166 CachedCSSStyleSheet *DocLoader::requestStyleSheet(const DOM::DOMString &url, const QString &charset,
1167         const char *accept, bool userSheet)
1168 {
1169     DOCLOADER_SECCHECK(!userSheet);
1170 
1171     CachedCSSStyleSheet *s = Cache::requestObject<CachedCSSStyleSheet, CachedObject::CSSStyleSheet>(this, fullURL, accept);
1172     if (s && !charset.isEmpty()) {
1173         s->setCharsetHint(charset);
1174     }
1175     return s;
1176 }
1177 
requestScript(const DOM::DOMString & url,const QString & charset)1178 CachedScript *DocLoader::requestScript(const DOM::DOMString &url, const QString &charset)
1179 {
1180     DOCLOADER_SECCHECK(true);
1181     if (! KHTMLGlobal::defaultHTMLSettings()->isJavaScriptEnabled(fullURL.host()) ||
1182             KHTMLGlobal::defaultHTMLSettings()->isAdFiltered(fullURL.url())) {
1183         return nullptr;
1184     }
1185 
1186     CachedScript *s = Cache::requestObject<CachedScript, CachedObject::Script>(this, fullURL, nullptr);
1187     if (s && !charset.isEmpty()) {
1188         s->setCharset(charset);
1189     }
1190     return s;
1191 }
1192 
requestSound(const DOM::DOMString & url)1193 CachedSound *DocLoader::requestSound(const DOM::DOMString &url)
1194 {
1195     DOCLOADER_SECCHECK(true);
1196     CachedSound *s = Cache::requestObject<CachedSound, CachedObject::Sound>(this, fullURL, nullptr);
1197     return s;
1198 }
1199 
requestFont(const DOM::DOMString & url)1200 CachedFont *DocLoader::requestFont(const DOM::DOMString &url)
1201 {
1202     DOCLOADER_SECCHECK(true);
1203     CachedFont *s = Cache::requestObject<CachedFont, CachedObject::Font>(this, fullURL, nullptr);
1204     return s;
1205 }
1206 
1207 #undef DOCLOADER_SECCHECK
1208 
setAutoloadImages(bool enable)1209 void DocLoader::setAutoloadImages(bool enable)
1210 {
1211     if (enable == m_bautoloadImages) {
1212         return;
1213     }
1214 
1215     m_bautoloadImages = enable;
1216 
1217     if (!m_bautoloadImages) {
1218         return;
1219     }
1220 
1221     for (QSetIterator<CachedObject *> it(m_docObjects); it.hasNext();) {
1222         CachedObject *cur = it.next();
1223         if (cur->type() == CachedObject::Image) {
1224             CachedImage *img = const_cast<CachedImage *>(static_cast<const CachedImage *>(cur));
1225 
1226             CachedObject::Status status = img->status();
1227             if (status != CachedObject::Unknown) {
1228                 continue;
1229             }
1230 
1231             Cache::loader()->load(this, img, true /*incremental*/);
1232         }
1233     }
1234 }
1235 
setShowAnimations(KHTMLSettings::KAnimationAdvice showAnimations)1236 void DocLoader::setShowAnimations(KHTMLSettings::KAnimationAdvice showAnimations)
1237 {
1238     if (showAnimations == m_showAnimations) {
1239         return;
1240     }
1241     m_showAnimations = showAnimations;
1242 
1243     for (QSetIterator<CachedObject *> it(m_docObjects); it.hasNext();) {
1244         CachedObject *cur = it.next();
1245         if (cur->type() == CachedObject::Image) {
1246             CachedImage *img = const_cast<CachedImage *>(static_cast<const CachedImage *>(cur));
1247 
1248             img->setShowAnimations(m_showAnimations);
1249         }
1250     }
1251 }
1252 
1253 // ------------------------------------------------------------------------------------------
1254 
Loader()1255 Loader::Loader() : QObject()
1256 {
1257     m_supportedImageTypes = khtmlImLoad::ImageManager::loaderDatabase()->supportedMimeTypes();
1258 }
1259 
~Loader()1260 Loader::~Loader()
1261 {
1262     qDeleteAll(m_requestsLoading);
1263 }
1264 
load(DocLoader * dl,CachedObject * object,bool incremental,int priority)1265 void Loader::load(DocLoader *dl, CachedObject *object, bool incremental, int priority)
1266 {
1267     Request *req = new Request(dl, object, incremental, priority);
1268     scheduleRequest(req);
1269     emit requestStarted(req->m_docLoader, req->object);
1270 }
1271 
scheduleRequest(Request * req)1272 void Loader::scheduleRequest(Request *req)
1273 {
1274 #ifdef LOADER_DEBUG
1275     qCDebug(KHTML_LOG) << "starting Loader url =" << req->object->url().string();
1276 #endif
1277 
1278     QUrl u(req->object->url().string());
1279     KIO::TransferJob *job = KIO::get(u, KIO::NoReload, KIO::HideProgressInfo /*no GUI*/);
1280 
1281     job->addMetaData("cache", KIO::getCacheControlString(req->object->cachePolicy()));
1282     if (!req->object->accept().isEmpty()) {
1283         job->addMetaData("accept", req->object->accept());
1284     }
1285     if (req->m_docLoader) {
1286         job->addMetaData("referrer",  req->m_docLoader->doc()->URL().url());
1287         KHTMLPart *part = req->m_docLoader->part();
1288         if (part) {
1289             job->addMetaData("cross-domain", part->toplevelURL().url());
1290             if (part->widget()) {
1291                 KJobWidgets::setWindow(job, part->widget()->topLevelWidget());
1292             }
1293         }
1294     }
1295 
1296     connect(job, SIGNAL(result(KJob*)), this, SLOT(slotFinished(KJob*)));
1297     connect(job, SIGNAL(mimetype(KIO::Job*,QString)), this, SLOT(slotMimetype(KIO::Job*,QString)));
1298     connect(job, SIGNAL(data(KIO::Job*,QByteArray)),
1299             SLOT(slotData(KIO::Job*,QByteArray)));
1300 
1301     KIO::Scheduler::setJobPriority(job, req->priority);
1302 
1303     m_requestsLoading.insertMulti(job, req);
1304 }
1305 
slotMimetype(KIO::Job * j,const QString & s)1306 void Loader::slotMimetype(KIO::Job *j, const QString &s)
1307 {
1308     Request *r = m_requestsLoading.value(j);
1309     if (!r) {
1310         return;
1311     }
1312     CachedObject *o = r->object;
1313 
1314     // Mozilla plain ignores any  mimetype that doesn't have / in it, and handles it as "",
1315     // including when being picky about mimetypes. Match that for better compatibility with broken servers.
1316     if (s.contains('/')) {
1317         o->m_mimetype = s;
1318     } else {
1319         o->m_mimetype = "";
1320     }
1321 }
1322 
slotFinished(KJob * job)1323 void Loader::slotFinished(KJob *job)
1324 {
1325     KIO::TransferJob *j = static_cast<KIO::TransferJob *>(job);
1326     Request *r = m_requestsLoading.take(j);
1327 
1328     if (!r) {
1329         return;
1330     }
1331 
1332     bool reqFailed = false;
1333     if (j->error()) {
1334         reqFailed = true;
1335     } else if (j->isErrorPage()) {
1336         if (r->object->type() == CachedObject::Image && m_supportedImageTypes.contains(r->object->m_mimetype)) {
1337             // Do not set the request as a failed, we asked for an image and got it
1338             // as the content of the error response (e.g. 404)
1339         } else {
1340             reqFailed = true;
1341         }
1342     }
1343 
1344     if (reqFailed) {
1345 #ifdef LOADER_DEBUG
1346         qCDebug(KHTML_LOG) << "ERROR: job->error() =" << j->error() << ", job->isErrorPage() =" << j->isErrorPage();
1347 #endif
1348         r->object->error(job->error(), job->errorText().toLatin1().constData());
1349         emit requestFailed(r->m_docLoader, r->object);
1350     } else {
1351         QString cs = j->queryMetaData("charset");
1352         if (!cs.isEmpty()) {
1353             r->object->setCharset(cs);
1354         }
1355         r->object->data(r->m_buffer, true);
1356         emit requestDone(r->m_docLoader, r->object);
1357         QDateTime expireDate = QDateTime::fromTime_t(j->queryMetaData("expire-date").toLong());
1358 #ifdef LOADER_DEBUG
1359         qCDebug(KHTML_LOG) << "url =" << j->url().url();
1360 #endif
1361         r->object->setExpireDate(expireDate);
1362 
1363         if (r->object->type() == CachedObject::Image) {
1364             QString fn = j->queryMetaData("content-disposition-filename");
1365             static_cast<CachedImage *>(r->object)->setSuggestedFilename(fn);
1366 #ifdef IMAGE_TITLES
1367             static_cast<CachedImage *>(r->object)->setSuggestedTitle(fn);
1368             QTemporaryFile tf;
1369             tf.open();
1370             tf.write((const char *)r->m_buffer.buffer().data(), r->m_buffer.size());
1371             tf.flush();
1372             KFileMetaInfo kfmi(tf.fileName());
1373             if (!kfmi.isEmpty()) {
1374                 KFileMetaInfoItem i = kfmi.item("Name");
1375                 if (i.isValid()) {
1376                     static_cast<CachedImage *>(r->object)->setSuggestedTitle(i.string());
1377                 } else {
1378                     i = kfmi.item("Title");
1379                     if (i.isValid()) {
1380                         static_cast<CachedImage *>(r->object)->setSuggestedTitle(i.string());
1381                     }
1382                 }
1383             }
1384 #endif
1385         }
1386     }
1387 
1388     r->object->finish();
1389 
1390 #ifdef LOADER_DEBUG
1391     qCDebug(KHTML_LOG) << "JOB FINISHED" << r->object << ":" << r->object->url().string();
1392 #endif
1393 
1394     delete r;
1395 }
1396 
slotData(KIO::Job * job,const QByteArray & data)1397 void Loader::slotData(KIO::Job *job, const QByteArray &data)
1398 {
1399     Request *r = m_requestsLoading.value(job);
1400     if (!r) {
1401         qCDebug(KHTML_LOG) << "got data for unknown request!";
1402         return;
1403     }
1404 
1405     if (!r->m_buffer.isOpen()) {
1406         r->m_buffer.open(QIODevice::WriteOnly);
1407     }
1408 
1409     r->m_buffer.write(data.data(), data.size());
1410 
1411     if (r->incremental) {
1412         r->object->data(r->m_buffer, false);
1413     }
1414 }
1415 
numRequests(DocLoader * dl) const1416 int Loader::numRequests(DocLoader *dl) const
1417 {
1418     int res = 0;
1419     foreach (Request *req, m_requestsLoading)
1420         if (req->m_docLoader == dl) {
1421             res++;
1422         }
1423 
1424     return res;
1425 }
1426 
cancelRequests(DocLoader * dl)1427 void Loader::cancelRequests(DocLoader *dl)
1428 {
1429     QMutableHashIterator<KIO::Job *, Request *> lIt(m_requestsLoading);
1430     while (lIt.hasNext()) {
1431         lIt.next();
1432         if (lIt.value()->m_docLoader == dl) {
1433             //qCDebug(KHTML_LOG) << "canceling loading request for" << lIt.current()->object->url().string();
1434             KIO::Job *job = static_cast<KIO::Job *>(lIt.key());
1435             Cache::removeCacheEntry(lIt.value()->object);
1436             delete lIt.value();
1437             lIt.remove();
1438             job->kill();
1439         }
1440     }
1441 }
1442 
jobForRequest(const DOM::DOMString & url) const1443 KIO::Job *Loader::jobForRequest(const DOM::DOMString &url) const
1444 {
1445     QHashIterator<KIO::Job *, Request *> it(m_requestsLoading);
1446     while (it.hasNext()) {
1447         it.next();
1448         if (it.value()->object && it.value()->object->url() == url) {
1449             return static_cast<KIO::Job *>(it.key());
1450         }
1451     }
1452 
1453     return nullptr;
1454 }
1455 
1456 // ----------------------------------------------------------------------------
1457 
1458 QHash<QString, CachedObject *> *Cache::cache;
1459 QLinkedList<DocLoader *>    *Cache::docloader;
1460 QLinkedList<CachedObject *> *Cache::freeList;
1461 Loader *Cache::m_loader;
1462 
1463 int Cache::maxSize = DEFCACHESIZE;
1464 int Cache::totalSizeOfLRU;
1465 
1466 QPixmap *Cache::nullPixmap;
1467 QPixmap *Cache::brokenPixmap;
1468 QPixmap *Cache::blockedPixmap;
1469 
init()1470 void Cache::init()
1471 {
1472     if (!cache) {
1473         cache = new QHash<QString, CachedObject *>();
1474     }
1475 
1476     if (!docloader) {
1477         docloader = new QLinkedList<DocLoader *>;
1478     }
1479 
1480     if (!nullPixmap) {
1481         nullPixmap = new QPixmap;
1482     }
1483 
1484     if (!brokenPixmap) {
1485         brokenPixmap = new QPixmap(KHTMLGlobal::iconLoader()->loadIcon("image-missing", KIconLoader::Desktop, 16, KIconLoader::DisabledState));
1486     }
1487 
1488     if (!blockedPixmap) {
1489         blockedPixmap = new QPixmap();
1490         blockedPixmap->loadFromData(blocked_icon_data, blocked_icon_len);
1491     }
1492 
1493     if (!m_loader) {
1494         m_loader = new Loader();
1495     }
1496 
1497     if (!freeList) {
1498         freeList = new QLinkedList<CachedObject *>;
1499     }
1500 }
1501 
clear()1502 void Cache::clear()
1503 {
1504     if (!cache) {
1505         return;
1506     }
1507 #ifdef CACHE_DEBUG
1508     qCDebug(KHTML_LOG) << "CLEAR!";
1509     statistics();
1510 #endif
1511 
1512 #ifndef NDEBUG
1513     bool crash = false;
1514     foreach (CachedObject *co, *cache) {
1515         if (!co->canDelete()) {
1516             qCDebug(KHTML_LOG) << " Object in cache still linked to";
1517             qCDebug(KHTML_LOG) << " -> URL :" << co->url();
1518             qCDebug(KHTML_LOG) << " -> #clients :" << co->count();
1519             crash = true;
1520 //         assert(co->canDelete());
1521         }
1522     }
1523     foreach (CachedObject *co, *freeList) {
1524         if (!co->canDelete()) {
1525             qCDebug(KHTML_LOG) << " Object in freelist still linked to";
1526             qCDebug(KHTML_LOG) << " -> URL :" << co->url();
1527             qCDebug(KHTML_LOG) << " -> #clients :" << co->count();
1528             crash = true;
1529             /*
1530             foreach (CachedObjectClient* cur, (*co->m_clients)))
1531             {
1532                 if (dynamic_cast<RenderObject*>(cur)) {
1533                     qCDebug(KHTML_LOG) << " --> RenderObject";
1534                 } else
1535                     qCDebug(KHTML_LOG) << " --> Something else";
1536             }*/
1537         }
1538 //         assert(freeList->current()->canDelete());
1539     }
1540     assert(!crash);
1541 #endif
1542     qDeleteAll(*cache);
1543     delete cache; cache = nullptr;
1544     delete nullPixmap; nullPixmap = nullptr;
1545     delete brokenPixmap; brokenPixmap = nullptr;
1546     delete blockedPixmap; blockedPixmap = nullptr;
1547     delete m_loader;  m_loader = nullptr;
1548     delete docloader; docloader = nullptr;
1549     qDeleteAll(*freeList);
1550     delete freeList; freeList = nullptr;
1551 }
1552 
1553 template<typename CachedObjectType, enum CachedObject::Type CachedType>
1554 CachedObjectType *Cache::requestObject(DocLoader *dl, const QUrl &kurl, const char *accept)
1555 {
1556     KIO::CacheControl cachePolicy = dl->cachePolicy();
1557 
1558     QString url = kurl.url();
1559     CachedObject *o = cache->value(url);
1560 
1561     if (o && o->type() != CachedType) {
1562         removeCacheEntry(o);
1563         o = nullptr;
1564     }
1565 
1566     if (o && dl->needReload(o, url)) {
1567         o = nullptr;
1568         assert(!cache->contains(url));
1569     }
1570 
1571     if (!o) {
1572 #ifdef CACHE_DEBUG
1573         qCDebug(KHTML_LOG) << "new:" << kurl.url();
1574 #endif
1575         CachedObjectType *cot = new CachedObjectType(dl, url, cachePolicy, accept);
1576         cache->insert(url, cot);
1577         if (cot->allowInLRUList()) {
1578             insertInLRUList(cot);
1579         }
1580         o = cot;
1581     }
1582 #ifdef CACHE_DEBUG
1583     else {
1584         qCDebug(KHTML_LOG) << "using pending/cached:" << kurl.url();
1585     }
1586 #endif
1587 
1588     dl->insertCachedObject(o);
1589 
1590     return static_cast<CachedObjectType *>(o);
1591 }
1592 
preloadStyleSheet(const QString & url,const QString & stylesheet_data)1593 void Cache::preloadStyleSheet(const QString &url, const QString &stylesheet_data)
1594 {
1595     if (cache->contains(url)) {
1596         removeCacheEntry(cache->value(url));
1597     }
1598 
1599     CachedCSSStyleSheet *stylesheet = new CachedCSSStyleSheet(url, stylesheet_data);
1600     cache->insert(url, stylesheet);
1601 }
1602 
preloadScript(const QString & url,const QString & script_data)1603 void Cache::preloadScript(const QString &url, const QString &script_data)
1604 {
1605     if (cache->contains(url)) {
1606         removeCacheEntry(cache->value(url));
1607     }
1608 
1609     CachedScript *script = new CachedScript(url, script_data);
1610     cache->insert(url, script);
1611 }
1612 
flush(bool force)1613 void Cache::flush(bool force)
1614 {
1615     init();
1616 
1617     if (force || totalSizeOfLRU > maxSize + maxSize / 4) {
1618         for (int i = MAX_LRU_LISTS - 1; i >= 0 && totalSizeOfLRU > maxSize; --i)
1619             while (totalSizeOfLRU > maxSize && m_LRULists[i].m_tail) {
1620                 removeCacheEntry(m_LRULists[i].m_tail);
1621             }
1622 
1623 #ifdef CACHE_DEBUG
1624         statistics();
1625 #endif
1626     }
1627 
1628     QMutableLinkedListIterator<CachedObject *> it(*freeList);
1629     while (it.hasNext()) {
1630         CachedObject *p = it.next();
1631         if (p->canDelete()) {
1632             it.remove();
1633             delete p;
1634         }
1635     }
1636 }
1637 
setSize(int bytes)1638 void Cache::setSize(int bytes)
1639 {
1640     maxSize = bytes;
1641     flush(true /* force */);
1642 }
1643 
statistics()1644 void Cache::statistics()
1645 {
1646     // this function is for debugging purposes only
1647     init();
1648 
1649     int size = 0;
1650     int msize = 0;
1651     int movie = 0;
1652     int images = 0;
1653     int scripts = 0;
1654     int stylesheets = 0;
1655     int sound = 0;
1656     int fonts = 0;
1657     foreach (CachedObject *o, *cache) {
1658         switch (o->type()) {
1659         case CachedObject::Image: {
1660             //CachedImage *im = static_cast<CachedImage *>(o);
1661             images++;
1662             /*if(im->m != 0)
1663             {
1664                 movie++;
1665                 msize += im->size();
1666             }*/
1667             break;
1668         }
1669         case CachedObject::CSSStyleSheet:
1670             stylesheets++;
1671             break;
1672         case CachedObject::Script:
1673             scripts++;
1674             break;
1675         case CachedObject::Sound:
1676             sound++;
1677             break;
1678         case CachedObject::Font:
1679             fonts++;
1680             break;
1681         }
1682         size += o->size();
1683     }
1684     size /= 1024;
1685 
1686     qCDebug(KHTML_LOG) << "------------------------- image cache statistics -------------------";
1687     qCDebug(KHTML_LOG) << "Number of items in cache:" << cache->count();
1688     qCDebug(KHTML_LOG) << "Number of cached images:" << images;
1689     qCDebug(KHTML_LOG) << "Number of cached movies:" << movie;
1690     qCDebug(KHTML_LOG) << "Number of cached scripts:" << scripts;
1691     qCDebug(KHTML_LOG) << "Number of cached stylesheets:" << stylesheets;
1692     qCDebug(KHTML_LOG) << "Number of cached sounds:" << sound;
1693     qCDebug(KHTML_LOG) << "Number of cached fonts:" << fonts;
1694     qCDebug(KHTML_LOG) << "pixmaps:   allocated space approx." << size << "kB";
1695     qCDebug(KHTML_LOG) << "movies :   allocated space approx." << msize / 1024 << "kB";
1696     qCDebug(KHTML_LOG) << "--------------------------------------------------------------------";
1697 }
1698 
removeCacheEntry(CachedObject * object)1699 void Cache::removeCacheEntry(CachedObject *object)
1700 {
1701     QString key = object->url().string();
1702 
1703     cache->remove(key);
1704     removeFromLRUList(object);
1705 
1706     foreach (DocLoader *dl, *docloader) {
1707         dl->removeCachedObject(object);
1708     }
1709 
1710     if (!object->free()) {
1711         Cache::freeList->append(object);
1712         object->m_free = true;
1713     }
1714 }
1715 
FastLog2(unsigned int j)1716 static inline int FastLog2(unsigned int j)
1717 {
1718     unsigned int log2;
1719     log2 = 0;
1720     if (j & (j - 1)) {
1721         log2 += 1;
1722     }
1723     if (j >> 16) {
1724         log2 += 16, j >>= 16;
1725     }
1726     if (j >> 8) {
1727         log2 += 8, j >>= 8;
1728     }
1729     if (j >> 4) {
1730         log2 += 4, j >>= 4;
1731     }
1732     if (j >> 2) {
1733         log2 += 2, j >>= 2;
1734     }
1735     if (j >> 1) {
1736         log2 += 1;
1737     }
1738 
1739     return log2;
1740 }
1741 
getLRUListFor(CachedObject * o)1742 static LRUList *getLRUListFor(CachedObject *o)
1743 {
1744     int accessCount = o->accessCount();
1745     int queueIndex;
1746     if (accessCount == 0) {
1747         queueIndex = 0;
1748     } else {
1749         int sizeLog = FastLog2(o->size());
1750         queueIndex = sizeLog / o->accessCount() - 1;
1751         if (queueIndex < 0) {
1752             queueIndex = 0;
1753         }
1754         if (queueIndex >= MAX_LRU_LISTS) {
1755             queueIndex = MAX_LRU_LISTS - 1;
1756         }
1757     }
1758     return &m_LRULists[queueIndex];
1759 }
1760 
removeFromLRUList(CachedObject * object)1761 void Cache::removeFromLRUList(CachedObject *object)
1762 {
1763     CachedObject *next = object->m_next;
1764     CachedObject *prev = object->m_prev;
1765 
1766     LRUList *list = getLRUListFor(object);
1767     CachedObject *&head = getLRUListFor(object)->m_head;
1768 
1769     if (next == nullptr && prev == nullptr && head != object) {
1770         return;
1771     }
1772 
1773     object->m_next = nullptr;
1774     object->m_prev = nullptr;
1775 
1776     if (next) {
1777         next->m_prev = prev;
1778     } else if (list->m_tail == object) {
1779         list->m_tail = prev;
1780     }
1781 
1782     if (prev) {
1783         prev->m_next = next;
1784     } else if (head == object) {
1785         head = next;
1786     }
1787 
1788     totalSizeOfLRU -= object->size();
1789 }
1790 
insertInLRUList(CachedObject * object)1791 void Cache::insertInLRUList(CachedObject *object)
1792 {
1793     removeFromLRUList(object);
1794 
1795     assert(object);
1796     assert(!object->free());
1797     assert(object->canDelete());
1798     assert(object->allowInLRUList());
1799 
1800     LRUList *list = getLRUListFor(object);
1801 
1802     CachedObject *&head = list->m_head;
1803 
1804     object->m_next = head;
1805     if (head) {
1806         head->m_prev = object;
1807     }
1808     head = object;
1809 
1810     if (object->m_next == nullptr) {
1811         list->m_tail = object;
1812     }
1813 
1814     totalSizeOfLRU += object->size();
1815 }
1816 
1817 // --------------------------------------
1818 
updatePixmap(const QRect &,CachedImage *)1819 void CachedObjectClient::updatePixmap(const QRect &, CachedImage *) {}
setStyleSheet(const DOM::DOMString &,const DOM::DOMString &,const DOM::DOMString &,const DOM::DOMString &)1820 void CachedObjectClient::setStyleSheet(const DOM::DOMString &/*url*/, const DOM::DOMString &/*sheet*/, const DOM::DOMString &/*charset*/, const DOM::DOMString &/*mimetype*/) {}
notifyFinished(CachedObject *)1821 void CachedObjectClient::notifyFinished(CachedObject * /*finishedObj*/) {}
error(int,const QString &)1822 void CachedObjectClient::error(int /*err*/, const QString &/*text*/) {}
1823 
1824 #undef CDEBUG
1825 
1826