1 /* ============================================================
2 * QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader
3 * Copyright (C) 2011-2020 QuiteRSS Team <quiterssteam@gmail.com>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 * ============================================================ */
18 #include "webpage.h"
19
20 #include "mainapplication.h"
21 #include "networkmanagerproxy.h"
22 #include "webpluginfactory.h"
23 #include "adblockicon.h"
24 #include "adblockmanager.h"
25
26 #include <QAction>
27 #include <QDesktopServices>
28 #include <QNetworkRequest>
29
30 QList<WebPage*> WebPage::livingPages_;
31
WebPage(QObject * parent)32 WebPage::WebPage(QObject *parent)
33 : QWebPage(parent)
34 , loadProgress_(-1)
35 {
36 networkManagerProxy_ = new NetworkManagerProxy(this, this);
37 setNetworkAccessManager(networkManagerProxy_);
38
39 setPluginFactory(new WebPluginFactory(this));
40 setForwardUnsupportedContent(true);
41
42 action(QWebPage::OpenFrameInNewWindow)->setVisible(false);
43 action(QWebPage::OpenImageInNewWindow)->setVisible(false);
44
45 connect(this, SIGNAL(loadProgress(int)), this, SLOT(progress(int)));
46 connect(this, SIGNAL(loadFinished(bool)), this, SLOT(finished()));
47
48 connect(this, SIGNAL(unsupportedContent(QNetworkReply*)),
49 this, SLOT(handleUnsupportedContent(QNetworkReply*)));
50 connect(this, SIGNAL(downloadRequested(QNetworkRequest)),
51 this, SLOT(downloadRequested(QNetworkRequest)));
52 connect(this, SIGNAL(printRequested(QWebFrame*)),
53 mainApp->mainWindow(), SLOT(slotPrint(QWebFrame*)));
54 #if QT_VERSION >= 0x050905
55 connect(this, SIGNAL(fullScreenRequested(QWebFullScreenRequest)),
56 this, SLOT(slotFullScreenRequested(QWebFullScreenRequest)));
57 #endif
58 livingPages_.append(this);
59 }
60
~WebPage()61 WebPage::~WebPage()
62 {
63 livingPages_.removeOne(this);
64 }
65
disconnectObjects()66 void WebPage::disconnectObjects()
67 {
68 livingPages_.removeOne(this);
69
70 disconnect(this);
71 networkManagerProxy_->disconnectObjects();
72 }
73
acceptNavigationRequest(QWebFrame * frame,const QNetworkRequest & request,NavigationType type)74 bool WebPage::acceptNavigationRequest(QWebFrame *frame,
75 const QNetworkRequest &request,
76 NavigationType type)
77 {
78 lastRequestType_ = type;
79 lastRequestUrl_ = request.url();
80
81 return QWebPage::acceptNavigationRequest(frame,request,type);
82 }
83
createWindow(WebWindowType type)84 QWebPage *WebPage::createWindow(WebWindowType type)
85 {
86 Q_UNUSED(type)
87
88 return mainApp->mainWindow()->createWebTab();
89 }
90
scheduleAdjustPage()91 void WebPage::scheduleAdjustPage()
92 {
93 WebView* webView = qobject_cast<WebView*>(view());
94 if (!webView) {
95 return;
96 }
97
98 if (webView->isLoading()) {
99 adjustingScheduled_ = true;
100 } else {
101 const QSize &originalSize = webView->size();
102 QSize newSize(originalSize.width() - 1, originalSize.height() - 1);
103
104 webView->resize(newSize);
105 webView->resize(originalSize);
106 }
107 }
108
isLoading() const109 bool WebPage::isLoading() const
110 {
111 return loadProgress_ < 100;
112 }
113
urlChanged(const QUrl & url)114 void WebPage::urlChanged(const QUrl &url)
115 {
116 Q_UNUSED(url)
117
118 if (isLoading()) {
119 adBlockedEntries_.clear();
120 }
121 }
122
progress(int prog)123 void WebPage::progress(int prog)
124 {
125 loadProgress_ = prog;
126 }
127
finished()128 void WebPage::finished()
129 {
130 progress(100);
131
132 if (adjustingScheduled_) {
133 adjustingScheduled_ = false;
134
135 WebView* webView = qobject_cast<WebView*>(view());
136 const QSize &originalSize = webView->size();
137 QSize newSize(originalSize.width() - 1, originalSize.height() - 1);
138
139 webView->resize(newSize);
140 webView->resize(originalSize);
141 }
142
143 // AdBlock
144 cleanBlockedObjects();
145 }
146
downloadRequested(const QNetworkRequest & request)147 void WebPage::downloadRequested(const QNetworkRequest &request)
148 {
149 mainApp->downloadManager()->download(request);
150 }
151
handleUnsupportedContent(QNetworkReply * reply)152 void WebPage::handleUnsupportedContent(QNetworkReply* reply)
153 {
154 if (!reply)
155 return;
156
157 const QUrl &url = reply->url();
158
159 switch (reply->error()) {
160 case QNetworkReply::NoError:
161 if (reply->header(QNetworkRequest::ContentTypeHeader).isValid()) {
162 QString requestUrl = reply->request().url().toString(QUrl::RemoveFragment | QUrl::RemoveQuery);
163 if (requestUrl.endsWith(QLatin1String(".swf"))) {
164 const QWebElement &docElement = mainFrame()->documentElement();
165 const QWebElement &object = docElement.findFirst(QString("object[src=\"%1\"]").arg(requestUrl));
166 const QWebElement &embed = docElement.findFirst(QString("embed[src=\"%1\"]").arg(requestUrl));
167
168 if (!object.isNull() || !embed.isNull()) {
169 qDebug() << "WebPage::UnsupportedContent" << url << "Attempt to download flash object on site!";
170 reply->deleteLater();
171 return;
172 }
173 }
174 mainApp->downloadManager()->handleUnsupportedContent(reply, mainApp->mainWindow()->askDownloadLocation_);
175 return;
176 } // fall through
177
178 case QNetworkReply::ProtocolUnknownError: {
179 qDebug() << "WebPage::UnsupportedContent" << url << "ProtocolUnknowError";
180 QDesktopServices::openUrl(url);
181
182 reply->deleteLater();
183 return;
184 }
185 default:
186 break;
187 }
188
189 qDebug() << "WebPage::UnsupportedContent error" << url << reply->errorString();
190 reply->deleteLater();
191 }
192
isPointerSafeToUse(WebPage * page)193 bool WebPage::isPointerSafeToUse(WebPage* page)
194 {
195 // Pointer to WebPage is passed with every QNetworkRequest casted to void*
196 // So there is no way to test whether pointer is still valid or not, except
197 // this hack.
198
199 return page == 0 ? false : livingPages_.contains(page);
200 }
201
populateNetworkRequest(QNetworkRequest & request)202 void WebPage::populateNetworkRequest(QNetworkRequest &request)
203 {
204 WebPage* pagePointer = this;
205
206 QVariant variant = QVariant::fromValue((void*) pagePointer);
207 request.setAttribute((QNetworkRequest::Attribute)(QNetworkRequest::User + 100), variant);
208
209 if (lastRequestUrl_ == request.url()) {
210 request.setAttribute((QNetworkRequest::Attribute)(QNetworkRequest::User + 101), lastRequestType_);
211 if (lastRequestType_ == NavigationTypeLinkClicked) {
212 request.setRawHeader("X-QuiteRSS-UserLoadAction", QByteArray("1"));
213 }
214 }
215 }
216
addAdBlockRule(const AdBlockRule * rule,const QUrl & url)217 void WebPage::addAdBlockRule(const AdBlockRule* rule, const QUrl &url)
218 {
219 AdBlockedEntry entry;
220 entry.rule = rule;
221 entry.url = url;
222
223 if (!adBlockedEntries_.contains(entry)) {
224 adBlockedEntries_.append(entry);
225 }
226 }
227
adBlockedEntries() const228 QVector<WebPage::AdBlockedEntry> WebPage::adBlockedEntries() const
229 {
230 return adBlockedEntries_;
231 }
232
cleanBlockedObjects()233 void WebPage::cleanBlockedObjects()
234 {
235 AdBlockManager* manager = AdBlockManager::instance();
236 if (!manager->isEnabled()) {
237 return;
238 }
239
240 const QWebElement docElement = mainFrame()->documentElement();
241
242 foreach (const AdBlockedEntry &entry, adBlockedEntries_) {
243 const QString urlString = entry.url.toString();
244 if (urlString.endsWith(QLatin1String(".js")) || urlString.endsWith(QLatin1String(".css"))) {
245 continue;
246 }
247
248 QString urlEnd;
249
250 int pos = urlString.lastIndexOf(QLatin1Char('/'));
251 if (pos > 8) {
252 urlEnd = urlString.mid(pos + 1);
253 }
254
255 if (urlString.endsWith(QLatin1Char('/'))) {
256 urlEnd = urlString.left(urlString.size() - 1);
257 }
258
259 QString selector("img[src$=\"%1\"], iframe[src$=\"%1\"],embed[src$=\"%1\"]");
260 QWebElementCollection elements = docElement.findAll(selector.arg(urlEnd));
261
262 foreach (QWebElement element, elements) {
263 QString src = element.attribute("src");
264 src.remove(QLatin1String("../"));
265
266 if (urlString.contains(src)) {
267 element.setStyleProperty("display", "none");
268 }
269 }
270 }
271
272 // Apply domain-specific element hiding rules
273 QString elementHiding = manager->elementHidingRulesForDomain(mainFrame()->url());
274 if (elementHiding.isEmpty()) {
275 return;
276 }
277
278 elementHiding.append(QLatin1String("\n</style>"));
279
280 QWebElement bodyElement = docElement.findFirst("body");
281 bodyElement.appendInside("<style type=\"text/css\">\n/* AdBlock */\n" + elementHiding);
282
283 // When hiding some elements, scroll position of page will change
284 // If user loaded anchor link in background tab (and didn't show it yet), fix the scroll position
285 if (view() && !view()->isVisible() && !mainFrame()->url().fragment().isEmpty()) {
286 mainFrame()->scrollToAnchor(mainFrame()->url().fragment());
287 }
288 }
289
290 #if QT_VERSION >= 0x050905
slotFullScreenRequested(QWebFullScreenRequest fullScreenRequest)291 void WebPage::slotFullScreenRequested(QWebFullScreenRequest fullScreenRequest)
292 {
293 fullScreenRequest.accept();
294 mainApp->mainWindow()->webViewFullScreen(fullScreenRequest.toggleOn());
295 }
296 #endif
297