com.adguard.filter.rules.FilterRule.java Source code

Java tutorial

Introduction

Here is the source code for com.adguard.filter.rules.FilterRule.java

Source

/**
 This file is part of Adguard Content Blocker (https://github.com/AdguardTeam/ContentBlocker).
 Copyright  2016 Performix LLC. All rights reserved.
    
 Adguard Content Blocker 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.
    
 Adguard Content Blocker 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
 Adguard Content Blocker.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.adguard.filter.rules;

import com.adguard.commons.collections.Lists;
import com.adguard.commons.web.UrlUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * Base class for all filter rules
 */
public abstract class FilterRule {

    public static final int MIN_RULE_LENGTH = 4;
    public static final String PARAMETER_START = "[";
    public static final String PARAMETER_END = "]";
    public static final String MASK_WHITE_LIST = "@@";
    public static final String MASK_CONTENT_RULE = "$$";
    public static final String MASK_CSS_RULE = "##";
    public static final String MASK_CSS_EXCEPTION_RULE = "#@#";
    public static final String MASK_CSS_INJECT_RULE = "#$#";
    public static final String MASK_CSS_INJECT_EXCEPTION_RULE = "#@$#";
    public static final String MASK_SCRIPT_RULE = "#%#";
    public static final String COMMENT = "!";
    public static final String EQUAL = "=";
    public static final char COMA_DELIMITER = ',';
    public static final char LINE_DELIMITER = '|';
    public static final String NOT_MARK = "~";
    public static final String MASK_OBSOLETE_SCRIPT_INJECTION = "###adg_start_script_inject";
    public static final String MASK_OBSOLETE_STYLE_INJECTION = "###adg_start_style_inject";
    public static final String META_START = "[";

    private final String ruleText;
    private List<String> permittedDomains;
    private List<String> restrictedDomains;

    /**
     * Creates FilterRule text
     *
     * @param ruleText Rule text
     */
    protected FilterRule(String ruleText) {
        if (StringUtils.isEmpty(ruleText)) {
            throw new IllegalArgumentException("ruleText cannot be empty");
        }

        this.ruleText = ruleText;
    }

    /**
     * Returns true if rule is CSS, JS or Content
     *
     * @param ruleText Rule text
     * @return true if rule is CSS, JS or Content
     */
    public static boolean isCosmeticRule(String ruleText) {
        return StringUtils.isEmpty(ruleText) || ruleText.contains(MASK_CSS_RULE)
                || ruleText.contains(MASK_CSS_EXCEPTION_RULE) || ruleText.contains(MASK_CSS_INJECT_RULE)
                || ruleText.contains(MASK_CSS_INJECT_EXCEPTION_RULE) || ruleText.contains(MASK_SCRIPT_RULE)
                || ruleText.contains(MASK_CONTENT_RULE);
    }

    /**
     * Creates filter rule.
     * If this rule text is not valid - returns null.
     *
     * @param ruleText Rule text
     * @return Filter rule of the proper type
     */
    public static FilterRule createRule(String ruleText) {

        ruleText = StringUtils.trim(ruleText);

        if (StringUtils.isBlank(ruleText) || StringUtils.length(ruleText) < MIN_RULE_LENGTH
                || StringUtils.startsWith(ruleText, COMMENT) || StringUtils.startsWith(ruleText, META_START)
                || StringUtils.contains(ruleText, MASK_OBSOLETE_SCRIPT_INJECTION)
                || StringUtils.contains(ruleText, MASK_OBSOLETE_STYLE_INJECTION)) {
            return null;
        }

        try {
            if (StringUtils.startsWith(ruleText, MASK_WHITE_LIST)) {
                return new UrlFilterRule(ruleText);
            }

            if (StringUtils.contains(ruleText, MASK_CONTENT_RULE)) {
                return new ContentFilterRule(ruleText);
            }

            if (StringUtils.contains(ruleText, MASK_CSS_RULE)
                    || StringUtils.contains(ruleText, MASK_CSS_EXCEPTION_RULE)
                    || StringUtils.contains(ruleText, MASK_CSS_INJECT_RULE)
                    || StringUtils.contains(ruleText, MASK_CSS_INJECT_EXCEPTION_RULE)) {
                return new CssFilterRule(ruleText);
            }

            if (StringUtils.contains(ruleText, MASK_SCRIPT_RULE)) {
                return new ScriptFilterRule(ruleText);
            }

            return new UrlFilterRule(ruleText);
        } catch (Exception ex) {
            LoggerFactory.getLogger(FilterRule.class).warn("Error creating filter rule {}:\r\n{}", ruleText, ex);
            return null;
        }
    }

    /**
     * Gets rule text
     *
     * @return Rule text
     */
    public String getRuleText() {
        return ruleText;
    }

    /**
     * Gets list of domains this rule is permitted on
     *
     * @return List of permitted domains
     */
    public List<String> getPermittedDomains() {
        return permittedDomains;
    }

    /**
     * Gets list of domains this rule is restricted on
     *
     * @return List of restricted domains
     */
    public List<String> getRestrictedDomains() {
        return restrictedDomains;
    }

    /**
     * Generic filter rules count as rules that:
     * <br/>
     * 1. Do not have a domain specified. "Hide this element on all domains"
     * <br/>
     * 2. Have only domain exceptions specified. "Hide this element on all domains except example.com"
     * <br/>
     * ~example.com##.ad
     * ||example.com^$third-party
     *
     * @return true if this rule is generic
     */
    public boolean isGeneric() {
        return CollectionUtils.isEmpty(permittedDomains);
    }

    /**
     * Checks if this rule is domain sensitive or not
     *
     * @return true if rule has permitted or restricted domains
     */
    public boolean isDomainSensitive() {
        return CollectionUtils.isNotEmpty(permittedDomains) || CollectionUtils.isNotEmpty(restrictedDomains);
    }

    /**
     * Checks if this rule is permitted for the specified domain
     *
     * @param domainName Domain name
     * @return true if rule is permitted
     */
    public boolean isPermitted(String domainName) {
        if (StringUtils.isEmpty(domainName)) {
            return false;
        }

        if (UrlUtils.isDomainOrSubDomain(domainName, restrictedDomains)) {
            return false;
        }

        //noinspection SimplifiableIfStatement
        if (CollectionUtils.isNotEmpty(permittedDomains)) {
            // If permitted domains set -- this rule work for permitted domains ONLY
            return UrlUtils.isDomainOrSubDomain(domainName, permittedDomains);
        }

        return true;
    }

    /**
     * Loads PermittedDomains and RestrictedDomains collections
     * from the rule text
     *
     * @param domains Domains part of the rule text
     */
    protected void loadDomains(String domains) {
        if (StringUtils.isEmpty(domains)) {
            return;
        }

        String[] parts = StringUtils.split(domains, new String(new char[] { COMA_DELIMITER, LINE_DELIMITER }));

        try {
            for (String domain : parts) {
                if (StringUtils.startsWith(domain, NOT_MARK)) {
                    String domainName = toPunycode(domain.substring(1).trim());
                    if (StringUtils.isNotEmpty(domainName)) {
                        addRestrictedDomain(domainName);
                    }
                } else {
                    String domainName = toPunycode(domain.trim());
                    if (StringUtils.isNotEmpty(domainName)) {
                        addPermittedDomain(domainName);
                    }
                }
            }
        } catch (Exception ex) {
            LoggerFactory.getLogger(this.getClass()).error("Error while loading domains from " + domains, ex);
        }
    }

    /**
     * Removes specified domain from the list of permitted.
     *
     * @param domainName Domain to remove
     * @return true if domain was removed successfully
     */
    public boolean removePermittedDomain(String domainName) {
        return Lists.remove(permittedDomains, domainName);
    }

    /**
     * Removes specified domains from the list of permitted
     *
     * @param domainNames Collection of domain names to remove
     */
    public void removePermittedDomains(Collection<String> domainNames) {
        Lists.removeAll(permittedDomains, domainNames);
    }

    /**
     * Adds specified domain name to the list of permitted domains
     *
     * @param domainName Domain to add
     */
    public void addPermittedDomain(String domainName) {
        if (StringUtils.isEmpty(domainName)) {
            return;
        }
        if (permittedDomains == null) {
            permittedDomains = new ArrayList<>();
        }
        permittedDomains.add(domainName);
    }

    /**
     * Adds specified domains to the list of permitted domains
     *
     * @param domainNames Domains to add
     */
    public void addPermittedDomains(Collection<String> domainNames) {
        if (domainNames == null) {
            return;
        }
        for (String domainName : domainNames) {
            addPermittedDomain(domainName);
        }
    }

    /**
     * Removes the specified domain from the list of restricted
     *
     * @param domainName Domain to remove
     * @return true if domain has been removed
     */
    public boolean removeRestrictedDomain(String domainName) {
        return Lists.remove(restrictedDomains, domainName);
    }

    /**
     * Removes specified domains from the list of restricted
     *
     * @param domainNames Domains to remove
     */
    public void removeRestrictedDomains(Collection<String> domainNames) {
        Lists.removeAll(restrictedDomains, domainNames);
    }

    /**
     * Adds specified domain name to the list of restricted domains
     *
     * @param domainName Domain name to restrict
     */
    public void addRestrictedDomain(String domainName) {
        if (StringUtils.isEmpty(domainName)) {
            return;
        }
        if (restrictedDomains == null) {
            restrictedDomains = new ArrayList<>();
        }
        restrictedDomains.add(domainName);
    }

    /**
     * Adds specified domains to the list of restricted domains
     *
     * @param domainNames Domain names too restict
     */
    public void addRestrictedDomains(Collection<String> domainNames) {
        if (domainNames == null) {
            return;
        }
        for (String domainName : domainNames) {
            addRestrictedDomain(domainName);
        }
    }

    @Override
    public String toString() {
        return getRuleText();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;

        FilterRule that = (FilterRule) o;
        String ruleText = getRuleText();
        return !(ruleText != null ? !ruleText.equals(that.getRuleText()) : that.getRuleText() != null);
    }

    @Override
    public int hashCode() {
        return getRuleText() != null ? getRuleText().hashCode() : 0;
    }

    /**
     * Transforms domain name to punycode if needed
     *
     * @param domainName Domain name
     * @return Domain name in punycode
     */
    private static String toPunycode(String domainName) {
        if (UrlUtils.isASCII(domainName)) {
            return domainName;
        }

        return UrlUtils.toPunycode(domainName);
    }
}