use crate::filters::network::{NetworkFilter, NetworkFilterError};
use crate::filters::cosmetic::{CosmeticFilter, CosmeticFilterError};
use itertools::{Either, Itertools};
use serde::{Deserialize, Serialize};
#[cfg(feature = "content-blocking")]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum RuleTypes {
All,
NetworkOnly,
CosmeticOnly,
}
#[cfg(feature = "content-blocking")]
impl Default for RuleTypes {
fn default() -> Self {
Self::All
}
}
#[cfg(feature = "content-blocking")]
impl RuleTypes {
fn loads_network_rules(&self) -> bool {
match self {
Self::All => true,
Self::NetworkOnly => true,
_ => false,
}
}
fn loads_cosmetic_rules(&self) -> bool {
match self {
Self::All => true,
Self::CosmeticOnly => true,
_ => false,
}
}
}
#[derive(Clone)]
pub struct FilterSet {
debug: bool,
pub(crate) network_filters: Vec<NetworkFilter>,
pub(crate) cosmetic_filters: Vec<CosmeticFilter>,
}
impl Default for FilterSet {
fn default() -> Self {
#[cfg(not(test))]
let debug = false;
#[cfg(test)]
let debug = true;
Self::new(debug)
}
}
impl FilterSet {
pub fn new(debug: bool) -> Self {
Self {
debug,
network_filters: Vec::new(),
cosmetic_filters: Vec::new(),
}
}
pub fn add_filter_list(&mut self, filter_list: &str, format: FilterFormat) {
let rules = filter_list.lines().map(str::to_string).collect::<Vec<_>>();
self.add_filters(&rules, format);
}
pub fn add_filters(&mut self, filters: &[String], format: FilterFormat) {
let (mut parsed_network_filters, mut parsed_cosmetic_filters) = parse_filters(&filters, self.debug, format);
self.network_filters.append(&mut parsed_network_filters);
self.cosmetic_filters.append(&mut parsed_cosmetic_filters);
}
pub fn add_filter(&mut self, filter: &str, format: FilterFormat) -> Result<(), FilterParseError> {
let filter_parsed = parse_filter(filter, self.debug, format);
match filter_parsed? {
ParsedFilter::Network(filter) => self.network_filters.push(filter),
ParsedFilter::Cosmetic(filter) => self.cosmetic_filters.push(filter),
}
Ok(())
}
#[cfg(feature = "content-blocking")]
pub fn into_content_blocking(self, rule_types: RuleTypes) -> Result<(Vec<crate::content_blocking::CbRule>, Vec<String>), ()> {
use std::convert::TryInto;
use crate::content_blocking;
if !self.debug {
return Err(())
}
let mut ignore_previous_rules = vec![];
let mut other_rules = vec![];
let mut filters_used = vec![];
if rule_types.loads_network_rules() {
self.network_filters.into_iter().for_each(|filter| {
let original_rule = filter.raw_line.clone().expect("All rules should be in debug mode");
if let Ok(equivalent) = TryInto::<content_blocking::CbRuleEquivalent>::try_into(filter) {
filters_used.push(original_rule);
equivalent.into_iter().for_each(|cb_rule| {
match &cb_rule.action.typ {
content_blocking::CbType::IgnorePreviousRules => ignore_previous_rules.push(cb_rule),
_ => other_rules.push(cb_rule),
}
});
}
});
}
if rule_types.loads_cosmetic_rules() {
self.cosmetic_filters.into_iter().for_each(|filter| {
let original_rule = filter.raw_line.clone().expect("All rules should be in debug mode");
if let Ok(cb_rule) = TryInto::<content_blocking::CbRule>::try_into(filter) {
filters_used.push(original_rule);
match &cb_rule.action.typ {
content_blocking::CbType::IgnorePreviousRules => ignore_previous_rules.push(cb_rule),
_ => other_rules.push(cb_rule),
}
}
});
}
other_rules.append(&mut ignore_previous_rules);
if rule_types.loads_network_rules() {
other_rules.push(content_blocking::ignore_previous_fp_documents());
}
Ok((other_rules, filters_used))
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum FilterFormat {
Standard,
Hosts,
}
#[derive(Debug, PartialEq)]
pub enum FilterType {
Network,
Cosmetic,
NotSupported,
}
pub enum ParsedFilter {
Network(NetworkFilter),
Cosmetic(CosmeticFilter),
}
impl From<NetworkFilter> for ParsedFilter {
fn from(v: NetworkFilter) -> Self {
ParsedFilter::Network(v)
}
}
impl From<CosmeticFilter> for ParsedFilter {
fn from(v: CosmeticFilter) -> Self {
ParsedFilter::Cosmetic(v)
}
}
#[derive(Debug)]
pub enum FilterParseError {
Network(NetworkFilterError),
Cosmetic(CosmeticFilterError),
Unsupported,
Empty,
}
impl From<NetworkFilterError> for FilterParseError {
fn from(v: NetworkFilterError) -> Self {
FilterParseError::Network(v)
}
}
impl From<CosmeticFilterError> for FilterParseError {
fn from(v: CosmeticFilterError) -> Self {
FilterParseError::Cosmetic(v)
}
}
pub fn parse_filter(
line: &str,
debug: bool,
format: FilterFormat,
) -> Result<ParsedFilter, FilterParseError> {
let filter = line.trim();
if filter.is_empty() {
return Err(FilterParseError::Empty);
}
match format {
FilterFormat::Standard => {
match detect_filter_type(filter) {
FilterType::Network => NetworkFilter::parse(filter, debug)
.map(|f| f.into())
.map_err(|e| e.into()),
FilterType::Cosmetic => CosmeticFilter::parse(filter, debug)
.map(|f| f.into())
.map_err(|e| e.into()),
_ => Err(FilterParseError::Unsupported),
}
}
FilterFormat::Hosts => {
if filter.starts_with('!') {
return Err(FilterParseError::Unsupported);
}
let filter = if let Some(hash_loc) = filter.find('#') {
let filter = &filter[..hash_loc];
let filter = filter.trim();
if filter.is_empty() {
return Err(FilterParseError::Unsupported);
}
filter
} else {
&filter[..]
};
let mut filter_parts = filter.split_whitespace();
let hostname = match (filter_parts.next(), filter_parts.next(), filter_parts.next()) {
(None, None, None) => return Err(FilterParseError::Unsupported),
(Some(hostname), None, None) => hostname,
(Some(_ip), Some(hostname), None) => hostname,
(Some(_), Some(_), Some(_)) => return Err(FilterParseError::Unsupported),
_ => unreachable!(),
};
if hostname == "localhost" {
return Err(FilterParseError::Unsupported);
}
NetworkFilter::parse_hosts_style(hostname, debug)
.map(|f| f.into())
.map_err(|e| e.into())
}
}
}
pub fn parse_filters(
list: &[String],
debug: bool,
format: FilterFormat,
) -> (Vec<NetworkFilter>, Vec<CosmeticFilter>) {
let list_iter = list.iter();
let (network_filters, cosmetic_filters): (Vec<_>, Vec<_>) = list_iter
.map(|line| parse_filter(line, debug, format))
.filter_map(Result::ok)
.partition_map(|filter| match filter {
ParsedFilter::Network(f) => Either::Left(f),
ParsedFilter::Cosmetic(f) => Either::Right(f),
});
(network_filters, cosmetic_filters)
}
fn detect_filter_type(filter: &str) -> FilterType {
if filter.len() == 1
|| filter.starts_with('!')
|| (filter.starts_with('#') && filter[1..].starts_with(char::is_whitespace))
|| filter.starts_with("[Adblock")
{
return FilterType::NotSupported;
}
if filter.starts_with('|') || filter.starts_with("@@|") {
return FilterType::Network;
}
if filter.find("$$").is_some() {
return FilterType::NotSupported;
}
if let Some(sharp_index) = filter.find('#') {
let after_sharp_index = sharp_index + 1;
if filter[after_sharp_index..].starts_with( "@$#")
|| filter[after_sharp_index..].starts_with( "@%#")
|| filter[after_sharp_index..].starts_with( "%#")
|| filter[after_sharp_index..].starts_with( "$#")
|| filter[after_sharp_index..].starts_with( "?#")
{
return FilterType::NotSupported;
} else if filter[after_sharp_index..].starts_with( '#')
|| filter[after_sharp_index..].starts_with( "@#")
{
return FilterType::Cosmetic;
}
}
FilterType::Network
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_hosts_style() {
{
let input = "www.malware.com";
let result = parse_filter(input, true, FilterFormat::Hosts);
assert!(result.is_ok());
}
{
let input = "www.malware.com/virus.txt";
let result = parse_filter(input, true, FilterFormat::Hosts);
assert!(result.is_err());
}
{
let input = "127.0.0.1 www.malware.com";
let result = parse_filter(input, true, FilterFormat::Hosts);
assert!(result.is_ok());
}
{
let input = "127.0.0.1\t\twww.malware.com";
let result = parse_filter(input, true, FilterFormat::Hosts);
assert!(result.is_ok());
}
{
let input = "0.0.0.0 www.malware.com";
let result = parse_filter(input, true, FilterFormat::Hosts);
assert!(result.is_ok());
}
{
let input = "0.0.0.0 www.malware.com # replace after issue #289336 is addressed";
let result = parse_filter(input, true, FilterFormat::Hosts);
assert!(result.is_ok());
}
{
let input = "! Title: list.txt";
let result = parse_filter(input, true, FilterFormat::Hosts);
assert!(result.is_err());
}
{
let input = "127.0.0.1 localhost";
let result = parse_filter(input, true, FilterFormat::Hosts);
assert!(result.is_err());
}
{
let input = "127.0.0.1 com";
let result = parse_filter(input, true, FilterFormat::Hosts);
assert!(result.is_err());
}
{
let input = ".com";
let result = parse_filter(input, true, FilterFormat::Hosts);
assert!(result.is_err());
}
{
let input = "*.com";
let result = parse_filter(input, true, FilterFormat::Hosts);
assert!(result.is_err());
}
{
let input = "www.";
let result = parse_filter(input, true, FilterFormat::Hosts);
assert!(result.is_err());
}
}
#[test]
fn parse_filter_failed_fuzz_1() {
let input = "Ѥ";
let result = parse_filter(input, true, FilterFormat::Standard);
assert!(result.is_ok());
}
#[test]
fn parse_filter_failed_fuzz_2() {
assert!(parse_filter(r#"###\\\00DB \008D"#, true, FilterFormat::Standard).is_ok());
assert!(parse_filter(r#"###\Û"#, true, FilterFormat::Standard).is_ok());
}
#[test]
fn parse_filter_failed_fuzz_3() {
let input = "||$3p=/";
let result = parse_filter(input, true, FilterFormat::Standard);
assert!(result.is_ok());
}
#[test]
fn parse_filter_failed_fuzz_4() {
assert!(parse_filter(
&String::from_utf8(vec![92, 35, 35, 43, 106, 115, 40, 44, 221, 141]).unwrap(),
true,
FilterFormat::Standard,
).is_ok());
}
}