LCOV - code coverage report
Current view: top level - lib/FangFeedDiscovery - WebPageGrabber.cpp (source / functions) Coverage Total Hit
Test: coverage.info.cleaned Lines: 74.5 % 110 82
Test Date: 2026-04-19 00:35:54 Functions: 91.7 % 12 11

            Line data    Source code
       1              : #include "WebPageGrabber.h"
       2              : #include "QWebDownload.h"
       3              : #include "QTidyLibClassic.h"
       4              : #include <QXmlStreamReader>
       5              : 
       6              : #include "FeedDiscoveryLogging.h"
       7              : 
       8           77 : WebPageGrabber::WebPageGrabber(bool handleMetaRefresh, int timeoutMS, QObject *parent, QNetworkAccessManager* networkManager) :
       9              :     QObject(parent),
      10           77 :     core(nullptr),
      11           77 :     handleMetaRefresh(handleMetaRefresh),
      12           77 :     redirectAttempts(0),
      13           77 :     error(true),
      14           77 :     done(false)
      15              : {
      16           77 :     WebDownloadConfig config;
      17           77 :     config.timeoutMs = timeoutMS;
      18           77 :     config.maxRedirects = 10;
      19           77 :     config.useInactivityTimeout = true;
      20           77 :     core = new QWebDownload(config, this, networkManager);
      21           77 :     init();
      22           77 : }
      23              : 
      24           41 : WebPageGrabber::WebPageGrabber(QObject *parent) :
      25              :     QObject(parent),
      26           41 :     core(nullptr),
      27           41 :     handleMetaRefresh(defaultHandleMetaRefresh),
      28           41 :     redirectAttempts(0),
      29           41 :     error(true),
      30           41 :     done(false)
      31              : {
      32           41 :     WebDownloadConfig config;
      33           41 :     config.timeoutMs = defaultTimeoutMs;
      34           41 :     config.maxRedirects = 10;
      35           41 :     config.useInactivityTimeout = true;
      36           41 :     core = new QWebDownload(config, this, nullptr);
      37           41 :     init();
      38           41 : }
      39              : 
      40              : 
      41            7 : void WebPageGrabber::load(const QUrl& url)
      42              : {
      43              :     // Reset!
      44            7 :     redirectAttempts = 0;
      45            7 :     error = true;
      46              : 
      47              :     // Now GO!
      48            7 :     loadInternal(url);
      49            7 : }
      50              : 
      51           40 : QString *WebPageGrabber::load(const QString& htmlString)
      52              : {
      53              :     // Reset!
      54           40 :     error = true;
      55              : 
      56           40 :     return loadInternal(htmlString, false);
      57              : }
      58              : 
      59            7 : void WebPageGrabber::loadInternal(const QUrl& url)
      60              : {
      61            7 :     originalUrl = url;
      62            7 :     core->get(url);
      63            7 : }
      64              : 
      65           45 : QString *WebPageGrabber::loadInternal(const QString& htmlString, bool handleRefresh)
      66              : {
      67           45 :     document.clear();
      68              : 
      69           45 :     QString result = QTidyLibClassic::toXhtml(htmlString);
      70           45 :     if (result.isEmpty()) {
      71            0 :         qCDebug(logFeedDiscovery) << "WebPageGrabber error!";
      72            0 :         emitReadySignal(nullptr);
      73            0 :         return nullptr;
      74              :     }
      75              : 
      76           45 :     document = result;
      77              : 
      78              :     // Check for an HTML meta refresh if requested.
      79           45 :     if (handleRefresh) {
      80            5 :         QString redirectURL = searchForRedirect(document);
      81            5 :         if (redirectAttempts > maxRedirects) {
      82            0 :             qCDebug(logFeedDiscovery) << "Error: Maximum HTML redirects";
      83            0 :             emitReadySignal(nullptr);
      84            0 :             return nullptr;
      85            5 :         } else if (!redirectURL.isEmpty()) {
      86            0 :             QUrl url(redirectURL);
      87            0 :             if (url.isValid()) {
      88              :                 // Bump counter and call our internal load method that doesn't reset it.
      89            0 :                 redirectAttempts++;
      90            0 :                 loadInternal(url);
      91            0 :                 return nullptr;
      92              :             }
      93            0 :         }
      94            5 :     }
      95              : 
      96              :     // Woo-hoo! We have a document!
      97           45 :     error = false;
      98           45 :     emitReadySignal(&document);
      99           45 :     return &document;
     100           45 : }
     101              : 
     102            2 : void WebPageGrabber::onDownloadError(const QUrl& url, const QString& errorString)
     103              : {
     104              :     Q_UNUSED(url);
     105              :     Q_UNUSED(errorString);
     106              : 
     107              :     // Crap. :(
     108            2 :     emitReadySignal(nullptr);
     109            2 : }
     110              : 
     111            5 : void WebPageGrabber::onDownloadFinished(const QUrl& url, const QByteArray& data)
     112              : {
     113              :     Q_UNUSED(url);
     114            5 :     loadInternal(data, handleMetaRefresh);
     115            5 : }
     116              : 
     117            5 : QString WebPageGrabber::searchForRedirect(const QString& document)
     118              : {
     119              :     // Examples of what we're looking for:
     120              :     //     <meta http-equiv="refresh" content="0; url=http://example.com/">
     121              :     //     <meta http-equiv="refresh" content="0;URL='http://thetudors.example.com/'" />
     122              :     //     <meta http-equiv="refresh" content="0;URL=http://www.mrericsir.com/blog/" />
     123              : 
     124            5 :     QXmlStreamReader xml;
     125            5 :     xml.addData(document);
     126              : 
     127           97 :     while (!xml.atEnd()) {
     128           92 :         xml.readNext();
     129              : 
     130           92 :         if (xml.isStartElement()) {
     131           29 :             QString tagName = xml.name().toString().toLower();
     132           29 :             if (tagName == "body") {
     133            5 :                 return QString();
     134              :             }
     135              : 
     136           24 :             if (tagName == "meta") {
     137            5 :                 QXmlStreamAttributes attributes = xml.attributes();
     138              : 
     139           10 :                 if (attributes.hasAttribute("http-equiv") && attributes.hasAttribute("content") &&
     140            5 :                         attributes.value("", "http-equiv").toString().toLower() == "refresh") {
     141              : 
     142              :                     // For this method we're assuming that URL is always the last parameter in
     143              :                     // the content attribute.
     144            0 :                     QString content = attributes.value("", "content").toString();
     145            0 :                     int index = content.indexOf("url=", 0, Qt::CaseInsensitive);
     146            0 :                     if (index >= 0) {
     147              :                         // URLs are allowed to be in quotes, so we have to check for that.
     148            0 :                         QString url = content.mid(index + 4).trimmed();  // "url=" is 4 chars
     149            0 :                         if (!url.isEmpty()) {
     150            0 :                             QChar firstChar = url.at(0);
     151            0 :                             if (firstChar == '\'' || firstChar == '\"') {
     152            0 :                                 url = url.mid(1);
     153            0 :                                 if (url.endsWith('\'') || url.endsWith('\"')) {
     154            0 :                                     url.chop(1);
     155              :                                 }
     156              :                             }
     157            0 :                             return url;
     158              :                         }
     159            0 :                     }
     160            0 :                 }
     161            5 :             }
     162           29 :         }
     163              :     }
     164              : 
     165            0 :     return QString();
     166            5 : }
     167              : 
     168           47 : void WebPageGrabber::emitReadySignal(QString* document)
     169              : {
     170              :     // Remember that we're done.
     171           47 :     done = true;
     172              : 
     173              :     // Now emit the signal.
     174           47 :     emit ready(this, document);
     175           47 : }
     176              : 
     177          118 : void WebPageGrabber::init()
     178              : {
     179          118 :     connect(core, &QWebDownload::error, this, &WebPageGrabber::onDownloadError);
     180          118 :     connect(core, &QWebDownload::finished, this, &WebPageGrabber::onDownloadFinished);
     181          118 : }
     182              : 
     183            0 : QString WebPageGrabber::htmlToXhtml(const QByteArray& html)
     184              : {
     185            0 :     return QTidyLibClassic::toXhtml(html);
     186              : }
     187              : 
     188              : 
        

Generated by: LCOV version 2.0-1