Compare commits

...

7 Commits

Author SHA1 Message Date
285960bdd6 refactor: rework fs error system 2024-02-09 13:46:57 +02:00
cf99845f6b refactor: rework permission system 2024-01-13 11:05:43 +02:00
48bd1e2015 bump 2024-01-12 09:54:32 +02:00
304665904f feat: extract log API to console 2024-01-12 09:53:56 +02:00
56ae3a85a6 build: improve build scripts 2024-01-12 09:48:20 +02:00
0178cb2194 build: specify java toolchain 2024-01-11 11:46:51 +02:00
a2cb5cd473 build: add gradle wrapper props 2024-01-11 11:46:41 +02:00
35 changed files with 882 additions and 536 deletions

4
.gitignore vendored
View File

@@ -14,9 +14,11 @@
!/.gitignore
!/.gitattributes
!/build.js
!/LICENSE
!/README.md
!/settings.gradle
!/build.gradle
!/gradle.properties
!/gradle
!/gradle/wrapper
!/gradle/wrapper/gradle-wrapper.properties

View File

@@ -1,37 +1,32 @@
plugins {
id 'application'
id "application"
}
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
toolchain.languageVersion = JavaLanguageVersion.of(11)
withSourcesJar()
}
jar {
manifest {
attributes (
'Main-Class': project.main_class
)
}
manifest.attributes["Main-class"] = project.main_class
}
sourceSets {
main.java.srcDirs = [ 'src/java' ]
main.resources.srcDirs = [ 'src/assets' ]
main.java.srcDirs = [ "src/java" ]
main.resources.srcDirs = [ "src/assets" ]
}
processResources {
filesMatching('metadata.json') {
expand (
'version': project.project_version,
'name': project.project_name
filesMatching "metadata.json", {
expand(
version: project.project_version,
name: project.project_name
)
}
}
application {
mainClass = project.main_class
}
archivesBaseName = project.project_name
version = project.project_version
base.archivesName = project.project_name
version = project.project_version
group = project.project_group

View File

@@ -1,3 +1,4 @@
project_group = me.topchetoeu
project_name = jscript
project_version = 0.8.4-beta
project_version = 0.8.6-beta
main_class = me.topchetoeu.jscript.utils.JScriptRepl

View File

@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -0,0 +1,5 @@
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0'
}
rootProject.name = properties.project_name

View File

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

View File

@@ -711,14 +711,14 @@ public class Values {
res.append("{\n");
for (var el : obj.values.entrySet()) {
for (int i = 0; i < tab + 1; i++) System.out.print(" ");
for (int i = 0; i < tab + 1; i++) res.append(" ");
res.append(toReadable(ctx, el.getKey(), passed, tab + 1));
res.append(": ");
res.append(toReadable(ctx, el.getValue(), passed, tab + 1));
res.append(",\n");
}
for (var el : obj.properties.entrySet()) {
for (int i = 0; i < tab + 1; i++) System.out.print(" ");
for (int i = 0; i < tab + 1; i++) res.append(" ");
res.append(toReadable(ctx, el.getKey(), passed, tab + 1));
res.append(": [prop],\n");
}

View File

@@ -0,0 +1,38 @@
package me.topchetoeu.jscript.lib;
import java.io.IOException;
import me.topchetoeu.jscript.core.engine.values.Values;
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 {
public static interface Writer {
void writeLine(String val) throws IOException;
}
private File file;
@Expose
public void __log(Arguments args) {
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());
}
for (var line : res.toString().split("\n", -1)) {
file.write(line.getBytes());
}
}
public ConsoleLib(File file) {
this.file = file;
}
}

View File

@@ -10,12 +10,12 @@ import me.topchetoeu.jscript.utils.interop.WrapperName;
@WrapperName("File")
public class FileLib {
public final File file;
public final File fd;
@Expose public PromiseLib __pointer(Arguments args) {
return PromiseLib.await(args.ctx, () -> {
try {
return file.seek(0, 1);
return fd.seek(0, 1);
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
@@ -23,9 +23,9 @@ public class FileLib {
@Expose public PromiseLib __length(Arguments args) {
return PromiseLib.await(args.ctx, () -> {
try {
long curr = file.seek(0, 1);
long res = file.seek(0, 2);
file.seek(curr, 0);
long curr = fd.seek(0, 1);
long res = fd.seek(0, 2);
fd.seek(curr, 0);
return res;
}
catch (FilesystemException e) { throw e.toEngineException(); }
@@ -38,8 +38,8 @@ public class FileLib {
try {
var buff = new byte[n];
var res = new ArrayValue();
int resI = file.read(buff);
int resI = fd.read(buff);
for (var i = resI - 1; i >= 0; i--) res.set(args.ctx, i, (int)buff[i]);
return res;
}
@@ -53,7 +53,7 @@ public class FileLib {
var res = new byte[val.size()];
for (var i = 0; i < val.size(); i++) res[i] = (byte)Values.toNumber(args.ctx, val.get(i));
file.write(res);
fd.write(res);
return null;
}
@@ -62,7 +62,7 @@ public class FileLib {
}
@Expose public PromiseLib __close(Arguments args) {
return PromiseLib.await(args.ctx, () -> {
file.close();
fd.close();
return null;
});
}
@@ -72,13 +72,13 @@ public class FileLib {
var whence = args.getInt(1);
try {
return file.seek(ptr, whence);
return fd.seek(ptr, whence);
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
}
public FileLib(File file) {
this.file = file;
public FileLib(File fd) {
this.fd = fd;
}
}

View File

@@ -8,13 +8,14 @@ import me.topchetoeu.jscript.core.engine.Context;
import me.topchetoeu.jscript.core.engine.values.ObjectValue;
import me.topchetoeu.jscript.core.engine.values.Values;
import me.topchetoeu.jscript.core.exceptions.EngineException;
import me.topchetoeu.jscript.utils.filesystem.ActionType;
import me.topchetoeu.jscript.utils.filesystem.EntryType;
import me.topchetoeu.jscript.utils.filesystem.ErrorReason;
import me.topchetoeu.jscript.utils.filesystem.File;
import me.topchetoeu.jscript.utils.filesystem.FileStat;
import me.topchetoeu.jscript.utils.filesystem.Filesystem;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException;
import me.topchetoeu.jscript.utils.filesystem.Mode;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
import me.topchetoeu.jscript.utils.interop.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeField;
@@ -50,11 +51,10 @@ public class FilesystemLib {
try {
if (fs.stat(path).type != EntryType.FILE) {
throw new FilesystemException(path, FSCode.NOT_FILE);
throw new FilesystemException(ErrorReason.DOESNT_EXIST, "Not a file").setAction(ActionType.OPEN);
}
var file = fs.open(path, _mode);
return new FileLib(file);
return new FileLib(fs.open(path, _mode));
}
catch (FilesystemException e) { throw e.toEngineException(); }
});
@@ -75,7 +75,7 @@ public class FilesystemLib {
var path = fs.normalize(args.getString(0));
if (fs.stat(path).type != EntryType.FOLDER) {
throw new FilesystemException(path, FSCode.NOT_FOLDER);
throw new FilesystemException(ErrorReason.DOESNT_EXIST, "Not a directory").setAction(ActionType.OPEN);
}
file = fs.open(path, Mode.READ);

View File

@@ -1,9 +1,7 @@
package me.topchetoeu.jscript.lib;
import java.io.IOException;
import java.util.HashMap;
import me.topchetoeu.jscript.common.Reading;
import me.topchetoeu.jscript.core.engine.Context;
import me.topchetoeu.jscript.core.engine.Environment;
import me.topchetoeu.jscript.core.engine.scope.GlobalScope;
@@ -34,27 +32,6 @@ public class Internals {
else throw EngineException.ofError("Modules are not supported.");
}
@Expose(target = ExposeTarget.STATIC)
public static Object __log(Arguments args) {
for (var arg : args.args) {
Values.printValue(args.ctx, arg);
System.out.print(" ");
}
System.out.println();
return args.get(0);
}
@Expose(target = ExposeTarget.STATIC)
public static String __readline() {
try {
return Reading.readline();
}
catch (IOException e) {
e.printStackTrace();
return null;
}
}
@Expose(target = ExposeTarget.STATIC)
public static Thread __setTimeout(Arguments args) {
var func = args.convert(0, FunctionValue.class);
@@ -175,6 +152,8 @@ public class Internals {
glob.define(null, "Encoding", false, wp.getNamespace(EncodingLib.class));
glob.define(null, "Filesystem", false, wp.getNamespace(FilesystemLib.class));
glob.define(false, wp.getConstr(FileLib.class));
glob.define(false, wp.getConstr(DateLib.class));
glob.define(false, wp.getConstr(ObjectLib.class));
glob.define(false, wp.getConstr(FunctionLib.class));

View File

@@ -24,6 +24,7 @@ import me.topchetoeu.jscript.utils.filesystem.MemoryFilesystem;
import me.topchetoeu.jscript.utils.filesystem.Mode;
import me.topchetoeu.jscript.utils.filesystem.PhysicalFilesystem;
import me.topchetoeu.jscript.utils.filesystem.RootFilesystem;
import me.topchetoeu.jscript.utils.filesystem.STDFilesystem;
import me.topchetoeu.jscript.utils.modules.ModuleRepo;
import me.topchetoeu.jscript.utils.permissions.PermissionsManager;
import me.topchetoeu.jscript.utils.permissions.PermissionsProvider;
@@ -108,6 +109,7 @@ public class JScriptRepl {
var fs = new RootFilesystem(PermissionsProvider.get(environment));
fs.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE));
fs.protocols.put("file", new PhysicalFilesystem("."));
fs.protocols.put("std", STDFilesystem.ofStd(System.in, System.out, System.err));
environment.add(PermissionsProvider.ENV_KEY, PermissionsManager.ALL_PERMS);
environment.add(Filesystem.ENV_KEY, fs);

View File

@@ -0,0 +1,28 @@
package me.topchetoeu.jscript.utils.filesystem;
public enum ActionType {
UNKNOWN(0, "An operation performed upon", "An operation was performed upon"),
READ(1, "Reading from", "Read from"),
WRITE(2, "Writting to", "Wrote to"),
SEEK(3, "Seeking in", "Sought in"),
CLOSE(4, "Closing", "Closed"),
STAT(5, "Stat of", "Statted"),
OPEN(6, "Opening", "Opened"),
CREATE(7, "Creating", "Created"),
DELETE(8, "Deleting", "Deleted"),
CLOSE_FS(9, "Closing filesystem", "Closed filesystem");
public final int code;
public final String continuous, past;
public String readable(boolean usePast) {
if (usePast) return past;
else return continuous;
}
private ActionType(int code, String continuous, String past) {
this.code = code;
this.continuous = continuous;
this.past = past;
}
}

View File

@@ -0,0 +1,59 @@
package me.topchetoeu.jscript.utils.filesystem;
public abstract class BaseFile<T> implements File {
private T handle;
private Mode mode;
protected final T handle() {
return handle;
}
protected abstract int onRead(byte[] buff);
protected abstract void onWrite(byte[] buff);
protected abstract long onSeek(long offset, int pos);
protected abstract boolean onClose();
@Override public int read(byte[] buff) {
try {
if (handle == null) throw new FilesystemException(ErrorReason.CLOSED);
if (!mode.readable) throw new FilesystemException(ErrorReason.NO_PERMISSION, "File not open for reading.");
return onRead(buff);
}
catch (FilesystemException e) { throw e.setAction(ActionType.READ); }
}
@Override public void write(byte[] buff) {
try {
if (handle == null) throw new FilesystemException(ErrorReason.CLOSED);
if (!mode.writable) throw new FilesystemException(ErrorReason.NO_PERMISSION, "File not open for writting.");
onWrite(buff);
}
catch (FilesystemException e) { throw e.setAction(ActionType.WRITE); }
}
@Override public long seek(long offset, int pos) {
try {
if (handle == null) throw new FilesystemException(ErrorReason.CLOSED);
if (!mode.writable) throw new FilesystemException(ErrorReason.NO_PERMISSION, "File not open for seeking.");
return onSeek(offset, pos);
}
catch (FilesystemException e) { throw e.setAction(ActionType.SEEK); }
}
@Override public boolean close() {
if (handle != null) {
try {
var res = onClose();
handle = null;
mode = Mode.NONE;
return res;
}
catch (FilesystemException e) { throw e.setAction(ActionType.CLOSE); }
}
else return false;
}
public BaseFile(T handle, Mode mode) {
this.mode = mode;
this.handle = handle;
if (mode == Mode.NONE) this.handle = null;
}
}

View File

@@ -0,0 +1,23 @@
package me.topchetoeu.jscript.utils.filesystem;
public enum ErrorReason {
UNKNOWN(0, "failed", false),
NO_PERMISSION(1, "is not allowed", false),
CLOSED(1, "that was closed", true),
UNSUPPORTED(2, "is not supported", false),
ILLEGAL_ARGS(3, "with illegal arguments", true),
DOESNT_EXIST(4, "that doesn't exist", true),
ALREADY_EXISTS(5, "that already exists", true),
ILLEGAL_PATH(6, "with illegal path", true),
NO_PARENT(7, "with a missing parent folder", true);
public final int code;
public final boolean usePast;
public final String readable;
private ErrorReason(int code, String readable, boolean usePast) {
this.code = code;
this.readable = readable;
this.usePast = usePast;
}
}

View File

@@ -1,22 +1,44 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.LinkedList;
import me.topchetoeu.jscript.common.Buffer;
public interface File {
int read(byte[] buff);
void write(byte[] buff);
long seek(long offset, int pos);
void close();
default int read(byte[] buff) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.READ); }
default void write(byte[] buff) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.WRITE); }
default long seek(long offset, int pos) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.SEEK); }
default boolean close() { return false; }
default byte[] readAll() {
var parts = new LinkedList<byte[]>();
var buff = new byte[1024];
var size = 0;
while (true) {
var n = read(buff);
if (n == 0) break;
parts.add(buff);
size += n;
}
buff = new byte[size];
var i = 0;
for (var part : parts) {
System.arraycopy(part, 0, buff, i, part.length);
i += part.length;
}
return buff;
}
default String readToString() {
long len = seek(0, 2);
if (len < 0) return null;
seek(0, 0);
byte[] res = new byte[(int)len];
len = read(res);
return new String(res);
return new String(readAll());
}
default String readLine() {
var res = new Buffer();
@@ -34,4 +56,106 @@ public interface File {
}
return new String(res.data());
}
public static File ofStream(InputStream str) {
return new File() {
@Override public int read(byte[] buff) {
try {
try { return str.read(buff); }
catch (NullPointerException e) { throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, e.getMessage()); }
catch (IOException e) { throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage()); }
}
catch (FilesystemException e) { throw e.setAction(ActionType.READ); }
}
};
}
public static File ofStream(OutputStream str) {
return new File() {
@Override public void write(byte[] buff) {
try {
try { str.write(buff); }
catch (NullPointerException e) {throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, e.getMessage()); }
catch (IOException e) { throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage()); }
}
catch (FilesystemException e) { throw e.setAction(ActionType.WRITE); }
}
};
}
public static File ofLineWriter(LineWriter writer) {
var buff = new Buffer();
return new File() {
@Override public void write(byte[] val) {
try {
if (val == null) throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, "Given buffer is null.");
for (var b : val) {
if (b == '\n') {
try {
writer.writeLine(new String(buff.data()));
}
catch (IOException e) {
throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage());
}
}
else buff.append(b);
}
}
catch (FilesystemException e) { throw e.setAction(ActionType.WRITE); }
}
};
}
public static File ofLineReader(LineReader reader) {
return new File() {
private int offset = 0;
private byte[] prev = new byte[0];
@Override
public int read(byte[] buff) {
try {
if (buff == null) throw new FilesystemException(ErrorReason.ILLEGAL_ARGS, "Given buffer is null.");
var ptr = 0;
while (true) {
if (prev == null) break;
if (offset >= prev.length) {
try {
var line = reader.readLine();
if (line == null) {
prev = null;
break;
}
else prev = (line + "\n").getBytes();
offset = 0;
}
catch (IOException e) {
throw new FilesystemException(ErrorReason.UNKNOWN, e.getMessage());
}
}
if (ptr + prev.length - offset > buff.length) {
var n = buff.length - ptr;
System.arraycopy(prev, offset, buff, ptr, buff.length - ptr);
offset += n;
ptr += n;
break;
}
else {
var n = prev.length - offset;
System.arraycopy(prev, offset, buff, ptr, n);
offset += n;
ptr += n;
}
}
return ptr;
}
catch (FilesystemException e) { throw e.setAction(ActionType.READ); }
}
};
}
public static File ofIterator(Iterator<String> it) {
return ofLineReader(LineReader.ofIterator(it));
}
}

View File

@@ -5,6 +5,9 @@ public class FileStat {
public final EntryType type;
public FileStat(Mode mode, EntryType type) {
if (mode == Mode.NONE) type = EntryType.NONE;
if (type == EntryType.NONE) mode = Mode.NONE;
this.mode = mode;
this.type = type;
}

View File

@@ -6,10 +6,11 @@ import me.topchetoeu.jscript.core.engine.values.Symbol;
public interface Filesystem {
public static final Symbol ENV_KEY = Symbol.get("Environment.fs");
String normalize(String... path);
File open(String path, Mode mode) throws FilesystemException;
void create(String path, EntryType type) throws FilesystemException;
FileStat stat(String path) throws FilesystemException;
default String normalize(String... path) { return Paths.normalize(path); }
default boolean create(String path, EntryType type) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.CREATE); }
File open(String path, Mode mode);
FileStat stat(String path);
void close();
public static Filesystem get(Extensions exts) {
return exts.get(ENV_KEY);

View File

@@ -1,57 +1,90 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.util.ArrayList;
import me.topchetoeu.jscript.core.engine.values.Values;
import me.topchetoeu.jscript.core.exceptions.EngineException;
public class FilesystemException extends RuntimeException {
public static enum FSCode {
DOESNT_EXIST(0x1),
NOT_FILE(0x2),
NOT_FOLDER(0x3),
NO_PERMISSIONS_R(0x4),
NO_PERMISSIONS_RW(0x5),
FOLDER_NOT_EMPTY(0x6),
ALREADY_EXISTS(0x7),
FOLDER_EXISTS(0x8),
UNSUPPORTED_OPERATION(0x9);
public final ErrorReason reason;
public final String details;
private ActionType action;
private EntryType entry = EntryType.FILE;
private String path;
public final int code;
public FilesystemException setPath(String path) {
this.path = path;
return this;
}
public FilesystemException setAction(ActionType action) {
if (action == null) action = ActionType.UNKNOWN;
private FSCode(int code) { this.code = code; }
this.action = action;
return this;
}
public FilesystemException setEntry(EntryType entry) {
if (entry == null) entry = EntryType.NONE;
this.entry = entry;
return this;
}
public static final String[] MESSAGES = {
"How did we get here?",
"The file or folder '%s' doesn't exist or is inaccessible.",
"'%s' is not a file",
"'%s' is not a folder",
"No permissions to read '%s'",
"No permissions to write '%s'",
"Can't delete '%s', since it is a full folder.",
"'%s' already exists.",
"An unsupported operation was performed on the file '%s'."
};
public final String message, filename;
public final FSCode code;
public FilesystemException(String message, String filename, FSCode code) {
super(code + ": " + String.format(message, filename));
this.message = message;
this.code = code;
this.filename = filename;
public ActionType action() {
return action;
}
public FilesystemException(String filename, FSCode code) {
super(code + ": " + String.format(MESSAGES[code.code], filename));
this.message = MESSAGES[code.code];
this.code = code;
this.filename = filename;
public String path() {
return path;
}
public EntryType entry() {
return entry;
}
public EngineException toEngineException() {
var res = EngineException.ofError("IOError", getMessage());
Values.setMember(null, res.value, "code", code);
Values.setMember(null, res.value, "filename", filename.toString());
Values.setMember(null, res.value, "action", action.code);
Values.setMember(null, res.value, "reason", reason.code);
Values.setMember(null, res.value, "path", path);
Values.setMember(null, res.value, "entry", entry.name);
if (details != null) Values.setMember(null, res.value, "details", details);
return res;
}
@Override public String getMessage() {
var parts = new ArrayList<String>(10);
path = String.join(" ", parts).trim();
if (path.isEmpty()) path = null;
parts.clear();
parts.add(action == null ? "An action performed upon " : action.readable(reason.usePast));
if (entry == EntryType.FILE) parts.add("file");
if (entry == EntryType.FOLDER) parts.add("folder");
if (path != null) parts.add(path);
parts.add(reason.readable);
var msg = String.join(" ", parts);
if (details != null) msg += ": " + details;
return msg;
}
public FilesystemException(ErrorReason type, String details) {
super();
if (type == null) type = ErrorReason.UNKNOWN;
this.details = details;
this.reason = type;
}
public FilesystemException(ErrorReason type) {
this(type, null);
}
public FilesystemException() {
this(null);
}
}

View File

@@ -0,0 +1,32 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.util.HashSet;
import java.util.Set;
public class HandleManager {
private Set<File> files = new HashSet<>();
public File put(File val) {
var handle = new File() {
@Override public int read(byte[] buff) {
return val.read(buff);
}
@Override public void write(byte[] buff) {
val.write(buff);
}
@Override public long seek(long offset, int pos) {
return val.seek(offset, pos);
}
@Override public boolean close() {
return files.remove(this) && val.close();
}
};
files.add(handle);
return handle;
}
public void close() {
while (!files.isEmpty()) {
files.stream().findFirst().get().close();
}
}
}

View File

@@ -0,0 +1,16 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.io.IOException;
import java.util.Iterator;
public interface LineReader {
String readLine() throws IOException;
public static LineReader ofIterator(Iterator<String> it) {
return () -> {
if (it.hasNext()) return it.next();
else return null;
};
}
}

View File

@@ -0,0 +1,7 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.io.IOException;
public interface LineWriter {
void writeLine(String value) throws IOException;
}

View File

@@ -1,73 +0,0 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.io.IOException;
import java.util.Iterator;
import java.util.stream.Stream;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
public class ListFile implements File {
private Iterator<String> it;
private String filename;
private byte[] currFile;
private long ptr = 0, start = 0, end = 0;
private void next() {
if (it != null && it.hasNext()) {
start = end;
currFile = (it.next() + "\n").getBytes();
end = start + currFile.length;
}
else {
it = null;
currFile = null;
end = -1;
}
}
@Override
public void close() {
it = null;
currFile = null;
}
@Override
public int read(byte[] buff) {
if (ptr < start) return 0;
if (it == null) return 0;
var i = 0;
while (i < buff.length) {
while (i + ptr >= end) {
next();
if (it == null) return 0;
}
int cpyN = Math.min(currFile.length, buff.length - i);
System.arraycopy(currFile, (int)(ptr + i - start), buff, i, cpyN);
i += cpyN;
}
ptr += i;
return i;
}
@Override
public long seek(long offset, int pos) {
if (pos == 2) throw new FilesystemException(filename, FSCode.UNSUPPORTED_OPERATION);
if (pos == 1) offset += ptr;
return ptr = offset;
}
@Override
public void write(byte[] buff) {
throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
}
public ListFile(String filename, Stream<String> stream) throws IOException {
this.it = stream.iterator();
this.filename = filename;
}
}

View File

@@ -1,62 +1,35 @@
package me.topchetoeu.jscript.utils.filesystem;
import me.topchetoeu.jscript.common.Buffer;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
public class MemoryFile implements File {
class MemoryFile extends BaseFile<Buffer> {
private int ptr;
private Mode mode;
private Buffer data;
private String filename;
public Buffer data() { return data; }
@Override
public int read(byte[] buff) {
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
var res = data.read(ptr, buff);
@Override protected int onRead(byte[] buff) {
var res = handle().read(ptr, buff);
ptr += res;
return res;
}
@Override
public void write(byte[] buff) {
if (data == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
data.write(ptr, buff);
@Override protected void onWrite(byte[] buff) {
handle().write(ptr, buff);
ptr += buff.length;
}
@Override
public long seek(long offset, int pos) {
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
@Override protected long onSeek(long offset, int pos) {
if (pos == 0) ptr = (int)offset;
else if (pos == 1) ptr += (int)offset;
else if (pos == 2) ptr = data.length() - (int)offset;
else if (pos == 2) ptr = handle().length() - (int)offset;
if (ptr < 0) ptr = 0;
if (ptr > data.length()) ptr = data.length();
if (ptr > handle().length()) ptr = handle().length();
return pos;
}
@Override
public void close() {
mode = Mode.NONE;
@Override protected boolean onClose() {
ptr = 0;
return true;
}
public MemoryFile(String filename, Buffer buff, Mode mode) {
this.filename = filename;
this.data = buff;
this.mode = mode;
public MemoryFile(Buffer buff, Mode mode) {
super(buff, mode);
}
public static MemoryFile fromFileList(String filename, java.io.File[] list) {
var res = new StringBuilder();
for (var el : list) res.append(el.getName()).append('\n');
return new MemoryFile(filename, new Buffer(res.toString().getBytes()), Mode.READ);
}
}
}

View File

@@ -6,75 +6,79 @@ import java.util.HashSet;
import me.topchetoeu.jscript.common.Buffer;
import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
public class MemoryFilesystem implements Filesystem {
public final Mode mode;
private HashMap<Path, Buffer> files = new HashMap<>();
private HashSet<Path> folders = new HashSet<>();
private HandleManager handles = new HandleManager();
private Path realPath(String path) {
return Filename.normalize(path);
}
@Override
public String normalize(String... path) {
@Override public String normalize(String... path) {
return Paths.normalize(path);
}
@Override public File open(String _path, Mode perms) {
try {
var path = realPath(_path);
var pcount = path.getNameCount();
@Override
public void create(String _path, EntryType type) {
var path = realPath(_path);
if (files.containsKey(path)) return handles.put(new MemoryFile(files.get(path), perms));
else if (folders.contains(path)) {
var res = new StringBuilder();
switch (type) {
case FILE:
if (!folders.contains(path.getParent())) throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST);
if (folders.contains(path) || files.containsKey(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
if (folders.contains(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
files.put(path, new Buffer());
break;
case FOLDER:
if (!folders.contains(path.getParent())) throw new FilesystemException(_path, FSCode.DOESNT_EXIST);
if (folders.contains(path) || files.containsKey(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
folders.add(path);
break;
default:
case NONE:
if (!folders.remove(path) && files.remove(path) == null) throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST);
}
}
for (var folder : folders) {
if (pcount + 1 != folder.getNameCount()) continue;
if (!folder.startsWith(path)) continue;
res.append(folder.toFile().getName()).append('\n');
}
@Override
public File open(String _path, Mode perms) {
var path = realPath(_path);
var pcount = path.getNameCount();
for (var file : files.keySet()) {
if (pcount + 1 != file.getNameCount()) continue;
if (!file.startsWith(path)) continue;
res.append(file.toFile().getName()).append('\n');
}
if (files.containsKey(path)) return new MemoryFile(path.toString(), files.get(path), perms);
else if (folders.contains(path)) {
var res = new StringBuilder();
for (var folder : folders) {
if (pcount + 1 != folder.getNameCount()) continue;
if (!folder.startsWith(path)) continue;
res.append(folder.toFile().getName()).append('\n');
return handles.put(new MemoryFile(new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ)));
}
for (var file : files.keySet()) {
if (pcount + 1 != file.getNameCount()) continue;
if (!file.startsWith(path)) continue;
res.append(file.toFile().getName()).append('\n');
}
return new MemoryFile(path.toString(), new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ));
else throw new FilesystemException(ErrorReason.DOESNT_EXIST);
}
else throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST);
catch (FilesystemException e) { throw e.setPath(_path).setAction(ActionType.OPEN); }
}
@Override
public FileStat stat(String _path) {
@Override public boolean create(String _path, EntryType type) {
try {
var path = realPath(_path);
switch (type) {
case FILE:
if (!folders.contains(path.getParent())) throw new FilesystemException(ErrorReason.NO_PARENT);
if (folders.contains(path) || files.containsKey(path)) return false;
files.put(path, new Buffer());
return true;
case FOLDER:
if (!folders.contains(path.getParent())) throw new FilesystemException(ErrorReason.NO_PARENT);
if (folders.contains(path) || files.containsKey(path)) return false;
folders.add(path);
return true;
default:
case NONE:
return folders.remove(path) || files.remove(path) != null;
}
}
catch (FilesystemException e) { throw e.setPath(_path).setAction(ActionType.CREATE); }
}
@Override public FileStat stat(String _path) {
var path = realPath(_path);
if (files.containsKey(path)) return new FileStat(mode, EntryType.FILE);
else if (folders.contains(path)) return new FileStat(mode, EntryType.FOLDER);
else return new FileStat(Mode.NONE, EntryType.NONE);
}
@Override public void close() throws FilesystemException {
handles.close();
}
public MemoryFilesystem put(String path, byte[] data) {
var _path = realPath(path);

View File

@@ -3,6 +3,7 @@ package me.topchetoeu.jscript.utils.filesystem;
public enum Mode {
NONE("", false, false),
READ("r", true, false),
WRITE("rw", false, true),
READ_WRITE("rw", true, true);
public final String name;
@@ -10,9 +11,7 @@ public enum Mode {
public final boolean writable;
public Mode intersect(Mode other) {
if (this == NONE || other == NONE) return NONE;
if (this == READ_WRITE && other == READ_WRITE) return READ_WRITE;
return READ;
return of(readable && other.readable, writable && other.writable);
}
private Mode(String mode, boolean r, boolean w) {
@@ -21,9 +20,20 @@ public enum Mode {
this.writable = w;
}
public static Mode of(boolean read, boolean write) {
if (read && write) return READ_WRITE;
if (read) return READ;
if (write) return WRITE;
return NONE;
}
public static Mode parse(String mode) {
switch (mode) {
switch (mode.toLowerCase()) {
case "r": return READ;
case "w": return WRITE;
case "r+":
case "w+":
case "wr":
case "rw": return READ_WRITE;
default: return NONE;
}

View File

@@ -3,55 +3,33 @@ package me.topchetoeu.jscript.utils.filesystem;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Path;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
public class PhysicalFile implements File {
private String filename;
private RandomAccessFile file;
private Mode mode;
@Override
public int read(byte[] buff) {
if (file == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
else try { return file.read(buff); }
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); }
public class PhysicalFile extends BaseFile<RandomAccessFile> {
@Override protected int onRead(byte[] buff) {
try { return handle().read(buff); }
catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PERMISSION).setAction(ActionType.READ); }
}
@Override
public void write(byte[] buff) {
if (file == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
else try { file.write(buff); }
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); }
@Override protected void onWrite(byte[] buff) {
try { handle().write(buff); }
catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PERMISSION).setAction(ActionType.WRITE); }
}
@Override
public long seek(long offset, int pos) {
if (file == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
@Override protected long onSeek(long offset, int pos) {
try {
if (pos == 1) offset += file.getFilePointer();
else if (pos == 2) offset += file.length();
file.seek(offset);
if (pos == 1) offset += handle().getFilePointer();
else if (pos == 2) offset += handle().length();
handle().seek(offset);
return offset;
}
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); }
catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PERMISSION).setAction(ActionType.SEEK); }
}
@Override
public void close() {
if (file == null) return;
try { file.close(); }
@Override protected boolean onClose() {
try { handle().close(); }
catch (IOException e) {} // SHUT
file = null;
mode = Mode.NONE;
return true;
}
public PhysicalFile(String name, String path, Mode mode) throws FileNotFoundException {
this.filename = name;
this.mode = mode;
if (mode == Mode.NONE) file = null;
else try { file = new RandomAccessFile(path, mode.name); }
catch (FileNotFoundException e) { throw new FilesystemException(filename, FSCode.DOESNT_EXIST); }
public PhysicalFile(Path path, Mode mode) throws FileNotFoundException {
super(new RandomAccessFile(path.toFile(), mode.name), mode);
}
}

View File

@@ -1,68 +1,69 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
public class PhysicalFilesystem implements Filesystem {
public final String root;
private HandleManager handles = new HandleManager();
private void checkMode(Path path, Mode mode) {
if (!path.startsWith(root)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R);
if (!path.startsWith(root)) throw new FilesystemException(ErrorReason.NO_PERMISSION, "Tried to jailbreak the sandbox.");
if (mode.readable && !Files.isReadable(path)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R);
if (mode.writable && !Files.isWritable(path)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_RW);
if (mode.readable && !Files.isReadable(path)) throw new FilesystemException(ErrorReason.NO_PERMISSION, "No read permissions");
if (mode.writable && !Files.isWritable(path)) throw new FilesystemException(ErrorReason.NO_PERMISSION, "No write permissions");
}
private Path realPath(String path) {
return Path.of(Paths.chroot(root, path));
}
@Override
public String normalize(String... paths) {
@Override public String normalize(String... paths) {
return Paths.normalize(paths);
}
@Override
public File open(String _path, Mode perms) {
_path = normalize(_path);
var path = realPath(_path);
checkMode(path, perms);
@Override public File open(String _path, Mode perms) {
try {
if (Files.isDirectory(path)) return new ListFile(_path, Files.list(path).map((v -> v.getFileName().toString())));
else return new PhysicalFile(_path, path.toString(), perms);
var path = realPath(normalize(_path));
checkMode(path, perms);
try {
if (Files.isDirectory(path)) return handles.put(File.ofIterator(
Files.list(path).map(v -> v.getFileName().toString()).iterator()
));
else return handles.put(new PhysicalFile(path, perms));
}
catch (IOException e) { throw new FilesystemException(ErrorReason.DOESNT_EXIST); }
}
catch (IOException e) { throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST); }
catch (FilesystemException e) { throw e.setAction(ActionType.OPEN).setPath(_path); }
}
@Override
public void create(String _path, EntryType type) {
var path = realPath(_path);
if (type == EntryType.NONE != Files.exists(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS);
@Override public boolean create(String _path, EntryType type) {
try {
switch (type) {
case FILE:
Files.createFile(path);
break;
case FOLDER:
Files.createDirectories(path);
break;
case NONE:
default:
Files.delete(path);
}
}
catch (IOException e) { throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_RW); }
}
var path = realPath(_path);
@Override
public FileStat stat(String _path) {
try {
switch (type) {
case FILE:
Files.createFile(path);
break;
case FOLDER:
Files.createDirectories(path);
break;
case NONE:
default:
Files.delete(path);
}
}
catch (FileAlreadyExistsException | NoSuchFileException e) { return false; }
catch (IOException e) { throw new FilesystemException(ErrorReason.NO_PARENT); }
}
catch (FilesystemException e) { throw e.setAction(ActionType.CREATE).setPath(_path); }
return true;
}
@Override public FileStat stat(String _path) {
var path = realPath(_path);
if (!Files.exists(path)) return new FileStat(Mode.NONE, EntryType.NONE);
@@ -81,6 +82,12 @@ public class PhysicalFilesystem implements Filesystem {
Files.isDirectory(path) ? EntryType.FOLDER : EntryType.FILE
);
}
@Override public void close() throws FilesystemException {
try {
handles.close();
}
catch (FilesystemException e) { throw e.setAction(ActionType.CLOSE_FS); }
}
public PhysicalFilesystem(String root) {
this.root = Paths.normalize(Path.of(root).toAbsolutePath().toString());

View File

@@ -4,7 +4,7 @@ import java.util.HashMap;
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,15 +12,29 @@ 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 {
if (mode.readable && perms != null && !canRead(_path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_R);
if (mode.writable && perms != null && !canWrite(_path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_RW);
if (mode.readable && perms != null && !canRead(_path)) {
throw new FilesystemException(ErrorReason.NO_PERMISSION, "No read permissions").setPath(_path);
}
if (mode.writable && perms != null && !canWrite(_path)) {
throw new FilesystemException(ErrorReason.NO_PERMISSION, "No wtrite permissions").setPath(_path);
}
}
private Filesystem getProtocol(Filename filename) {
var protocol = protocols.get(filename.protocol);
if (protocol == null) {
throw new FilesystemException(ErrorReason.DOESNT_EXIST, "The protocol '" + filename.protocol + "' doesn't exist.");
}
return protocol;
}
@Override public String normalize(String... paths) {
@@ -36,30 +50,43 @@ public class RootFilesystem implements Filesystem {
}
}
@Override public File open(String path, Mode perms) throws FilesystemException {
var filename = Filename.parse(path);
var protocol = protocols.get(filename.protocol);
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST);
modeAllowed(filename.toString(), perms);
try {
var filename = Filename.parse(path);
var protocol = getProtocol(filename);
try { return protocol.open(filename.path, perms); }
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); }
modeAllowed(filename.toString(), perms);
return protocol.open(filename.path, perms);
}
catch (FilesystemException e) { throw e.setPath(path).setAction(ActionType.OPEN); }
}
@Override public void create(String path, EntryType type) throws FilesystemException {
var filename = Filename.parse(path);
var protocol = protocols.get(filename.protocol);
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST);
modeAllowed(filename.toString(), Mode.READ_WRITE);
@Override public boolean create(String path, EntryType type) throws FilesystemException {
try {
var filename = Filename.parse(path);
var protocol = getProtocol(filename);
try { protocol.create(filename.path, type); }
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); }
modeAllowed(filename.toString(), Mode.WRITE);
return protocol.create(filename.path, type);
}
catch (FilesystemException e) { throw e.setPath(path).setAction(ActionType.CREATE); }
}
@Override public FileStat stat(String path) throws FilesystemException {
var filename = Filename.parse(path);
var protocol = protocols.get(filename.protocol);
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST);
try {
var filename = Filename.parse(path);
var protocol = getProtocol(filename);
try { return protocol.stat(filename.path); }
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); }
return protocol.stat(filename.path);
}
catch (FilesystemException e) { throw e.setPath(path).setAction(ActionType.STAT); }
}
@Override public void close() throws FilesystemException {
try {
for (var protocol : protocols.values()) {
protocol.close();
}
protocols.clear();
}
catch (FilesystemException e) { throw e.setAction(ActionType.CLOSE_FS); }
}
public RootFilesystem(PermissionsProvider perms) {

View File

@@ -0,0 +1,42 @@
package me.topchetoeu.jscript.utils.filesystem;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
public class STDFilesystem implements Filesystem {
private final HashMap<String, File> handles = new HashMap<>();
@Override
public String normalize(String... path) {
var res = Paths.normalize(path);
while (res.startsWith("/")) res = res.substring(1);
return res;
}
@Override public File open(String path, Mode mode) {
path = normalize(path);
if (handles.containsKey(path)) return handles.get(path);
else throw new FilesystemException(ErrorReason.DOESNT_EXIST).setAction(ActionType.OPEN).setPath(path);
}
@Override public FileStat stat(String path) {
path = normalize(path);
if (handles.containsKey(path)) return new FileStat(Mode.READ_WRITE, EntryType.FILE);
else return new FileStat(Mode.NONE, EntryType.NONE);
}
@Override public void close() {
handles.clear();
}
public STDFilesystem add(String name, File handle) {
this.handles.put(name, handle);
return this;
}
public static STDFilesystem ofStd(InputStream in, OutputStream out, OutputStream err) {
return new STDFilesystem()
.add("in", File.ofStream(in))
.add("out", File.ofStream(out))
.add("err", File.ofStream(err));
}
}

View File

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

View File

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

View File

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

View File

@@ -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<PermissionPredicate> predicates = new ArrayList<>();
public final ArrayList<Permission> allowed = new ArrayList<>();
public final ArrayList<Permission> 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;
}
}

View File

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