From 2dbba7c5dc2b86a4e013754778bf395fa9f01365 Mon Sep 17 00:00:00 2001 From: Martin Rotter Date: Tue, 16 Jun 2015 12:37:38 +0200 Subject: [PATCH] VERY VERY experimental merge of AdBlock from Qupzilla and QuiteRSS. Code will undergoe heavy optimizations to fit RSS Guard philosophy. --- CMakeLists.txt | 29 + src/gui/statusbar.cpp | 3 + src/gui/statusbar.h | 7 +- src/miscellaneous/application.cpp | 3 + .../adblock/adblockaddsubscriptiondialog.cpp | 114 +++ .../adblock/adblockaddsubscriptiondialog.h | 78 ++ .../adblock/adblockaddsubscriptiondialog.ui | 94 +++ .../adblock/adblockblockednetworkreply.cpp | 99 +++ .../adblock/adblockblockednetworkreply.h | 90 +++ src/network-web/adblock/adblockdialog.cpp | 223 +++++ src/network-web/adblock/adblockdialog.h | 90 +++ src/network-web/adblock/adblockdialog.ui | 161 ++++ src/network-web/adblock/adblockicon.cpp | 270 +++++++ src/network-web/adblock/adblockicon.h | 84 ++ src/network-web/adblock/adblockmanager.cpp | 470 +++++++++++ src/network-web/adblock/adblockmanager.h | 138 ++++ src/network-web/adblock/adblockmatcher.cpp | 245 ++++++ src/network-web/adblock/adblockmatcher.h | 85 ++ src/network-web/adblock/adblockrule.cpp | 764 ++++++++++++++++++ src/network-web/adblock/adblockrule.h | 191 +++++ src/network-web/adblock/adblocksearchtree.cpp | 157 ++++ src/network-web/adblock/adblocksearchtree.h | 73 ++ .../adblock/adblocksubscription.cpp | 488 +++++++++++ src/network-web/adblock/adblocksubscription.h | 159 ++++ src/network-web/adblock/adblocktreewidget.cpp | 361 +++++++++ src/network-web/adblock/adblocktreewidget.h | 86 ++ .../adblock/followredirectreply.cpp | 96 +++ src/network-web/adblock/followredirectreply.h | 73 ++ src/network-web/downloader.cpp | 2 +- src/network-web/webbrowser.h | 4 + .../webbrowsernetworkaccessmanager.cpp | 26 +- .../webbrowsernetworkaccessmanager.h | 10 +- src/network-web/webpage.cpp | 142 +++- src/network-web/webpage.h | 37 + 34 files changed, 4944 insertions(+), 8 deletions(-) create mode 100755 src/network-web/adblock/adblockaddsubscriptiondialog.cpp create mode 100755 src/network-web/adblock/adblockaddsubscriptiondialog.h create mode 100755 src/network-web/adblock/adblockaddsubscriptiondialog.ui create mode 100755 src/network-web/adblock/adblockblockednetworkreply.cpp create mode 100755 src/network-web/adblock/adblockblockednetworkreply.h create mode 100755 src/network-web/adblock/adblockdialog.cpp create mode 100755 src/network-web/adblock/adblockdialog.h create mode 100755 src/network-web/adblock/adblockdialog.ui create mode 100755 src/network-web/adblock/adblockicon.cpp create mode 100755 src/network-web/adblock/adblockicon.h create mode 100755 src/network-web/adblock/adblockmanager.cpp create mode 100755 src/network-web/adblock/adblockmanager.h create mode 100755 src/network-web/adblock/adblockmatcher.cpp create mode 100755 src/network-web/adblock/adblockmatcher.h create mode 100755 src/network-web/adblock/adblockrule.cpp create mode 100755 src/network-web/adblock/adblockrule.h create mode 100755 src/network-web/adblock/adblocksearchtree.cpp create mode 100755 src/network-web/adblock/adblocksearchtree.h create mode 100755 src/network-web/adblock/adblocksubscription.cpp create mode 100755 src/network-web/adblock/adblocksubscription.h create mode 100755 src/network-web/adblock/adblocktreewidget.cpp create mode 100755 src/network-web/adblock/adblocktreewidget.h create mode 100755 src/network-web/adblock/followredirectreply.cpp create mode 100755 src/network-web/adblock/followredirectreply.h mode change 100644 => 100755 src/network-web/webbrowsernetworkaccessmanager.h mode change 100644 => 100755 src/network-web/webpage.cpp mode change 100644 => 100755 src/network-web/webpage.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b203fe19c..a64516256 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -419,6 +419,20 @@ set(APP_SOURCES src/network-web/discoverfeedsbutton.cpp src/network-web/googlesuggest.cpp + src/network-web/adblock/adblockaddsubscriptiondialog.cpp + src/network-web/adblock/adblockblockednetworkreply.cpp + src/network-web/adblock/adblockdialog.cpp + src/network-web/adblock/adblockicon.cpp + src/network-web/adblock/adblockmanager.cpp + src/network-web/adblock/adblockmatcher.cpp + src/network-web/adblock/adblockrule.cpp + src/network-web/adblock/adblocksearchtree.cpp + src/network-web/adblock/adblocksubscription.cpp + src/network-web/adblock/adblocktreewidget.cpp + src/network-web/adblock/followredirectreply.cpp + + + # MAIN sources. src/main.cpp ) @@ -505,6 +519,18 @@ set(APP_HEADERS src/network-web/downloadmanager.h src/network-web/discoverfeedsbutton.h src/network-web/googlesuggest.h + + src/network-web/adblock/adblockaddsubscriptiondialog.h + src/network-web/adblock/adblockblockednetworkreply.h + src/network-web/adblock/adblockdialog.h + src/network-web/adblock/adblockicon.h + src/network-web/adblock/adblockmanager.h + src/network-web/adblock/adblockmatcher.h + src/network-web/adblock/adblockrule.h + src/network-web/adblock/adblocksearchtree.h + src/network-web/adblock/adblocksubscription.h + src/network-web/adblock/adblocktreewidget.h + src/network-web/adblock/followredirectreply.h ) # APP form files. @@ -522,6 +548,9 @@ set(APP_FORMS src/gui/formdatabasecleanup.ui src/network-web/downloadmanager.ui src/network-web/downloaditem.ui + + src/network-web/adblock/adblockaddsubscriptiondialog.ui + src/network-web/adblock/adblockdialog.ui ) # APP translations. diff --git a/src/gui/statusbar.cpp b/src/gui/statusbar.cpp index 12f5ea450..d15a58349 100755 --- a/src/gui/statusbar.cpp +++ b/src/gui/statusbar.cpp @@ -31,6 +31,8 @@ StatusBar::StatusBar(QWidget *parent) : QStatusBar(parent) { setSizeGripEnabled(false); setContentsMargins(0, 0, 0, 0); + adblockIcon_ = new AdBlockIcon(this); + // Initializations of widgets for status bar. m_fullscreenSwitcher = new QToolButton(this); m_fullscreenSwitcher->setAutoRaise(true); @@ -67,6 +69,7 @@ StatusBar::StatusBar(QWidget *parent) : QStatusBar(parent) { addPermanentWidget(m_lblProgressDownload); addPermanentWidget(m_barProgressDownload); addPermanentWidget(m_fullscreenSwitcher); + addPermanentWidget(adblockIcon_); } StatusBar::~StatusBar() { diff --git a/src/gui/statusbar.h b/src/gui/statusbar.h index b6495bd6f..4a790b6ac 100755 --- a/src/gui/statusbar.h +++ b/src/gui/statusbar.h @@ -20,6 +20,8 @@ #include +#include + class QProgressBar; class QToolButton; @@ -37,6 +39,8 @@ class StatusBar : public QStatusBar { return m_fullscreenSwitcher; } + AdBlockIcon *adBlockIcon() { return adblockIcon_; } + public slots: // Progress bar operations void showProgressFeeds(int progress, const QString &label); @@ -55,7 +59,8 @@ class StatusBar : public QStatusBar { QLabel *m_lblProgressFeeds; QProgressBar *m_barProgressDownload; QLabel *m_lblProgressDownload; - QToolButton *m_fullscreenSwitcher; + QToolButton *m_fullscreenSwitcher; + AdBlockIcon* adblockIcon_; }; #endif // STATUSBAR_H diff --git a/src/miscellaneous/application.cpp b/src/miscellaneous/application.cpp index 17b64f2a1..528151473 100755 --- a/src/miscellaneous/application.cpp +++ b/src/miscellaneous/application.cpp @@ -31,6 +31,8 @@ #include #include +#include + Application::Application(const QString &id, int &argc, char **argv) : QtSingleApplication(id, argc, argv), @@ -208,6 +210,7 @@ void Application::onAboutToQuit() { mainForm()->tabWidget()->feedMessageViewer()->quit(); database()->saveDatabase(); mainForm()->saveSize(); + AdBlockManager::instance()->save(); if (locked_safely) { // Application obtained permission to close in a safe way. diff --git a/src/network-web/adblock/adblockaddsubscriptiondialog.cpp b/src/network-web/adblock/adblockaddsubscriptiondialog.cpp new file mode 100755 index 000000000..a011596b1 --- /dev/null +++ b/src/network-web/adblock/adblockaddsubscriptiondialog.cpp @@ -0,0 +1,114 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2010-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +#include "adblockaddsubscriptiondialog.h" +#include "adblockmanager.h" +#include "ui_adblockaddsubscriptiondialog.h" + +AdBlockAddSubscriptionDialog::AdBlockAddSubscriptionDialog(QWidget* parent) + : QDialog(parent) + , ui(new Ui::AdBlockAddSubscriptionDialog) +{ + ui->setupUi(this); + + m_knownSubscriptions << Subscription("EasyList (English)", ADBLOCK_EASYLIST_URL) + << Subscription("Fanboy's List (English)", "http://www.fanboy.co.nz/adblock/fanboy-adblock.txt") + << Subscription("Adversity (English)", "http://adversity.googlecode.com/hg/Adversity.txt") + << Subscription("BSI Lista Polska (Polish)", "http://www.bsi.info.pl/filtrABP.txt") + << Subscription("Czech List (Czech)", "http://adblock.dajbych.net/adblock.txt") + << Subscription("dutchblock (Dutch)", "http://groenewoudt.net/dutchblock/list.txt") + << Subscription("Filtros Nauscopicos (Spanish)", "http://abp.mozilla-hispano.org/nauscopio/filtros.txt") + << Subscription("hufilter (Hungarian)", "http://www.hufilter.hu/hufilter.txt") + << Subscription("IsraelList (Hebrew)", "http://secure.fanboy.co.nz/israelilist/IsraelList.txt") + << Subscription("Lista Basa (Polish)", "http://plok.studentlive.pl/abp.txt") + << Subscription("NLBlock (Dutch)", "http://www.verzijlbergh.com/adblock/nlblock.txt") + << Subscription("Peter Lowe's list (English)", "http://pgl.yoyo.org/adservers/serverlist.php?hostformat=adblockplus&mimetype=plaintext") + << Subscription("PLgeneral (Polish)", "http://www.niecko.pl/adblock/adblock.txt") + << Subscription("Schacks Adblock Plus liste (Danish)", "http://adblock.schack.dk/block.txt") + << Subscription("Xfiles (Italian)", "http://mozilla.gfsolone.com/filtri.txt") + << Subscription("EasyPrivacy (English)", "http://easylist-downloads.adblockplus.org/easyprivacy.txt") + << Subscription("Antisocial (English)", "http://adversity.googlecode.com/hg/Antisocial.txt") + << Subscription("RuAdList+EasyList (Russian, Ukrainian)", "https://easylist-downloads.adblockplus.org/ruadlist+easylist.txt") + << Subscription("RU AdList (Russian, Ukrainian)", "https://easylist-downloads.adblockplus.org/advblock.txt") + << Subscription("ABPindo (Indonesian)", "https://indonesianadblockrules.googlecode.com/hg/subscriptions/abpindo.txt") + << Subscription("ChinaList (Chinese)", "http://adblock-chinalist.googlecode.com/svn/trunk/adblock.txt") + << Subscription("Malware Domains list", "https://easylist-downloads.adblockplus.org/malwaredomains_full.txt"); + + foreach (const Subscription &subscription, m_knownSubscriptions) { + ui->comboBox->addItem(subscription.title); + } + + connect(ui->comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(indexChanged(int))); + indexChanged(0); +} + +QString AdBlockAddSubscriptionDialog::title() const +{ + return ui->title->text(); +} + +QString AdBlockAddSubscriptionDialog::url() const +{ + return ui->url->text(); +} + +void AdBlockAddSubscriptionDialog::indexChanged(int index) +{ + const Subscription subscription = m_knownSubscriptions.at(index); + + // "Other..." entry + if (subscription.url.isEmpty()) { + ui->title->clear(); + ui->url->clear(); + } + else { + int pos = subscription.title.indexOf(QLatin1Char('(')); + QString title = subscription.title; + + if (pos > 0) { + title = title.left(pos).trimmed(); + } + + ui->title->setText(title); + ui->title->setCursorPosition(0); + + ui->url->setText(subscription.url); + ui->url->setCursorPosition(0); + } +} + +AdBlockAddSubscriptionDialog::~AdBlockAddSubscriptionDialog() +{ + delete ui; +} diff --git a/src/network-web/adblock/adblockaddsubscriptiondialog.h b/src/network-web/adblock/adblockaddsubscriptiondialog.h new file mode 100755 index 000000000..e3dc794d1 --- /dev/null +++ b/src/network-web/adblock/adblockaddsubscriptiondialog.h @@ -0,0 +1,78 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2010-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +#ifndef ADBLOCKADDSUBSCRIPTIONDIALOG_H +#define ADBLOCKADDSUBSCRIPTIONDIALOG_H + +#include +#include + +namespace Ui +{ +class AdBlockAddSubscriptionDialog; +} + +class AdBlockAddSubscriptionDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AdBlockAddSubscriptionDialog(QWidget* parent = 0); + ~AdBlockAddSubscriptionDialog(); + + QString title() const; + QString url() const; + +private slots: + void indexChanged(int index); + +private: + Ui::AdBlockAddSubscriptionDialog* ui; + + struct Subscription { + QString title; + QString url; + + Subscription() {} + + Subscription(const QString &t, const QString &u) { + title = t; + url = u; + } + }; + + QVector m_knownSubscriptions; +}; + +#endif // ADBLOCKADDSUBSCRIPTIONDIALOG_H diff --git a/src/network-web/adblock/adblockaddsubscriptiondialog.ui b/src/network-web/adblock/adblockaddsubscriptiondialog.ui new file mode 100755 index 000000000..88b77db8a --- /dev/null +++ b/src/network-web/adblock/adblockaddsubscriptiondialog.ui @@ -0,0 +1,94 @@ + + + AdBlockAddSubscriptionDialog + + + + 0 + 0 + 557 + 162 + + + + Add Subscription + + + + + + + + + Title: + + + + + + + + + + Address: + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Add new subscription to AdBlock: + + + + + + + + + buttonBox + accepted() + AdBlockAddSubscriptionDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AdBlockAddSubscriptionDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/network-web/adblock/adblockblockednetworkreply.cpp b/src/network-web/adblock/adblockblockednetworkreply.cpp new file mode 100755 index 000000000..60fcbe705 --- /dev/null +++ b/src/network-web/adblock/adblockblockednetworkreply.cpp @@ -0,0 +1,99 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2010-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/** + * Copyright (c) 2009, Benjamin C. Meyer + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Benjamin Meyer nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "adblockblockednetworkreply.h" +#include "adblocksubscription.h" +#include "adblockrule.h" + +#include +#include + +AdBlockBlockedNetworkReply::AdBlockBlockedNetworkReply(const AdBlockRule* rule, QObject* parent) + : QNetworkReply(parent) +{ + setOperation(QNetworkAccessManager::GetOperation); + setError(QNetworkReply::ContentAccessDenied, QString("AdBlock: %1 (%2)").arg(rule->subscription()->title(), rule->filter())); + + open(QIODevice::ReadOnly); + + QTimer::singleShot(0, this, SLOT(delayedFinished())); +} + +void AdBlockBlockedNetworkReply::setRequest(const QNetworkRequest &request) +{ + QNetworkReply::setRequest(request); + setUrl(request.url()); +} + +qint64 AdBlockBlockedNetworkReply::readData(char* data, qint64 maxSize) +{ + Q_UNUSED(data); + Q_UNUSED(maxSize); + return -1; +} + +void AdBlockBlockedNetworkReply::delayedFinished() +{ + emit error(QNetworkReply::ContentAccessDenied); + emit finished(); +} + diff --git a/src/network-web/adblock/adblockblockednetworkreply.h b/src/network-web/adblock/adblockblockednetworkreply.h new file mode 100755 index 000000000..6d32615f9 --- /dev/null +++ b/src/network-web/adblock/adblockblockednetworkreply.h @@ -0,0 +1,90 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2010-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/** + * Copyright (c) 2009, Benjamin C. Meyer + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Benjamin Meyer nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef ADBLOCKBLOCKEDNETWORKREPLY_H +#define ADBLOCKBLOCKEDNETWORKREPLY_H + +#include + +class AdBlockRule; +class AdBlockSubscription; + +class AdBlockBlockedNetworkReply : public QNetworkReply +{ + Q_OBJECT + +public: + AdBlockBlockedNetworkReply(const AdBlockRule* rule, QObject* parent = 0); + void abort() {} + + void setRequest(const QNetworkRequest &request); + +protected: + qint64 readData(char* data, qint64 maxSize); + +private slots: + void delayedFinished(); + +}; + +#endif // ADBLOCKBLOCKEDNETWORKREPLY_H + diff --git a/src/network-web/adblock/adblockdialog.cpp b/src/network-web/adblock/adblockdialog.cpp new file mode 100755 index 000000000..8681b3e21 --- /dev/null +++ b/src/network-web/adblock/adblockdialog.cpp @@ -0,0 +1,223 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2010-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +#include "adblockdialog.h" +#include "adblockmanager.h" +#include "adblocksubscription.h" +#include "adblocktreewidget.h" +#include "adblockaddsubscriptiondialog.h" + +#include +#include +#include +#include +#include + +AdBlockDialog::AdBlockDialog(QWidget* parent) + : QWidget(parent) + , m_manager(AdBlockManager::instance()) + , m_currentTreeWidget(0) + , m_currentSubscription(0) + , m_loaded(false) + , m_useLimitedEasyList(false) +{ + setAttribute(Qt::WA_DeleteOnClose); + setupUi(this); + + const QRect screen = QApplication::desktop()->screenGeometry(); + const QRect size = geometry(); + move((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2); + tabWidget->setDocumentMode(false); +#ifdef Q_OS_MAC + tabWidget->setDocumentMode(false); +#endif + adblockCheckBox->setChecked(m_manager->isEnabled()); + + buttonOptions->setText(buttonOptions->text() % " "); + + QMenu* menu = new QMenu(buttonOptions); + m_actionAddRule = menu->addAction(tr("Add Rule"), this, SLOT(addRule())); + m_actionRemoveRule = menu->addAction(tr("Remove Rule"), this, SLOT(removeRule())); + menu->addSeparator(); + m_actionAddSubscription = menu->addAction(tr("Add Subscription"), this, SLOT(addSubscription())); + m_actionRemoveSubscription = menu->addAction(tr("Remove Subscription"), this, SLOT(removeSubscription())); + menu->addAction(tr("Update Subscriptions"), m_manager, SLOT(updateAllSubscriptions())); + menu->addSeparator(); + menu->addAction(tr("Learn about writing rules..."), this, SLOT(learnAboutRules())); + + buttonOptions->setMenu(menu); + connect(menu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowMenu())); + + connect(adblockCheckBox, SIGNAL(toggled(bool)), this, SLOT(enableAdBlock(bool))); + connect(search, SIGNAL(textChanged(QString)), this, SLOT(filterString(QString))); + connect(tabWidget, SIGNAL(currentChanged(int)), this, SLOT(currentChanged(int))); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(close())); + + load(); + + buttonBox->setFocus(); +} + +void AdBlockDialog::showRule(const AdBlockRule* rule) const +{ + AdBlockSubscription* subscription = rule->subscription(); + if (!subscription) { + return; + } + + for (int i = 0; i < tabWidget->count(); ++i) { + AdBlockTreeWidget* treeWidget = qobject_cast(tabWidget->widget(i)); + + if (subscription == treeWidget->subscription()) { + treeWidget->showRule(rule); + tabWidget->setCurrentIndex(i); + break; + } + } +} + +void AdBlockDialog::addRule() +{ + m_currentTreeWidget->addRule(); +} + +void AdBlockDialog::removeRule() +{ + m_currentTreeWidget->removeRule(); +} + +void AdBlockDialog::addSubscription() +{ + AdBlockAddSubscriptionDialog dialog(this); + if (dialog.exec() != QDialog::Accepted) { + return; + } + + QString title = dialog.title(); + QString url = dialog.url(); + + if (AdBlockSubscription* subscription = m_manager->addSubscription(title, url)) { + AdBlockTreeWidget* tree = new AdBlockTreeWidget(subscription, tabWidget); + int index = tabWidget->insertTab(tabWidget->count() - 1, tree, subscription->title()); + + tabWidget->setCurrentIndex(index); + } +} + +void AdBlockDialog::removeSubscription() +{ + if (m_manager->removeSubscription(m_currentSubscription)) { + delete m_currentTreeWidget; + } +} + +void AdBlockDialog::currentChanged(int index) +{ + if (index != -1) { + m_currentTreeWidget = qobject_cast(tabWidget->widget(index)); + m_currentSubscription = m_currentTreeWidget->subscription(); + + bool isEasyList = m_currentSubscription->url() == QUrl(ADBLOCK_EASYLIST_URL); + useLimitedEasyList->setVisible(isEasyList); + } +} + +void AdBlockDialog::filterString(const QString &string) +{ + if (m_currentTreeWidget && adblockCheckBox->isChecked()) { + m_currentTreeWidget->filterString(string); + } +} + +void AdBlockDialog::enableAdBlock(bool state) +{ + m_manager->setEnabled(state); + + if (state) { + load(); + } +} + +void AdBlockDialog::aboutToShowMenu() +{ + bool subscriptionEditable = m_currentSubscription && m_currentSubscription->canEditRules(); + bool subscriptionRemovable = m_currentSubscription && m_currentSubscription->canBeRemoved(); + + m_actionAddRule->setEnabled(subscriptionEditable); + m_actionRemoveRule->setEnabled(subscriptionEditable); + m_actionRemoveSubscription->setEnabled(subscriptionRemovable); +} + +void AdBlockDialog::learnAboutRules() +{ + // TODO + //mainApp->mainWindow()->openNewsTab_ = NEW_TAB_FOREGROUND; + //mainApp->mainWindow()->createWebTab(QUrl("http://adblockplus.org/en/filters")); +} + +void AdBlockDialog::loadSubscriptions() +{ + for (int i = 0; i < tabWidget->count(); ++i) { + AdBlockTreeWidget* treeWidget = qobject_cast(tabWidget->widget(i)); + treeWidget->refresh(); + } +} + +void AdBlockDialog::load() +{ + if (m_loaded || !adblockCheckBox->isChecked()) { + return; + } + + foreach (AdBlockSubscription* subscription, m_manager->subscriptions()) { + AdBlockTreeWidget* tree = new AdBlockTreeWidget(subscription, tabWidget); + tabWidget->addTab(tree, subscription->title()); + } + + m_useLimitedEasyList = m_manager->useLimitedEasyList(); + useLimitedEasyList->setChecked(m_useLimitedEasyList); + + m_loaded = true; + + QTimer::singleShot(50, this, SLOT(loadSubscriptions())); +} + +void AdBlockDialog::closeEvent(QCloseEvent* ev) +{ + if (useLimitedEasyList->isChecked() != m_useLimitedEasyList) { + m_manager->setUseLimitedEasyList(useLimitedEasyList->isChecked()); + } + + QWidget::closeEvent(ev); +} diff --git a/src/network-web/adblock/adblockdialog.h b/src/network-web/adblock/adblockdialog.h new file mode 100755 index 000000000..7563b23f0 --- /dev/null +++ b/src/network-web/adblock/adblockdialog.h @@ -0,0 +1,90 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2010-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +#ifndef ADBLOCKDIALOG_H +#define ADBLOCKDIALOG_H + +#include + +#include "ui_adblockdialog.h" + +class AdBlockSubscription; +class AdBlockTreeWidget; +class AdBlockManager; +class AdBlockRule; + +class AdBlockDialog : public QWidget, public Ui_AdBlockDialog +{ + Q_OBJECT + +public: + explicit AdBlockDialog(QWidget* parent = 0); + + void showRule(const AdBlockRule* rule) const; + +private slots: + void addRule(); + void removeRule(); + + void addSubscription(); + void removeSubscription(); + + void currentChanged(int index); + void filterString(const QString &string); + void enableAdBlock(bool state); + + void aboutToShowMenu(); + void learnAboutRules(); + + void loadSubscriptions(); + void load(); + +private: + void closeEvent(QCloseEvent* ev); + + AdBlockManager* m_manager; + AdBlockTreeWidget* m_currentTreeWidget; + AdBlockSubscription* m_currentSubscription; + + QAction* m_actionAddRule; + QAction* m_actionRemoveRule; + QAction* m_actionAddSubscription; + QAction* m_actionRemoveSubscription; + + bool m_loaded; + bool m_useLimitedEasyList; +}; + +#endif // ADBLOCKDIALOG_H + diff --git a/src/network-web/adblock/adblockdialog.ui b/src/network-web/adblock/adblockdialog.ui new file mode 100755 index 000000000..067a0437f --- /dev/null +++ b/src/network-web/adblock/adblockdialog.ui @@ -0,0 +1,161 @@ + + + AdBlockDialog + + + + 0 + 0 + 546 + 462 + + + + AdBlock Configuration + + + + + + Enable AdBlock + + + true + + + + + + + + + Search... + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 50 + 20 + + + + + + + + + + true + + + + 0 + + + + + + + Options + + + + :/images/images/adblock.png:/images/images/adblock.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + AdBlock + + + + + + + + + -1 + + + true + + + + + + + + + + + + Use only essential part of EasyList (for performance reasons) + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + + + + + + adblockCheckBox + toggled(bool) + adblockWidget + setEnabled(bool) + + + 106 + 39 + + + 349 + 74 + + + + + diff --git a/src/network-web/adblock/adblockicon.cpp b/src/network-web/adblock/adblockicon.cpp new file mode 100755 index 000000000..72b0065da --- /dev/null +++ b/src/network-web/adblock/adblockicon.cpp @@ -0,0 +1,270 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2010-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +#include "adblockicon.h" +#include "adblockrule.h" +#include "adblockmanager.h" +#include "adblocksubscription.h" +#include "webpage.h" + +#include "miscellaneous/application.h" +#include "gui/formmain.h" +#include "gui/tabwidget.h" +#include "network-web/webbrowser.h" + +#include +#include +#include +#include + + +AdBlockIcon::AdBlockIcon(QWidget *window, QWidget *parent) + : QToolButton(parent) + , m_window(window) + , m_menuAction(0) + , m_flashTimer(0) + , m_timerTicks(0) + , m_enabled(false) +{ + setFocusPolicy(Qt::NoFocus); + setStyleSheet("QToolButton { border: none; padding: 0px; }"); + + connect(this, SIGNAL(clicked(QPoint)), this, SLOT(showMenu(QPoint))); + connect(AdBlockManager::instance(), SIGNAL(enabledChanged(bool)), this, SLOT(setEnabled(bool))); +} + +void AdBlockIcon::retranslateStrings() +{ + setToolTip(tr("AdBlock lets you block unwanted content on web pages")); + + AdBlockManager::instance()->customList()->retranslateStrings(); +} + +void AdBlockIcon::popupBlocked(const QString &ruleString, const QUrl &url) +{ + int index = ruleString.lastIndexOf(QLatin1String(" (")); + + const QString subscriptionName = ruleString.left(index); + const QString filter = ruleString.mid(index + 2, ruleString.size() - index - 3); + AdBlockSubscription* subscription = AdBlockManager::instance()->subscriptionByName(subscriptionName); + if (filter.isEmpty() || !subscription) { + return; + } + + QPair pair; + pair.first = new AdBlockRule(filter, subscription); + pair.second = url; + m_blockedPopups.append(pair); + + //!** FIXME + // mApp->desktopNotifications()->showNotification(QPixmap(":images/images/adblock_big.png"), tr("Blocked popup window"), tr("AdBlock blocked unwanted popup window.")); + + if (!m_flashTimer) { + m_flashTimer = new QTimer(this); + } + + if (m_flashTimer->isActive()) { + stopAnimation(); + } + + m_flashTimer->setInterval(500); + m_flashTimer->start(); + + connect(m_flashTimer, SIGNAL(timeout()), this, SLOT(animateIcon())); +} + +QAction* AdBlockIcon::menuAction() +{ + if (!m_menuAction) { + m_menuAction = new QAction(tr("AdBlock"), this); + m_menuAction->setMenu(new QMenu); + connect(m_menuAction->menu(), SIGNAL(aboutToShow()), this, SLOT(createMenu())); + } + + m_menuAction->setIcon(QIcon(m_enabled ? ":images/images/adblock.png" : ":images/images/adblock-disabled.png")); + + return m_menuAction; +} + +void AdBlockIcon::createMenu(QMenu* menu) +{ + if (!menu) { + menu = qobject_cast(sender()); + if (!menu) { + return; + } + } + + menu->clear(); + + AdBlockManager* manager = AdBlockManager::instance(); + AdBlockCustomList* customList = manager->customList(); + + WebPage* page = qApp->mainForm()->tabWidget()->widget(qApp->mainForm()->tabWidget()->currentIndex())->webBrowser()->view()->page(); + const QUrl pageUrl = page->mainFrame()->url(); + + menu->addAction(tr("Show AdBlock &Settings"), manager, SLOT(showDialog())); + menu->addSeparator(); + + if (!pageUrl.host().isEmpty() && m_enabled && manager->canRunOnScheme(pageUrl.scheme())) { + const QString host = pageUrl.host().contains(QLatin1String("www.")) ? pageUrl.host().mid(4) : pageUrl.host(); + const QString hostFilter = QString("@@||%1^$document").arg(host); + const QString pageFilter = QString("@@|%1|$document").arg(pageUrl.toString()); + + QAction* act = menu->addAction(tr("Disable on %1").arg(host)); + act->setCheckable(true); + act->setChecked(customList->containsFilter(hostFilter)); + act->setData(hostFilter); + connect(act, SIGNAL(triggered()), this, SLOT(toggleCustomFilter())); + + act = menu->addAction(tr("Disable only on this page")); + act->setCheckable(true); + act->setChecked(customList->containsFilter(pageFilter)); + act->setData(pageFilter); + connect(act, SIGNAL(triggered()), this, SLOT(toggleCustomFilter())); + + menu->addSeparator(); + } + + if (!m_blockedPopups.isEmpty()) { + menu->addAction(tr("Blocked Popup Windows"))->setEnabled(false); + for (int i = 0; i < m_blockedPopups.count(); i++) { + const QPair &pair = m_blockedPopups.at(i); + + QString address = pair.second.toString().right(55); + QString actionText = tr("%1 with (%2)").arg(address, pair.first->filter()).replace(QLatin1Char('&'), QLatin1String("&&")); + + QAction* action = menu->addAction(actionText, manager, SLOT(showRule())); + action->setData(QVariant::fromValue((void*)pair.first)); + } + } + + menu->addSeparator(); + + QVector entries = page->adBlockedEntries(); + if (entries.isEmpty()) { + menu->addAction(tr("No content blocked"))->setEnabled(false); + } + else { + menu->addAction(tr("Blocked URL (AdBlock Rule) - click to edit rule"))->setEnabled(false); + foreach (const WebPage::AdBlockedEntry &entry, entries) { + QString address = entry.url.toString().right(55); + QString actionText = tr("%1 with (%2)").arg(address, entry.rule->filter()).replace(QLatin1Char('&'), QLatin1String("&&")); + + QAction* action = menu->addAction(actionText, manager, SLOT(showRule())); + action->setData(QVariant::fromValue((void*)entry.rule)); + } + } +} + +void AdBlockIcon::showMenu(const QPoint &pos) +{ + QMenu menu; + createMenu(&menu); + + menu.exec(pos); +} + +void AdBlockIcon::toggleCustomFilter() +{ + QAction* action = qobject_cast(sender()); + if (!action) { + return; + } + + const QString filter = action->data().toString(); + AdBlockManager* manager = AdBlockManager::instance(); + AdBlockCustomList* customList = manager->customList(); + + if (customList->containsFilter(filter)) { + customList->removeFilter(filter); + } + else { + AdBlockRule* rule = new AdBlockRule(filter, customList); + customList->addRule(rule); + } +} + +void AdBlockIcon::animateIcon() +{ + ++m_timerTicks; + if (m_timerTicks > 10) { + stopAnimation(); + return; + } + + if (icon().isNull()) { + setIcon(QIcon(":images/images/adblock.png")); + } + else { + setIcon(QIcon()); + } +} + +void AdBlockIcon::stopAnimation() +{ + m_timerTicks = 0; + m_flashTimer->stop(); + disconnect(m_flashTimer, SIGNAL(timeout()), this, SLOT(animateIcon())); + + setEnabled(m_enabled); +} + +void AdBlockIcon::setEnabled(bool enabled) +{ + if (enabled) { + setIcon(QIcon(":images/images/adblock.png")); + } + else { + setIcon(QIcon(":images/images/adblock-disabled.png")); + } + + m_enabled = enabled; +} + +void AdBlockIcon::mouseReleaseEvent(QMouseEvent* event) +{ + if (event->button() == Qt::LeftButton && rect().contains(event->pos())) { + if (event->modifiers() != Qt::ControlModifier) { + emit clicked(event->globalPos()); + } + } + else { + QToolButton::mouseReleaseEvent(event); + } +} + +AdBlockIcon::~AdBlockIcon() +{ +} diff --git a/src/network-web/adblock/adblockicon.h b/src/network-web/adblock/adblockicon.h new file mode 100755 index 000000000..c16b186ad --- /dev/null +++ b/src/network-web/adblock/adblockicon.h @@ -0,0 +1,84 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2010-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +#ifndef ADBLOCKICON_H +#define ADBLOCKICON_H + +#include + +#include "adblockrule.h" + +class QMenu; +class QUrl; +class MainWindow; + +class AdBlockIcon : public QToolButton +{ + Q_OBJECT +public: + explicit AdBlockIcon(QWidget *window, QWidget *parent = 0); + ~AdBlockIcon(); + + void retranslateStrings(); + void popupBlocked(const QString &ruleString, const QUrl &url); + QAction* menuAction(); + +signals: + void clicked(QPoint); + +public slots: + void setEnabled(bool enabled); + void createMenu(QMenu* menu = 0); + +private slots: + void showMenu(const QPoint &pos); + void toggleCustomFilter(); + + void animateIcon(); + void stopAnimation(); + +private: + void mouseReleaseEvent(QMouseEvent* event); + + QWidget *m_window; + QAction* m_menuAction; + + QVector > m_blockedPopups; + QTimer* m_flashTimer; + + int m_timerTicks; + bool m_enabled; +}; + +#endif // ADBLOCKICON_H diff --git a/src/network-web/adblock/adblockmanager.cpp b/src/network-web/adblock/adblockmanager.cpp new file mode 100755 index 000000000..b87bac832 --- /dev/null +++ b/src/network-web/adblock/adblockmanager.cpp @@ -0,0 +1,470 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2010-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +#include "adblockmanager.h" +#include "adblockdialog.h" +#include "adblockmatcher.h" +#include "adblocksubscription.h" +#include "adblockblockednetworkreply.h" +#include "adblockicon.h" +#include "webpage.h" + +#include "miscellaneous/application.h" +#include "miscellaneous/settings.h" +#include "network-web/silentnetworkaccessmanager.h" + +#include +#include +#include +#include +#include +#include + +#define ADBLOCK_DEBUG + +#ifdef ADBLOCK_DEBUG +#include +#endif + +AdBlockManager* AdBlockManager::s_adBlockManager = 0; + +AdBlockManager::AdBlockManager(QObject* parent) + : QObject(parent) + , m_loaded(false) + , m_enabled(true) + , m_useLimitedEasyList(true) + , m_matcher(new AdBlockMatcher(this)), m_subscriptions(QList()) +{ + load(); +} + +AdBlockManager::~AdBlockManager() +{ + qDeleteAll(m_subscriptions); +} + +AdBlockManager* AdBlockManager::instance() +{ + if (!s_adBlockManager) { + s_adBlockManager = new AdBlockManager(SilentNetworkAccessManager::instance()); + } + + return s_adBlockManager; +} + +QString AdBlockManager::filterCharsFromFilename(const QString &name) +{ + QString value = name; + + value.replace(QLatin1Char('/'), QLatin1Char('-')); + value.remove(QLatin1Char('\\')); + value.remove(QLatin1Char(':')); + value.remove(QLatin1Char('*')); + value.remove(QLatin1Char('?')); + value.remove(QLatin1Char('"')); + value.remove(QLatin1Char('<')); + value.remove(QLatin1Char('>')); + value.remove(QLatin1Char('|')); + + return value; +} + +QString AdBlockManager::ensureUniqueFilename(const QString &name, const QString &appendFormat) +{ + if (!QFile::exists(name)) { + return name; + } + + QString tmpFileName = name; + int i = 1; + while (QFile::exists(tmpFileName)) { + tmpFileName = name; + int index = tmpFileName.lastIndexOf(QLatin1Char('.')); + + QString appendString = appendFormat.arg(i); + if (index == -1) { + tmpFileName.append(appendString); + } + else { + tmpFileName = tmpFileName.left(index) + appendString + tmpFileName.mid(index); + } + i++; + } + return tmpFileName; +} + +void AdBlockManager::setEnabled(bool enabled) +{ + if (m_enabled == enabled) { + return; + } + + m_enabled = enabled; + emit enabledChanged(enabled); + + Settings *settings = qApp->settings(); + settings->beginGroup("AdBlock"); + settings->setValue("enabled", m_enabled); + settings->endGroup(); + + load(); + // TODO + //mainApp->reloadUserStyleBrowser(); +} + +QList AdBlockManager::subscriptions() const +{ + return m_subscriptions; +} + +QNetworkReply* AdBlockManager::block(const QNetworkRequest &request) +{ +#ifdef ADBLOCK_DEBUG + QElapsedTimer timer; + timer.start(); +#endif + const QString urlString = request.url().toEncoded().toLower(); + const QString urlDomain = request.url().host().toLower(); + const QString urlScheme = request.url().scheme().toLower(); + + if (!isEnabled() || !canRunOnScheme(urlScheme)) + return 0; + + const AdBlockRule* blockedRule = m_matcher->match(request, urlDomain, urlString); + + if (blockedRule) { + QVariant v = request.attribute((QNetworkRequest::Attribute)(QNetworkRequest::User + 100)); + WebPage* webPage = static_cast(v.value()); + if (WebPage::isPointerSafeToUse(webPage)) { + if (!canBeBlocked(webPage->mainFrame()->url())) { + return 0; + } + + webPage->addAdBlockRule(blockedRule, request.url()); + } + + AdBlockBlockedNetworkReply* reply = new AdBlockBlockedNetworkReply(blockedRule, this); + reply->setRequest(request); + +#ifdef ADBLOCK_DEBUG + qDebug() << "BLOCKED: " << timer.elapsed() << blockedRule->filter() << request.url(); +#endif + + return reply; + } + +#ifdef ADBLOCK_DEBUG + qDebug() << timer.elapsed() << request.url(); +#endif + + return 0; +} + +QStringList AdBlockManager::disabledRules() const +{ + return m_disabledRules; +} + +void AdBlockManager::addDisabledRule(const QString &filter) +{ + m_disabledRules.append(filter); +} + +void AdBlockManager::removeDisabledRule(const QString &filter) +{ + m_disabledRules.removeOne(filter); +} + +AdBlockSubscription* AdBlockManager::addSubscription(const QString &title, const QString &url) +{ + if (title.isEmpty() || url.isEmpty()) { + return 0; + } + + QString fileName = filterCharsFromFilename(title.toLower()) + ".txt"; + QString filePath = ensureUniqueFilename(qApp->homeFolderPath() + "/adblock/" + fileName); + + QByteArray data = QString("Title: %1\nUrl: %2\n[Adblock Plus 1.1.1]").arg(title, url).toLatin1(); + + QFile file(filePath); + if (!file.open(QFile::WriteOnly | QFile::Truncate)) { + qWarning() << "AdBlockManager: Cannot write to file" << filePath; + return 0; + } + + file.write(data); + file.close(); + + AdBlockSubscription* subscription = new AdBlockSubscription(title, this); + subscription->setUrl(QUrl(url)); + subscription->setFilePath(filePath); + subscription->loadSubscription(m_disabledRules); + + m_subscriptions.insert(m_subscriptions.count() - 1, subscription); + + return subscription; +} + +bool AdBlockManager::removeSubscription(AdBlockSubscription* subscription) +{ + if (!m_subscriptions.contains(subscription) || !subscription->canBeRemoved()) { + return false; + } + + QFile(subscription->filePath()).remove(); + m_subscriptions.removeOne(subscription); + + delete subscription; + return true; +} + +AdBlockCustomList* AdBlockManager::customList() const +{ + foreach (AdBlockSubscription* subscription, m_subscriptions) { + AdBlockCustomList* list = qobject_cast(subscription); + + if (list) { + return list; + } + } + + return 0; +} + +void AdBlockManager::load() +{ + if (m_loaded) { + return; + } + +#ifdef ADBLOCK_DEBUG + QElapsedTimer timer; + timer.start(); +#endif + + Settings *settings = qApp->settings(); + m_enabled = settings->value("AdBlock","enabled", m_enabled).toBool(); + m_useLimitedEasyList = settings->value("AdBlock","useLimitedEasyList", m_useLimitedEasyList).toBool(); + m_disabledRules = settings->value("AdBlock","disabledRules", QStringList()).toStringList(); + QDateTime lastUpdate = settings->value("AdBlock","lastUpdate", QDateTime()).toDateTime(); + + if (!m_enabled) { + return; + } + + QDir adblockDir(qApp->homeFolderPath() + "/adblock"); + // Create if neccessary + if (!adblockDir.exists()) { + QDir(qApp->homeFolderPath()).mkdir("adblock"); + } + + foreach (const QString &fileName, adblockDir.entryList(QStringList("*.txt"), QDir::Files)) { + if (fileName == QLatin1String("customlist.txt")) { + continue; + } + + const QString absolutePath = adblockDir.absoluteFilePath(fileName); + QFile file(absolutePath); + if (!file.open(QFile::ReadOnly)) { + continue; + } + + QTextStream textStream(&file); + textStream.setCodec("UTF-8"); + QString title = textStream.readLine(1024).remove(QLatin1String("Title: ")); + QUrl url = QUrl(textStream.readLine(1024).remove(QLatin1String("Url: "))); + + if (title.isEmpty() || !url.isValid()) { + qWarning() << "AdBlockManager: Invalid subscription file" << absolutePath; + continue; + } + + AdBlockSubscription* subscription = new AdBlockSubscription(title, this); + subscription->setUrl(url); + subscription->setFilePath(absolutePath); + + m_subscriptions.append(subscription); + } + + // Prepend EasyList if subscriptions are empty + if (m_subscriptions.isEmpty()) { + AdBlockSubscription* easyList = new AdBlockSubscription(tr("EasyList"), this); + easyList->setUrl(QUrl(ADBLOCK_EASYLIST_URL)); + easyList->setFilePath(qApp->homeFolderPath() + "/adblock/easylist.txt"); + + // TODO + //connect(easyList, SIGNAL(subscriptionUpdated()), mainApp, SLOT(reloadUserStyleBrowser())); + + m_subscriptions.prepend(easyList); + } + + // Append CustomList + AdBlockCustomList* customList = new AdBlockCustomList(this); + m_subscriptions.append(customList); + + // Load all subscriptions + foreach (AdBlockSubscription* subscription, m_subscriptions) { + subscription->loadSubscription(m_disabledRules); + + // TODO + //connect(subscription, SIGNAL(subscriptionUpdated()), mainApp, SLOT(reloadUserStyleBrowser())); + connect(subscription, SIGNAL(subscriptionChanged()), m_matcher, SLOT(update())); + } + + if (lastUpdate.addDays(5) < QDateTime::currentDateTime()) { + QTimer::singleShot(1000 * 60, this, SLOT(updateAllSubscriptions())); + } + +#ifdef ADBLOCK_DEBUG + qDebug() << "AdBlock loaded in" << timer.elapsed(); +#endif + + m_matcher->update(); + m_loaded = true; +} + +void AdBlockManager::updateAllSubscriptions() +{ + foreach (AdBlockSubscription* subscription, m_subscriptions) { + subscription->updateSubscription(); + } + + Settings *settings = qApp->settings(); + settings->beginGroup("AdBlock"); + settings->setValue("lastUpdate", QDateTime::currentDateTime()); + settings->endGroup(); +} + +void AdBlockManager::save() +{ + if (!m_loaded) { + return; + } + + foreach (AdBlockSubscription* subscription, m_subscriptions) { + subscription->saveSubscription(); + } + + Settings *settings = qApp->settings(); + settings->beginGroup("AdBlock"); + settings->setValue("enabled", m_enabled); + settings->setValue("useLimitedEasyList", m_useLimitedEasyList); + settings->setValue("disabledRules", m_disabledRules); + settings->endGroup(); +} + +bool AdBlockManager::isEnabled() const +{ + return m_enabled; +} + +bool AdBlockManager::canRunOnScheme(const QString &scheme) const +{ + return !(scheme == QLatin1String("file") || scheme == QLatin1String("qrc") + || scheme == QLatin1String("qupzilla") || scheme == QLatin1String("data") + || scheme == QLatin1String("abp")); +} + +bool AdBlockManager::useLimitedEasyList() const +{ + return m_useLimitedEasyList; +} + +void AdBlockManager::setUseLimitedEasyList(bool useLimited) +{ + m_useLimitedEasyList = useLimited; + + foreach (AdBlockSubscription* subscription, m_subscriptions) { + if (subscription->url() == QUrl(ADBLOCK_EASYLIST_URL)) { + subscription->updateSubscription(); + } + } +} + +bool AdBlockManager::canBeBlocked(const QUrl &url) const +{ + return !m_matcher->adBlockDisabledForUrl(url); +} + +QString AdBlockManager::elementHidingRules() const +{ + return m_matcher->elementHidingRules(); +} + +QString AdBlockManager::elementHidingRulesForDomain(const QUrl &url) const +{ + if (!isEnabled() || !canRunOnScheme(url.scheme()) || !canBeBlocked(url)) + return QString(); + + // Acid3 doesn't like the way element hiding rules are embedded into page + if (url.host() == QLatin1String("acid3.acidtests.org")) + return QString(); + + return m_matcher->elementHidingRulesForDomain(url.host()); +} + +AdBlockSubscription* AdBlockManager::subscriptionByName(const QString &name) const +{ + foreach (AdBlockSubscription* subscription, m_subscriptions) { + if (subscription->title() == name) { + return subscription; + } + } + + return 0; +} + +AdBlockDialog* AdBlockManager::showDialog() +{ + if (!m_adBlockDialog) { + m_adBlockDialog = new AdBlockDialog; + } + + m_adBlockDialog.data()->show(); + m_adBlockDialog.data()->raise(); + m_adBlockDialog.data()->activateWindow(); + + return m_adBlockDialog.data(); +} + +void AdBlockManager::showRule() +{ + if (QAction* action = qobject_cast(sender())) { + const AdBlockRule* rule = static_cast(action->data().value()); + + if (rule) { + showDialog()->showRule(rule); + } + } +} diff --git a/src/network-web/adblock/adblockmanager.h b/src/network-web/adblock/adblockmanager.h new file mode 100755 index 000000000..f3e76585d --- /dev/null +++ b/src/network-web/adblock/adblockmanager.h @@ -0,0 +1,138 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2010-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +#ifndef ADBLOCKMANAGER_H +#define ADBLOCKMANAGER_H + +#define ADBLOCK_EASYLIST_URL "https://easylist-downloads.adblockplus.org/easylist.txt" + +#ifndef QSL +#if QT_VERSION >= 0x050000 +#define QSL(x) QStringLiteral(x) +#else +#define QSL(x) QLatin1String(x) +#endif +#endif + +#ifndef QL1S +#define QL1S(x) QLatin1String(x) +#endif + +#ifndef QL1C +#define QL1C(x) QLatin1Char(x) +#endif + + +#include +#include +#include + +class QUrl; +class QNetworkReply; +class QNetworkRequest; + +class AdBlockRule; +class AdBlockDialog; +class AdBlockMatcher; +class AdBlockCustomList; +class AdBlockSubscription; + +class AdBlockManager : public QObject +{ + Q_OBJECT + +public: + AdBlockManager(QObject* parent = 0); + ~AdBlockManager(); + + static AdBlockManager* instance(); + + static QString filterCharsFromFilename(const QString &name); + + static QString ensureUniqueFilename(const QString &name, const QString &appendFormat = QString("(%1)")); + + void load(); + void save(); + + bool isEnabled() const; + bool canRunOnScheme(const QString &scheme) const; + + bool useLimitedEasyList() const; + void setUseLimitedEasyList(bool useLimited); + + QString elementHidingRules() const; + QString elementHidingRulesForDomain(const QUrl &url) const; + + AdBlockSubscription* subscriptionByName(const QString &name) const; + QList subscriptions() const; + + QNetworkReply* block(const QNetworkRequest &request); + + QStringList disabledRules() const; + void addDisabledRule(const QString &filter); + void removeDisabledRule(const QString &filter); + + AdBlockSubscription* addSubscription(const QString &title, const QString &url); + bool removeSubscription(AdBlockSubscription* subscription); + + AdBlockCustomList* customList() const; + +signals: + void enabledChanged(bool enabled); + +public slots: + void setEnabled(bool enabled); + void showRule(); + + void updateAllSubscriptions(); + + AdBlockDialog* showDialog(); + +private: + inline bool canBeBlocked(const QUrl &url) const; + + bool m_loaded; + bool m_enabled; + bool m_useLimitedEasyList; + + QList m_subscriptions; + static AdBlockManager* s_adBlockManager; + AdBlockMatcher* m_matcher; + QStringList m_disabledRules; + + QPointer m_adBlockDialog; +}; + +#endif // ADBLOCKMANAGER_H + diff --git a/src/network-web/adblock/adblockmatcher.cpp b/src/network-web/adblock/adblockmatcher.cpp new file mode 100755 index 000000000..a2bb29ede --- /dev/null +++ b/src/network-web/adblock/adblockmatcher.cpp @@ -0,0 +1,245 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +#include "adblockmatcher.h" +#include "adblockmanager.h" +#include "adblockrule.h" +#include "adblocksubscription.h" + +AdBlockMatcher::AdBlockMatcher(AdBlockManager* manager) + : QObject(manager) + , m_manager(manager) +{ + connect(manager, SIGNAL(enabledChanged(bool)), this, SLOT(enabledChanged(bool))); +} + +AdBlockMatcher::~AdBlockMatcher() +{ + clear(); +} + +const AdBlockRule* AdBlockMatcher::match(const QNetworkRequest &request, const QString &urlDomain, const QString &urlString) const +{ + // Exception rules + if (m_networkExceptionTree.find(request, urlDomain, urlString)) + return 0; + + int count = m_networkExceptionRules.count(); + for (int i = 0; i < count; ++i) { + const AdBlockRule* rule = m_networkExceptionRules.at(i); + if (rule->networkMatch(request, urlDomain, urlString)) + return 0; + } + + // Block rules + if (const AdBlockRule* rule = m_networkBlockTree.find(request, urlDomain, urlString)) + return rule; + + count = m_networkBlockRules.count(); + for (int i = 0; i < count; ++i) { + const AdBlockRule* rule = m_networkBlockRules.at(i); + if (rule->networkMatch(request, urlDomain, urlString)) + return rule; + } + + return 0; +} + +bool AdBlockMatcher::adBlockDisabledForUrl(const QUrl &url) const +{ + int count = m_documentRules.count(); + + for (int i = 0; i < count; ++i) + if (m_documentRules.at(i)->urlMatch(url)) + return true; + + return false; +} + +bool AdBlockMatcher::elemHideDisabledForUrl(const QUrl &url) const +{ + if (adBlockDisabledForUrl(url)) + return true; + + int count = m_elemhideRules.count(); + + for (int i = 0; i < count; ++i) + if (m_elemhideRules.at(i)->urlMatch(url)) + return true; + + return false; +} + +QString AdBlockMatcher::elementHidingRules() const +{ + return m_elementHidingRules; +} + +QString AdBlockMatcher::elementHidingRulesForDomain(const QString &domain) const +{ + QString rules; + int addedRulesCount = 0; + int count = m_domainRestrictedCssRules.count(); + + for (int i = 0; i < count; ++i) { + const AdBlockRule* rule = m_domainRestrictedCssRules.at(i); + if (!rule->matchDomain(domain)) + continue; + + if (Q_UNLIKELY(addedRulesCount == 1000)) { + rules.append(rule->cssSelector()); + rules.append(QLatin1String("{display:none !important;}\n")); + addedRulesCount = 0; + } + else { + rules.append(rule->cssSelector() + QLatin1Char(',')); + addedRulesCount++; + } + } + + if (addedRulesCount != 0) { + rules = rules.left(rules.size() - 1); + rules.append(QLatin1String("{display:none !important;}\n")); + } + + return rules; +} + +void AdBlockMatcher::update() +{ + clear(); + + QHash cssRulesHash; + QVector exceptionCssRules; + + foreach (AdBlockSubscription* subscription, m_manager->subscriptions()) { + foreach (const AdBlockRule* rule, subscription->allRules()) { + // Don't add internally disabled rules to cache + if (rule->isInternalDisabled()) + continue; + + if (rule->isCssRule()) { + // We will add only enabled css rules to cache, because there is no enabled/disabled + // check on match. They are directly embedded to pages. + if (!rule->isEnabled()) + continue; + + if (rule->isException()) + exceptionCssRules.append(rule); + else + cssRulesHash.insert(rule->cssSelector(), rule); + } + else if (rule->isDocument()) { + m_documentRules.append(rule); + } + else if (rule->isElemhide()) { + m_elemhideRules.append(rule); + } + else if (rule->isException()) { + if (!m_networkExceptionTree.add(rule)) + m_networkExceptionRules.append(rule); + } + else { + if (!m_networkBlockTree.add(rule)) + m_networkBlockRules.append(rule); + } + } + } + + foreach (const AdBlockRule* rule, exceptionCssRules) { + const AdBlockRule* originalRule = cssRulesHash.value(rule->cssSelector()); + + // If we don't have this selector, the exception does nothing + if (!originalRule) + continue; + + AdBlockRule* copiedRule = originalRule->copy(); + copiedRule->m_options |= AdBlockRule::DomainRestrictedOption; + copiedRule->m_blockedDomains.append(rule->m_allowedDomains); + + cssRulesHash[rule->cssSelector()] = copiedRule; + m_createdRules.append(copiedRule); + } + + // Apparently, excessive amount of selectors for one CSS rule is not what WebKit likes. + // (In my testings, 4931 is the number that makes it crash) + // So let's split it by 1000 selectors... + int hidingRulesCount = 0; + + QHashIterator it(cssRulesHash); + while (it.hasNext()) { + it.next(); + const AdBlockRule* rule = it.value(); + + if (rule->isDomainRestricted()) { + m_domainRestrictedCssRules.append(rule); + } + else if (Q_UNLIKELY(hidingRulesCount == 1000)) { + m_elementHidingRules.append(rule->cssSelector()); + m_elementHidingRules.append(QLatin1String("{display:none !important;} ")); + hidingRulesCount = 0; + } + else { + m_elementHidingRules.append(rule->cssSelector() + QLatin1Char(',')); + hidingRulesCount++; + } + } + + if (hidingRulesCount != 0) { + m_elementHidingRules = m_elementHidingRules.left(m_elementHidingRules.size() - 1); + m_elementHidingRules.append(QLatin1String("{display:none !important;} ")); + } +} + +void AdBlockMatcher::clear() +{ + m_networkExceptionTree.clear(); + m_networkExceptionRules.clear(); + m_networkBlockTree.clear(); + m_networkBlockRules.clear(); + m_domainRestrictedCssRules.clear(); + m_elementHidingRules.clear(); + m_documentRules.clear(); + m_elemhideRules.clear(); + qDeleteAll(m_createdRules); + m_createdRules.clear(); +} + +void AdBlockMatcher::enabledChanged(bool enabled) +{ + if (enabled) + update(); + else + clear(); +} diff --git a/src/network-web/adblock/adblockmatcher.h b/src/network-web/adblock/adblockmatcher.h new file mode 100755 index 000000000..2524d9df5 --- /dev/null +++ b/src/network-web/adblock/adblockmatcher.h @@ -0,0 +1,85 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +#ifndef ADBLOCKMATCHER_H +#define ADBLOCKMATCHER_H + +#include +#include +#include + +#include "adblocksearchtree.h" + +class AdBlockManager; +class AdBlockRule; + +class AdBlockMatcher : public QObject +{ + Q_OBJECT + +public: + explicit AdBlockMatcher(AdBlockManager* manager); + ~AdBlockMatcher(); + + const AdBlockRule* match(const QNetworkRequest &request, const QString &urlDomain, const QString &urlString) const; + + bool adBlockDisabledForUrl(const QUrl &url) const; + bool elemHideDisabledForUrl(const QUrl &url) const; + + QString elementHidingRules() const; + QString elementHidingRulesForDomain(const QString &domain) const; + +public slots: + void update(); + void clear(); + +private slots: + void enabledChanged(bool enabled); + +private: + AdBlockManager* m_manager; + + QVector m_createdRules; + QVector m_networkExceptionRules; + QVector m_networkBlockRules; + QVector m_domainRestrictedCssRules; + QVector m_documentRules; + QVector m_elemhideRules; + + QString m_elementHidingRules; + AdBlockSearchTree m_networkBlockTree; + AdBlockSearchTree m_networkExceptionTree; +}; + +#endif // ADBLOCKMATCHER_H diff --git a/src/network-web/adblock/adblockrule.cpp b/src/network-web/adblock/adblockrule.cpp new file mode 100755 index 000000000..1e824ea32 --- /dev/null +++ b/src/network-web/adblock/adblockrule.cpp @@ -0,0 +1,764 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2010-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/** + * Copyright (c) 2009, Zsombor Gegesy + * Copyright (c) 2009, Benjamin C. Meyer + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Benjamin Meyer nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "adblockrule.h" +#include "adblocksubscription.h" +#include "adblockmanager.h" + +#include +#include +#include +#include +#include +#include +#include + +// Version for Qt < 4.8 has one issue, it will wrongly +// count .co.uk (and others) as second-level domain +static QString toSecondLevelDomain(const QUrl &url) +{ +#if QT_VERSION >= 0x040800 + const QString topLevelDomain = url.topLevelDomain(); + const QString urlHost = url.host(); + + if (topLevelDomain.isEmpty() || urlHost.isEmpty()) { + return QString(); + } + + QString domain = urlHost.left(urlHost.size() - topLevelDomain.size()); + + if (domain.count(QL1C('.')) == 0) { + return urlHost; + } + + while (domain.count(QL1C('.')) != 0) { + domain = domain.mid(domain.indexOf(QL1C('.')) + 1); + } + + return domain + topLevelDomain; +#else + QString domain = url.host(); + + if (domain.count(QL1C('.')) == 0) { + return QString(); + } + + while (domain.count(QL1C('.')) != 1) { + domain = domain.mid(domain.indexOf(QL1C('.')) + 1); + } + + return domain; +#endif +} + +AdBlockRule::AdBlockRule(const QString &filter, AdBlockSubscription* subscription) + : m_subscription(subscription) + , m_type(StringContainsMatchRule) + , m_caseSensitivity(Qt::CaseInsensitive) + , m_isEnabled(true) + , m_isException(false) + , m_isInternalDisabled(false) + , m_regExp(0) +{ + setFilter(filter); +} + +AdBlockRule::~AdBlockRule() +{ + delete m_regExp; +} + +AdBlockRule* AdBlockRule::copy() const +{ + AdBlockRule* rule = new AdBlockRule(); + rule->m_subscription = m_subscription; + rule->m_type = m_type; + rule->m_options = m_options; + rule->m_exceptions = m_exceptions; + rule->m_filter = m_filter; + rule->m_matchString = m_matchString; + rule->m_caseSensitivity = m_caseSensitivity; + rule->m_isEnabled = m_isEnabled; + rule->m_isException = m_isException; + rule->m_isInternalDisabled = m_isInternalDisabled; + rule->m_allowedDomains = m_allowedDomains; + rule->m_blockedDomains = m_blockedDomains; + + if (m_regExp) { + rule->m_regExp = new RegExp; + rule->m_regExp->regExp = m_regExp->regExp; + rule->m_regExp->matchers = m_regExp->matchers; + } + + return rule; +} + +AdBlockSubscription* AdBlockRule::subscription() const +{ + return m_subscription; +} + +void AdBlockRule::setSubscription(AdBlockSubscription* subscription) +{ + m_subscription = subscription; +} + +QString AdBlockRule::filter() const +{ + return m_filter; +} + +void AdBlockRule::setFilter(const QString &filter) +{ + m_filter = filter; + parseFilter(); +} + +bool AdBlockRule::isCssRule() const +{ + return m_type == CssRule; +} + +QString AdBlockRule::cssSelector() const +{ + return m_matchString; +} + +bool AdBlockRule::isDocument() const +{ + return hasOption(DocumentOption); +} + +bool AdBlockRule::isElemhide() const +{ + return hasOption(ElementHideOption); +} + +bool AdBlockRule::isDomainRestricted() const +{ + return hasOption(DomainRestrictedOption); +} + +bool AdBlockRule::isException() const +{ + return m_isException; +} + +bool AdBlockRule::isComment() const +{ + return m_filter.startsWith(QL1C('!')); +} + +bool AdBlockRule::isEnabled() const +{ + return m_isEnabled; +} + +void AdBlockRule::setEnabled(bool enabled) +{ + m_isEnabled = enabled; +} + +bool AdBlockRule::isSlow() const +{ + return m_regExp != 0; +} + +bool AdBlockRule::isInternalDisabled() const +{ + return m_isInternalDisabled; +} + +bool AdBlockRule::urlMatch(const QUrl &url) const +{ + if (!hasOption(DocumentOption) && !hasOption(ElementHideOption)) { + return false; + } + + const QString encodedUrl = url.toEncoded(); + const QString domain = url.host(); + + return networkMatch(QNetworkRequest(url), domain, encodedUrl); +} + +bool AdBlockRule::networkMatch(const QNetworkRequest &request, const QString &domain, const QString &encodedUrl) const +{ + if (m_type == CssRule || !m_isEnabled || m_isInternalDisabled) { + return false; + } + + bool matched = false; + + if (m_type == StringContainsMatchRule) { + matched = encodedUrl.contains(m_matchString, m_caseSensitivity); + } + else if (m_type == DomainMatchRule) { + matched = isMatchingDomain(domain, m_matchString); + } + else if (m_type == StringEndsMatchRule) { + matched = encodedUrl.endsWith(m_matchString, m_caseSensitivity); + } + else if (m_type == RegExpMatchRule) { + if (!isMatchingRegExpStrings(encodedUrl)) { + return false; + } + + matched = (m_regExp->regExp.indexIn(encodedUrl) != -1); + } + + if (matched) { + // Check domain restrictions + if (hasOption(DomainRestrictedOption) && !matchDomain(domain)) { + return false; + } + + // Check third-party restriction + if (hasOption(ThirdPartyOption) && !matchThirdParty(request)) { + return false; + } + + // Check object restrictions + if (hasOption(ObjectOption) && !matchObject(request)) { + return false; + } + + // Check subdocument restriction + if (hasOption(SubdocumentOption) && !matchSubdocument(request)) { + return false; + } + + // Check xmlhttprequest restriction + if (hasOption(XMLHttpRequestOption) && !matchXmlHttpRequest(request)) { + return false; + } + + // Check image restriction + if (hasOption(ImageOption) && !matchImage(encodedUrl)) { + return false; + } + } + + return matched; +} + +bool AdBlockRule::matchDomain(const QString &domain) const +{ + if (!m_isEnabled) { + return false; + } + + if (!hasOption(DomainRestrictedOption)) { + return true; + } + + if (m_blockedDomains.isEmpty()) { + foreach (const QString &d, m_allowedDomains) { + if (isMatchingDomain(domain, d)) { + return true; + } + } + } + else if (m_allowedDomains.isEmpty()) { + foreach (const QString &d, m_blockedDomains) { + if (isMatchingDomain(domain, d)) { + return false; + } + } + return true; + } + else { + foreach (const QString &d, m_blockedDomains) { + if (isMatchingDomain(domain, d)) { + return false; + } + } + + foreach (const QString &d, m_allowedDomains) { + if (isMatchingDomain(domain, d)) { + return true; + } + } + } + + return false; +} + +bool AdBlockRule::matchThirdParty(const QNetworkRequest &request) const +{ + const QString referer = request.attribute(QNetworkRequest::Attribute(QNetworkRequest::User + 151), QString()).toString(); + + if (referer.isEmpty()) { + return false; + } + + // Third-party matching should be performed on second-level domains + const QString refererHost = toSecondLevelDomain(QUrl(referer)); + const QString host = toSecondLevelDomain(request.url()); + + bool match = refererHost != host; + + return hasException(ThirdPartyOption) ? !match : match; +} + +bool AdBlockRule::matchObject(const QNetworkRequest &request) const +{ + bool match = request.attribute(QNetworkRequest::Attribute(QNetworkRequest::User + 150)).toString() == QL1S("object"); + + return hasException(ObjectOption) ? !match : match; +} + +bool AdBlockRule::matchSubdocument(const QNetworkRequest &request) const +{ + QWebFrame* originatingFrame = static_cast(request.originatingObject()); + if (!originatingFrame) { + return false; + } + + QWebPage* page = originatingFrame->page(); + if (!page) { + return false; + } + + bool match = !(originatingFrame == page->mainFrame()); + + return hasException(SubdocumentOption) ? !match : match; +} + +bool AdBlockRule::matchXmlHttpRequest(const QNetworkRequest &request) const +{ + bool match = request.rawHeader("X-Requested-With") == QByteArray("XMLHttpRequest"); + + return hasException(XMLHttpRequestOption) ? !match : match; +} + +bool AdBlockRule::matchImage(const QString &encodedUrl) const +{ + bool match = encodedUrl.endsWith(QL1S(".png")) || + encodedUrl.endsWith(QL1S(".jpg")) || + encodedUrl.endsWith(QL1S(".gif")) || + encodedUrl.endsWith(QL1S(".jpeg")); + + return hasException(ImageOption) ? !match : match; +} + +void AdBlockRule::parseFilter() +{ + QString parsedLine = m_filter; + + // Empty rule or just comment + if (m_filter.trimmed().isEmpty() || m_filter.startsWith(QL1C('!'))) { + // We want to differentiate rule disabled by user and rule disabled in subscription file + // m_isInternalDisabled is also used when rule is disabled due to all options not being supported + m_isEnabled = false; + m_isInternalDisabled = true; + m_type = Invalid; + return; + } + + // CSS Element hiding rule + if (parsedLine.contains(QL1S("##")) || parsedLine.contains(QL1S("#@#"))) { + m_type = CssRule; + int pos = parsedLine.indexOf(QL1C('#')); + + // Domain restricted rule + if (!parsedLine.startsWith(QL1S("##"))) { + QString domains = parsedLine.left(pos); + parseDomains(domains, QL1C(',')); + } + + m_isException = parsedLine.at(pos + 1) == QL1C('@'); + m_matchString = parsedLine.mid(m_isException ? pos + 3 : pos + 2); + + // CSS rule cannot have more options -> stop parsing + return; + } + + // Exception always starts with @@ + if (parsedLine.startsWith(QL1S("@@"))) { + m_isException = true; + parsedLine = parsedLine.mid(2); + } + + // Parse all options following $ char + int optionsIndex = parsedLine.indexOf(QL1C('$')); + if (optionsIndex >= 0) { + const QStringList options = parsedLine.mid(optionsIndex + 1).split(QL1C(','), QString::SkipEmptyParts); + + int handledOptions = 0; + foreach (const QString &option, options) { + if (option.startsWith(QL1S("domain="))) { + parseDomains(option.mid(7), QL1C('|')); + ++handledOptions; + } + else if (option == QL1S("match-case")) { + m_caseSensitivity = Qt::CaseSensitive; + ++handledOptions; + } + else if (option.endsWith(QL1S("third-party"))) { + setOption(ThirdPartyOption); + setException(ThirdPartyOption, option.startsWith(QL1C('~'))); + ++handledOptions; + } + else if (option.endsWith(QL1S("object"))) { + setOption(ObjectOption); + setException(ObjectOption, option.startsWith(QL1C('~'))); + ++handledOptions; + } + else if (option.endsWith(QL1S("subdocument"))) { + setOption(SubdocumentOption); + setException(SubdocumentOption, option.startsWith(QL1C('~'))); + ++handledOptions; + } + else if (option.endsWith(QL1S("xmlhttprequest"))) { + setOption(XMLHttpRequestOption); + setException(XMLHttpRequestOption, option.startsWith(QL1C('~'))); + ++handledOptions; + } + else if (option.endsWith(QL1S("image"))) { + setOption(ImageOption); + setException(ImageOption, option.startsWith(QL1C('~'))); + ++handledOptions; + } + else if (option == QL1S("document") && m_isException) { + setOption(DocumentOption); + ++handledOptions; + } + else if (option == QL1S("elemhide") && m_isException) { + setOption(ElementHideOption); + ++handledOptions; + } + else if (option == QL1S("collapse")) { + // Hiding placeholders of blocked elements is enabled by default + ++handledOptions; + } + } + + // If we don't handle all options, it's safer to just disable this rule + if (handledOptions != options.count()) { + m_isInternalDisabled = true; + m_type = Invalid; + return; + } + + parsedLine = parsedLine.left(optionsIndex); + } + + // Rule is classic regexp + if (parsedLine.startsWith(QL1C('/')) && parsedLine.endsWith(QL1C('/'))) { + parsedLine = parsedLine.mid(1); + parsedLine = parsedLine.left(parsedLine.size() - 1); + + m_type = RegExpMatchRule; + m_regExp = new RegExp; + m_regExp->regExp = QRegExp(parsedLine, m_caseSensitivity); + m_regExp->matchers = createStringMatchers(parseRegExpFilter(parsedLine)); + return; + } + + // Remove starting and ending wildcards (*) + if (parsedLine.startsWith(QL1C('*'))) { + parsedLine = parsedLine.mid(1); + } + + if (parsedLine.endsWith(QL1C('*'))) { + parsedLine = parsedLine.left(parsedLine.size() - 1); + } + + // We can use fast string matching for domain here + if (filterIsOnlyDomain(parsedLine)) { + parsedLine = parsedLine.mid(2); + parsedLine = parsedLine.left(parsedLine.size() - 1); + + m_type = DomainMatchRule; + m_matchString = parsedLine; + return; + } + + // If rule contains only | at end, we can also use string matching + if (filterIsOnlyEndsMatch(parsedLine)) { + parsedLine = parsedLine.left(parsedLine.size() - 1); + + m_type = StringEndsMatchRule; + m_matchString = parsedLine; + return; + } + + // If we still find a wildcard (*) or separator (^) or (|) + // we must modify parsedLine to comply with QzRegExp + if (parsedLine.contains(QL1C('*')) || + parsedLine.contains(QL1C('^')) || + parsedLine.contains(QL1C('|')) + ) { + m_type = RegExpMatchRule; + m_regExp = new RegExp; + m_regExp->regExp = QRegExp(createRegExpFromFilter(parsedLine), m_caseSensitivity); + m_regExp->matchers = createStringMatchers(parseRegExpFilter(parsedLine)); + return; + } + + // We haven't found anything that needs use of regexp, yay! + m_type = StringContainsMatchRule; + m_matchString = parsedLine; +} + +void AdBlockRule::parseDomains(const QString &domains, const QChar &separator) +{ + QStringList domainsList = domains.split(separator, QString::SkipEmptyParts); + + foreach (const QString domain, domainsList) { + if (domain.isEmpty()) { + continue; + } + if (domain.startsWith(QL1C('~'))) { + m_blockedDomains.append(domain.mid(1)); + } + else { + m_allowedDomains.append(domain); + } + } + + if (!m_blockedDomains.isEmpty() || !m_allowedDomains.isEmpty()) { + setOption(DomainRestrictedOption); + } +} + +bool AdBlockRule::filterIsOnlyDomain(const QString &filter) const +{ + if (!filter.endsWith(QL1C('^')) || !filter.startsWith(QL1S("||"))) + return false; + + for (int i = 0; i < filter.size(); ++i) { + switch (filter.at(i).toLatin1()) { + case '/': + case ':': + case '?': + case '=': + case '&': + case '*': + return false; + default: + break; + } + } + + return true; +} + +bool AdBlockRule::filterIsOnlyEndsMatch(const QString &filter) const +{ + for (int i = 0; i < filter.size(); ++i) { + switch (filter.at(i).toLatin1()) { + case '^': + case '*': + return false; + case '|': + return i == filter.size() - 1; + default: + break; + } + } + + return false; +} + +static bool wordCharacter(const QChar &c) +{ + return c.isLetterOrNumber() || c.isMark() || c == QL1C('_'); +} + +QString AdBlockRule::createRegExpFromFilter(const QString &filter) const +{ + QString parsed; + parsed.reserve(filter.size()); + + bool hadWildcard = false; // Filter multiple wildcards + + for (int i = 0; i < filter.size(); ++i) { + const QChar c = filter.at(i); + switch (c.toLatin1()) { + case '^': + parsed.append(QL1S("(?:[^\\w\\d\\-.%]|$)")); + break; + + case '*': + if (!hadWildcard) + parsed.append(QL1S(".*")); + break; + + case '|': + if (i == 0) { + if (filter.size() > 1 && filter.at(1) == QL1C('|')) { + parsed.append(QL1S("^[\\w\\-]+:\\/+(?!\\/)(?:[^\\/]+\\.)?")); + i++; + } + else { + parsed.append('^'); + } + break; + } + else if (i == filter.size() - 1) { + parsed.append(QL1C('$')); + break; + } + // fallthrough + + default: + if (!wordCharacter(c)) + parsed.append(QL1C('\\') + c); + else + parsed.append(c); + } + + hadWildcard = c == QL1C('*'); + } + + return parsed; +} + +QList AdBlockRule::createStringMatchers(const QStringList &filters) const +{ + QList matchers; + matchers.reserve(filters.size()); + + foreach (const QString &filter, filters) { + matchers.append(QStringMatcher(filter, m_caseSensitivity)); + } + + return matchers; +} + +bool AdBlockRule::isMatchingDomain(const QString &domain, const QString &pattern) const +{ + if (pattern == domain) { + return true; + } + + if (!domain.endsWith(pattern)) { + return false; + } + + int index = domain.indexOf(pattern); + + return index > 0 && domain[index - 1] == QLatin1Char('.'); +} + +bool AdBlockRule::isMatchingRegExpStrings(const QString &url) const +{ + Q_ASSERT(m_regExp); + + foreach (const QStringMatcher &matcher, m_regExp->matchers) { + if (matcher.indexIn(url) == -1) + return false; + } + + return true; +} + +// Split regexp filter into strings that can be used with QString::contains +// Don't use parts that contains only 1 char and duplicated parts +QStringList AdBlockRule::parseRegExpFilter(const QString &filter) const +{ + QStringList list; + int startPos = -1; + + for (int i = 0; i < filter.size(); ++i) { + const QChar c = filter.at(i); + // Meta characters in AdBlock rules are | * ^ + if (c == QL1C('|') || c == QL1C('*') || c == QL1C('^')) { + const QString sub = filter.mid(startPos, i - startPos); + if (sub.size() > 1) + list.append(sub); + startPos = i + 1; + } + } + + const QString sub = filter.mid(startPos); + if (sub.size() > 1) + list.append(sub); + + list.removeDuplicates(); + + return list; +} + +bool AdBlockRule::hasOption(const AdBlockRule::RuleOption &opt) const +{ + return (m_options & opt); +} + +bool AdBlockRule::hasException(const AdBlockRule::RuleOption &opt) const +{ + return (m_exceptions & opt); +} + +void AdBlockRule::setOption(const AdBlockRule::RuleOption &opt) +{ + m_options |= opt; +} + +void AdBlockRule::setException(const AdBlockRule::RuleOption &opt, bool on) +{ + if (on) { + m_exceptions |= opt; + } +} diff --git a/src/network-web/adblock/adblockrule.h b/src/network-web/adblock/adblockrule.h new file mode 100755 index 000000000..0a5a53d0e --- /dev/null +++ b/src/network-web/adblock/adblockrule.h @@ -0,0 +1,191 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2010-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/** + * Copyright (c) 2009, Benjamin C. Meyer + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Benjamin Meyer nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef ADBLOCKRULE_H +#define ADBLOCKRULE_H + +#include +#include + +class QNetworkRequest; +class QUrl; + +class AdBlockSubscription; + +class AdBlockRule +{ +public: + AdBlockRule(const QString &filter = QString(), AdBlockSubscription* subscription = 0); + ~AdBlockRule(); + + AdBlockRule* copy() const; + + AdBlockSubscription* subscription() const; + void setSubscription(AdBlockSubscription* subscription); + + QString filter() const; + void setFilter(const QString &filter); + + bool isCssRule() const; + QString cssSelector() const; + + bool isDocument() const; + bool isElemhide() const; + + bool isDomainRestricted() const; + bool isException() const; + + bool isComment() const; + bool isEnabled() const; + void setEnabled(bool enabled); + + bool isSlow() const; + bool isInternalDisabled() const; + + bool urlMatch(const QUrl &url) const; + bool networkMatch(const QNetworkRequest &request, const QString &domain, const QString &encodedUrl) const; + + bool matchDomain(const QString &domain) const; + bool matchThirdParty(const QNetworkRequest &request) const; + bool matchObject(const QNetworkRequest &request) const; + bool matchSubdocument(const QNetworkRequest &request) const; + bool matchXmlHttpRequest(const QNetworkRequest &request) const; + bool matchImage(const QString &encodedUrl) const; + +protected: + bool isMatchingDomain(const QString &domain, const QString &pattern) const; + bool isMatchingRegExpStrings(const QString &url) const; + QStringList parseRegExpFilter(const QString &filter) const; + +private: + enum RuleType { + CssRule = 0, + DomainMatchRule = 1, + RegExpMatchRule = 2, + StringEndsMatchRule = 3, + StringContainsMatchRule = 4, + Invalid = 5 + }; + + enum RuleOption { + DomainRestrictedOption = 1, + ThirdPartyOption = 2, + ObjectOption = 4, + SubdocumentOption = 8, + XMLHttpRequestOption = 16, + ImageOption = 32, + + // Exception only options + DocumentOption = 64, + ElementHideOption = 128 + }; + + Q_DECLARE_FLAGS(RuleOptions, RuleOption) + + inline bool hasOption(const RuleOption &opt) const; + inline bool hasException(const RuleOption &opt) const; + + inline void setOption(const RuleOption &opt); + inline void setException(const RuleOption &opt, bool on); + + void parseFilter(); + void parseDomains(const QString &domains, const QChar &separator); + bool filterIsOnlyDomain(const QString &filter) const; + bool filterIsOnlyEndsMatch(const QString &filter) const; + QString createRegExpFromFilter(const QString &filter) const; + QList createStringMatchers(const QStringList &filters) const; + + AdBlockSubscription* m_subscription; + + RuleType m_type; + RuleOptions m_options; + RuleOptions m_exceptions; + + // Original rule filter + QString m_filter; + // Parsed rule for string matching (CSS Selector for CSS rules) + QString m_matchString; + // Case sensitivity for string matching + Qt::CaseSensitivity m_caseSensitivity; + + bool m_isEnabled; + bool m_isException; + bool m_isInternalDisabled; + + QStringList m_allowedDomains; + QStringList m_blockedDomains; + + struct RegExp { + QRegExp regExp; + QList matchers; + }; + + // Use dynamic allocation to save memory + RegExp* m_regExp; + + friend class AdBlockMatcher; + friend class AdBlockSearchTree; + friend class AdBlockSubscription; +}; + +#endif // ADBLOCKRULE_H + diff --git a/src/network-web/adblock/adblocksearchtree.cpp b/src/network-web/adblock/adblocksearchtree.cpp new file mode 100755 index 000000000..c0c6d0060 --- /dev/null +++ b/src/network-web/adblock/adblocksearchtree.cpp @@ -0,0 +1,157 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2013-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +#include "adblocksearchtree.h" +#include "adblockrule.h" + +#include + +AdBlockSearchTree::AdBlockSearchTree() + : m_root(new Node) +{ +} + +AdBlockSearchTree::~AdBlockSearchTree() +{ + deleteNode(m_root); +} + +void AdBlockSearchTree::clear() +{ + deleteNode(m_root); + m_root = new Node; +} + +bool AdBlockSearchTree::add(const AdBlockRule* rule) +{ + if (rule->m_type != AdBlockRule::StringContainsMatchRule) { + return false; + } + + const QString filter = rule->m_matchString; + int len = filter.size(); + + if (len <= 0) { + qDebug() << "AdBlockSearchTree: Inserting rule with filter len <= 0!"; + return false; + } + + Node* node = m_root; + + for (int i = 0; i < len; ++i) { + const QChar c = filter.at(i); + if (!node->children.contains(c)) { + Node* n = new Node; + n->c = c; + + node->children[c] = n; + } + + node = node->children[c]; + } + + node->rule = rule; + + return true; +} + +const AdBlockRule* AdBlockSearchTree::find(const QNetworkRequest &request, const QString &domain, const QString &urlString) const +{ + int len = urlString.size(); + + if (len <= 0) { + return 0; + } + + const QChar* string = urlString.constData(); + + for (int i = 0; i < len; ++i) { + const AdBlockRule* rule = prefixSearch(request, domain, urlString, string++, len - i); + if (rule) { + return rule; + } + } + + return 0; +} + +const AdBlockRule* AdBlockSearchTree::prefixSearch(const QNetworkRequest &request, const QString &domain, const QString &urlString, const QChar* string, int len) const +{ + if (len <= 0) { + return 0; + } + + QChar c = string[0]; + + if (!m_root->children.contains(c)) { + return 0; + } + + Node* node = m_root->children[c]; + + for (int i = 1; i < len; ++i) { + const QChar c = (++string)[0]; + + if (node->rule && node->rule->networkMatch(request, domain, urlString)) { + return node->rule; + } + + if (!node->children.contains(c)) { + return 0; + } + + node = node->children[c]; + } + + if (node->rule && node->rule->networkMatch(request, domain, urlString)) { + return node->rule; + } + + return 0; +} + +void AdBlockSearchTree::deleteNode(AdBlockSearchTree::Node* node) +{ + if (!node) { + return; + } + + QHashIterator i(node->children); + while (i.hasNext()) { + i.next(); + deleteNode(i.value()); + } + + delete node; +} diff --git a/src/network-web/adblock/adblocksearchtree.h b/src/network-web/adblock/adblocksearchtree.h new file mode 100755 index 000000000..884f875bf --- /dev/null +++ b/src/network-web/adblock/adblocksearchtree.h @@ -0,0 +1,73 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2013-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +#ifndef ADBLOCKSEARCHTREE_H +#define ADBLOCKSEARCHTREE_H + +#include +#include + +class QNetworkRequest; + +class AdBlockRule; + +class AdBlockSearchTree +{ +public: + explicit AdBlockSearchTree(); + ~AdBlockSearchTree(); + + void clear(); + + bool add(const AdBlockRule* rule); + const AdBlockRule* find(const QNetworkRequest &request, const QString &domain, const QString &urlString) const; + +private: + struct Node { + QChar c; + const AdBlockRule* rule; + QHash children; + + Node() : c(0) , rule(0) { } + }; + + const AdBlockRule* prefixSearch(const QNetworkRequest &request, const QString &domain, + const QString &urlString, const QChar* string, int len) const; + + void deleteNode(Node* node); + + Node* m_root; +}; + +#endif // ADBLOCKSEARCHTREE_H diff --git a/src/network-web/adblock/adblocksubscription.cpp b/src/network-web/adblock/adblocksubscription.cpp new file mode 100755 index 000000000..9edce5222 --- /dev/null +++ b/src/network-web/adblock/adblocksubscription.cpp @@ -0,0 +1,488 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2010-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/** + * Copyright (c) 2009, Benjamin C. Meyer + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Benjamin Meyer nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include "adblocksubscription.h" +#include "adblockmanager.h" +#include "adblocksearchtree.h" +#include "followredirectreply.h" + +#include "network-web/silentnetworkaccessmanager.h" +#include "miscellaneous/iofactory.h" +#include "miscellaneous/application.h" + +#include +#include +#include +#include +#include + +#include + +AdBlockSubscription::AdBlockSubscription(const QString &title, QObject* parent) + : QObject(parent) + , m_reply(0) + , m_title(title) + , m_updated(false) +{ +} + +QString AdBlockSubscription::title() const +{ + return m_title; +} + +void AdBlockSubscription::setTitle(const QString &title) +{ + m_title = title; +} + +QString AdBlockSubscription::filePath() const +{ + return m_filePath; +} + +void AdBlockSubscription::setFilePath(const QString &path) +{ + m_filePath = path; +} + +QUrl AdBlockSubscription::url() const +{ + return m_url; +} + +void AdBlockSubscription::setUrl(const QUrl &url) +{ + m_url = url; +} + +void AdBlockSubscription::loadSubscription(const QStringList &disabledRules) +{ + QFile file(m_filePath); + + if (!file.exists()) { + QTimer::singleShot(0, this, SLOT(updateSubscription())); + return; + } + + if (!file.open(QFile::ReadOnly)) { + qWarning() << "AdBlockSubscription::" << __FUNCTION__ << "Unable to open adblock file for reading" << m_filePath; + QTimer::singleShot(0, this, SLOT(updateSubscription())); + return; + } + + QTextStream textStream(&file); + textStream.setCodec("UTF-8"); + // Header is on 3rd line + textStream.readLine(1024); + textStream.readLine(1024); + QString header = textStream.readLine(1024); + + if (!header.startsWith(QLatin1String("[Adblock")) || m_title.isEmpty()) { + qWarning() << "AdBlockSubscription::" << __FUNCTION__ << "invalid format of adblock file" << m_filePath; + QTimer::singleShot(0, this, SLOT(updateSubscription())); + return; + } + + m_rules.clear(); + + while (!textStream.atEnd()) { + AdBlockRule* rule = new AdBlockRule(textStream.readLine(), this); + + if (disabledRules.contains(rule->filter())) { + rule->setEnabled(false); + } + + m_rules.append(rule); + } + + // Initial update + if (m_rules.isEmpty() && !m_updated) { + QTimer::singleShot(0, this, SLOT(updateSubscription())); + } +} + +void AdBlockSubscription::saveSubscription() +{ +} + +void AdBlockSubscription::updateSubscription() +{ + if (m_reply || !m_url.isValid()) { + return; + } + + m_reply = new FollowRedirectReply(m_url, SilentNetworkAccessManager::instance()); + + connect(m_reply, SIGNAL(finished()), this, SLOT(subscriptionDownloaded())); +} + +void AdBlockSubscription::subscriptionDownloaded() +{ + if (m_reply != qobject_cast(sender())) { + return; + } + + bool error = false; + const QByteArray response = QString::fromUtf8(m_reply->readAll()).toUtf8(); + + if (m_reply->error() != QNetworkReply::NoError || + !response.startsWith(QByteArray("[Adblock")) || + !saveDownloadedData(response) + ) { + error = true; + } + + m_reply->deleteLater(); + m_reply = 0; + + if (error) { + emit subscriptionError(tr("Cannot load subscription!")); + return; + } + + loadSubscription(AdBlockManager::instance()->disabledRules()); + + emit subscriptionUpdated(); + emit subscriptionChanged(); +} + +bool AdBlockSubscription::saveDownloadedData(const QByteArray &data) +{ + QFile file(m_filePath); + + if (!file.open(QFile::ReadWrite | QFile::Truncate)) { + qWarning() << "AdBlockSubscription::" << __FUNCTION__ << "Unable to open adblock file for writing:" << m_filePath; + return false; + } + + // Write subscription header + file.write(QString("Title: %1\nUrl: %2\n").arg(title(), url().toString()).toUtf8()); + + if (AdBlockManager::instance()->useLimitedEasyList() && m_url == QUrl(ADBLOCK_EASYLIST_URL)) { + // Third-party advertisers rules are with start domain (||) placeholder which needs regexps + // So we are ignoring it for keeping good performance + // But we will use whitelist rules at the end of list + + QByteArray part1 = data.left(data.indexOf(QLatin1String("!-----------------------------Third-party adverts-----------------------------!"))); + QByteArray part2 = data.mid(data.indexOf(QLatin1String("!---------------------------------Whitelists----------------------------------!"))); + + file.write(part1); + file.write(part2); + file.close(); + return true; + } + + file.write(data); + file.close(); + return true; +} + +const AdBlockRule* AdBlockSubscription::rule(int offset) const +{ + if (!(offset >= 0 && m_rules.count() > offset)) { + return 0; + } + + return m_rules[offset]; +} + +QVector AdBlockSubscription::allRules() const +{ + return m_rules; +} + +const AdBlockRule* AdBlockSubscription::enableRule(int offset) +{ + if (!(offset >= 0 && m_rules.count() > offset)) { + return 0; + } + + AdBlockRule* rule = m_rules[offset]; + rule->setEnabled(true); + AdBlockManager::instance()->removeDisabledRule(rule->filter()); + + emit subscriptionChanged(); + + // TODO + /* + if (rule->isCssRule()) + mainApp->reloadUserStyleBrowser(); +*/ + return rule; +} + +const AdBlockRule* AdBlockSubscription::disableRule(int offset) +{ + if (!(offset >= 0 && m_rules.count() > offset)) { + return 0; + } + + AdBlockRule* rule = m_rules[offset]; + rule->setEnabled(false); + AdBlockManager::instance()->addDisabledRule(rule->filter()); + + emit subscriptionChanged(); + + // TODO + /* + if (rule->isCssRule()) + mainApp->reloadUserStyleBrowser(); +*/ + + return rule; +} + +bool AdBlockSubscription::canEditRules() const +{ + return false; +} + +bool AdBlockSubscription::canBeRemoved() const +{ + return true; +} + +int AdBlockSubscription::addRule(AdBlockRule* rule) +{ + Q_UNUSED(rule) + return -1; +} + +bool AdBlockSubscription::removeRule(int offset) +{ + Q_UNUSED(offset) + return false; +} + +const AdBlockRule* AdBlockSubscription::replaceRule(AdBlockRule* rule, int offset) +{ + Q_UNUSED(rule) + Q_UNUSED(offset) + return 0; +} + +AdBlockSubscription::~AdBlockSubscription() +{ + qDeleteAll(m_rules); +} + +// AdBlockCustomList + +AdBlockCustomList::AdBlockCustomList(QObject* parent) + : AdBlockSubscription(tr("Custom Rules"), parent) +{ + // TODO + setFilePath(qApp->homeFolderPath() + "/adblock/customlist.txt"); +} + +void AdBlockCustomList::retranslateStrings() +{ + setTitle(tr("Custom Rules")); +} + +void AdBlockCustomList::loadSubscription(const QStringList &disabledRules) +{ + // DuckDuckGo ad whitelist rules + // They cannot be removed, but can be disabled. + // Please consider not disabling them. Thanks! + + const QString ddg1 = QSL("@@||duckduckgo.com^$document"); + const QString ddg2 = QSL("duckduckgo.com#@#.has-ad"); + + QString rules = QString(); + + try { + rules = QString::fromUtf8(IOFactory::readTextFile((filePath()))); + } + catch (ApplicationException &ex) { + } + + QFile file(filePath()); + if (file.open(QFile::WriteOnly | QFile::Append)) { + QTextStream stream(&file); + stream.setCodec("UTF-8"); + + if (!rules.contains(ddg1 + QLatin1String("\n"))) + stream << ddg1 << endl; + + if (!rules.contains(QLatin1String("\n") + ddg2)) + stream << ddg2 << endl; + } + file.close(); + + AdBlockSubscription::loadSubscription(disabledRules); +} + +void AdBlockCustomList::saveSubscription() +{ + QFile file(filePath()); + + if (!file.open(QFile::ReadWrite | QFile::Truncate)) { + qWarning() << "AdBlockSubscription::" << __FUNCTION__ << "Unable to open adblock file for writing:" << filePath(); + return; + } + + QTextStream textStream(&file); + textStream.setCodec("UTF-8"); + textStream << "Title: " << title() << endl; + textStream << "Url: " << url().toString() << endl; + textStream << "[Adblock Plus 1.1.1]" << endl; + + foreach (const AdBlockRule* rule, m_rules) { + textStream << rule->filter() << endl; + } + + file.close(); +} + +bool AdBlockCustomList::canEditRules() const +{ + return true; +} + +bool AdBlockCustomList::canBeRemoved() const +{ + return false; +} + +bool AdBlockCustomList::containsFilter(const QString &filter) const +{ + foreach (const AdBlockRule* rule, m_rules) { + if (rule->filter() == filter) { + return true; + } + } + + return false; +} + +bool AdBlockCustomList::removeFilter(const QString &filter) +{ + for (int i = 0; i < m_rules.count(); ++i) { + const AdBlockRule* rule = m_rules.at(i); + + if (rule->filter() == filter) { + return removeRule(i); + } + } + + return false; +} + +int AdBlockCustomList::addRule(AdBlockRule* rule) +{ + m_rules.append(rule); + + emit subscriptionChanged(); + + // TODO + /* + if (rule->isCssRule()) + mainApp->reloadUserStyleBrowser(); +*/ + return m_rules.count() - 1; +} + +bool AdBlockCustomList::removeRule(int offset) +{ + if (!(offset >= 0 && m_rules.count() > offset)) { + return false; + } + + AdBlockRule* rule = m_rules.at(offset); + const QString filter = rule->filter(); + + m_rules.remove(offset); + + emit subscriptionChanged(); + // TODO + /* + if (rule->isCssRule()) + mainApp->reloadUserStyleBrowser(); +*/ + + AdBlockManager::instance()->removeDisabledRule(filter); + + delete rule; + return true; +} + +const AdBlockRule* AdBlockCustomList::replaceRule(AdBlockRule* rule, int offset) +{ + if (!(offset >= 0 && m_rules.count() > offset)) { + return 0; + } + + AdBlockRule* oldRule = m_rules.at(offset); + m_rules[offset] = rule; + + emit subscriptionChanged(); + + // TODO + /* + if (rule->isCssRule() || oldRule->isCssRule()) + mainApp->reloadUserStyleBrowser(); +*/ + + delete oldRule; + return m_rules[offset]; +} diff --git a/src/network-web/adblock/adblocksubscription.h b/src/network-web/adblock/adblocksubscription.h new file mode 100755 index 000000000..6e79d0f0f --- /dev/null +++ b/src/network-web/adblock/adblocksubscription.h @@ -0,0 +1,159 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2010-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/** + * Copyright (c) 2009, Benjamin C. Meyer + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Benjamin Meyer nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef ADBLOCKSUBSCRIPTION_H +#define ADBLOCKSUBSCRIPTION_H + +#include +#include + +#include "adblockrule.h" +#include "adblocksearchtree.h" + +class QNetworkRequest; +class QNetworkReply; +class QUrl; + +class FollowRedirectReply; + +class AdBlockSubscription : public QObject +{ + Q_OBJECT +public: + explicit AdBlockSubscription(const QString &title, QObject* parent = 0); + ~AdBlockSubscription(); + + QString title() const; + void setTitle(const QString &title); + + QString filePath() const; + void setFilePath(const QString &path); + + QUrl url() const; + void setUrl(const QUrl &url); + + virtual void loadSubscription(const QStringList &disabledRules); + virtual void saveSubscription(); + + const AdBlockRule* rule(int offset) const; + QVector allRules() const; + + const AdBlockRule* enableRule(int offset); + const AdBlockRule* disableRule(int offset); + + virtual bool canEditRules() const; + virtual bool canBeRemoved() const; + + virtual int addRule(AdBlockRule* rule); + virtual bool removeRule(int offset); + virtual const AdBlockRule* replaceRule(AdBlockRule* rule, int offset); + +public slots: + void updateSubscription(); + +signals: + void subscriptionChanged(); + void subscriptionUpdated(); + void subscriptionError(const QString &message); + +protected slots: + void subscriptionDownloaded(); + +protected: + virtual bool saveDownloadedData(const QByteArray &data); + + FollowRedirectReply* m_reply; + + QVector m_rules; + +private: + QString m_title; + QString m_filePath; + + QUrl m_url; + bool m_updated; +}; + +class AdBlockCustomList : public AdBlockSubscription +{ + Q_OBJECT +public: + explicit AdBlockCustomList(QObject* parent = 0); + + void retranslateStrings(); + + void loadSubscription(const QStringList &disabledRules); + void saveSubscription(); + + bool canEditRules() const; + bool canBeRemoved() const; + + bool containsFilter(const QString &filter) const; + bool removeFilter(const QString &filter); + + int addRule(AdBlockRule* rule); + bool removeRule(int offset); + const AdBlockRule* replaceRule(AdBlockRule* rule, int offset); +}; + +#endif // ADBLOCKSUBSCRIPTION_H + diff --git a/src/network-web/adblock/adblocktreewidget.cpp b/src/network-web/adblock/adblocktreewidget.cpp new file mode 100755 index 000000000..02b8a39a4 --- /dev/null +++ b/src/network-web/adblock/adblocktreewidget.cpp @@ -0,0 +1,361 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2010-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +#include "adblocktreewidget.h" +#include "adblocksubscription.h" + +#include +#include +#include +#include +#include + +AdBlockTreeWidget::AdBlockTreeWidget(AdBlockSubscription* subscription, QWidget* parent) + : QTreeWidget(parent) + , m_subscription(subscription) + , m_topItem(0) + , m_itemChangingBlock(false) + , m_refreshAllItemsNeeded(true) +{ + setContextMenuPolicy(Qt::CustomContextMenu); + setHeaderHidden(true); + setAlternatingRowColors(true); + setFrameStyle(QFrame::NoFrame); + + connect(this, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuRequested(QPoint))); + connect(this, SIGNAL(itemChanged(QTreeWidgetItem*,int)), this, SLOT(itemChanged(QTreeWidgetItem*))); + connect(m_subscription, SIGNAL(subscriptionUpdated()), this, SLOT(subscriptionUpdated())); + connect(m_subscription, SIGNAL(subscriptionError(QString)), this, SLOT(subscriptionError(QString))); +} + +AdBlockSubscription* AdBlockTreeWidget::subscription() const +{ + return m_subscription; +} + +void AdBlockTreeWidget::showRule(const AdBlockRule* rule) +{ + if (!m_topItem && rule) { + m_ruleToBeSelected = rule->filter(); + } + else if (!m_ruleToBeSelected.isEmpty()) { + QList items = findItems(m_ruleToBeSelected, Qt::MatchRecursive); + if (!items.isEmpty()) { + QTreeWidgetItem* item = items.at(0); + + setCurrentItem(item); + scrollToItem(item, QAbstractItemView::PositionAtCenter); + } + + m_ruleToBeSelected.clear(); + } +} + +void AdBlockTreeWidget::contextMenuRequested(const QPoint &pos) +{ + if (!m_subscription->canEditRules()) { + return; + } + + QTreeWidgetItem* item = itemAt(pos); + if (!item) { + return; + } + + QMenu menu; + menu.addAction(tr("Add Rule"), this, SLOT(addRule())); + menu.addSeparator(); + QAction* deleteAction = menu.addAction(tr("Remove Rule"), this, SLOT(removeRule())); + + if (!item->parent()) { + deleteAction->setDisabled(true); + } + + menu.exec(viewport()->mapToGlobal(pos)); +} + +void AdBlockTreeWidget::itemChanged(QTreeWidgetItem* item) +{ + m_refreshAllItemsNeeded = true; + + if (!item || m_itemChangingBlock) { + return; + } + + m_itemChangingBlock = true; + + int offset = item->data(0, Qt::UserRole + 10).toInt(); + const AdBlockRule* oldRule = m_subscription->rule(offset); + + if (item->checkState(0) == Qt::Unchecked && oldRule->isEnabled()) { + // Disable rule + const AdBlockRule* rule = m_subscription->disableRule(offset); + + adjustItemFeatures(item, rule); + } + else if (item->checkState(0) == Qt::Checked && !oldRule->isEnabled()) { + // Enable rule + const AdBlockRule* rule = m_subscription->enableRule(offset); + + adjustItemFeatures(item, rule); + } + else if (m_subscription->canEditRules()) { + // Custom rule has been changed + AdBlockRule* newRule = new AdBlockRule(item->text(0), m_subscription); + const AdBlockRule* rule = m_subscription->replaceRule(newRule, offset); + + adjustItemFeatures(item, rule); + } + + m_itemChangingBlock = false; +} + +void AdBlockTreeWidget::copyFilter() +{ + QTreeWidgetItem* item = currentItem(); + if (!item) { + return; + } + + QApplication::clipboard()->setText(item->text(0)); +} + +void AdBlockTreeWidget::addRule() +{ + if (!m_subscription->canEditRules()) { + return; + } + + QString newRule = QInputDialog::getText(this, tr("Add Custom Rule"), tr("Please write your rule here:")); + if (newRule.isEmpty()) { + return; + } + + AdBlockRule* rule = new AdBlockRule(newRule, m_subscription); + int offset = m_subscription->addRule(rule); + + QTreeWidgetItem* item = new QTreeWidgetItem(); + item->setText(0, newRule); + item->setData(0, Qt::UserRole + 10, offset); + item->setFlags(item->flags() | Qt::ItemIsEditable); + + m_itemChangingBlock = true; + m_topItem->addChild(item); + m_itemChangingBlock = false; + + adjustItemFeatures(item, rule); +} + +void AdBlockTreeWidget::removeRule() +{ + QTreeWidgetItem* item = currentItem(); + if (!item || !m_subscription->canEditRules() || item == m_topItem) { + return; + } + + int offset = item->data(0, Qt::UserRole + 10).toInt(); + + m_subscription->removeRule(offset); + delete item; +} + +void AdBlockTreeWidget::subscriptionUpdated() +{ + refresh(); + + m_itemChangingBlock = true; + m_topItem->setText(0, tr("%1 (recently updated)").arg(m_subscription->title())); + m_itemChangingBlock = false; +} + +void AdBlockTreeWidget::subscriptionError(const QString &message) +{ + refresh(); + + m_itemChangingBlock = true; + m_topItem->setText(0, tr("%1 (Error: %2)").arg(m_subscription->title(), message)); + m_itemChangingBlock = false; +} + +void AdBlockTreeWidget::adjustItemFeatures(QTreeWidgetItem* item, const AdBlockRule* rule) +{ + if (!rule->isEnabled()) { + QFont font; + font.setItalic(true); + item->setForeground(0, QColor(Qt::gray)); + + if (!rule->isComment()) { + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(0, Qt::Unchecked); + item->setFont(0, font); + } + + return; + } + + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + item->setCheckState(0, Qt::Checked); + + if (rule->isException()) { + item->setForeground(0, QColor(Qt::darkGreen)); + item->setFont(0, QFont()); + } + else if (rule->isCssRule()) { + item->setForeground(0, QColor(Qt::darkBlue)); + item->setFont(0, QFont()); + } + else { + item->setForeground(0, QColor()); + item->setFont(0, QFont()); + } +} + +void AdBlockTreeWidget::keyPressEvent(QKeyEvent* event) +{ + if (event->key() == Qt::Key_C && event->modifiers() & Qt::ControlModifier) { + copyFilter(); + } + + if (event->key() == Qt::Key_Delete) { + removeRule(); + } + + QTreeWidget::keyPressEvent(event); +} + +void AdBlockTreeWidget::refresh() +{ + m_itemChangingBlock = true; + clear(); + + QFont boldFont; + boldFont.setBold(true); + + m_topItem = new QTreeWidgetItem(this); + m_topItem->setText(0, m_subscription->title()); + m_topItem->setFont(0, boldFont); + m_topItem->setExpanded(true); + addTopLevelItem(m_topItem); + + const QVector &allRules = m_subscription->allRules(); + + int index = 0; + foreach (const AdBlockRule* rule, allRules) { + QTreeWidgetItem* item = new QTreeWidgetItem(m_topItem); + item->setText(0, rule->filter()); + item->setData(0, Qt::UserRole + 10, index); + + if (m_subscription->canEditRules()) { + item->setFlags(item->flags() | Qt::ItemIsEditable); + } + + adjustItemFeatures(item, rule); + ++index; + } + + showRule(0); + m_itemChangingBlock = false; +} + +void AdBlockTreeWidget::clear() +{ + QTreeWidget::clear(); + m_allTreeItems.clear(); +} + +void AdBlockTreeWidget::addTopLevelItem(QTreeWidgetItem* item) +{ + m_allTreeItems.append(item); + QTreeWidget::addTopLevelItem(item); +} + +void AdBlockTreeWidget::iterateAllItems(QTreeWidgetItem* parent) +{ + int count = parent ? parent->childCount() : topLevelItemCount(); + + for (int i = 0; i < count; i++) { + QTreeWidgetItem* item = parent ? parent->child(i) : topLevelItem(i); + + if (item->childCount() == 0) { + m_allTreeItems.append(item); + } + + iterateAllItems(item); + } +} + +QList AdBlockTreeWidget::allItems() +{ + if (m_refreshAllItemsNeeded) { + m_allTreeItems.clear(); + iterateAllItems(0); + m_refreshAllItemsNeeded = false; + } + + return m_allTreeItems; +} + +void AdBlockTreeWidget::filterString(const QString &string) +{ + QList _allItems = allItems(); + QList parents; + bool stringIsEmpty = string.isEmpty(); + foreach (QTreeWidgetItem* item, _allItems) { + bool containsString = stringIsEmpty || item->text(0).contains(string, Qt::CaseInsensitive); + if (containsString) { + item->setHidden(false); + if (item->parent()) { + if (!parents.contains(item->parent())) { + parents << item->parent(); + } + } + } + else { + item->setHidden(true); + if (item->parent()) { + item->parent()->setHidden(true); + } + } + } + + for (int i = 0; i < parents.size(); ++i) { + QTreeWidgetItem* parentItem = parents.at(i); + parentItem->setHidden(false); + parentItem->setExpanded(true); + + if (parentItem->parent() && !parents.contains(parentItem->parent())) { + parents << parentItem->parent(); + } + } +} diff --git a/src/network-web/adblock/adblocktreewidget.h b/src/network-web/adblock/adblocktreewidget.h new file mode 100755 index 000000000..ea0c2e96e --- /dev/null +++ b/src/network-web/adblock/adblocktreewidget.h @@ -0,0 +1,86 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2010-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +#ifndef ADBLOCKTREEWIDGET_H +#define ADBLOCKTREEWIDGET_H + +#include + +class AdBlockSubscription; +class AdBlockRule; + +class AdBlockTreeWidget : public QTreeWidget +{ + Q_OBJECT +public: + explicit AdBlockTreeWidget(AdBlockSubscription* subscription, QWidget* parent = 0); + + AdBlockSubscription* subscription() const; + + void showRule(const AdBlockRule* rule); + void refresh(); + +public slots: + void addRule(); + void removeRule(); + void filterString(const QString &string); + void clear(); + +private slots: + void contextMenuRequested(const QPoint &pos); + void itemChanged(QTreeWidgetItem* item); + void copyFilter(); + + void subscriptionUpdated(); + void subscriptionError(const QString &message); + +private: + void adjustItemFeatures(QTreeWidgetItem* item, const AdBlockRule* rule); + void keyPressEvent(QKeyEvent* event); + void addTopLevelItem(QTreeWidgetItem* item); + QList allItems(); + void iterateAllItems(QTreeWidgetItem* parent); + + AdBlockSubscription* m_subscription; + QTreeWidgetItem* m_topItem; + + QString m_ruleToBeSelected; + bool m_itemChangingBlock; + + bool m_refreshAllItemsNeeded; + QList m_allTreeItems; + +}; + +#endif // ADBLOCKTREEWIDGET_H diff --git a/src/network-web/adblock/followredirectreply.cpp b/src/network-web/adblock/followredirectreply.cpp new file mode 100755 index 000000000..7c41902b0 --- /dev/null +++ b/src/network-web/adblock/followredirectreply.cpp @@ -0,0 +1,96 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2010-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +#include "followredirectreply.h" + +#include + +FollowRedirectReply::FollowRedirectReply(const QUrl &url, QNetworkAccessManager* manager) + : QObject() + , m_manager(manager) + , m_redirectCount(0) +{ + m_reply = m_manager->get(QNetworkRequest(url)); + connect(m_reply, SIGNAL(finished()), this, SLOT(replyFinished())); +} + +QNetworkReply* FollowRedirectReply::reply() const +{ + return m_reply; +} + +QUrl FollowRedirectReply::originalUrl() const +{ + return m_reply->request().url(); +} + +QUrl FollowRedirectReply::url() const +{ + return m_reply->url(); +} + +QNetworkReply::NetworkError FollowRedirectReply::error() const +{ + return m_reply->error(); +} + +QByteArray FollowRedirectReply::readAll() +{ + return m_reply->readAll(); +} + +void FollowRedirectReply::replyFinished() +{ + int replyStatus = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if ((replyStatus != 301 && replyStatus != 302) || m_redirectCount == 5) { + emit finished(); + return; + } + + m_redirectCount++; + + QUrl redirectUrl = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); + m_reply->close(); + m_reply->deleteLater(); + + m_reply = m_manager->get(QNetworkRequest(redirectUrl)); + connect(m_reply, SIGNAL(finished()), this, SLOT(replyFinished())); +} + +FollowRedirectReply::~FollowRedirectReply() +{ + m_reply->close(); + m_reply->deleteLater(); +} diff --git a/src/network-web/adblock/followredirectreply.h b/src/network-web/adblock/followredirectreply.h new file mode 100755 index 000000000..636b272ea --- /dev/null +++ b/src/network-web/adblock/followredirectreply.h @@ -0,0 +1,73 @@ +/* ============================================================ +* QuiteRSS is a open-source cross-platform RSS/Atom news feeds reader +* Copyright (C) 2011-2015 QuiteRSS Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ +/* ============================================================ +* QupZilla - WebKit based browser +* Copyright (C) 2010-2014 David Rosca +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* ============================================================ */ + +#ifndef FOLLOWREDIRECTREPLY_H +#define FOLLOWREDIRECTREPLY_H + +#include +#include + +class QNetworkAccessManager; +class QNetworkReply; +class QUrl; + +class FollowRedirectReply : public QObject +{ + Q_OBJECT +public: + explicit FollowRedirectReply(const QUrl &url, QNetworkAccessManager* manager); + ~FollowRedirectReply(); + + QNetworkReply* reply() const; + QUrl originalUrl() const; + QUrl url() const; + + QNetworkReply::NetworkError error() const; + QByteArray readAll(); + +signals: + void finished(); + +private slots: + void replyFinished(); + +private: + QNetworkAccessManager* m_manager; + QNetworkReply* m_reply; + int m_redirectCount; + +}; + +#endif // FOLLOWREDIRECTREPLY_H diff --git a/src/network-web/downloader.cpp b/src/network-web/downloader.cpp index 43ff99c43..a36c9fa68 100755 --- a/src/network-web/downloader.cpp +++ b/src/network-web/downloader.cpp @@ -34,7 +34,7 @@ Downloader::Downloader(QObject *parent) } Downloader::~Downloader() { - m_downloadManager->deleteLater(); + //m_downloadManager->deleteLater(); } void Downloader::downloadFile(const QString &url, int timeout, bool protected_contents, const QString &username, const QString &password) { diff --git a/src/network-web/webbrowser.h b/src/network-web/webbrowser.h index 3c3f7266a..91a27ac54 100755 --- a/src/network-web/webbrowser.h +++ b/src/network-web/webbrowser.h @@ -56,6 +56,10 @@ class WebBrowser : public TabContent { return m_webView->icon(); } + inline WebView *view() { + return m_webView; + } + // Sets this WebBrowser instance as focused. inline void setFocus(Qt::FocusReason reason) { m_txtLocation->setFocus(reason); diff --git a/src/network-web/webbrowsernetworkaccessmanager.cpp b/src/network-web/webbrowsernetworkaccessmanager.cpp index 760dc8b4c..c72538eb8 100755 --- a/src/network-web/webbrowsernetworkaccessmanager.cpp +++ b/src/network-web/webbrowsernetworkaccessmanager.cpp @@ -19,13 +19,15 @@ #include "miscellaneous/application.h" +#include "network-web/adblock/adblockmanager.h" + #include QPointer WebBrowserNetworkAccessManager::s_instance; -WebBrowserNetworkAccessManager::WebBrowserNetworkAccessManager(QObject *parent) - : BaseNetworkAccessManager(parent) { +WebBrowserNetworkAccessManager::WebBrowserNetworkAccessManager(WebPage *page, QObject *parent) + : BaseNetworkAccessManager(parent), page_(page) { connect(this, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), this, SLOT(onAuthenticationRequired(QNetworkReply*,QAuthenticator*))); } @@ -41,9 +43,27 @@ void WebBrowserNetworkAccessManager::onAuthenticationRequired(QNetworkReply *rep qDebug("URL '%s' requested authentication but username/password is not available.", qPrintable(reply->url().toString())); } +QNetworkReply *WebBrowserNetworkAccessManager::createRequest(QNetworkAccessManager::Operation op, const QNetworkRequest &request, QIODevice *outgoingData) { + if (page_) { + QNetworkRequest pageRequest = request; + page_->populateNetworkRequest(pageRequest); + return WebBrowserNetworkAccessManager::instance()->createRequest(op, pageRequest, outgoingData); + } + + if (op == QNetworkAccessManager::GetOperation) { + QNetworkReply *reply = AdBlockManager::instance()->block(request); + + if (reply) { + return reply; + } + } + + return BaseNetworkAccessManager::createRequest(op, request, outgoingData); +} + WebBrowserNetworkAccessManager *WebBrowserNetworkAccessManager::instance() { if (s_instance.isNull()) { - s_instance = new WebBrowserNetworkAccessManager(qApp); + s_instance = new WebBrowserNetworkAccessManager(0, qApp); } return s_instance; diff --git a/src/network-web/webbrowsernetworkaccessmanager.h b/src/network-web/webbrowsernetworkaccessmanager.h old mode 100644 new mode 100755 index 8f6099bce..af7ce7423 --- a/src/network-web/webbrowsernetworkaccessmanager.h +++ b/src/network-web/webbrowsernetworkaccessmanager.h @@ -20,6 +20,8 @@ #include "network-web/basenetworkaccessmanager.h" +#include "webpage.h" + #include @@ -29,7 +31,7 @@ class WebBrowserNetworkAccessManager : public BaseNetworkAccessManager { public: // Constructors and destructors. - explicit WebBrowserNetworkAccessManager(QObject *parent = 0); + explicit WebBrowserNetworkAccessManager(WebPage *page = 0, QObject *parent = 0); virtual ~WebBrowserNetworkAccessManager(); // Returns pointer to global network access manager @@ -40,7 +42,13 @@ class WebBrowserNetworkAccessManager : public BaseNetworkAccessManager { void onAuthenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator); private: + WebPage* page_; + static QPointer s_instance; + + // QNetworkAccessManager interface + protected: + QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData); }; #endif // WEBBROWSERNETWORKACCESSMANAGER_H diff --git a/src/network-web/webpage.cpp b/src/network-web/webpage.cpp old mode 100644 new mode 100755 index 6097aafe6..6db84270b --- a/src/network-web/webpage.cpp +++ b/src/network-web/webpage.cpp @@ -21,26 +21,161 @@ #include "network-web/webbrowser.h" #include "miscellaneous/application.h" +#include "network-web/adblock/adblockmanager.h" + #include +#include #include +QList WebPage::livingPages_; + WebPage::WebPage(QObject *parent) - : QWebPage(parent) { + : QWebPage(parent), loadProgress_(-1) { // Setup global network access manager. // NOTE: This makes network settings easy for all web browsers. - setNetworkAccessManager(WebBrowserNetworkAccessManager::instance()); + setNetworkAccessManager(new WebBrowserNetworkAccessManager(this, this)); setForwardUnsupportedContent(true); connect(this, SIGNAL(unsupportedContent(QNetworkReply*)), this, SLOT(handleUnsupportedContent(QNetworkReply*))); + + connect(this, SIGNAL(loadProgress(int)), this, SLOT(progress(int))); + connect(this, SIGNAL(loadFinished(bool)), this, SLOT(finished())); + + livingPages_.append(this); } WebPage::~WebPage() { + livingPages_.removeOne(this); +} + +bool WebPage::isLoading() const +{ + return loadProgress_ < 100; +} + +void WebPage::progress(int prog) +{ + loadProgress_ = prog; +} + +void WebPage::finished() +{ + progress(100); + + // AdBlock + cleanBlockedObjects(); +} + +void WebPage::cleanBlockedObjects() +{ + AdBlockManager* manager = AdBlockManager::instance(); + if (!manager->isEnabled()) { + return; + } + + const QWebElement docElement = mainFrame()->documentElement(); + + foreach (const AdBlockedEntry &entry, adBlockedEntries_) { + const QString urlString = entry.url.toString(); + if (urlString.endsWith(QLatin1String(".js")) || urlString.endsWith(QLatin1String(".css"))) { + continue; + } + + QString urlEnd; + + int pos = urlString.lastIndexOf(QLatin1Char('/')); + if (pos > 8) { + urlEnd = urlString.mid(pos + 1); + } + + if (urlString.endsWith(QLatin1Char('/'))) { + urlEnd = urlString.left(urlString.size() - 1); + } + + QString selector("img[src$=\"%1\"], iframe[src$=\"%1\"],embed[src$=\"%1\"]"); + QWebElementCollection elements = docElement.findAll(selector.arg(urlEnd)); + + foreach (QWebElement element, elements) { + QString src = element.attribute("src"); + src.remove(QLatin1String("../")); + + if (urlString.contains(src)) { + element.setStyleProperty("display", "none"); + } + } + } + + // Apply domain-specific element hiding rules + QString elementHiding = manager->elementHidingRulesForDomain(mainFrame()->url()); + if (elementHiding.isEmpty()) { + return; + } + + elementHiding.append(QLatin1String("\n")); + + QWebElement bodyElement = docElement.findFirst("body"); + bodyElement.appendInside("