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 :
|