diff --git a/src/me/topchetoeu/jscript/permissions/Permission.java b/src/me/topchetoeu/jscript/permissions/Permission.java index 0f81dfe..7c8b591 100644 --- a/src/me/topchetoeu/jscript/permissions/Permission.java +++ b/src/me/topchetoeu/jscript/permissions/Permission.java @@ -1,170 +1,124 @@ package me.topchetoeu.jscript.permissions; -import java.util.ArrayList; +import java.util.LinkedList; public class Permission { - public final String[][] segments; + private static class State { + public final int predI, trgI, wildcardI; + public final boolean wildcard; - private boolean matchSeg(String a, String b) { - if (a.contains("**") || b.contains("**")) throw new IllegalArgumentException("A '**' segment may not contain other characters."); - - var segA = a.split("\\*", -1); - var segB = b.split("\\*", -1); - - if (segA.length == 1 || segB.length == 1) { - if (segA.length == 1 && segB.length == 1) return a.equals(b); - else if (segA.length == 1) return matchSeg(b, a); - else { - if (!b.startsWith(segA[0]) || !b.endsWith(segA[segA.length - 1])) return false; - - int end = b.length() - segA[segA.length - 1].length(); - - for (int i = 1, findI = 1; i < segA.length - 1; i++) { - findI = b.indexOf(segA[i], findI); - if (findI < 0 || findI + segA[i].length() > end) return false; - } - - return true; - } + @Override + public String toString() { + return "State [pr=%s;trg=%s;wildN=%s;wild=%s]".formatted(predI, trgI, wildcardI, wildcard); } - String firstA = segA[0], firstB = segB[0]; - String lastA = segA[segA.length - 1], lastB = segB[segB.length - 1]; - - if (!firstA.startsWith(firstB) && !firstB.startsWith(firstA)) return false; - if (!lastA.endsWith(lastB) && !lastB.endsWith(lastA)) return false; - - return true; - } - private boolean matchArrs(String[] a, String[] b, int start, int end) { - if (a.length != end - start) return false; - if (a.length == 0) return true; - if (start >= b.length || end > b.length) return false; - if (start < 0 || end <= 0) return false; - - for (var i = start; i < end; i++) { - if (!matchSeg(a[i - start], b[i])) return false; + public State(int predicateI, int targetI, int wildcardI, boolean wildcard) { + this.predI = predicateI; + this.trgI = targetI; + this.wildcardI = wildcardI; + this.wildcard = wildcard; } - - return true; - } - private boolean matchFull(String[] a, String[] b) { - return matchArrs(a, b, 0, b.length); - } - private boolean matchStart(String[] a, String[] b) { - return matchArrs(a, b, 0, a.length); - } - private boolean matchEnd(String[] a, String[] b) { - return matchArrs(a, b, b.length - a.length, b.length); } - private int find(String[] query, String[] target, int start, int end) { - var findI = 0; + public final String namespace; + public final String value; - if (start < 0) start = 0; - if (query.length == 0) return start; - if (start != 0 && start >= target.length) return -1; - - for (var i = start; i < end; i++) { - if (findI == query.length) return i - findI; - else if (matchSeg(query[findI], target[i])) findI++; - else { - i -= findI; - findI = 0; - } - } - - return -1; + public boolean match(Permission perm) { + if (!Permission.match(namespace, perm.namespace, '.')) return false; + if (value == null || perm.value == null) return true; + return Permission.match(value, perm.value); + } + public boolean match(Permission perm, char delim) { + if (!Permission.match(namespace, perm.namespace, '.')) return false; + if (value == null || perm.value == null) return true; + return Permission.match(value, perm.value, delim); } - public boolean match(Permission other) { - var an = this.segments.length; - var bn = other.segments.length; - - // We must have at least one segment, even if empty - if (an == 0 || bn == 0) throw new IllegalArgumentException("Can't have a permission with 0 segments."); - - if (an == 1 || bn == 1) { - // If both perms are one segment, we just match the segments themselves - if (an == 1 && bn == 1) return matchFull(this.segments[0], other.segments[0]); - else if (an == 1) return other.match(this); - else { - // If just the other perm is one segment, we neet to find all - // the segments of this perm sequentially in the other segment. - - var seg = other.segments[0]; - // Here we check for the prefix and suffix - if (!matchStart(this.segments[0], seg)) return false; - if (!matchEnd(this.segments[this.segments.length - 1], seg)) return false; - - int end = seg.length - this.segments[this.segments.length - 1].length; - - // Here we go and look for the segments one by one, until one isn't found - for (int i = 1, findI = 1; i < this.segments.length - 1; i++) { - findI = find(this.segments[i], seg, findI, end); - if (findI < 0) return false; - } - - return true; - } - } - - // If both perms have more than one segment (a.k.a both have **), - // we can ignore everything in the middle, as it will always match. - // Instead, we check if the prefixes and suffixes match - - var firstA = this.segments[0]; - var firstB = other.segments[0]; - - var lastA = this.segments[this.segments.length - 1]; - var lastB = other.segments[other.segments.length - 1]; - - if (!matchStart(firstA, firstB) && !matchStart(firstB, firstA)) return false; - if (!matchEnd(lastA, lastB) && !matchEnd(lastB, lastA)) return false; - - return true; + public boolean match(String perm) { + return match(new Permission(perm)); + } + public boolean match(String perm, char delim) { + return match(new Permission(perm), delim); } @Override public String toString() { - var sb = new StringBuilder(); - var firstSeg = true; - var firstEl = true; - - for (var seg : segments) { - if (!firstSeg) { - if (!firstEl) sb.append("."); - sb.append("**"); - } - firstSeg = false; - for (var el : seg) { - if (!firstEl) sb.append("."); - sb.append(el); - firstEl = false; - } - } - - return sb.toString(); + if (value != null) return namespace + ":" + value; + else return namespace; } public Permission(String raw) { - var segs = raw.split("\\."); - var curr = new ArrayList(); - var res = new ArrayList(); + var i = raw.indexOf(':'); - for (var seg : segs) { - if (seg.equals("**")) { - res.add(curr.toArray(String[]::new)); - curr.clear(); - } - else curr.add(seg); + if (i > 0) { + value = raw.substring(i + 1); + namespace = raw.substring(0, i); + } + else { + value = null; + namespace = raw; } - res.add(curr.toArray(String[]::new)); - - segments = res.toArray(String[][]::new); } - public static boolean match(String a, String b) { - return new Permission(a).match(new Permission(b)); + public static boolean match(String predicate, String target, char delim) { + if (predicate.equals("")) return target.equals(""); + + var queue = new LinkedList(); + queue.push(new State(0, 0, 0, false)); + + while (!queue.isEmpty()) { + var state = queue.poll(); + var predEnd = state.predI >= predicate.length(); + + if (state.trgI >= target.length()) return predEnd; + var predC = predEnd ? 0 : predicate.charAt(state.predI); + var trgC = target.charAt(state.trgI); + + if (state.wildcard) { + if (state.wildcardI == 2 || trgC != delim) { + queue.add(new State(state.predI, state.trgI + 1, state.wildcardI, true)); + } + queue.add(new State(state.predI, state.trgI, 0, false)); + } + else if (predC == '*') { + queue.add(new State(state.predI + 1, state.trgI, state.wildcardI + 1, false)); + } + else if (state.wildcardI > 0) { + if (state.wildcardI > 2) throw new IllegalArgumentException("Too many sequential stars."); + queue.add(new State(state.predI, state.trgI, state.wildcardI, true)); + } + else if (!predEnd && (predC == '?' || predC == trgC)) { + queue.add(new State(state.predI + 1, state.trgI + 1, 0, false)); + } + } + + return false; + } + public static boolean match(String predicate, String target) { + if (predicate.equals("")) return target.equals(""); + + var queue = new LinkedList(); + queue.push(new State(0, 0, 0, false)); + + while (!queue.isEmpty()) { + var state = queue.poll(); + + if (state.predI >= predicate.length() || state.trgI >= target.length()) { + return state.predI >= predicate.length() && state.trgI >= target.length(); + } + + var predC = predicate.charAt(state.predI); + var trgC = target.charAt(state.trgI); + + if (predC == '*') { + queue.add(new State(state.predI, state.trgI + 1, state.wildcardI, true)); + queue.add(new State(state.predI + 1, state.trgI, 0, false)); + } + else if (predC == '?' || predC == trgC) { + queue.add(new State(state.predI + 1, state.trgI + 1, 0, false)); + } + } + + return false; } } diff --git a/src/me/topchetoeu/jscript/permissions/PermissionsManager.java b/src/me/topchetoeu/jscript/permissions/PermissionsManager.java index 0653795..1505fb8 100644 --- a/src/me/topchetoeu/jscript/permissions/PermissionsManager.java +++ b/src/me/topchetoeu/jscript/permissions/PermissionsManager.java @@ -1,8 +1,39 @@ package me.topchetoeu.jscript.permissions; -public interface PermissionsManager { - public static final PermissionsManager ALL_PERMS = perm -> true; - public static final PermissionsManager NO_PERMS = perm -> false; +import java.util.ArrayList; - boolean hasPermissions(String perm); +public class PermissionsManager { + public static final PermissionsManager ALL_PERMS = new PermissionsManager().add(new Permission("**")); + + public final ArrayList allowed = new ArrayList<>(); + public final ArrayList denied = new ArrayList<>(); + + public PermissionsManager add(Permission perm) { + allowed.add(perm); + return this; + } + public PermissionsManager add(String perm) { + allowed.add(new Permission(perm)); + return this; + } + + public boolean has(Permission perm, char delim) { + for (var el : denied) if (el.match(perm, delim)) return false; + for (var el : allowed) if (el.match(perm, delim)) return true; + + return false; + } + public boolean has(Permission perm) { + for (var el : denied) if (el.match(perm)) return false; + for (var el : allowed) if (el.match(perm)) return true; + + return false; + } + + public boolean has(String perm, char delim) { + return has(new Permission(perm), delim); + } + public boolean has(String perm) { + return has(new Permission(perm)); + } }