Browse Source

WIP. adding support for filter lists

tags/v0.5.0
Jonathan Cobb 5 years ago
parent
commit
1fe74a3c38
18 changed files with 465 additions and 45 deletions
  1. +22
    -4
      bubble-server/src/main/java/bubble/resources/stream/FilterHttpResource.java
  2. +27
    -0
      bubble-server/src/main/java/bubble/resources/stream/FilterMatchResponse.java
  3. +2
    -0
      bubble-server/src/main/java/bubble/resources/stream/FilterMatchersResponse.java
  4. +8
    -7
      bubble-server/src/main/java/bubble/rule/AppRuleDriver.java
  5. +2
    -2
      bubble-server/src/main/java/bubble/rule/FilterMatchDecision.java
  6. +8
    -8
      bubble-server/src/main/java/bubble/rule/analytics/TrafficAnalytics.java
  7. +63
    -15
      bubble-server/src/main/java/bubble/rule/bblock/BubbleBlock.java
  8. +12
    -0
      bubble-server/src/main/java/bubble/rule/bblock/BubbleBlockConfig.java
  9. +59
    -0
      bubble-server/src/main/java/bubble/rule/bblock/spec/BlockDecision.java
  10. +13
    -0
      bubble-server/src/main/java/bubble/rule/bblock/spec/BlockDecisionType.java
  11. +55
    -0
      bubble-server/src/main/java/bubble/rule/bblock/spec/BlockList.java
  12. +55
    -0
      bubble-server/src/main/java/bubble/rule/bblock/spec/BlockListSource.java
  13. +76
    -0
      bubble-server/src/main/java/bubble/rule/bblock/spec/BlockSpec.java
  14. +40
    -0
      bubble-server/src/main/java/bubble/rule/bblock/spec/BlockSpecTarget.java
  15. +7
    -7
      bubble-server/src/main/java/bubble/service/stream/RuleEngineService.java
  16. +1
    -1
      bubble-server/src/main/resources/models/apps/bblock/bubbleApp_bblock.json
  17. +14
    -0
      bubble-server/src/test/java/bubble/rule/bblock/spec/BlockListTest.java
  18. +1
    -1
      utils/cobbzilla-wizard

+ 22
- 4
bubble-server/src/main/java/bubble/resources/stream/FilterHttpResource.java View File

@@ -131,6 +131,8 @@ 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;
if (matchers.isEmpty()) { if (matchers.isEmpty()) {
removeMatchers = Collections.emptyList(); removeMatchers = Collections.emptyList();
} else { } else {
@@ -138,12 +140,23 @@ public class FilterHttpResource {
removeMatchers = new ArrayList<>(); removeMatchers = new ArrayList<>();
for (AppMatcher matcher : matchers) { for (AppMatcher matcher : matchers) {
if (matcher.matches(uri)) { if (matcher.matches(uri)) {
switch (ruleEngine.preprocess(filterRequest, req, request, caller, device, matcher.getUuid())) {
final FilterMatchResponse matchResponse = ruleEngine.preprocess(filterRequest, req, request, caller, device, matcher.getUuid());
switch (matchResponse.getDecision()) {
case abort_ok: return ABORT_OK;
case abort_not_found: return ABORT_NOT_FOUND;
case no_match: case no_match:
removeMatchers.add(matcher); removeMatchers.add(matcher);
break; break;
case abort_ok: return ABORT_OK;
case abort_not_found: return ABORT_NOT_FOUND;
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());
}
break;
} }
} }
} }
@@ -151,7 +164,12 @@ public class FilterHttpResource {
matchers.removeAll(removeMatchers); matchers.removeAll(removeMatchers);


if (log.isDebugEnabled()) log.debug("findMatchers: after pre-processing, returning "+matchers.size()+" matchers"); if (log.isDebugEnabled()) log.debug("findMatchers: after pre-processing, returning "+matchers.size()+" matchers");
return new FilterMatchersResponse().setMatchers(matchers).setDevice(device.getUuid());
final FilterMatchersResponse response = new FilterMatchersResponse();
return response
.setMatchers(matchers)
.setDevice(device.getUuid())
.setOptions(options)
.setSelectors(selectors);
} }


@POST @Path(EP_APPLY+"/{requestId}") @POST @Path(EP_APPLY+"/{requestId}")


+ 27
- 0
bubble-server/src/main/java/bubble/resources/stream/FilterMatchResponse.java View File

@@ -0,0 +1,27 @@
package bubble.resources.stream;

import bubble.rule.FilterMatchDecision;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;

import java.util.List;

@NoArgsConstructor @Accessors(chain=true)
public class FilterMatchResponse {

public static final FilterMatchResponse NO_MATCH = new FilterMatchResponse().setDecision(FilterMatchDecision.no_match);
public static final FilterMatchResponse MATCH = new FilterMatchResponse().setDecision(FilterMatchDecision.match);
public static final FilterMatchResponse ABORT_NOT_FOUND = new FilterMatchResponse().setDecision(FilterMatchDecision.abort_not_found);
public static final FilterMatchResponse ABORT_OK = new FilterMatchResponse().setDecision(FilterMatchDecision.abort_ok);

@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(); }

}

+ 2
- 0
bubble-server/src/main/java/bubble/resources/stream/FilterMatchersResponse.java View File

@@ -16,5 +16,7 @@ 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;


} }

+ 8
- 7
bubble-server/src/main/java/bubble/rule/AppRuleDriver.java View File

@@ -4,6 +4,7 @@ import bubble.model.account.Account;
import bubble.model.app.AppMatcher; import bubble.model.app.AppMatcher;
import bubble.model.app.AppRule; import bubble.model.app.AppRule;
import bubble.model.device.Device; import bubble.model.device.Device;
import bubble.resources.stream.FilterMatchResponse;
import bubble.service.stream.AppRuleHarness; import bubble.service.stream.AppRuleHarness;
import bubble.resources.stream.FilterMatchersRequest; import bubble.resources.stream.FilterMatchersRequest;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
@@ -32,13 +33,13 @@ public interface AppRuleDriver {
Account account, Account account,
Device device) {} Device device) {}


default PreprocessDecision preprocess(AppRuleHarness ruleHarness,
FilterMatchersRequest filter,
Account account,
Device device,
Request req,
ContainerRequest request) {
return PreprocessDecision.match;
default FilterMatchResponse preprocess(AppRuleHarness ruleHarness,
FilterMatchersRequest filter,
Account account,
Device device,
Request req,
ContainerRequest request) {
return FilterMatchResponse.MATCH;
} }


default InputStream filterRequest(InputStream in) { default InputStream filterRequest(InputStream in) {


bubble-server/src/main/java/bubble/rule/PreprocessDecision.java → bubble-server/src/main/java/bubble/rule/FilterMatchDecision.java View File

@@ -4,13 +4,13 @@ import com.fasterxml.jackson.annotation.JsonCreator;


import static bubble.ApiConstants.enumFromString; import static bubble.ApiConstants.enumFromString;


public enum PreprocessDecision {
public enum FilterMatchDecision {


no_match, // associated matcher should not be included in request processing no_match, // associated matcher should not be included in request processing
match, // associated should be included in request processing match, // associated should be included in request processing
abort_ok, // abort request processing, return empty 200 OK response to client abort_ok, // abort request processing, return empty 200 OK response to client
abort_not_found; // abort request processing, return empty 404 Not Found response to client abort_not_found; // abort request processing, return empty 404 Not Found response to client


@JsonCreator public static PreprocessDecision fromString (String v) { return enumFromString(PreprocessDecision.class, v); }
@JsonCreator public static FilterMatchDecision fromString (String v) { return enumFromString(FilterMatchDecision.class, v); }


} }

+ 8
- 8
bubble-server/src/main/java/bubble/rule/analytics/TrafficAnalytics.java View File

@@ -3,9 +3,9 @@ package bubble.rule.analytics;
import bubble.model.account.Account; import bubble.model.account.Account;
import bubble.model.app.AppData; import bubble.model.app.AppData;
import bubble.model.device.Device; import bubble.model.device.Device;
import bubble.resources.stream.FilterMatchResponse;
import bubble.resources.stream.FilterMatchersRequest; import bubble.resources.stream.FilterMatchersRequest;
import bubble.rule.AbstractAppRuleDriver; import bubble.rule.AbstractAppRuleDriver;
import bubble.rule.PreprocessDecision;
import bubble.service.stream.AppRuleHarness; import bubble.service.stream.AppRuleHarness;
import lombok.Getter; import lombok.Getter;
import org.cobbzilla.wizard.cache.redis.RedisService; import org.cobbzilla.wizard.cache.redis.RedisService;
@@ -30,12 +30,12 @@ public class TrafficAnalytics extends AbstractAppRuleDriver {


@Getter(lazy=true) private final RedisService recentTraffic = redis.prefixNamespace(RECENT_TRAFFIC_PREFIX); @Getter(lazy=true) private final RedisService recentTraffic = redis.prefixNamespace(RECENT_TRAFFIC_PREFIX);


@Override public PreprocessDecision preprocess(AppRuleHarness ruleHarness,
FilterMatchersRequest filter,
Account account,
Device device,
Request req,
ContainerRequest request) {
@Override public FilterMatchResponse preprocess(AppRuleHarness ruleHarness,
FilterMatchersRequest filter,
Account account,
Device device,
Request req,
ContainerRequest request) {
final String app = ruleHarness.getRule().getApp(); final String app = ruleHarness.getRule().getApp();
final String site = ruleHarness.getMatcher().getSite(); final String site = ruleHarness.getMatcher().getSite();
final String fqdn = filter.getFqdn(); final String fqdn = filter.getFqdn();
@@ -43,7 +43,7 @@ public class TrafficAnalytics extends AbstractAppRuleDriver {
getRecentTraffic().set(now()+"_"+randomAlphanumeric(10), json(new TrafficRecord(filter, account, device, req)), EX, RECENT_TRAFFIC_EXPIRATION); getRecentTraffic().set(now()+"_"+randomAlphanumeric(10), json(new TrafficRecord(filter, account, device, req)), EX, RECENT_TRAFFIC_EXPIRATION);
incr(account, device, app, site, fqdn, DATE_FORMAT_YYYY_MM_DD_HH.print(now())); incr(account, device, app, site, fqdn, DATE_FORMAT_YYYY_MM_DD_HH.print(now()));
incr(account, null, app, site, fqdn, DATE_FORMAT_YYYY_MM_DD_HH.print(now())); incr(account, null, app, site, fqdn, DATE_FORMAT_YYYY_MM_DD_HH.print(now()));
return PreprocessDecision.no_match; // we are done, don't need to look at/modify stream
return FilterMatchResponse.NO_MATCH; // we are done, don't need to look at/modify stream
} }


// we use synchronized here but in a multi-node scenario this is not sufficient, we still have some risk // we use synchronized here but in a multi-node scenario this is not sufficient, we still have some risk


+ 63
- 15
bubble-server/src/main/java/bubble/rule/bblock/BubbleBlock.java View File

@@ -1,39 +1,87 @@
package bubble.rule.bblock; package bubble.rule.bblock;


import bubble.model.account.Account; import bubble.model.account.Account;
import bubble.model.app.AppMatcher;
import bubble.model.app.AppRule;
import bubble.model.device.Device; import bubble.model.device.Device;
import bubble.resources.stream.FilterMatchResponse;
import bubble.resources.stream.FilterMatchersRequest; import bubble.resources.stream.FilterMatchersRequest;
import bubble.rule.PreprocessDecision;
import bubble.rule.analytics.TrafficAnalytics; import bubble.rule.analytics.TrafficAnalytics;
import bubble.rule.bblock.spec.BlockDecision;
import bubble.rule.bblock.spec.BlockList;
import bubble.rule.bblock.spec.BlockListSource;
import bubble.service.stream.AppRuleHarness; import bubble.service.stream.AppRuleHarness;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.glassfish.grizzly.http.server.Request; import org.glassfish.grizzly.http.server.Request;
import org.glassfish.jersey.server.ContainerRequest; import org.glassfish.jersey.server.ContainerRequest;


import java.io.InputStream;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import static org.cobbzilla.util.daemon.ZillaRuntime.now; import static org.cobbzilla.util.daemon.ZillaRuntime.now;
import static org.cobbzilla.util.daemon.ZillaRuntime.shortError;
import static org.cobbzilla.util.json.JsonUtil.json;
import static org.cobbzilla.util.time.TimeUtil.DATE_FORMAT_YYYY_MM_DD_HH; import static org.cobbzilla.util.time.TimeUtil.DATE_FORMAT_YYYY_MM_DD_HH;


@Slf4j
public class BubbleBlock extends TrafficAnalytics { public class BubbleBlock extends TrafficAnalytics {


@Override public PreprocessDecision preprocess(AppRuleHarness ruleHarness,
FilterMatchersRequest filter,
Account account,
Device device,
Request req,
ContainerRequest request) {
private BubbleBlockConfig bubbleBlockConfig;

private BlockList blockList = new BlockList();

private static Map<String, BlockListSource> blockListCache = new ConcurrentHashMap<>();

@Override public void init(JsonNode config, JsonNode userConfig, AppRule rule, AppMatcher matcher, Account account, Device device) {
super.init(config, userConfig, rule, matcher, account, device);

bubbleBlockConfig = json(json(config), BubbleBlockConfig.class);
for (String listUrl : bubbleBlockConfig.getBlockLists()) {
BlockListSource blockListSource = blockListCache.get(listUrl);
if (blockListSource == null || blockListSource.age() > TimeUnit.DAYS.toMillis(5)) {
try {
final BlockListSource newList = new BlockListSource()
.setUrl(listUrl)
.download();
blockListCache.put(listUrl, newList);
blockListSource = newList;
} catch (Exception e) {
log.error("init: error downloading blocklist "+listUrl+": "+shortError(e));
continue;
}
}
blockList.merge(blockListSource.getBlockList());
}
}

@Override public FilterMatchResponse preprocess(AppRuleHarness ruleHarness,
FilterMatchersRequest filter,
Account account,
Device device,
Request req,
ContainerRequest request) {
final String app = ruleHarness.getRule().getApp(); final String app = ruleHarness.getRule().getApp();
final String site = ruleHarness.getMatcher().getSite(); final String site = ruleHarness.getMatcher().getSite();
final String fqdn = filter.getFqdn(); final String fqdn = filter.getFqdn();


if (shouldBlock(account, device, filter)) {
incr(account, device, app, site, fqdn, DATE_FORMAT_YYYY_MM_DD_HH.print(now()));
incr(account, null, app, site, fqdn, DATE_FORMAT_YYYY_MM_DD_HH.print(now()));
return PreprocessDecision.abort_not_found; // block this request
final BlockDecision decision = blockList.getDecision(filter.getFqdn(), filter.getUri());
switch (decision.getDecisionType()) {
case block:
incr(account, device, app, site, fqdn, DATE_FORMAT_YYYY_MM_DD_HH.print(now()));
incr(account, null, app, site, fqdn, DATE_FORMAT_YYYY_MM_DD_HH.print(now()));
return FilterMatchResponse.ABORT_NOT_FOUND; // block this request
case allow: default:
return FilterMatchResponse.NO_MATCH;
case filter:
return decision.getFilterMatchResponse();
} }
return PreprocessDecision.no_match; // don't block, and don't process this matcher (we do not modify streams)
} }


private boolean shouldBlock(Account account, Device device, FilterMatchersRequest filter) {
return false;
@Override public InputStream doFilterResponse(String requestId, InputStream in) {
// todo : insert selector-based block JS
return in;
} }

} }

+ 12
- 0
bubble-server/src/main/java/bubble/rule/bblock/BubbleBlockConfig.java View File

@@ -0,0 +1,12 @@
package bubble.rule.bblock;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@NoArgsConstructor
public class BubbleBlockConfig {

@Getter @Setter private String[] blockLists;

}

+ 59
- 0
bubble-server/src/main/java/bubble/rule/bblock/spec/BlockDecision.java View File

@@ -0,0 +1,59 @@
package bubble.rule.bblock.spec;

import bubble.resources.stream.FilterMatchResponse;
import bubble.rule.FilterMatchDecision;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;

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

import static org.cobbzilla.util.daemon.ZillaRuntime.die;
import static org.cobbzilla.util.daemon.ZillaRuntime.empty;

@NoArgsConstructor @Accessors(chain=true)
public class BlockDecision {

public static final BlockDecision BLOCK = new BlockDecision().setDecisionType(BlockDecisionType.block);
public static final BlockDecision ALLOW = new BlockDecision().setDecisionType(BlockDecisionType.allow);

@Getter @Setter BlockDecisionType decisionType = BlockDecisionType.allow;
@Getter @Setter List<String> selectors;
@Getter @Setter List<String> options;

public BlockDecision add(BlockSpec block) {
if (block.hasSelector()) {
if (selectors == null) selectors = new ArrayList<>();
selectors.add(block.getSelector());
}
if (block.hasOptions()) {
if (options == null) options = new ArrayList<>();
options.addAll(block.getOptions());
}
if (!empty(selectors) || !empty(options)) decisionType = BlockDecisionType.filter;
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() {
switch (decisionType) {
case block: return FilterMatchResponse.ABORT_NOT_FOUND;
case allow: return FilterMatchResponse.NO_MATCH;
case filter: return new FilterMatchResponse()
.setDecision(FilterMatchDecision.match)
.setOptions(getOptions())
.setSelectors(getSelectors());
}
return die("getFilterMatchResponse: invalid decisionType: "+decisionType);
}
}

+ 13
- 0
bubble-server/src/main/java/bubble/rule/bblock/spec/BlockDecisionType.java View File

@@ -0,0 +1,13 @@
package bubble.rule.bblock.spec;

import com.fasterxml.jackson.annotation.JsonCreator;

import static bubble.ApiConstants.enumFromString;

public enum BlockDecisionType {

block, allow, filter;

@JsonCreator public static BlockDecisionType fromString (String v) { return enumFromString(BlockDecisionType.class, v); }

}

+ 55
- 0
bubble-server/src/main/java/bubble/rule/bblock/spec/BlockList.java View File

@@ -0,0 +1,55 @@
package bubble.rule.bblock.spec;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

import java.util.*;

@NoArgsConstructor @Accessors(chain=true) @Slf4j
public class BlockList {

@Getter private Set<BlockSpec> blacklist = new HashSet<>();
@Getter private Set<BlockSpec> whitelist = new HashSet<>();

public void addToWhitelist(BlockSpec spec) {
whitelist.add(spec);
}

public void addToWhitelist(List<BlockSpec> specs) {
for (BlockSpec spec : specs) addToWhitelist(spec);
}

public void addToBlacklist(BlockSpec spec) {
blacklist.add(spec);
}

public void addToBlacklist(List<BlockSpec> specs) {
for (BlockSpec spec : specs) addToBlacklist(spec);
}

public void merge(BlockList other) {
for (BlockSpec allow : other.getWhitelist()) {
addToWhitelist(allow);
}
for (BlockSpec block : other.getBlacklist()) {
addToBlacklist(block);
}
}

public BlockDecision getDecision(String fqdn, String path) {
for (BlockSpec allow : whitelist) {
if (allow.matches(fqdn, path)) return BlockDecision.ALLOW;
}
final BlockDecision decision = new BlockDecision();
for (BlockSpec block : blacklist) {
if (block.matches(fqdn, path)) {
if (!block.hasSelector()) return BlockDecision.BLOCK;
decision.add(block);
}
}
return decision;
}

}

+ 55
- 0
bubble-server/src/main/java/bubble/rule/bblock/spec/BlockListSource.java View File

@@ -0,0 +1,55 @@
package bubble.rule.bblock.spec;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.util.http.HttpUtil;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import static org.cobbzilla.util.daemon.ZillaRuntime.*;
import static org.cobbzilla.util.daemon.ZillaRuntime.now;

@NoArgsConstructor @Accessors(chain=true) @Slf4j
public class BlockListSource {

@Getter @Setter private String url;
@Getter @Setter private String format;

@Getter @Setter private Long lastDownloaded;
public long age () { return lastDownloaded == null ? Long.MAX_VALUE : now() - lastDownloaded; }

@Getter @Setter private BlockList blockList = new BlockList();

public BlockListSource download() throws IOException {
try (BufferedReader r = new BufferedReader(new InputStreamReader(HttpUtil.get(url)))) {
String line;
boolean firstLine = true;
while ( (line = r.readLine()) != null ) {
if (empty(line)) continue;
line = line.trim();
if (firstLine && line.startsWith("[") && line.endsWith("]")) {
format = line.substring(1, line.length()-1);
}
firstLine = false;
if (line.startsWith("!")) continue;
try {
if (line.startsWith("@@")) {
blockList.addToWhitelist(BlockSpec.parse(line));
} else {
blockList.addToBlacklist(BlockSpec.parse(line));
}
} catch (Exception e) {
log.warn("download("+url+"): error parsing line (skipping due to "+shortError(e)+"): " + line);
}
}
}
lastDownloaded = now();
return this;
}

}

+ 76
- 0
bubble-server/src/main/java/bubble/rule/bblock/spec/BlockSpec.java View File

@@ -0,0 +1,76 @@
package bubble.rule.bblock.spec;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.cobbzilla.util.string.StringUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import static org.cobbzilla.util.daemon.ZillaRuntime.empty;

@AllArgsConstructor
public class BlockSpec {

@Getter private BlockSpecTarget target;

@Getter private List<String> options;
@Getter(lazy=true) private final Pattern domainPattern = Pattern.compile(target.getDomainRegex());

public boolean hasOptions () { return options != null && !options.isEmpty(); }

@Getter private String selector;
public boolean hasSelector() { return !empty(selector); }

public boolean isBlanket() { return !hasOptions() && !hasSelector(); }

public static List<BlockSpec> parse(String line) {

line = line.trim();
int optionStartPos = line.indexOf('$');
int selectorStartPos = line.indexOf("##");

// sanity check that selectorStartPos > optionStartPos -- $ may occur AFTER ## if the selector contains a regex
if (selectorStartPos != -1 && optionStartPos > selectorStartPos) optionStartPos = -1;

final List<BlockSpecTarget> targets;
final List<String> options;
final String selector;
if (optionStartPos == -1) {
if (selectorStartPos == -1) {
// no options, no selector, entire line is the target
targets = BlockSpecTarget.parse(line);
options = null;
selector = null;
} else {
// no options, but selector present. split into target + selector
targets = BlockSpecTarget.parse(line.substring(0, selectorStartPos));
options = null;
selector = line.substring(selectorStartPos+1);
}
} else {
if (selectorStartPos == -1) {
// no selector, split into target + options
targets = BlockSpecTarget.parse(line.substring(0, optionStartPos));
options = StringUtil.splitAndTrim(line.substring(optionStartPos+1), ",");
selector = null;
} else {
// all 3 elements present
targets = BlockSpecTarget.parse(line.substring(0, optionStartPos));
options = StringUtil.splitAndTrim(line.substring(optionStartPos + 1, selectorStartPos), ",");
selector = line.substring(selectorStartPos+1);
}
}
final List<BlockSpec> specs = new ArrayList<>();
for (BlockSpecTarget target : targets) specs.add(new BlockSpec(target, options, selector));
return specs;
}

public boolean matches(String fqdn, String path) {
if (getDomainPattern().matcher(fqdn).find()) {
return true;
}
return false;
}
}

+ 40
- 0
bubble-server/src/main/java/bubble/rule/bblock/spec/BlockSpecTarget.java View File

@@ -0,0 +1,40 @@
package bubble.rule.bblock.spec;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

@NoArgsConstructor @Accessors(chain=true)
public class BlockSpecTarget {

@Getter @Setter private String domainRegex;
@Getter @Setter private String regex;

public static List<BlockSpecTarget> parse(String data) {
final List<BlockSpecTarget> targets = new ArrayList<>();
for (String part : data.split(",")) {
targets.add(parseTarget(part));
}
return targets;
}

private static BlockSpecTarget parseTarget(String data) {
String domainRegex = null;
String regex = null;
if (data.startsWith("||")) {
final int caretPos = data.indexOf("^");
if (caretPos != -1) {
domainRegex = ".*?"+Pattern.quote(data.substring(2, caretPos))+"$";
} else {

}
}
return new BlockSpecTarget().setDomainRegex(domainRegex).setRegex(regex);
}

}

+ 7
- 7
bubble-server/src/main/java/bubble/service/stream/RuleEngineService.java View File

@@ -9,9 +9,9 @@ import bubble.model.app.AppRule;
import bubble.model.app.RuleDriver; import bubble.model.app.RuleDriver;
import bubble.model.device.Device; import bubble.model.device.Device;
import bubble.resources.stream.FilterHttpRequest; import bubble.resources.stream.FilterHttpRequest;
import bubble.resources.stream.FilterMatchResponse;
import bubble.resources.stream.FilterMatchersRequest; import bubble.resources.stream.FilterMatchersRequest;
import bubble.rule.AppRuleDriver; import bubble.rule.AppRuleDriver;
import bubble.rule.PreprocessDecision;
import bubble.server.BubbleConfiguration; import bubble.server.BubbleConfiguration;
import lombok.Cleanup; import lombok.Cleanup;
import lombok.Getter; import lombok.Getter;
@@ -71,12 +71,12 @@ public class RuleEngineService {
@Autowired private RuleDriverDAO driverDAO; @Autowired private RuleDriverDAO driverDAO;
@Autowired private BubbleConfiguration configuration; @Autowired private BubbleConfiguration configuration;


public PreprocessDecision preprocess(FilterMatchersRequest filter,
Request req,
ContainerRequest request,
Account account,
Device device,
String matcherUuid) {
public FilterMatchResponse preprocess(FilterMatchersRequest filter,
Request req,
ContainerRequest request,
Account account,
Device device,
String matcherUuid) {
final AppRuleHarness ruleHarness = initRules(account, device, new String[]{ matcherUuid }).get(0); final AppRuleHarness ruleHarness = initRules(account, device, new String[]{ matcherUuid }).get(0);
return ruleHarness.getDriver().preprocess(ruleHarness, filter, account, device, req, request); return ruleHarness.getDriver().preprocess(ruleHarness, filter, account, device, req, request);
} }


+ 1
- 1
bubble-server/src/main/resources/models/apps/bblock/bubbleApp_bblock.json View File

@@ -38,7 +38,7 @@
"priority": -1000, "priority": -1000,
"config": { "config": {
"blockLists": [ "blockLists": [
"https://v.firebog.net/hosts/Easylist.txt"
] ]
} }
}], }],


+ 14
- 0
bubble-server/src/test/java/bubble/rule/bblock/spec/BlockListTest.java View File

@@ -0,0 +1,14 @@
package bubble.rule.bblock.spec;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

public class BlockListTest {

@Test public void testBlanketBlock () throws Exception {
final BlockList blockList = new BlockList();
blockList.addToBlacklist(BlockSpec.parse("||fredfiber.no^"));
assertEquals("expected block", BlockDecisionType.block, blockList.getDecision("fredfiber.no", "/somepath").getDecisionType());
}
}

+ 1
- 1
utils/cobbzilla-wizard

@@ -1 +1 @@
Subproject commit 0200670256919c7b11ce19b340c5cfb843551379
Subproject commit 78b8da16659be214013288328f932509ed4c9224

Loading…
Cancel
Save