File indexing completed on 2024-05-12 04:57:50

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2010-2018 David Rosca <nowrep@gmail.com>
0004 *
0005 * This program is free software: you can redistribute it and/or modify
0006 * it under the terms of the GNU General Public License as published by
0007 * the Free Software Foundation, either version 3 of the License, or
0008 * (at your option) any later version.
0009 *
0010 * This program is distributed in the hope that it will be useful,
0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0013 * GNU General Public License for more details.
0014 *
0015 * You should have received a copy of the GNU General Public License
0016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
0017 * ============================================================ */
0018 /**
0019  * Copyright (c) 2009, Benjamin C. Meyer <ben@meyerhome.net>
0020  *
0021  * Redistribution and use in source and binary forms, with or without
0022  * modification, are permitted provided that the following conditions
0023  * are met:
0024  * 1. Redistributions of source code must retain the above copyright
0025  *    notice, this list of conditions and the following disclaimer.
0026  * 2. Redistributions in binary form must reproduce the above copyright
0027  *    notice, this list of conditions and the following disclaimer in the
0028  *    documentation and/or other materials provided with the distribution.
0029  * 3. Neither the name of the Benjamin Meyer nor the names of its contributors
0030  *    may be used to endorse or promote products derived from this software
0031  *    without specific prior written permission.
0032  *
0033  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
0034  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0035  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0036  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
0037  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
0038  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
0039  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
0040  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
0041  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
0042  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0043  * SUCH DAMAGE.
0044  */
0045 #include "adblocksubscription.h"
0046 #include "adblockmanager.h"
0047 #include "mainapplication.h"
0048 #include "networkmanager.h"
0049 #include "datapaths.h"
0050 #include "qztools.h"
0051 
0052 #include <QFile>
0053 #include <QTimer>
0054 #include <QNetworkReply>
0055 #include <QSaveFile>
0056 
0057 AdBlockSubscription::AdBlockSubscription(const QString &title, QObject* parent)
0058     : QObject(parent)
0059     , m_reply(nullptr)
0060     , m_title(title)
0061     , m_updated(false)
0062 {
0063 }
0064 
0065 QString AdBlockSubscription::title() const
0066 {
0067     return m_title;
0068 }
0069 
0070 QString AdBlockSubscription::filePath() const
0071 {
0072     return m_filePath;
0073 }
0074 
0075 void AdBlockSubscription::setFilePath(const QString &path)
0076 {
0077     m_filePath = path;
0078 }
0079 
0080 QUrl AdBlockSubscription::url() const
0081 {
0082     return m_url;
0083 }
0084 
0085 void AdBlockSubscription::setUrl(const QUrl &url)
0086 {
0087     m_url = url;
0088 }
0089 
0090 void AdBlockSubscription::loadSubscription(const QStringList &disabledRules)
0091 {
0092     QFile file(m_filePath);
0093 
0094     if (!file.exists()) {
0095         QTimer::singleShot(0, this, &AdBlockSubscription::updateSubscription);
0096         return;
0097     }
0098 
0099     if (!file.open(QFile::ReadOnly)) {
0100         qWarning() << "AdBlockSubscription::" << __FUNCTION__ << "Unable to open adblock file for reading" << m_filePath;
0101         QTimer::singleShot(0, this, &AdBlockSubscription::updateSubscription);
0102         return;
0103     }
0104 
0105     QTextStream textStream(&file);
0106     textStream.setEncoding(QStringConverter::Utf8);
0107     // Header is on 3rd line
0108     textStream.readLine(1024);
0109     textStream.readLine(1024);
0110     QString header = textStream.readLine(1024);
0111 
0112     if (!header.startsWith(QLatin1String("[Adblock")) || m_title.isEmpty()) {
0113         qWarning() << "AdBlockSubscription::" << __FUNCTION__ << "invalid format of adblock file" << m_filePath;
0114         QTimer::singleShot(0, this, &AdBlockSubscription::updateSubscription);
0115         return;
0116     }
0117 
0118     qDeleteAll(m_rules);
0119     m_rules.clear();
0120 
0121     while (!textStream.atEnd()) {
0122         const QString line = textStream.readLine().trimmed();
0123         if (line.isEmpty()) {
0124             continue;
0125         }
0126         auto *rule = new AdBlockRule(line, this);
0127         if (disabledRules.contains(rule->filter())) {
0128             rule->setEnabled(false);
0129         }
0130         m_rules.append(rule);
0131     }
0132 
0133     // Initial update
0134     if (m_rules.isEmpty() && !m_updated) {
0135         QTimer::singleShot(0, this, &AdBlockSubscription::updateSubscription);
0136     }
0137 }
0138 
0139 void AdBlockSubscription::saveSubscription()
0140 {
0141 }
0142 
0143 void AdBlockSubscription::updateSubscription()
0144 {
0145     if (m_reply || !m_url.isValid()) {
0146         return;
0147     }
0148 
0149     m_reply = mApp->networkManager()->get(QNetworkRequest(m_url));
0150     connect(m_reply, &QNetworkReply::finished, this, &AdBlockSubscription::subscriptionDownloaded);
0151 }
0152 
0153 void AdBlockSubscription::subscriptionDownloaded()
0154 {
0155     if (m_reply != qobject_cast<QNetworkReply*>(sender())) {
0156         return;
0157     }
0158 
0159     bool error = false;
0160     const QByteArray response = QString::fromUtf8(m_reply->readAll()).toUtf8();
0161 
0162     if (m_reply->error() != QNetworkReply::NoError ||
0163         !response.startsWith(QByteArray("[Adblock")) ||
0164         !saveDownloadedData(response)
0165        ) {
0166         error = true;
0167     }
0168 
0169     m_reply->deleteLater();
0170     m_reply = nullptr;
0171 
0172     if (error) {
0173         Q_EMIT subscriptionError(tr("Cannot load subscription!"));
0174         return;
0175     }
0176 
0177     loadSubscription(AdBlockManager::instance()->disabledRules());
0178 
0179     Q_EMIT subscriptionUpdated();
0180     Q_EMIT subscriptionChanged();
0181 }
0182 
0183 bool AdBlockSubscription::saveDownloadedData(const QByteArray &data)
0184 {
0185     QSaveFile file(m_filePath);
0186 
0187     if (!file.open(QFile::WriteOnly)) {
0188         qWarning() << "AdBlockSubscription::" << __FUNCTION__ << "Unable to open adblock file for writing:" << m_filePath;
0189         return false;
0190     }
0191 
0192     // Write subscription header
0193     file.write(QSL("Title: %1\nUrl: %2\n").arg(title(), url().toString()).toUtf8());
0194     file.write(data);
0195     file.commit();
0196     return true;
0197 }
0198 
0199 const AdBlockRule* AdBlockSubscription::rule(int offset) const
0200 {
0201     if (!QzTools::containsIndex(m_rules, offset)) {
0202         return nullptr;
0203     }
0204 
0205     return m_rules[offset];
0206 }
0207 
0208 QVector<AdBlockRule*> AdBlockSubscription::allRules() const
0209 {
0210     return m_rules;
0211 }
0212 
0213 const AdBlockRule* AdBlockSubscription::enableRule(int offset)
0214 {
0215     if (!QzTools::containsIndex(m_rules, offset)) {
0216         return nullptr;
0217     }
0218 
0219     AdBlockRule* rule = m_rules[offset];
0220     rule->setEnabled(true);
0221     AdBlockManager::instance()->removeDisabledRule(rule->filter());
0222 
0223     Q_EMIT subscriptionChanged();
0224 
0225     if (rule->isCssRule())
0226         mApp->reloadUserStyleSheet();
0227 
0228     return rule;
0229 }
0230 
0231 const AdBlockRule* AdBlockSubscription::disableRule(int offset)
0232 {
0233     if (!QzTools::containsIndex(m_rules, offset)) {
0234         return nullptr;
0235     }
0236 
0237     AdBlockRule* rule = m_rules[offset];
0238     rule->setEnabled(false);
0239     AdBlockManager::instance()->addDisabledRule(rule->filter());
0240 
0241     Q_EMIT subscriptionChanged();
0242 
0243     if (rule->isCssRule())
0244         mApp->reloadUserStyleSheet();
0245 
0246     return rule;
0247 }
0248 
0249 bool AdBlockSubscription::canEditRules() const
0250 {
0251     return false;
0252 }
0253 
0254 bool AdBlockSubscription::canBeRemoved() const
0255 {
0256     return true;
0257 }
0258 
0259 int AdBlockSubscription::addRule(AdBlockRule* rule)
0260 {
0261     Q_UNUSED(rule)
0262     return -1;
0263 }
0264 
0265 bool AdBlockSubscription::removeRule(int offset)
0266 {
0267     Q_UNUSED(offset)
0268     return false;
0269 }
0270 
0271 const AdBlockRule* AdBlockSubscription::replaceRule(AdBlockRule* rule, int offset)
0272 {
0273     Q_UNUSED(rule)
0274     Q_UNUSED(offset)
0275     return nullptr;
0276 }
0277 
0278 AdBlockSubscription::~AdBlockSubscription()
0279 {
0280     qDeleteAll(m_rules);
0281 }
0282 
0283 // AdBlockCustomList
0284 
0285 AdBlockCustomList::AdBlockCustomList(QObject* parent)
0286     : AdBlockSubscription(tr("Custom Rules"), parent)
0287 {
0288     setFilePath(DataPaths::currentProfilePath() + QLatin1String("/adblock/customlist.txt"));
0289 }
0290 
0291 void AdBlockCustomList::loadSubscription(const QStringList &disabledRules)
0292 {
0293     // DuckDuckGo ad whitelist rules
0294     // They cannot be removed, but can be disabled.
0295     // Please consider not disabling them. Thanks!
0296 
0297     const QString ddg1 = QSL("@@||duckduckgo.com^$document");
0298     const QString ddg2 = QSL("duckduckgo.com#@#.has-ad");
0299 
0300     const QString rules = QzTools::readAllFileContents(filePath());
0301 
0302     QFile file(filePath());
0303     if (!file.exists()) {
0304         saveSubscription();
0305     }
0306 
0307     if (file.open(QFile::WriteOnly | QFile::Append)) {
0308         QTextStream stream(&file);
0309         stream.setEncoding(QStringConverter::Utf8);
0310 
0311         if (!rules.contains(ddg1 + QL1S("\n")))
0312             stream << ddg1 << Qt::endl;
0313 
0314         if (!rules.contains(QL1S("\n") + ddg2))
0315             stream << ddg2 << Qt::endl;
0316     }
0317     file.close();
0318 
0319     AdBlockSubscription::loadSubscription(disabledRules);
0320 }
0321 
0322 void AdBlockCustomList::saveSubscription()
0323 {
0324     QFile file(filePath());
0325 
0326     if (!file.open(QFile::ReadWrite | QFile::Truncate)) {
0327         qWarning() << "AdBlockSubscription::" << __FUNCTION__ << "Unable to open adblock file for writing:" << filePath();
0328         return;
0329     }
0330 
0331     QTextStream textStream(&file);
0332     textStream.setEncoding(QStringConverter::Utf8);
0333     textStream << "Title: " << title() << Qt::endl;
0334     textStream << "Url: " << url().toString() << Qt::endl;
0335     textStream << "[Adblock Plus 1.1.1]" << Qt::endl;
0336 
0337     for (const AdBlockRule* rule : std::as_const(m_rules)) {
0338         textStream << rule->filter() << Qt::endl;
0339     }
0340 
0341     file.close();
0342 }
0343 
0344 bool AdBlockCustomList::canEditRules() const
0345 {
0346     return true;
0347 }
0348 
0349 bool AdBlockCustomList::canBeRemoved() const
0350 {
0351     return false;
0352 }
0353 
0354 bool AdBlockCustomList::containsFilter(const QString &filter) const
0355 {
0356     for (const AdBlockRule* rule : std::as_const(m_rules)) {
0357         if (rule->filter() == filter) {
0358             return true;
0359         }
0360     }
0361 
0362     return false;
0363 }
0364 
0365 bool AdBlockCustomList::removeFilter(const QString &filter)
0366 {
0367     for (int i = 0; i < m_rules.count(); ++i) {
0368         const AdBlockRule* rule = m_rules.at(i);
0369 
0370         if (rule->filter() == filter) {
0371             return removeRule(i);
0372         }
0373     }
0374 
0375     return false;
0376 }
0377 
0378 int AdBlockCustomList::addRule(AdBlockRule* rule)
0379 {
0380     m_rules.append(rule);
0381 
0382     Q_EMIT subscriptionChanged();
0383 
0384     if (rule->isCssRule())
0385         mApp->reloadUserStyleSheet();
0386 
0387     return m_rules.count() - 1;
0388 }
0389 
0390 bool AdBlockCustomList::removeRule(int offset)
0391 {
0392     if (!QzTools::containsIndex(m_rules, offset)) {
0393         return false;
0394     }
0395 
0396     AdBlockRule* rule = m_rules.at(offset);
0397     const QString filter = rule->filter();
0398 
0399     m_rules.remove(offset);
0400 
0401     Q_EMIT subscriptionChanged();
0402 
0403     if (rule->isCssRule())
0404         mApp->reloadUserStyleSheet();
0405 
0406     AdBlockManager::instance()->removeDisabledRule(filter);
0407 
0408     delete rule;
0409     return true;
0410 }
0411 
0412 const AdBlockRule* AdBlockCustomList::replaceRule(AdBlockRule* rule, int offset)
0413 {
0414     if (!QzTools::containsIndex(m_rules, offset)) {
0415         return nullptr;
0416     }
0417 
0418     AdBlockRule* oldRule = m_rules.at(offset);
0419     m_rules[offset] = rule;
0420 
0421     Q_EMIT subscriptionChanged();
0422 
0423     if (rule->isCssRule() || oldRule->isCssRule())
0424         mApp->reloadUserStyleSheet();
0425 
0426     delete oldRule;
0427     return m_rules[offset];
0428 }