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