adblockmatcher.cpp 8.13 KB
Newer Older
Laurent Montel's avatar
Laurent Montel committed
1
/*
Laurent Montel's avatar
Laurent Montel committed
2
   Copyright (C) 2016 Montel Laurent <montel@kde.org>
Laurent Montel's avatar
Laurent Montel committed
3

Laurent Montel's avatar
Laurent Montel committed
4 5 6 7
   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 2 of the License, or (at your option) any later version.
Laurent Montel's avatar
Laurent Montel committed
8

Laurent Montel's avatar
Laurent Montel committed
9 10 11 12
   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.
Laurent Montel's avatar
Laurent Montel committed
13

Laurent Montel's avatar
Laurent Montel committed
14 15 16 17
   You should have received a copy of the GNU General Public License
   along with this program; see the file COPYING.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
Laurent Montel's avatar
Laurent Montel committed
18 19 20 21 22 23 24
*/
/* ============================================================
* QupZilla - WebKit based browser
* Copyright (C) 2014  David Rosca <nowrep@gmail.com>
*
* 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
25
* the Free Software Foundation, either version 2 of the License, or
Laurent Montel's avatar
Laurent Montel committed
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
* (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 <http://www.gnu.org/licenses/>.
* ============================================================ */
#include "adblockmatcher.h"
#include "adblockmanager.h"
#include "adblockrule.h"
#include "adblocksubscription.h"

Laurent Montel's avatar
Laurent Montel committed
41
using namespace AdBlock;
Laurent Montel's avatar
Laurent Montel committed
42
AdBlockMatcher::AdBlockMatcher(AdblockManager *manager)
Laurent Montel's avatar
Laurent Montel committed
43
    : QObject(manager)
Laurent Montel's avatar
Laurent Montel committed
44 45
    , m_manager(manager),
      m_enabled(false)
Laurent Montel's avatar
Laurent Montel committed
46
{
Laurent Montel's avatar
Laurent Montel committed
47
    connect(manager, &AdblockManager::enabledChanged, this, &AdBlockMatcher::enabledChanged);
Laurent Montel's avatar
Laurent Montel committed
48 49 50 51 52 53 54
}

AdBlockMatcher::~AdBlockMatcher()
{
    clear();
}

Laurent Montel's avatar
Laurent Montel committed
55
const AdBlockRule *AdBlockMatcher::match(const QWebEngineUrlRequestInfo &request, const QString &urlDomain, const QString &urlString) const
Laurent Montel's avatar
Laurent Montel committed
56 57
{
    // Exception rules
Laurent Montel's avatar
Laurent Montel committed
58
    if (m_networkExceptionTree.find(request, urlDomain, urlString)) {
Laurent Montel's avatar
Minor  
Laurent Montel committed
59
        return Q_NULLPTR;
Laurent Montel's avatar
Laurent Montel committed
60
    }
Laurent Montel's avatar
Laurent Montel committed
61 62 63

    int count = m_networkExceptionRules.count();
    for (int i = 0; i < count; ++i) {
Laurent Montel's avatar
Laurent Montel committed
64 65
        const AdBlockRule *rule = m_networkExceptionRules.at(i);
        if (rule->networkMatch(request, urlDomain, urlString)) {
Laurent Montel's avatar
Minor  
Laurent Montel committed
66
            return Q_NULLPTR;
Laurent Montel's avatar
Laurent Montel committed
67
        }
Laurent Montel's avatar
Laurent Montel committed
68 69 70
    }

    // Block rules
Laurent Montel's avatar
Laurent Montel committed
71
    if (const AdBlockRule *rule = m_networkBlockTree.find(request, urlDomain, urlString)) {
Laurent Montel's avatar
Laurent Montel committed
72
        return rule;
Laurent Montel's avatar
Laurent Montel committed
73
    }
Laurent Montel's avatar
Laurent Montel committed
74 75 76

    count = m_networkBlockRules.count();
    for (int i = 0; i < count; ++i) {
Laurent Montel's avatar
Laurent Montel committed
77 78
        const AdBlockRule *rule = m_networkBlockRules.at(i);
        if (rule->networkMatch(request, urlDomain, urlString)) {
Laurent Montel's avatar
Laurent Montel committed
79
            return rule;
Laurent Montel's avatar
Laurent Montel committed
80
        }
Laurent Montel's avatar
Laurent Montel committed
81 82
    }

Laurent Montel's avatar
Minor  
Laurent Montel committed
83
    return Q_NULLPTR;
Laurent Montel's avatar
Laurent Montel committed
84 85 86 87
}

bool AdBlockMatcher::adBlockDisabledForUrl(const QUrl &url) const
{
Laurent Montel's avatar
Minor  
Laurent Montel committed
88
    const int count = m_documentRules.count();
Laurent Montel's avatar
Laurent Montel committed
89 90

    for (int i = 0; i < count; ++i)
Laurent Montel's avatar
Laurent Montel committed
91
        if (m_documentRules.at(i)->urlMatch(url)) {
Laurent Montel's avatar
Laurent Montel committed
92
            return true;
Laurent Montel's avatar
Laurent Montel committed
93
        }
Laurent Montel's avatar
Laurent Montel committed
94 95 96 97 98 99

    return false;
}

bool AdBlockMatcher::elemHideDisabledForUrl(const QUrl &url) const
{
Laurent Montel's avatar
Laurent Montel committed
100
    if (adBlockDisabledForUrl(url)) {
Laurent Montel's avatar
Laurent Montel committed
101
        return true;
Laurent Montel's avatar
Laurent Montel committed
102
    }
Laurent Montel's avatar
Laurent Montel committed
103

Laurent Montel's avatar
Minor  
Laurent Montel committed
104
    const int count = m_elemhideRules.count();
Laurent Montel's avatar
Laurent Montel committed
105 106

    for (int i = 0; i < count; ++i)
Laurent Montel's avatar
Laurent Montel committed
107
        if (m_elemhideRules.at(i)->urlMatch(url)) {
Laurent Montel's avatar
Laurent Montel committed
108
            return true;
Laurent Montel's avatar
Laurent Montel committed
109
        }
Laurent Montel's avatar
Laurent Montel committed
110 111 112 113 114 115 116 117 118 119 120 121 122

    return false;
}

QString AdBlockMatcher::elementHidingRules() const
{
    return m_elementHidingRules;
}

QString AdBlockMatcher::elementHidingRulesForDomain(const QString &domain) const
{
    QString rules;
    int addedRulesCount = 0;
Laurent Montel's avatar
Minor  
Laurent Montel committed
123
    const int count = m_domainRestrictedCssRules.count();
Laurent Montel's avatar
Laurent Montel committed
124 125

    for (int i = 0; i < count; ++i) {
Laurent Montel's avatar
Laurent Montel committed
126 127
        const AdBlockRule *rule = m_domainRestrictedCssRules.at(i);
        if (!rule->matchDomain(domain)) {
Laurent Montel's avatar
Laurent Montel committed
128
            continue;
Laurent Montel's avatar
Laurent Montel committed
129
        }
Laurent Montel's avatar
Laurent Montel committed
130 131 132

        if (Q_UNLIKELY(addedRulesCount == 1000)) {
            rules.append(rule->cssSelector());
Laurent Montel's avatar
Laurent Montel committed
133
            rules.append(QLatin1String("{display:none !important;}\n"));
Laurent Montel's avatar
Laurent Montel committed
134
            addedRulesCount = 0;
Laurent Montel's avatar
Laurent Montel committed
135
        } else {
Laurent Montel's avatar
Laurent Montel committed
136 137 138 139 140 141 142
            rules.append(rule->cssSelector() + QLatin1Char(','));
            addedRulesCount++;
        }
    }

    if (addedRulesCount != 0) {
        rules = rules.left(rules.size() - 1);
Laurent Montel's avatar
Laurent Montel committed
143
        rules.append(QLatin1String("{display:none !important;}\n"));
Laurent Montel's avatar
Laurent Montel committed
144 145 146 147 148
    }

    return rules;
}

Laurent Montel's avatar
Laurent Montel committed
149 150 151 152 153
bool AdBlockMatcher::isEnabled() const
{
    return m_enabled;
}

Laurent Montel's avatar
Laurent Montel committed
154 155 156 157
void AdBlockMatcher::update()
{
    clear();

Laurent Montel's avatar
Laurent Montel committed
158 159
    QHash<QString, const AdBlockRule *> cssRulesHash;
    QVector<const AdBlockRule *> exceptionCssRules;
Laurent Montel's avatar
Laurent Montel committed
160 161
    Q_FOREACH (AdBlockSubscription *subscription, m_manager->subscriptions()) {
        Q_FOREACH (const AdBlockRule *rule, subscription->allRules()) {
Laurent Montel's avatar
Laurent Montel committed
162
            // Don't add internally disabled rules to cache
Laurent Montel's avatar
Laurent Montel committed
163
            if (rule->isInternalDisabled()) {
Laurent Montel's avatar
Laurent Montel committed
164
                continue;
Laurent Montel's avatar
Laurent Montel committed
165
            }
Laurent Montel's avatar
Laurent Montel committed
166 167 168 169

            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.
Laurent Montel's avatar
Laurent Montel committed
170
                if (!rule->isEnabled()) {
Laurent Montel's avatar
Laurent Montel committed
171
                    continue;
Laurent Montel's avatar
Laurent Montel committed
172
                }
Laurent Montel's avatar
Laurent Montel committed
173

Laurent Montel's avatar
Laurent Montel committed
174
                if (rule->isException()) {
Laurent Montel's avatar
Laurent Montel committed
175
                    exceptionCssRules.append(rule);
Laurent Montel's avatar
Laurent Montel committed
176
                } else {
Laurent Montel's avatar
Laurent Montel committed
177
                    cssRulesHash.insert(rule->cssSelector(), rule);
Laurent Montel's avatar
Laurent Montel committed
178 179
                }
            } else if (rule->isDocument()) {
Laurent Montel's avatar
Laurent Montel committed
180
                m_documentRules.append(rule);
Laurent Montel's avatar
Laurent Montel committed
181
            } else if (rule->isElemhide()) {
Laurent Montel's avatar
Laurent Montel committed
182
                m_elemhideRules.append(rule);
Laurent Montel's avatar
Laurent Montel committed
183 184
            } else if (rule->isException()) {
                if (!m_networkExceptionTree.add(rule)) {
Laurent Montel's avatar
Laurent Montel committed
185
                    m_networkExceptionRules.append(rule);
Laurent Montel's avatar
Laurent Montel committed
186 187 188
                }
            } else {
                if (!m_networkBlockTree.add(rule)) {
Laurent Montel's avatar
Laurent Montel committed
189
                    m_networkBlockRules.append(rule);
Laurent Montel's avatar
Laurent Montel committed
190
                }
Laurent Montel's avatar
Laurent Montel committed
191 192 193 194
            }
        }
    }

Laurent Montel's avatar
Laurent Montel committed
195 196
    foreach (const AdBlockRule *rule, exceptionCssRules) {
        const AdBlockRule *originalRule = cssRulesHash.value(rule->cssSelector());
Laurent Montel's avatar
Laurent Montel committed
197 198

        // If we don't have this selector, the exception does nothing
Laurent Montel's avatar
Laurent Montel committed
199
        if (!originalRule) {
Laurent Montel's avatar
Laurent Montel committed
200
            continue;
Laurent Montel's avatar
Laurent Montel committed
201
        }
Laurent Montel's avatar
Laurent Montel committed
202

Laurent Montel's avatar
Laurent Montel committed
203
        AdBlockRule *copiedRule = originalRule->copy();
Laurent Montel's avatar
Laurent Montel committed
204 205 206 207 208 209 210 211 212 213 214 215
        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;

Laurent Montel's avatar
Laurent Montel committed
216
    QHashIterator<QString, const AdBlockRule *> it(cssRulesHash);
Laurent Montel's avatar
Laurent Montel committed
217 218
    while (it.hasNext()) {
        it.next();
Laurent Montel's avatar
Laurent Montel committed
219
        const AdBlockRule *rule = it.value();
Laurent Montel's avatar
Laurent Montel committed
220 221 222

        if (rule->isDomainRestricted()) {
            m_domainRestrictedCssRules.append(rule);
Laurent Montel's avatar
Laurent Montel committed
223
        } else if (Q_UNLIKELY(hidingRulesCount == 1000)) {
Laurent Montel's avatar
Laurent Montel committed
224
            m_elementHidingRules.append(rule->cssSelector());
Laurent Montel's avatar
Laurent Montel committed
225
            m_elementHidingRules.append(QLatin1String("{display:none !important;} "));
Laurent Montel's avatar
Laurent Montel committed
226
            hidingRulesCount = 0;
Laurent Montel's avatar
Laurent Montel committed
227
        } else {
Laurent Montel's avatar
Laurent Montel committed
228 229 230 231 232 233 234
            m_elementHidingRules.append(rule->cssSelector() + QLatin1Char(','));
            hidingRulesCount++;
        }
    }

    if (hidingRulesCount != 0) {
        m_elementHidingRules = m_elementHidingRules.left(m_elementHidingRules.size() - 1);
Laurent Montel's avatar
Laurent Montel committed
235
        m_elementHidingRules.append(QLatin1String("{display:none !important;} "));
Laurent Montel's avatar
Laurent Montel committed
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
    }
}

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)
{
Laurent Montel's avatar
Laurent Montel committed
255 256
    m_enabled = enabled;
    if (m_enabled) {
Laurent Montel's avatar
Laurent Montel committed
257
        update();
Laurent Montel's avatar
Laurent Montel committed
258
    } else {
Laurent Montel's avatar
Laurent Montel committed
259
        clear();
Laurent Montel's avatar
Laurent Montel committed
260
    }
Laurent Montel's avatar
Laurent Montel committed
261
}