@@ -131,8 +131,7 @@ public class FilterHttpResource { | |||||
final List<AppMatcher> matchers = matcherDAO.findByAccountAndFqdnAndEnabled(accountUuid, fqdn); | final List<AppMatcher> matchers = matcherDAO.findByAccountAndFqdnAndEnabled(accountUuid, fqdn); | ||||
if (log.isDebugEnabled()) log.debug("findMatchers: found "+matchers.size()+" candidate matchers"); | if (log.isDebugEnabled()) log.debug("findMatchers: found "+matchers.size()+" candidate matchers"); | ||||
final List<AppMatcher> removeMatchers; | final List<AppMatcher> removeMatchers; | ||||
List<String> options = null; | |||||
List<String> selectors = null; | |||||
List<String> filters = null; | |||||
if (matchers.isEmpty()) { | if (matchers.isEmpty()) { | ||||
removeMatchers = Collections.emptyList(); | removeMatchers = Collections.emptyList(); | ||||
} else { | } else { | ||||
@@ -148,13 +147,9 @@ public class FilterHttpResource { | |||||
removeMatchers.add(matcher); | removeMatchers.add(matcher); | ||||
break; | break; | ||||
case match: | case match: | ||||
if (matchResponse.hasOptions()) { | |||||
if (options == null) options = new ArrayList<>(); | |||||
options.addAll(matchResponse.getOptions()); | |||||
} | |||||
if (matchResponse.hasSelectors()) { | |||||
if (selectors == null) selectors = new ArrayList<>(); | |||||
selectors.addAll(matchResponse.getSelectors()); | |||||
if (matchResponse.hasFilters()) { | |||||
if (filters == null) filters = new ArrayList<>(); | |||||
filters.addAll(matchResponse.getFilters()); | |||||
} | } | ||||
break; | break; | ||||
} | } | ||||
@@ -168,8 +163,7 @@ public class FilterHttpResource { | |||||
return response | return response | ||||
.setMatchers(matchers) | .setMatchers(matchers) | ||||
.setDevice(device.getUuid()) | .setDevice(device.getUuid()) | ||||
.setOptions(options) | |||||
.setSelectors(selectors); | |||||
.setFilters(filters); | |||||
} | } | ||||
@POST @Path(EP_APPLY+"/{requestId}") | @POST @Path(EP_APPLY+"/{requestId}") | ||||
@@ -8,6 +8,8 @@ import lombok.experimental.Accessors; | |||||
import java.util.List; | import java.util.List; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||||
@NoArgsConstructor @Accessors(chain=true) | @NoArgsConstructor @Accessors(chain=true) | ||||
public class FilterMatchResponse { | public class FilterMatchResponse { | ||||
@@ -18,10 +20,7 @@ public class FilterMatchResponse { | |||||
@Getter @Setter private FilterMatchDecision decision; | @Getter @Setter private FilterMatchDecision decision; | ||||
@Getter @Setter private List<String> options; | |||||
public boolean hasOptions () { return options != null && !options.isEmpty(); } | |||||
@Getter @Setter private List<String> selectors; | |||||
public boolean hasSelectors () { return selectors != null && !selectors.isEmpty(); } | |||||
@Getter @Setter private List<String> filters; | |||||
public boolean hasFilters() { return !empty(filters); } | |||||
} | } |
@@ -16,7 +16,6 @@ public class FilterMatchersResponse { | |||||
@Getter @Setter private Integer abort; | @Getter @Setter private Integer abort; | ||||
@Getter @Setter private String device; | @Getter @Setter private String device; | ||||
@Getter @Setter private List<AppMatcher> matchers; | @Getter @Setter private List<AppMatcher> matchers; | ||||
@Getter @Setter private List<String> options; | |||||
@Getter @Setter private List<String> selectors; | |||||
@Getter @Setter private List<String> filters; | |||||
} | } |
@@ -9,9 +9,9 @@ import lombok.experimental.Accessors; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.stream.Collectors; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | import static org.cobbzilla.util.daemon.ZillaRuntime.die; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||||
@NoArgsConstructor @Accessors(chain=true) | @NoArgsConstructor @Accessors(chain=true) | ||||
public class BlockDecision { | public class BlockDecision { | ||||
@@ -20,39 +20,24 @@ public class BlockDecision { | |||||
public static final BlockDecision ALLOW = new BlockDecision().setDecisionType(BlockDecisionType.allow); | public static final BlockDecision ALLOW = new BlockDecision().setDecisionType(BlockDecisionType.allow); | ||||
@Getter @Setter BlockDecisionType decisionType = BlockDecisionType.allow; | @Getter @Setter BlockDecisionType decisionType = BlockDecisionType.allow; | ||||
@Getter @Setter List<String> selectors; | |||||
@Getter @Setter List<String> options; | |||||
@Getter @Setter List<BlockSpec> specs; | |||||
public BlockDecision add(BlockSpec block) { | |||||
if (block.hasSelector()) { | |||||
if (selectors == null) selectors = new ArrayList<>(); | |||||
selectors.add(block.getSelector()); | |||||
public BlockDecision add(BlockSpec spec) { | |||||
if (specs == null) specs = new ArrayList<>(); | |||||
specs.add(spec); | |||||
if (decisionType != BlockDecisionType.block && (spec.hasTypeMatches() || spec.hasSelector())) { | |||||
decisionType = BlockDecisionType.filter; | |||||
} | } | ||||
if (block.hasOptions()) { | |||||
if (options == null) options = new ArrayList<>(); | |||||
options.addAll(block.getOptions()); | |||||
} | |||||
if (!empty(selectors) || !empty(options)) decisionType = BlockDecisionType.filter; | |||||
return this; | return this; | ||||
} | } | ||||
public FilterMatchDecision getFilterMatchDecision() { | |||||
switch (decisionType) { | |||||
case block: return FilterMatchDecision.abort_not_found; | |||||
case allow: return FilterMatchDecision.no_match; | |||||
case filter: return FilterMatchDecision.match; | |||||
} | |||||
return die("getFilterMatchDecision: invalid decisionType: "+decisionType); | |||||
} | |||||
public FilterMatchResponse getFilterMatchResponse() { | public FilterMatchResponse getFilterMatchResponse() { | ||||
switch (decisionType) { | switch (decisionType) { | ||||
case block: return FilterMatchResponse.ABORT_NOT_FOUND; | case block: return FilterMatchResponse.ABORT_NOT_FOUND; | ||||
case allow: return FilterMatchResponse.NO_MATCH; | case allow: return FilterMatchResponse.NO_MATCH; | ||||
case filter: return new FilterMatchResponse() | case filter: return new FilterMatchResponse() | ||||
.setDecision(FilterMatchDecision.match) | .setDecision(FilterMatchDecision.match) | ||||
.setOptions(getOptions()) | |||||
.setSelectors(getSelectors()); | |||||
.setFilters(specs == null ? null : getSpecs().stream().map(BlockSpec::getLine).collect(Collectors.toList())); | |||||
} | } | ||||
return die("getFilterMatchResponse: invalid decisionType: "+decisionType); | return die("getFilterMatchResponse: invalid decisionType: "+decisionType); | ||||
} | } | ||||
@@ -1,30 +1,82 @@ | |||||
package bubble.rule.bblock.spec; | package bubble.rule.bblock.spec; | ||||
import lombok.AllArgsConstructor; | |||||
import lombok.Getter; | import lombok.Getter; | ||||
import lombok.extern.slf4j.Slf4j; | |||||
import org.cobbzilla.util.http.HttpContentTypes; | |||||
import org.cobbzilla.util.string.StringUtil; | import org.cobbzilla.util.string.StringUtil; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.regex.Pattern; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | ||||
import static org.cobbzilla.util.http.HttpContentTypes.contentType; | |||||
@AllArgsConstructor | |||||
@Slf4j | |||||
public class BlockSpec { | public class BlockSpec { | ||||
public static final String OPT_DOMAIN_PREFIX = "domain="; | |||||
public static final String OPT_SCRIPT = "script"; | |||||
public static final String OPT_IMAGE = "image"; | |||||
public static final String OPT_STYLESHEET = "stylesheet"; | |||||
@Getter private String line; | |||||
@Getter private BlockSpecTarget target; | @Getter private BlockSpecTarget target; | ||||
@Getter private List<String> options; | |||||
@Getter(lazy=true) private final Pattern domainPattern = Pattern.compile(target.getDomainRegex()); | |||||
@Getter private List<String> domainExclusions; | |||||
@Getter private List<String> typeMatches; | |||||
public boolean hasTypeMatches () { return !empty(typeMatches); } | |||||
@Getter private List<String> typeExclusions; | |||||
public BlockSpec(String line, BlockSpecTarget target, List<String> options, String selector) { | |||||
this.line = line; | |||||
this.target = target; | |||||
this.selector = selector; | |||||
if (options != null) { | |||||
for (String opt : options) { | |||||
if (opt.startsWith(OPT_DOMAIN_PREFIX)) { | |||||
processDomainOptions(opt.substring(OPT_DOMAIN_PREFIX.length())); | |||||
} else if (opt.startsWith("~")) { | |||||
final String type = opt.substring(1); | |||||
if (isTypeOption(type)) { | |||||
if (typeExclusions == null) typeExclusions = new ArrayList<>(); | |||||
typeExclusions.add(type); | |||||
} else { | |||||
log.warn("unsupported option (ignoring): " + opt); | |||||
} | |||||
} else { | |||||
if (isTypeOption(opt)) { | |||||
if (typeMatches == null) typeMatches = new ArrayList<>(); | |||||
typeMatches.add(opt); | |||||
} else { | |||||
log.warn("unsupported option (ignoring): "+opt); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
private void processDomainOptions(String option) { | |||||
final String[] parts = option.split("\\|"); | |||||
for (String domainOption : parts) { | |||||
if (domainOption.startsWith("~")) { | |||||
if (domainExclusions == null) domainExclusions = new ArrayList<>(); | |||||
domainExclusions.add(domainOption.substring(1)); | |||||
} else { | |||||
log.warn("ignoring included domain: "+domainOption); | |||||
} | |||||
} | |||||
} | |||||
public boolean hasOptions () { return options != null && !options.isEmpty(); } | |||||
public boolean isTypeOption(String type) { | |||||
return type.equals(OPT_SCRIPT) || type.equals(OPT_IMAGE) || type.equals(OPT_STYLESHEET); | |||||
} | |||||
@Getter private String selector; | @Getter private String selector; | ||||
public boolean hasSelector() { return !empty(selector); } | public boolean hasSelector() { return !empty(selector); } | ||||
public boolean isBlanket() { return !hasOptions() && !hasSelector(); } | |||||
public static List<BlockSpec> parse(String line) { | public static List<BlockSpec> parse(String line) { | ||||
line = line.trim(); | line = line.trim(); | ||||
@@ -40,7 +92,7 @@ public class BlockSpec { | |||||
if (optionStartPos == -1) { | if (optionStartPos == -1) { | ||||
if (selectorStartPos == -1) { | if (selectorStartPos == -1) { | ||||
// no options, no selector, entire line is the target | // no options, no selector, entire line is the target | ||||
targets = BlockSpecTarget.parse(line); | |||||
targets = BlockSpecTarget.parseBareLine(line); | |||||
options = null; | options = null; | ||||
selector = null; | selector = null; | ||||
} else { | } else { | ||||
@@ -63,14 +115,64 @@ public class BlockSpec { | |||||
} | } | ||||
} | } | ||||
final List<BlockSpec> specs = new ArrayList<>(); | final List<BlockSpec> specs = new ArrayList<>(); | ||||
for (BlockSpecTarget target : targets) specs.add(new BlockSpec(target, options, selector)); | |||||
for (BlockSpecTarget target : targets) specs.add(new BlockSpec(line, target, options, selector)); | |||||
return specs; | return specs; | ||||
} | } | ||||
public boolean matches(String fqdn, String path) { | public boolean matches(String fqdn, String path) { | ||||
if (getDomainPattern().matcher(fqdn).find()) { | |||||
return true; | |||||
if (target.hasDomainRegex() && target.getDomainPattern().matcher(fqdn).find()) { | |||||
return checkDomainExclusionsAndType(fqdn, contentType(path)); | |||||
} else if (target.hasRegex()) { | |||||
if (target.getRegexPattern().matcher(path).find()) { | |||||
return checkDomainExclusionsAndType(fqdn, contentType(path)); | |||||
} | |||||
final String full = fqdn + path; | |||||
if (target.getRegexPattern().matcher(full).find()) { | |||||
return checkDomainExclusionsAndType(fqdn, contentType(path)); | |||||
}; | |||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
public boolean checkDomainExclusionsAndType(String fqdn, String contentType) { | |||||
if (domainExclusions != null) { | |||||
for (String domain : domainExclusions) { | |||||
if (domain.equals(fqdn)) return false; | |||||
} | |||||
} | |||||
if (typeExclusions != null) { | |||||
for (String type : typeExclusions) { | |||||
switch (type) { | |||||
case OPT_SCRIPT: | |||||
if (contentType.equals(HttpContentTypes.APPLICATION_JAVASCRIPT)) return false; | |||||
break; | |||||
case OPT_IMAGE: | |||||
if (contentType.startsWith(HttpContentTypes.IMAGE_PREFIX)) return false; | |||||
break; | |||||
case OPT_STYLESHEET: | |||||
if (contentType.equals(HttpContentTypes.TEXT_CSS)) return false; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
if (typeMatches != null) { | |||||
for (String type : typeMatches) { | |||||
switch (type) { | |||||
case OPT_SCRIPT: | |||||
if (contentType.equals(HttpContentTypes.APPLICATION_JAVASCRIPT)) return true; | |||||
break; | |||||
case OPT_IMAGE: | |||||
if (contentType.startsWith(HttpContentTypes.IMAGE_PREFIX)) return true; | |||||
break; | |||||
case OPT_STYLESHEET: | |||||
if (contentType.equals(HttpContentTypes.TEXT_CSS)) return true; | |||||
break; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
return true; | |||||
} | |||||
} | } |
@@ -7,13 +7,22 @@ import lombok.experimental.Accessors; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.StringTokenizer; | |||||
import java.util.regex.Pattern; | import java.util.regex.Pattern; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||||
import static org.cobbzilla.util.http.HttpSchemes.stripScheme; | |||||
@NoArgsConstructor @Accessors(chain=true) | @NoArgsConstructor @Accessors(chain=true) | ||||
public class BlockSpecTarget { | public class BlockSpecTarget { | ||||
@Getter @Setter private String domainRegex; | @Getter @Setter private String domainRegex; | ||||
public boolean hasDomainRegex() { return !empty(domainRegex); } | |||||
@Getter(lazy=true) private final Pattern domainPattern = hasDomainRegex() ? Pattern.compile(getDomainRegex()) : null; | |||||
@Getter @Setter private String regex; | @Getter @Setter private String regex; | ||||
public boolean hasRegex() { return !empty(regex); } | |||||
@Getter(lazy=true) private final Pattern regexPattern = hasRegex() ? Pattern.compile(getRegex()) : null; | |||||
public static List<BlockSpecTarget> parse(String data) { | public static List<BlockSpecTarget> parse(String data) { | ||||
final List<BlockSpecTarget> targets = new ArrayList<>(); | final List<BlockSpecTarget> targets = new ArrayList<>(); | ||||
@@ -23,18 +32,70 @@ public class BlockSpecTarget { | |||||
return targets; | return targets; | ||||
} | } | ||||
public static List<BlockSpecTarget> parseBareLine(String data) { | |||||
if (data.contains("|") || data.contains("/") || data.contains("^")) return parse(data); | |||||
final List<BlockSpecTarget> targets = new ArrayList<>(); | |||||
for (String part : data.split(",")) { | |||||
targets.add(new BlockSpecTarget().setDomainRegex(matchDomainOrAnySubdomains(part))); | |||||
} | |||||
return targets; | |||||
} | |||||
private static BlockSpecTarget parseTarget(String data) { | private static BlockSpecTarget parseTarget(String data) { | ||||
String domainRegex = null; | String domainRegex = null; | ||||
String regex = null; | String regex = null; | ||||
if (data.startsWith("||")) { | if (data.startsWith("||")) { | ||||
final int caretPos = data.indexOf("^"); | final int caretPos = data.indexOf("^"); | ||||
if (caretPos != -1) { | if (caretPos != -1) { | ||||
domainRegex = ".*?"+Pattern.quote(data.substring(2, caretPos))+"$"; | |||||
// domain match | |||||
final String domain = data.substring(2, caretPos); | |||||
domainRegex = matchDomainOrAnySubdomains(domain); | |||||
} else { | |||||
final String domain = data.substring(2); | |||||
domainRegex = matchDomainOrAnySubdomains(domain); | |||||
} | |||||
} else if (data.startsWith("|") && data.endsWith("|")) { | |||||
// exact match | |||||
final String verbatimMatch = stripScheme(data.substring(1, data.length() - 1)); | |||||
regex = "^" + Pattern.quote(verbatimMatch) + "$"; | |||||
} else if (data.startsWith("/")) { | |||||
// path match, possibly regex | |||||
if (data.endsWith("/") && ( | |||||
data.contains("|") || data.contains("?") | |||||
|| (data.contains("(") && data.contains(")")) | |||||
|| (data.contains("{") && data.contains("}")))) { | |||||
regex = data.substring(1, data.length()-1); | |||||
} else if (data.contains("*")) { | |||||
regex = parseWildcardMatch(data); | |||||
} else { | } else { | ||||
regex = "^" + Pattern.quote(data) + ".*"; | |||||
} | |||||
} else { | |||||
if (data.contains("*")) { | |||||
regex = parseWildcardMatch(data); | |||||
} else { | |||||
regex = "^" + Pattern.quote(data) + ".*"; | |||||
} | } | ||||
} | } | ||||
return new BlockSpecTarget().setDomainRegex(domainRegex).setRegex(regex); | return new BlockSpecTarget().setDomainRegex(domainRegex).setRegex(regex); | ||||
} | } | ||||
private static String parseWildcardMatch(String data) { | |||||
final StringBuilder b = new StringBuilder("^"); | |||||
final StringTokenizer st = new StringTokenizer(data, "*", true); | |||||
while (st.hasMoreTokens()) { | |||||
final String token = st.nextToken(); | |||||
b.append(token.equals("*") ? ".*?" : token); | |||||
} | |||||
return b.append("$").toString(); | |||||
} | |||||
private static String matchDomainOrAnySubdomains(String domain) { | |||||
return ".*?"+ Pattern.quote(domain)+"$"; | |||||
} | |||||
} | } |
@@ -2,13 +2,156 @@ package bubble.rule.bblock.spec; | |||||
import org.junit.Test; | import org.junit.Test; | ||||
import java.util.Arrays; | |||||
import static org.junit.Assert.assertEquals; | import static org.junit.Assert.assertEquals; | ||||
public class BlockListTest { | public class BlockListTest { | ||||
@Test public void testBlanketBlock () throws Exception { | |||||
public static final String BLOCK = BlockDecisionType.block.name(); | |||||
public static final String ALLOW = BlockDecisionType.allow.name(); | |||||
public static final String FILTER = BlockDecisionType.filter.name(); | |||||
public static final String[][] BLOCK_TESTS = { | |||||
// rule // fqdn // path // expected decision | |||||
// bare hosts example (ala EasyList) | |||||
{"example.com", "example.com", "/some_path", BLOCK}, | |||||
{"example.com", "foo.example.com", "/some_path", BLOCK}, | |||||
{"example.com", "example.org", "/some_path", ALLOW}, | |||||
// block example.com and all subdomains | |||||
{"||example.com^", "example.com", "/some_path", BLOCK}, | |||||
{"||example.com^", "foo.example.com", "/some_path", BLOCK}, | |||||
{"||example.com^", "example.org", "/some_path", ALLOW}, | |||||
// block exact string | |||||
{"|example.com/|", "example.com", "/", BLOCK}, | |||||
{"|example.com/|", "example.com", "/some_path", ALLOW}, | |||||
{"|example.com/|", "foo.example.com", "/some_path", ALLOW}, | |||||
// block example.com, but not foo.example.com or bar.example.com | |||||
{"||example.com^$domain=~foo.example.com|~bar.example.com", | |||||
"example.com", "/some_path", BLOCK}, | |||||
{"||example.com^$domain=~foo.example.com|~bar.example.com", | |||||
"foo.example.com", "/some_path", ALLOW}, | |||||
{"||example.com^$domain=~foo.example.com|~bar.example.com", | |||||
"bar.example.com", "/some_path", ALLOW}, | |||||
{"||example.com^$domain=~foo.example.com|~bar.example.com", | |||||
"baz.example.com", "/some_path", BLOCK}, | |||||
// block images and scripts on example.com, but not foo.example.com or bar.example.com | |||||
{"||example.com^$image,script,domain=~foo.example.com|~bar.example.com", | |||||
"example.com", "/some_path", ALLOW}, | |||||
// test image blocking | |||||
{"||example.com^$image,script,domain=~foo.example.com|~bar.example.com", | |||||
"example.com", "/some_path.png", BLOCK}, | |||||
{"||example.com^$image,script,domain=~foo.example.com|~bar.example.com", | |||||
"foo.example.com", "/some_path.png", ALLOW}, | |||||
{"||example.com^$image,script,domain=~foo.example.com|~bar.example.com", | |||||
"bar.example.com", "/some_path.png", ALLOW}, | |||||
{"||example.com^$image,script,domain=~foo.example.com|~bar.example.com", | |||||
"baz.example.com", "/some_path.png", BLOCK}, | |||||
// test script blocking | |||||
{"||example.com^$image,script,domain=~foo.example.com|~bar.example.com", | |||||
"example.com", "/some_path.js", BLOCK}, | |||||
{"||example.com^$image,script,domain=~foo.example.com|~bar.example.com", | |||||
"foo.example.com", "/some_path.js", ALLOW}, | |||||
{"||example.com^$image,script,domain=~foo.example.com|~bar.example.com", | |||||
"bar.example.com", "/some_path.js", ALLOW}, | |||||
{"||example.com^$image,script,domain=~foo.example.com|~bar.example.com", | |||||
"baz.example.com", "/some_path.js", BLOCK}, | |||||
// test stylesheet blocking | |||||
{"||example.com^stylesheet,domain=~foo.example.com|~bar.example.com", | |||||
"example.com", "/some_path.css", BLOCK}, | |||||
{"||example.com^$stylesheet,domain=~foo.example.com|~bar.example.com", | |||||
"foo.example.com", "/some_path.css", ALLOW}, | |||||
{"||example.com^$stylesheet,domain=~foo.example.com|~bar.example.com", | |||||
"bar.example.com", "/some_path.css", ALLOW}, | |||||
{"||example.com^$stylesheet,domain=~foo.example.com|~bar.example.com", | |||||
"baz.example.com", "/some_path.css", BLOCK}, | |||||
// path matching | |||||
{"/foo", "example.com", "/some_path", ALLOW}, | |||||
{"/foo", "example.com", "/foo", BLOCK}, | |||||
{"/foo", "example.com", "/foo/bar", BLOCK}, | |||||
// path matching with wildcard | |||||
{"/foo/*/img", "example.com", "/some_path", ALLOW}, | |||||
{"/foo/*/img", "example.com", "/foo", ALLOW}, | |||||
{"/foo/*/img", "example.com", "/foo/img", ALLOW}, | |||||
{"/foo/*/img", "example.com", "/foo/x/img", BLOCK}, | |||||
{"/foo/*/img", "example.com", "/foo/x/img.png", ALLOW}, | |||||
{"/foo/*/img", "example.com", "/foo/x/y/z//img", BLOCK}, | |||||
// path matching with regex | |||||
{"/foo/(apps|ads)/img.+/", "example.com", "/foo/x/y/z//img", ALLOW}, | |||||
{"/foo/(apps|ads)/img.+/", "example.com", "/foo/apps/img.png", BLOCK}, | |||||
{"/foo/(apps|ads)/img.+/", "example.com", "/foo/ads/img.png", BLOCK}, | |||||
{"/foo/(apps|ads)/img.+/", "example.com", "/foo/bar/ads/img.png", ALLOW}, | |||||
{"/(apps|ads)\\.example\\.(com|org)/", | |||||
"example.com", "/ad.png", ALLOW}, | |||||
{"/(apps|ads)\\.example\\.(com|org)/", | |||||
"ads.example.com", "/ad.png", BLOCK}, | |||||
{"/(apps|ads)\\.example\\.(com|org)/", | |||||
"apps.example.com", "/ad.png", BLOCK}, | |||||
{"/(apps|ads)\\.example\\.(com|org)/", | |||||
"ads.example.org", "/ad.png", BLOCK}, | |||||
{"/(apps|ads)\\.example\\.(com|org)/", | |||||
"apps.example.org", "/ad.png", BLOCK}, | |||||
{"/(apps|ads)\\.example\\.(com|org)/", | |||||
"ads.example.net", "/ad.png", ALLOW}, | |||||
{"/(apps|ads)\\.example\\.(com|org)/", | |||||
"apps.example.net", "/ad.png", ALLOW}, | |||||
// selectors | |||||
{"example.com##.banner-ad", "example.com", "/ad.png", FILTER}, | |||||
// putting it all together | |||||
{"||example.com^$domain=~foo.example.com|~bar.example.com##.banner-ad", | |||||
"example.com", "/some_path", FILTER}, | |||||
{"||example.com^$domain=~foo.example.com|~bar.example.com##.banner-ad", | |||||
"baz.example.com", "/some_path", FILTER}, | |||||
{"||example.com^$domain=~foo.example.com|~bar.example.com##.banner-ad", | |||||
"foo.example.com", "/some_path", ALLOW}, | |||||
{"||example.com^$domain=~foo.example.com|~bar.example.com##.banner-ad", | |||||
"bar.example.com", "/some_path", ALLOW}, | |||||
}; | |||||
@Test public void testRules () throws Exception { | |||||
for (String[] test : BLOCK_TESTS) { | |||||
final BlockDecisionType expectedDecision = BlockDecisionType.fromString(test[3]); | |||||
final BlockList blockList = new BlockList(); | |||||
blockList.addToBlacklist(BlockSpec.parse(test[0])); | |||||
assertEquals("testBlanketBlock: expected "+expectedDecision+" decision, test=" + Arrays.toString(test), | |||||
expectedDecision, | |||||
blockList.getDecision(test[1], test[2]).getDecisionType()); | |||||
} | |||||
} | |||||
public String[] SELECTOR_SPECS = { | |||||
"||example.com##.banner-ad", | |||||
"||foo.example.com##.more-ads", | |||||
}; | |||||
@Test public void testMultipleSelectorMatches () throws Exception { | |||||
final BlockList blockList = new BlockList(); | final BlockList blockList = new BlockList(); | ||||
blockList.addToBlacklist(BlockSpec.parse("||fredfiber.no^")); | |||||
assertEquals("expected block", BlockDecisionType.block, blockList.getDecision("fredfiber.no", "/somepath").getDecisionType()); | |||||
for (String line : SELECTOR_SPECS) { | |||||
blockList.addToBlacklist(BlockSpec.parse(line)); | |||||
} | |||||
BlockDecision decision; | |||||
decision = blockList.getDecision("example.com", "/some_path"); | |||||
assertEquals("expected filter decision", BlockDecisionType.filter, decision.getDecisionType()); | |||||
assertEquals("expected 1 filter specs", 1, decision.getSpecs().size()); | |||||
decision = blockList.getDecision("foo.example.com", "/some_path"); | |||||
assertEquals("expected filter decision", BlockDecisionType.filter, decision.getDecisionType()); | |||||
assertEquals("expected 2 filter specs", 2, decision.getSpecs().size()); | |||||
} | } | ||||
} | } |
@@ -1 +1 @@ | |||||
Subproject commit 006bcd1ff4390bb37ae35d50fa12a70de7e408c1 | |||||
Subproject commit a17850215a54359929b83a9c5b061fd608090444 |
@@ -1 +1 @@ | |||||
Subproject commit 78b8da16659be214013288328f932509ed4c9224 | |||||
Subproject commit 6a3824eee748ded81eb4caf065f444f565708b1c |