From cf99845f6b829d75c1dbf9dcb6c8df32ef34dc2b Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 13 Jan 2024 11:05:43 +0200 Subject: [PATCH] refactor: rework permission system --- .../topchetoeu/jscript/common/Filename.java | 14 +-- .../me/topchetoeu/jscript/lib/ConsoleLib.java | 34 ++--- .../topchetoeu/jscript/utils/LineWriter.java | 7 ++ .../jscript/utils/filesystem/File.java | 102 +++++++++++++++ .../utils/filesystem/RootFilesystem.java | 5 +- .../jscript/utils/filesystem/Stdio.java | 24 ++++ .../jscript/utils/permissions/Matcher.java | 69 ++++++++++ .../jscript/utils/permissions/Permission.java | 119 ++---------------- .../permissions/PermissionPredicate.java | 44 +++++++ .../utils/permissions/PermissionsManager.java | 55 +++++--- .../permissions/PermissionsProvider.java | 28 ++--- 11 files changed, 330 insertions(+), 171 deletions(-) create mode 100644 src/java/me/topchetoeu/jscript/utils/LineWriter.java create mode 100644 src/java/me/topchetoeu/jscript/utils/filesystem/Stdio.java create mode 100644 src/java/me/topchetoeu/jscript/utils/permissions/Matcher.java create mode 100644 src/java/me/topchetoeu/jscript/utils/permissions/PermissionPredicate.java diff --git a/src/java/me/topchetoeu/jscript/common/Filename.java b/src/java/me/topchetoeu/jscript/common/Filename.java index d64f763..7ced6cb 100644 --- a/src/java/me/topchetoeu/jscript/common/Filename.java +++ b/src/java/me/topchetoeu/jscript/common/Filename.java @@ -7,22 +7,17 @@ public class Filename { public final String protocol; public final String path; - public String toString() { + @Override public String toString() { return protocol + "://" + path; } - - @Override - public int hashCode() { + @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + protocol.hashCode(); result = prime * result + path.hashCode(); return result; } - - - @Override - public boolean equals(Object obj) { + @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; @@ -41,9 +36,6 @@ public class Filename { return true; } - - - public Filename(String protocol, String path) { path = path.trim(); protocol = protocol.trim(); diff --git a/src/java/me/topchetoeu/jscript/lib/ConsoleLib.java b/src/java/me/topchetoeu/jscript/lib/ConsoleLib.java index 46a1dda..14bb9d6 100644 --- a/src/java/me/topchetoeu/jscript/lib/ConsoleLib.java +++ b/src/java/me/topchetoeu/jscript/lib/ConsoleLib.java @@ -1,36 +1,38 @@ package me.topchetoeu.jscript.lib; import java.io.IOException; -import java.io.OutputStream; import me.topchetoeu.jscript.core.engine.values.Values; -import me.topchetoeu.jscript.utils.filesystem.FilesystemException; -import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode; +import me.topchetoeu.jscript.utils.filesystem.File; import me.topchetoeu.jscript.utils.interop.Arguments; import me.topchetoeu.jscript.utils.interop.Expose; import me.topchetoeu.jscript.utils.interop.WrapperName; @WrapperName("Console") public class ConsoleLib { - private final OutputStream stream; + public static interface Writer { + void writeLine(String val) throws IOException; + } + + private File file; @Expose public void __log(Arguments args) { - try { - var first = true; - for (var el : args.args) { - if (!first) stream.write(" ".getBytes()); - first = false; - stream.write(Values.toReadable(args.ctx, el).getBytes()); - } - stream.write((byte)'\n'); + var res = new StringBuilder(); + var first = true; + + for (var el : args.args) { + if (!first) res.append(" "); + first = false; + res.append(Values.toReadable(args.ctx, el).getBytes()); } - catch (IOException e) { - throw new FilesystemException("stdout", FSCode.NO_PERMISSIONS_RW); + + for (var line : res.toString().split("\n", -1)) { + file.write(line.getBytes()); } } - public ConsoleLib(OutputStream stream) { - this.stream = stream; + public ConsoleLib(File file) { + this.file = file; } } diff --git a/src/java/me/topchetoeu/jscript/utils/LineWriter.java b/src/java/me/topchetoeu/jscript/utils/LineWriter.java new file mode 100644 index 0000000..5ad2836 --- /dev/null +++ b/src/java/me/topchetoeu/jscript/utils/LineWriter.java @@ -0,0 +1,7 @@ +package me.topchetoeu.jscript.utils; + +import java.io.IOException; + +public interface LineWriter { + void writeLine(String value) throws IOException; +} \ No newline at end of file diff --git a/src/java/me/topchetoeu/jscript/utils/filesystem/File.java b/src/java/me/topchetoeu/jscript/utils/filesystem/File.java index 0542a76..561c93c 100644 --- a/src/java/me/topchetoeu/jscript/utils/filesystem/File.java +++ b/src/java/me/topchetoeu/jscript/utils/filesystem/File.java @@ -1,6 +1,14 @@ package me.topchetoeu.jscript.utils.filesystem; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + import me.topchetoeu.jscript.common.Buffer; +import me.topchetoeu.jscript.utils.LineWriter; +import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode; +import me.topchetoeu.jscript.utils.permissions.Permission; +import me.topchetoeu.jscript.utils.permissions.PermissionsProvider; public interface File { int read(byte[] buff); @@ -8,6 +16,29 @@ public interface File { long seek(long offset, int pos); void close(); + default File wrap(String name, PermissionsProvider perms, Permission read, Permission write, Permission seek, Permission close) { + var self = this; + + return new File() { + @Override public int read(byte[] buff) { + if (read != null && perms.hasPermission(read, name)) return self.read(buff); + else throw new FilesystemException(name, FSCode.NO_PERMISSIONS_R); + } + @Override public void write(byte[] buff) { + if (write != null && perms.hasPermission(write, name)) self.write(buff); + else throw new FilesystemException(name, FSCode.NO_PERMISSIONS_RW); + } + @Override public long seek(long offset, int pos) { + if (seek != null && perms.hasPermission(seek, name)) return self.seek(offset, pos); + else throw new FilesystemException(name, FSCode.NO_PERMISSIONS_R); + } + @Override public void close() { + if (close != null && perms.hasPermission(close, name)) self.close(); + else throw new FilesystemException(name, FSCode.NO_PERMISSIONS_R); + } + }; + } + default String readToString() { long len = seek(0, 2); if (len < 0) return null; @@ -34,4 +65,75 @@ public interface File { } return new String(res.data()); } + + public static File ofStream(String name, InputStream str) { + return new File() { + @Override public int read(byte[] buff) { + try { + return str.read(buff); + } + catch (IOException e) { + throw new FilesystemException(name, FSCode.NO_PERMISSIONS_R); + } + } + @Override public void write(byte[] buff) { + throw new FilesystemException(name, FSCode.NO_PERMISSIONS_RW); + } + @Override public long seek(long offset, int pos) { + throw new FilesystemException(name, FSCode.UNSUPPORTED_OPERATION); + } + @Override public void close() { + throw new FilesystemException(name, FSCode.UNSUPPORTED_OPERATION); + } + }; + } + public static File ofStream(String name, OutputStream str) { + return new File() { + @Override public int read(byte[] buff) { + throw new FilesystemException(name, FSCode.NO_PERMISSIONS_R); + } + @Override public void write(byte[] buff) { + try { + str.write(buff); + } + catch (IOException e) { + throw new FilesystemException(name, FSCode.NO_PERMISSIONS_RW); + } + } + @Override public long seek(long offset, int pos) { + throw new FilesystemException(name, FSCode.UNSUPPORTED_OPERATION); + } + @Override public void close() { + throw new FilesystemException(name, FSCode.UNSUPPORTED_OPERATION); + } + }; + } + public static File ofLineWriter(String name, LineWriter writer) { + var buff = new Buffer(); + + return new File() { + @Override public int read(byte[] buff) { + throw new FilesystemException(name, FSCode.NO_PERMISSIONS_R); + } + @Override public void write(byte[] val) { + for (var b : val) { + if (b == '\n') { + try { + writer.writeLine(new String(buff.data())); + } + catch (IOException e) { + throw new FilesystemException(name, FSCode.NO_PERMISSIONS_RW); + } + } + else buff.append(b); + } + } + @Override public long seek(long offset, int pos) { + throw new FilesystemException(name, FSCode.UNSUPPORTED_OPERATION); + } + @Override public void close() { + throw new FilesystemException(name, FSCode.UNSUPPORTED_OPERATION); + } + }; + } } \ No newline at end of file diff --git a/src/java/me/topchetoeu/jscript/utils/filesystem/RootFilesystem.java b/src/java/me/topchetoeu/jscript/utils/filesystem/RootFilesystem.java index 68a39d4..38386a9 100644 --- a/src/java/me/topchetoeu/jscript/utils/filesystem/RootFilesystem.java +++ b/src/java/me/topchetoeu/jscript/utils/filesystem/RootFilesystem.java @@ -5,6 +5,7 @@ import java.util.Map; import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode; +import me.topchetoeu.jscript.utils.permissions.Matcher; import me.topchetoeu.jscript.utils.permissions.PermissionsProvider; public class RootFilesystem implements Filesystem { @@ -12,10 +13,10 @@ public class RootFilesystem implements Filesystem { public final PermissionsProvider perms; private boolean canRead(String _path) { - return perms.hasPermission("jscript.file.read:" + _path, '/'); + return perms.hasPermission("jscript.file.read:" + _path, Matcher.fileWildcard()); } private boolean canWrite(String _path) { - return perms.hasPermission("jscript.file.write:" + _path, '/'); + return perms.hasPermission("jscript.file.write:" + _path, Matcher.fileWildcard()); } private void modeAllowed(String _path, Mode mode) throws FilesystemException { diff --git a/src/java/me/topchetoeu/jscript/utils/filesystem/Stdio.java b/src/java/me/topchetoeu/jscript/utils/filesystem/Stdio.java new file mode 100644 index 0000000..1407c31 --- /dev/null +++ b/src/java/me/topchetoeu/jscript/utils/filesystem/Stdio.java @@ -0,0 +1,24 @@ +package me.topchetoeu.jscript.utils.filesystem; + +import me.topchetoeu.jscript.core.engine.Extensions; +import me.topchetoeu.jscript.core.engine.values.Symbol; +import me.topchetoeu.jscript.core.exceptions.EngineException; + +public class Stdio { + public static final Symbol STDIN = Symbol.get("IO.stdin"); + public static final Symbol STDOUT = Symbol.get("IO.stdout"); + public static final Symbol STDERR = Symbol.get("IO.stderr"); + + public static File stdout(Extensions exts) { + if (exts.hasNotNull(STDOUT)) return exts.get(STDOUT); + else throw EngineException.ofError("stdout is not supported."); + } + public static File stdin(Extensions exts) { + if (exts.hasNotNull(STDIN)) return exts.get(STDIN); + else throw EngineException.ofError("stdin is not supported."); + } + public static File stderr(Extensions exts) { + if (exts.hasNotNull(STDERR)) return exts.get(STDERR); + else throw EngineException.ofError("stderr is not supported."); + } +} diff --git a/src/java/me/topchetoeu/jscript/utils/permissions/Matcher.java b/src/java/me/topchetoeu/jscript/utils/permissions/Matcher.java new file mode 100644 index 0000000..fc1138c --- /dev/null +++ b/src/java/me/topchetoeu/jscript/utils/permissions/Matcher.java @@ -0,0 +1,69 @@ +package me.topchetoeu.jscript.utils.permissions; + +import java.util.LinkedList; + +public interface Matcher { + static class State { + public final int predI, trgI, wildcardI; + public final boolean wildcard; + + @Override + public String toString() { + return String.format("State [pr=%s;trg=%s;wildN=%s;wild=%s]", predI, trgI, wildcardI, wildcard); + } + + public State(int predicateI, int targetI, int wildcardI, boolean wildcard) { + this.predI = predicateI; + this.trgI = targetI; + this.wildcardI = wildcardI; + this.wildcard = wildcard; + } + } + + boolean match(String predicate, String value); + + public static Matcher fileWildcard() { + return (predicate, value) -> execWildcard(predicate, value, '/'); + } + public static Matcher namespaceWildcard() { + return (predicate, value) -> execWildcard(predicate, value, '.'); + } + public static Matcher wildcard() { + return (predicate, value) -> execWildcard(predicate, value, '\0'); + } + + public static boolean execWildcard(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; + } +} diff --git a/src/java/me/topchetoeu/jscript/utils/permissions/Permission.java b/src/java/me/topchetoeu/jscript/utils/permissions/Permission.java index 3089558..f120500 100644 --- a/src/java/me/topchetoeu/jscript/utils/permissions/Permission.java +++ b/src/java/me/topchetoeu/jscript/utils/permissions/Permission.java @@ -1,124 +1,19 @@ package me.topchetoeu.jscript.utils.permissions; -import java.util.LinkedList; public class Permission { - private static class State { - public final int predI, trgI, wildcardI; - public final boolean wildcard; - - @Override - public String toString() { - return String.format("State [pr=%s;trg=%s;wildN=%s;wild=%s]", predI, trgI, wildcardI, wildcard); - } - - public State(int predicateI, int targetI, int wildcardI, boolean wildcard) { - this.predI = predicateI; - this.trgI = targetI; - this.wildcardI = wildcardI; - this.wildcard = wildcard; - } - } - public final String namespace; - public final String value; + public final Matcher matcher; - 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); + @Override public String toString() { + return namespace; } - public boolean match(String perm) { - return match(new Permission(perm)); + public Permission(String namespace, Matcher matcher) { + this.namespace = namespace; + this.matcher = matcher; } - public boolean match(String perm, char delim) { - return match(new Permission(perm), delim); - } - - @Override - public String toString() { - if (value != null) return namespace + ":" + value; - else return namespace; - } - public Permission(String raw) { - var i = raw.indexOf(':'); - - if (i > 0) { - value = raw.substring(i + 1); - namespace = raw.substring(0, i); - } - else { - value = null; - namespace = raw; - } - } - - 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; + this(raw, null); } } diff --git a/src/java/me/topchetoeu/jscript/utils/permissions/PermissionPredicate.java b/src/java/me/topchetoeu/jscript/utils/permissions/PermissionPredicate.java new file mode 100644 index 0000000..5bedf8d --- /dev/null +++ b/src/java/me/topchetoeu/jscript/utils/permissions/PermissionPredicate.java @@ -0,0 +1,44 @@ +package me.topchetoeu.jscript.utils.permissions; + +public class PermissionPredicate { + public final String namespace; + public final String value; + public final boolean denies; + + public boolean match(Permission permission, String value) { + if (!match(permission)) return false; + if (this.value == null || value == null) return true; + if (permission.matcher == null) return true; + else return permission.matcher.match(this.value, value); + } + public boolean match(Permission permission) { + return Matcher.namespaceWildcard().match(namespace, permission.namespace); + } + + @Override + public String toString() { + if (value != null) return namespace + ":" + value; + else return namespace; + } + + public PermissionPredicate(String raw) { + raw = raw.trim(); + + if (raw.startsWith("!")) { + denies = true; + raw = raw.substring(1).trim(); + } + else denies = false; + + var i = raw.indexOf(':'); + + if (i > 0) { + value = raw.substring(i + 1); + namespace = raw.substring(0, i); + } + else { + value = null; + namespace = raw; + } + } +} diff --git a/src/java/me/topchetoeu/jscript/utils/permissions/PermissionsManager.java b/src/java/me/topchetoeu/jscript/utils/permissions/PermissionsManager.java index ad512e8..72468cc 100644 --- a/src/java/me/topchetoeu/jscript/utils/permissions/PermissionsManager.java +++ b/src/java/me/topchetoeu/jscript/utils/permissions/PermissionsManager.java @@ -1,34 +1,59 @@ package me.topchetoeu.jscript.utils.permissions; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.util.ArrayList; public class PermissionsManager implements PermissionsProvider { - public static final PermissionsProvider ALL_PERMS = new PermissionsManager().add(new Permission("**")); + public final ArrayList predicates = new ArrayList<>(); - public final ArrayList allowed = new ArrayList<>(); - public final ArrayList denied = new ArrayList<>(); - - public PermissionsProvider add(Permission perm) { - allowed.add(perm); + public PermissionsProvider add(PermissionPredicate perm) { + predicates.add(perm); return this; } public PermissionsProvider add(String perm) { - allowed.add(new Permission(perm)); + predicates.add(new PermissionPredicate(perm)); return this; } - @Override - public boolean hasPermission(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; + @Override public boolean hasPermission(Permission perm, String value) { + for (var el : predicates) { + if (el.match(perm, value)) { + if (el.denies) return false; + else return true; + } + } return false; } - @Override - public boolean hasPermission(Permission perm) { - for (var el : denied) if (el.match(perm)) return false; - for (var el : allowed) if (el.match(perm)) return true; + @Override public boolean hasPermission(Permission perm) { + for (var el : predicates) { + if (el.match(perm)) { + if (el.denies) return false; + else return true; + } + } return false; } + + public PermissionsProvider addFromStream(InputStream stream) throws IOException { + var reader = new BufferedReader(new InputStreamReader(stream)); + String line; + + while ((line = reader.readLine()) != null) { + var i = line.indexOf('#'); + if (i >= 0) line = line.substring(0, i); + + line = line.trim(); + + if (line.isEmpty()) continue; + + add(line); + } + + return this; + } } diff --git a/src/java/me/topchetoeu/jscript/utils/permissions/PermissionsProvider.java b/src/java/me/topchetoeu/jscript/utils/permissions/PermissionsProvider.java index 2c12d8a..4a5ea60 100644 --- a/src/java/me/topchetoeu/jscript/utils/permissions/PermissionsProvider.java +++ b/src/java/me/topchetoeu/jscript/utils/permissions/PermissionsProvider.java @@ -5,27 +5,25 @@ import me.topchetoeu.jscript.core.engine.values.Symbol; public interface PermissionsProvider { public static final Symbol ENV_KEY = new Symbol("Environment.perms"); + public static final PermissionsProvider ALL_PERMS = (perm, value) -> true; - boolean hasPermission(Permission perm, char delim); - boolean hasPermission(Permission perm); + boolean hasPermission(Permission perm, String value); - default boolean hasPermission(String perm, char delim) { - return hasPermission(new Permission(perm), delim); + default boolean hasPermission(Permission perm) { + return hasPermission(perm, null); } - default boolean hasPermission(String perm) { - return hasPermission(new Permission(perm)); + + default boolean hasPermission(String perm, String value, Matcher matcher) { + return hasPermission(new Permission(perm, matcher), value); + } + default boolean hasPermission(String perm, Matcher matcher) { + return hasPermission(new Permission(perm, matcher)); } public static PermissionsProvider get(Extensions exts) { - return new PermissionsProvider() { - @Override public boolean hasPermission(Permission perm) { - if (exts.hasNotNull(ENV_KEY)) return ((PermissionsProvider)exts.get(ENV_KEY)).hasPermission(perm); - else return true; - } - @Override public boolean hasPermission(Permission perm, char delim) { - if (exts.hasNotNull(ENV_KEY)) return ((PermissionsProvider)exts.get(ENV_KEY)).hasPermission(perm, delim); - else return true; - } + return (perm, value) -> { + if (exts.hasNotNull(ENV_KEY)) return ((PermissionsProvider)exts.get(ENV_KEY)).hasPermission(perm); + else return true; }; } } \ No newline at end of file