diff --git a/bubble-server/src/main/java/bubble/resources/stream/FilterHttpResource.java b/bubble-server/src/main/java/bubble/resources/stream/FilterHttpResource.java index 7a5fa46e..41478bd5 100644 --- a/bubble-server/src/main/java/bubble/resources/stream/FilterHttpResource.java +++ b/bubble-server/src/main/java/bubble/resources/stream/FilterHttpResource.java @@ -131,6 +131,8 @@ public class FilterHttpResource { final List matchers = matcherDAO.findByAccountAndFqdnAndEnabled(accountUuid, fqdn); if (log.isDebugEnabled()) log.debug("findMatchers: found "+matchers.size()+" candidate matchers"); final List removeMatchers; + List options = null; + List selectors = null; if (matchers.isEmpty()) { removeMatchers = Collections.emptyList(); } else { @@ -138,12 +140,23 @@ public class FilterHttpResource { removeMatchers = new ArrayList<>(); for (AppMatcher matcher : matchers) { 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: removeMatchers.add(matcher); 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); 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}") diff --git a/bubble-server/src/main/java/bubble/resources/stream/FilterMatchResponse.java b/bubble-server/src/main/java/bubble/resources/stream/FilterMatchResponse.java new file mode 100644 index 00000000..b8753265 --- /dev/null +++ b/bubble-server/src/main/java/bubble/resources/stream/FilterMatchResponse.java @@ -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 options; + public boolean hasOptions () { return options != null && !options.isEmpty(); } + + @Getter @Setter private List selectors; + public boolean hasSelectors () { return selectors != null && !selectors.isEmpty(); } + +} diff --git a/bubble-server/src/main/java/bubble/resources/stream/FilterMatchersResponse.java b/bubble-server/src/main/java/bubble/resources/stream/FilterMatchersResponse.java index 6041a272..f3dff5be 100644 --- a/bubble-server/src/main/java/bubble/resources/stream/FilterMatchersResponse.java +++ b/bubble-server/src/main/java/bubble/resources/stream/FilterMatchersResponse.java @@ -16,5 +16,7 @@ public class FilterMatchersResponse { @Getter @Setter private Integer abort; @Getter @Setter private String device; @Getter @Setter private List matchers; + @Getter @Setter private List options; + @Getter @Setter private List selectors; } diff --git a/bubble-server/src/main/java/bubble/rule/AppRuleDriver.java b/bubble-server/src/main/java/bubble/rule/AppRuleDriver.java index 6d0385ae..af2ffb99 100644 --- a/bubble-server/src/main/java/bubble/rule/AppRuleDriver.java +++ b/bubble-server/src/main/java/bubble/rule/AppRuleDriver.java @@ -4,6 +4,7 @@ import bubble.model.account.Account; import bubble.model.app.AppMatcher; import bubble.model.app.AppRule; import bubble.model.device.Device; +import bubble.resources.stream.FilterMatchResponse; import bubble.service.stream.AppRuleHarness; import bubble.resources.stream.FilterMatchersRequest; import com.fasterxml.jackson.databind.JsonNode; @@ -32,13 +33,13 @@ public interface AppRuleDriver { Account account, 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) { diff --git a/bubble-server/src/main/java/bubble/rule/PreprocessDecision.java b/bubble-server/src/main/java/bubble/rule/FilterMatchDecision.java similarity index 74% rename from bubble-server/src/main/java/bubble/rule/PreprocessDecision.java rename to bubble-server/src/main/java/bubble/rule/FilterMatchDecision.java index 9c0afd9f..d233830e 100644 --- a/bubble-server/src/main/java/bubble/rule/PreprocessDecision.java +++ b/bubble-server/src/main/java/bubble/rule/FilterMatchDecision.java @@ -4,13 +4,13 @@ import com.fasterxml.jackson.annotation.JsonCreator; import static bubble.ApiConstants.enumFromString; -public enum PreprocessDecision { +public enum FilterMatchDecision { no_match, // associated matcher should not 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_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); } } diff --git a/bubble-server/src/main/java/bubble/rule/analytics/TrafficAnalytics.java b/bubble-server/src/main/java/bubble/rule/analytics/TrafficAnalytics.java index e628f621..cb7b8e61 100644 --- a/bubble-server/src/main/java/bubble/rule/analytics/TrafficAnalytics.java +++ b/bubble-server/src/main/java/bubble/rule/analytics/TrafficAnalytics.java @@ -3,9 +3,9 @@ package bubble.rule.analytics; import bubble.model.account.Account; import bubble.model.app.AppData; import bubble.model.device.Device; +import bubble.resources.stream.FilterMatchResponse; import bubble.resources.stream.FilterMatchersRequest; import bubble.rule.AbstractAppRuleDriver; -import bubble.rule.PreprocessDecision; import bubble.service.stream.AppRuleHarness; import lombok.Getter; 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); - @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 site = ruleHarness.getMatcher().getSite(); 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); 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.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 diff --git a/bubble-server/src/main/java/bubble/rule/bblock/BubbleBlock.java b/bubble-server/src/main/java/bubble/rule/bblock/BubbleBlock.java index 2db00313..3a669096 100644 --- a/bubble-server/src/main/java/bubble/rule/bblock/BubbleBlock.java +++ b/bubble-server/src/main/java/bubble/rule/bblock/BubbleBlock.java @@ -1,39 +1,87 @@ package bubble.rule.bblock; import bubble.model.account.Account; +import bubble.model.app.AppMatcher; +import bubble.model.app.AppRule; import bubble.model.device.Device; +import bubble.resources.stream.FilterMatchResponse; import bubble.resources.stream.FilterMatchersRequest; -import bubble.rule.PreprocessDecision; 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 com.fasterxml.jackson.databind.JsonNode; +import lombok.extern.slf4j.Slf4j; import org.glassfish.grizzly.http.server.Request; 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.shortError; +import static org.cobbzilla.util.json.JsonUtil.json; import static org.cobbzilla.util.time.TimeUtil.DATE_FORMAT_YYYY_MM_DD_HH; +@Slf4j 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 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 site = ruleHarness.getMatcher().getSite(); 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; } - } diff --git a/bubble-server/src/main/java/bubble/rule/bblock/BubbleBlockConfig.java b/bubble-server/src/main/java/bubble/rule/bblock/BubbleBlockConfig.java new file mode 100644 index 00000000..f50bd89a --- /dev/null +++ b/bubble-server/src/main/java/bubble/rule/bblock/BubbleBlockConfig.java @@ -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; + +} diff --git a/bubble-server/src/main/java/bubble/rule/bblock/spec/BlockDecision.java b/bubble-server/src/main/java/bubble/rule/bblock/spec/BlockDecision.java new file mode 100644 index 00000000..179b9977 --- /dev/null +++ b/bubble-server/src/main/java/bubble/rule/bblock/spec/BlockDecision.java @@ -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 selectors; + @Getter @Setter List 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); + } +} diff --git a/bubble-server/src/main/java/bubble/rule/bblock/spec/BlockDecisionType.java b/bubble-server/src/main/java/bubble/rule/bblock/spec/BlockDecisionType.java new file mode 100644 index 00000000..d0b7f642 --- /dev/null +++ b/bubble-server/src/main/java/bubble/rule/bblock/spec/BlockDecisionType.java @@ -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); } + +} diff --git a/bubble-server/src/main/java/bubble/rule/bblock/spec/BlockList.java b/bubble-server/src/main/java/bubble/rule/bblock/spec/BlockList.java new file mode 100644 index 00000000..7ea23a36 --- /dev/null +++ b/bubble-server/src/main/java/bubble/rule/bblock/spec/BlockList.java @@ -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 blacklist = new HashSet<>(); + @Getter private Set whitelist = new HashSet<>(); + + public void addToWhitelist(BlockSpec spec) { + whitelist.add(spec); + } + + public void addToWhitelist(List specs) { + for (BlockSpec spec : specs) addToWhitelist(spec); + } + + public void addToBlacklist(BlockSpec spec) { + blacklist.add(spec); + } + + public void addToBlacklist(List 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; + } + +} diff --git a/bubble-server/src/main/java/bubble/rule/bblock/spec/BlockListSource.java b/bubble-server/src/main/java/bubble/rule/bblock/spec/BlockListSource.java new file mode 100644 index 00000000..8610e64c --- /dev/null +++ b/bubble-server/src/main/java/bubble/rule/bblock/spec/BlockListSource.java @@ -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; + } + +} diff --git a/bubble-server/src/main/java/bubble/rule/bblock/spec/BlockSpec.java b/bubble-server/src/main/java/bubble/rule/bblock/spec/BlockSpec.java new file mode 100644 index 00000000..b4118da4 --- /dev/null +++ b/bubble-server/src/main/java/bubble/rule/bblock/spec/BlockSpec.java @@ -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 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 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 targets; + final List 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 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; + } +} diff --git a/bubble-server/src/main/java/bubble/rule/bblock/spec/BlockSpecTarget.java b/bubble-server/src/main/java/bubble/rule/bblock/spec/BlockSpecTarget.java new file mode 100644 index 00000000..280b5242 --- /dev/null +++ b/bubble-server/src/main/java/bubble/rule/bblock/spec/BlockSpecTarget.java @@ -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 parse(String data) { + final List 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); + } + +} diff --git a/bubble-server/src/main/java/bubble/service/stream/RuleEngineService.java b/bubble-server/src/main/java/bubble/service/stream/RuleEngineService.java index 22e284ec..cdcb5ef9 100644 --- a/bubble-server/src/main/java/bubble/service/stream/RuleEngineService.java +++ b/bubble-server/src/main/java/bubble/service/stream/RuleEngineService.java @@ -9,9 +9,9 @@ import bubble.model.app.AppRule; import bubble.model.app.RuleDriver; import bubble.model.device.Device; import bubble.resources.stream.FilterHttpRequest; +import bubble.resources.stream.FilterMatchResponse; import bubble.resources.stream.FilterMatchersRequest; import bubble.rule.AppRuleDriver; -import bubble.rule.PreprocessDecision; import bubble.server.BubbleConfiguration; import lombok.Cleanup; import lombok.Getter; @@ -71,12 +71,12 @@ public class RuleEngineService { @Autowired private RuleDriverDAO driverDAO; @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); return ruleHarness.getDriver().preprocess(ruleHarness, filter, account, device, req, request); } diff --git a/bubble-server/src/main/resources/models/apps/bblock/bubbleApp_bblock.json b/bubble-server/src/main/resources/models/apps/bblock/bubbleApp_bblock.json index 65a35803..65b230e0 100644 --- a/bubble-server/src/main/resources/models/apps/bblock/bubbleApp_bblock.json +++ b/bubble-server/src/main/resources/models/apps/bblock/bubbleApp_bblock.json @@ -38,7 +38,7 @@ "priority": -1000, "config": { "blockLists": [ - + "https://v.firebog.net/hosts/Easylist.txt" ] } }], diff --git a/bubble-server/src/test/java/bubble/rule/bblock/spec/BlockListTest.java b/bubble-server/src/test/java/bubble/rule/bblock/spec/BlockListTest.java new file mode 100644 index 00000000..aa3cdca5 --- /dev/null +++ b/bubble-server/src/test/java/bubble/rule/bblock/spec/BlockListTest.java @@ -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()); + } +} diff --git a/utils/cobbzilla-wizard b/utils/cobbzilla-wizard index 02006702..78b8da16 160000 --- a/utils/cobbzilla-wizard +++ b/utils/cobbzilla-wizard @@ -1 +1 @@ -Subproject commit 0200670256919c7b11ce19b340c5cfb843551379 +Subproject commit 78b8da16659be214013288328f932509ed4c9224