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

0001 /* ============================================================
0002 * Falkon - Qt web browser
0003 * Copyright (C) 2014-2017 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 #include "adblockmatcher.h"
0019 #include "adblockmanager.h"
0020 #include "adblockrule.h"
0021 #include "adblocksubscription.h"
0022 
0023 AdBlockMatcher::AdBlockMatcher(AdBlockManager* manager)
0024     : QObject(manager)
0025     , m_manager(manager)
0026 {
0027 }
0028 
0029 AdBlockMatcher::~AdBlockMatcher()
0030 {
0031     clear();
0032 }
0033 
0034 const AdBlockRule* AdBlockMatcher::match(const QWebEngineUrlRequestInfo &request, const QString &urlDomain, const QString &urlString) const
0035 {
0036     // Exception rules
0037     if (m_networkExceptionTree.find(request, urlDomain, urlString))
0038         return nullptr;
0039 
0040     int count = m_networkExceptionRules.count();
0041     for (int i = 0; i < count; ++i) {
0042         const AdBlockRule* rule = m_networkExceptionRules.at(i);
0043         if (rule->networkMatch(request, urlDomain, urlString))
0044             return nullptr;
0045     }
0046 
0047     // Block rules
0048     if (const AdBlockRule* rule = m_networkBlockTree.find(request, urlDomain, urlString))
0049         return rule;
0050 
0051     count = m_networkBlockRules.count();
0052     for (int i = 0; i < count; ++i) {
0053         const AdBlockRule* rule = m_networkBlockRules.at(i);
0054         if (rule->networkMatch(request, urlDomain, urlString))
0055             return rule;
0056     }
0057 
0058     return nullptr;
0059 }
0060 
0061 bool AdBlockMatcher::adBlockDisabledForUrl(const QUrl &url) const
0062 {
0063     int count = m_documentRules.count();
0064 
0065     for (int i = 0; i < count; ++i)
0066         if (m_documentRules.at(i)->urlMatch(url))
0067             return true;
0068 
0069     return false;
0070 }
0071 
0072 bool AdBlockMatcher::elemHideDisabledForUrl(const QUrl &url) const
0073 {
0074     if (adBlockDisabledForUrl(url))
0075         return true;
0076 
0077     int count = m_elemhideRules.count();
0078 
0079     for (int i = 0; i < count; ++i)
0080         if (m_elemhideRules.at(i)->urlMatch(url))
0081             return true;
0082 
0083     return false;
0084 }
0085 
0086 bool AdBlockMatcher::genericElemHideDisabledForUrl(const QUrl &url) const
0087 {
0088     if (elemHideDisabledForUrl(url))
0089         return true;
0090 
0091     int count = m_generichideRules.count();
0092 
0093     for (int i = 0; i < count; ++i)
0094         if (m_generichideRules.at(i)->urlMatch(url))
0095             return true;
0096 
0097     return false;
0098 }
0099 
0100 QString AdBlockMatcher::elementHidingRules() const
0101 {
0102     return m_elementHidingRules;
0103 }
0104 
0105 QString AdBlockMatcher::elementHidingRulesForDomain(const QString &domain) const
0106 {
0107     QString rules;
0108     int addedRulesCount = 0;
0109     int count = m_domainRestrictedCssRules.count();
0110 
0111     for (int i = 0; i < count; ++i) {
0112         const AdBlockRule* rule = m_domainRestrictedCssRules.at(i);
0113         if (!rule->matchDomain(domain))
0114             continue;
0115 
0116         if (Q_UNLIKELY(addedRulesCount == 1000)) {
0117             rules.append(rule->cssSelector());
0118             rules.append(QL1S("{display:none !important;}\n"));
0119             addedRulesCount = 0;
0120         }
0121         else {
0122             rules.append(rule->cssSelector() + QLatin1Char(','));
0123             addedRulesCount++;
0124         }
0125     }
0126 
0127     if (addedRulesCount != 0) {
0128         rules = rules.left(rules.size() - 1);
0129         rules.append(QL1S("{display:none !important;}\n"));
0130     }
0131 
0132     return rules;
0133 }
0134 
0135 void AdBlockMatcher::update()
0136 {
0137     clear();
0138 
0139     QHash<QString, const AdBlockRule*> cssRulesHash;
0140     QVector<const AdBlockRule*> exceptionCssRules;
0141 
0142     const auto subscriptions = m_manager->subscriptions();
0143     for (AdBlockSubscription* subscription : subscriptions) {
0144         const auto rules = subscription->allRules();
0145         for (const AdBlockRule* rule : rules) {
0146             // Don't add internally disabled rules to cache
0147             if (rule->isInternalDisabled())
0148                 continue;
0149             // Or unsupported ones
0150             if (rule->isUnsupportedRule())
0151                 continue;
0152 
0153             if (rule->isCssRule()) {
0154                 // We will add only enabled css rules to cache, because there is no enabled/disabled
0155                 // check on match. They are directly embedded to pages.
0156                 if (!rule->isEnabled())
0157                     continue;
0158 
0159                 if (rule->isException())
0160                     exceptionCssRules.append(rule);
0161                 else
0162                     cssRulesHash.insert(rule->cssSelector(), rule);
0163             }
0164             else if (rule->isDocument()) {
0165                 m_documentRules.append(rule);
0166             }
0167             else if (rule->isElemhide()) {
0168                 m_elemhideRules.append(rule);
0169             }
0170             else if (rule->isGenerichide()) {
0171                 m_generichideRules.append(rule);
0172             }
0173             else if (rule->isException()) {
0174                 if (!m_networkExceptionTree.add(rule))
0175                     m_networkExceptionRules.append(rule);
0176             } else {
0177                 if (!m_networkBlockTree.add(rule))
0178                     m_networkBlockRules.append(rule);
0179             }
0180         }
0181     }
0182 
0183     for (const AdBlockRule* rule : std::as_const(exceptionCssRules)) {
0184         const AdBlockRule* originalRule = cssRulesHash.value(rule->cssSelector());
0185 
0186         // If we don't have this selector, the exception does nothing
0187         if (!originalRule)
0188             continue;
0189 
0190         AdBlockRule* copiedRule = originalRule->copy();
0191         copiedRule->m_options |= AdBlockRule::DomainRestrictedOption;
0192         copiedRule->m_blockedDomains.append(rule->m_allowedDomains);
0193 
0194         cssRulesHash[rule->cssSelector()] = copiedRule;
0195         m_createdRules.append(copiedRule);
0196     }
0197 
0198     // Apparently, excessive amount of selectors for one CSS rule is not what WebKit likes.
0199     // (In my testings, 4931 is the number that makes it crash)
0200     // So let's split it by 1000 selectors...
0201     int hidingRulesCount = 0;
0202 
0203     QHashIterator<QString, const AdBlockRule*> it(cssRulesHash);
0204     while (it.hasNext()) {
0205         it.next();
0206         const AdBlockRule* rule = it.value();
0207 
0208         if (rule->isDomainRestricted()) {
0209             m_domainRestrictedCssRules.append(rule);
0210         }
0211         else if (Q_UNLIKELY(hidingRulesCount == 1000)) {
0212             m_elementHidingRules.append(rule->cssSelector());
0213             m_elementHidingRules.append(QL1S("{display:none !important;} "));
0214             hidingRulesCount = 0;
0215         }
0216         else {
0217             m_elementHidingRules.append(rule->cssSelector() + QLatin1Char(','));
0218             hidingRulesCount++;
0219         }
0220     }
0221 
0222     if (hidingRulesCount != 0) {
0223         m_elementHidingRules = m_elementHidingRules.left(m_elementHidingRules.size() - 1);
0224         m_elementHidingRules.append(QL1S("{display:none !important;} "));
0225     }
0226 }
0227 
0228 void AdBlockMatcher::clear()
0229 {
0230     m_networkExceptionTree.clear();
0231     m_networkExceptionRules.clear();
0232     m_networkBlockTree.clear();
0233     m_networkBlockRules.clear();
0234     m_domainRestrictedCssRules.clear();
0235     m_elementHidingRules.clear();
0236     m_documentRules.clear();
0237     m_elemhideRules.clear();
0238     m_generichideRules.clear();
0239     qDeleteAll(m_createdRules);
0240     m_createdRules.clear();
0241 }