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 !/.gitignore
!/.gitattributes !/.gitattributes
!/build.js
!/LICENSE !/LICENSE
!/README.md !/README.md
!/settings.gradle !/settings.gradle
!/build.gradle !/build.gradle
!/gradle.properties !/gradle.properties
!/gradle
!/gradle/wrapper
!/gradle/wrapper/gradle-wrapper.properties

View File

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

View File

@@ -1,3 +1,4 @@
project_group = me.topchetoeu
project_name = jscript project_name = jscript
project_version = 0.8.4-beta project_version = 0.8.6-beta
main_class = me.topchetoeu.jscript.utils.JScriptRepl 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 protocol;
public final String path; public final String path;
public String toString() { @Override public String toString() {
return protocol + "://" + path; return protocol + "://" + path;
} }
@Override public int hashCode() {
@Override
public int hashCode() {
final int prime = 31; final int prime = 31;
int result = 1; int result = 1;
result = prime * result + protocol.hashCode(); result = prime * result + protocol.hashCode();
result = prime * result + path.hashCode(); result = prime * result + path.hashCode();
return result; return result;
} }
@Override public boolean equals(Object obj) {
@Override
public boolean equals(Object obj) {
if (this == obj) return true; if (this == obj) return true;
if (obj == null) return false; if (obj == null) return false;
if (getClass() != obj.getClass()) return false; if (getClass() != obj.getClass()) return false;
@@ -41,9 +36,6 @@ public class Filename {
return true; return true;
} }
public Filename(String protocol, String path) { public Filename(String protocol, String path) {
path = path.trim(); path = path.trim();
protocol = protocol.trim(); protocol = protocol.trim();

View File

@@ -711,14 +711,14 @@ public class Values {
res.append("{\n"); res.append("{\n");
for (var el : obj.values.entrySet()) { 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(toReadable(ctx, el.getKey(), passed, tab + 1));
res.append(": "); res.append(": ");
res.append(toReadable(ctx, el.getValue(), passed, tab + 1)); res.append(toReadable(ctx, el.getValue(), passed, tab + 1));
res.append(",\n"); res.append(",\n");
} }
for (var el : obj.properties.entrySet()) { 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(toReadable(ctx, el.getKey(), passed, tab + 1));
res.append(": [prop],\n"); 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") @WrapperName("File")
public class FileLib { public class FileLib {
public final File file; public final File fd;
@Expose public PromiseLib __pointer(Arguments args) { @Expose public PromiseLib __pointer(Arguments args) {
return PromiseLib.await(args.ctx, () -> { return PromiseLib.await(args.ctx, () -> {
try { try {
return file.seek(0, 1); return fd.seek(0, 1);
} }
catch (FilesystemException e) { throw e.toEngineException(); } catch (FilesystemException e) { throw e.toEngineException(); }
}); });
@@ -23,9 +23,9 @@ public class FileLib {
@Expose public PromiseLib __length(Arguments args) { @Expose public PromiseLib __length(Arguments args) {
return PromiseLib.await(args.ctx, () -> { return PromiseLib.await(args.ctx, () -> {
try { try {
long curr = file.seek(0, 1); long curr = fd.seek(0, 1);
long res = file.seek(0, 2); long res = fd.seek(0, 2);
file.seek(curr, 0); fd.seek(curr, 0);
return res; return res;
} }
catch (FilesystemException e) { throw e.toEngineException(); } catch (FilesystemException e) { throw e.toEngineException(); }
@@ -38,8 +38,8 @@ public class FileLib {
try { try {
var buff = new byte[n]; var buff = new byte[n];
var res = new ArrayValue(); 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]); for (var i = resI - 1; i >= 0; i--) res.set(args.ctx, i, (int)buff[i]);
return res; return res;
} }
@@ -53,7 +53,7 @@ public class FileLib {
var res = new byte[val.size()]; var res = new byte[val.size()];
for (var i = 0; i < val.size(); i++) res[i] = (byte)Values.toNumber(args.ctx, val.get(i)); 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; return null;
} }
@@ -62,7 +62,7 @@ public class FileLib {
} }
@Expose public PromiseLib __close(Arguments args) { @Expose public PromiseLib __close(Arguments args) {
return PromiseLib.await(args.ctx, () -> { return PromiseLib.await(args.ctx, () -> {
file.close(); fd.close();
return null; return null;
}); });
} }
@@ -72,13 +72,13 @@ public class FileLib {
var whence = args.getInt(1); var whence = args.getInt(1);
try { try {
return file.seek(ptr, whence); return fd.seek(ptr, whence);
} }
catch (FilesystemException e) { throw e.toEngineException(); } catch (FilesystemException e) { throw e.toEngineException(); }
}); });
} }
public FileLib(File file) { public FileLib(File fd) {
this.file = file; 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.ObjectValue;
import me.topchetoeu.jscript.core.engine.values.Values; import me.topchetoeu.jscript.core.engine.values.Values;
import me.topchetoeu.jscript.core.exceptions.EngineException; 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.EntryType;
import me.topchetoeu.jscript.utils.filesystem.ErrorReason;
import me.topchetoeu.jscript.utils.filesystem.File; import me.topchetoeu.jscript.utils.filesystem.File;
import me.topchetoeu.jscript.utils.filesystem.FileStat; import me.topchetoeu.jscript.utils.filesystem.FileStat;
import me.topchetoeu.jscript.utils.filesystem.Filesystem; import me.topchetoeu.jscript.utils.filesystem.Filesystem;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException; import me.topchetoeu.jscript.utils.filesystem.FilesystemException;
import me.topchetoeu.jscript.utils.filesystem.Mode; 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.Arguments;
import me.topchetoeu.jscript.utils.interop.Expose; import me.topchetoeu.jscript.utils.interop.Expose;
import me.topchetoeu.jscript.utils.interop.ExposeField; import me.topchetoeu.jscript.utils.interop.ExposeField;
@@ -50,11 +51,10 @@ public class FilesystemLib {
try { try {
if (fs.stat(path).type != EntryType.FILE) { 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(fs.open(path, _mode));
return new FileLib(file);
} }
catch (FilesystemException e) { throw e.toEngineException(); } catch (FilesystemException e) { throw e.toEngineException(); }
}); });
@@ -75,7 +75,7 @@ public class FilesystemLib {
var path = fs.normalize(args.getString(0)); var path = fs.normalize(args.getString(0));
if (fs.stat(path).type != EntryType.FOLDER) { 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); file = fs.open(path, Mode.READ);

View File

@@ -1,9 +1,7 @@
package me.topchetoeu.jscript.lib; package me.topchetoeu.jscript.lib;
import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import me.topchetoeu.jscript.common.Reading;
import me.topchetoeu.jscript.core.engine.Context; import me.topchetoeu.jscript.core.engine.Context;
import me.topchetoeu.jscript.core.engine.Environment; import me.topchetoeu.jscript.core.engine.Environment;
import me.topchetoeu.jscript.core.engine.scope.GlobalScope; import me.topchetoeu.jscript.core.engine.scope.GlobalScope;
@@ -34,27 +32,6 @@ public class Internals {
else throw EngineException.ofError("Modules are not supported."); 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) @Expose(target = ExposeTarget.STATIC)
public static Thread __setTimeout(Arguments args) { public static Thread __setTimeout(Arguments args) {
var func = args.convert(0, FunctionValue.class); 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, "Encoding", false, wp.getNamespace(EncodingLib.class));
glob.define(null, "Filesystem", false, wp.getNamespace(FilesystemLib.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(DateLib.class));
glob.define(false, wp.getConstr(ObjectLib.class)); glob.define(false, wp.getConstr(ObjectLib.class));
glob.define(false, wp.getConstr(FunctionLib.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.Mode;
import me.topchetoeu.jscript.utils.filesystem.PhysicalFilesystem; import me.topchetoeu.jscript.utils.filesystem.PhysicalFilesystem;
import me.topchetoeu.jscript.utils.filesystem.RootFilesystem; 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.modules.ModuleRepo;
import me.topchetoeu.jscript.utils.permissions.PermissionsManager; import me.topchetoeu.jscript.utils.permissions.PermissionsManager;
import me.topchetoeu.jscript.utils.permissions.PermissionsProvider; import me.topchetoeu.jscript.utils.permissions.PermissionsProvider;
@@ -108,6 +109,7 @@ public class JScriptRepl {
var fs = new RootFilesystem(PermissionsProvider.get(environment)); var fs = new RootFilesystem(PermissionsProvider.get(environment));
fs.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE)); fs.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE));
fs.protocols.put("file", new PhysicalFilesystem(".")); 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(PermissionsProvider.ENV_KEY, PermissionsManager.ALL_PERMS);
environment.add(Filesystem.ENV_KEY, fs); 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; 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; import me.topchetoeu.jscript.common.Buffer;
public interface File { public interface File {
int read(byte[] buff); default int read(byte[] buff) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.READ); }
void write(byte[] buff); default void write(byte[] buff) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.WRITE); }
long seek(long offset, int pos); default long seek(long offset, int pos) { throw new FilesystemException(ErrorReason.UNSUPPORTED).setAction(ActionType.SEEK); }
void close(); 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() { default String readToString() {
long len = seek(0, 2); return new String(readAll());
if (len < 0) return null;
seek(0, 0);
byte[] res = new byte[(int)len];
len = read(res);
return new String(res);
} }
default String readLine() { default String readLine() {
var res = new Buffer(); var res = new Buffer();
@@ -34,4 +56,106 @@ public interface File {
} }
return new String(res.data()); 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 final EntryType type;
public FileStat(Mode mode, 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.mode = mode;
this.type = type; this.type = type;
} }

View File

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

View File

@@ -1,57 +1,90 @@
package me.topchetoeu.jscript.utils.filesystem; package me.topchetoeu.jscript.utils.filesystem;
import java.util.ArrayList;
import me.topchetoeu.jscript.core.engine.values.Values; import me.topchetoeu.jscript.core.engine.values.Values;
import me.topchetoeu.jscript.core.exceptions.EngineException; import me.topchetoeu.jscript.core.exceptions.EngineException;
public class FilesystemException extends RuntimeException { public class FilesystemException extends RuntimeException {
public static enum FSCode { public final ErrorReason reason;
DOESNT_EXIST(0x1), public final String details;
NOT_FILE(0x2), private ActionType action;
NOT_FOLDER(0x3), private EntryType entry = EntryType.FILE;
NO_PERMISSIONS_R(0x4), private String path;
NO_PERMISSIONS_RW(0x5),
FOLDER_NOT_EMPTY(0x6),
ALREADY_EXISTS(0x7),
FOLDER_EXISTS(0x8),
UNSUPPORTED_OPERATION(0x9);
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 = { public ActionType action() {
"How did we get here?", return action;
"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 FilesystemException(String filename, FSCode code) { public String path() {
super(code + ": " + String.format(MESSAGES[code.code], filename)); return path;
this.message = MESSAGES[code.code]; }
this.code = code; public EntryType entry() {
this.filename = filename; return entry;
} }
public EngineException toEngineException() { public EngineException toEngineException() {
var res = EngineException.ofError("IOError", getMessage()); 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; 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; package me.topchetoeu.jscript.utils.filesystem;
import me.topchetoeu.jscript.common.Buffer; 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 int ptr;
private Mode mode;
private Buffer data;
private String filename;
public Buffer data() { return data; } @Override protected int onRead(byte[] buff) {
var res = handle().read(ptr, buff);
@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);
ptr += res; ptr += res;
return res; return res;
} }
@Override @Override protected void onWrite(byte[] buff) {
public void write(byte[] buff) { handle().write(ptr, buff);
if (data == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
data.write(ptr, buff);
ptr += buff.length; ptr += buff.length;
} }
@Override protected long onSeek(long offset, int pos) {
@Override
public long seek(long offset, int pos) {
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
if (pos == 0) ptr = (int)offset; if (pos == 0) ptr = (int)offset;
else if (pos == 1) 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 < 0) ptr = 0;
if (ptr > data.length()) ptr = data.length(); if (ptr > handle().length()) ptr = handle().length();
return pos; return pos;
} }
@Override protected boolean onClose() {
@Override
public void close() {
mode = Mode.NONE;
ptr = 0; ptr = 0;
return true;
} }
public MemoryFile(String filename, Buffer buff, Mode mode) { public MemoryFile(Buffer buff, Mode mode) {
this.filename = filename; super(buff, mode);
this.data = buff;
this.mode = 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.Buffer;
import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Filename;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
public class MemoryFilesystem implements Filesystem { public class MemoryFilesystem implements Filesystem {
public final Mode mode; public final Mode mode;
private HashMap<Path, Buffer> files = new HashMap<>(); private HashMap<Path, Buffer> files = new HashMap<>();
private HashSet<Path> folders = new HashSet<>(); private HashSet<Path> folders = new HashSet<>();
private HandleManager handles = new HandleManager();
private Path realPath(String path) { private Path realPath(String path) {
return Filename.normalize(path); return Filename.normalize(path);
} }
@Override @Override public String normalize(String... path) {
public String normalize(String... path) {
return Paths.normalize(path); return Paths.normalize(path);
} }
@Override public File open(String _path, Mode perms) {
try {
var path = realPath(_path);
var pcount = path.getNameCount();
@Override if (files.containsKey(path)) return handles.put(new MemoryFile(files.get(path), perms));
public void create(String _path, EntryType type) { else if (folders.contains(path)) {
var path = realPath(_path); var res = new StringBuilder();
switch (type) { for (var folder : folders) {
case FILE: if (pcount + 1 != folder.getNameCount()) continue;
if (!folders.contains(path.getParent())) throw new FilesystemException(path.toString(), FSCode.DOESNT_EXIST); if (!folder.startsWith(path)) continue;
if (folders.contains(path) || files.containsKey(path)) throw new FilesystemException(path.toString(), FSCode.ALREADY_EXISTS); res.append(folder.toFile().getName()).append('\n');
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);
}
}
@Override for (var file : files.keySet()) {
public File open(String _path, Mode perms) { if (pcount + 1 != file.getNameCount()) continue;
var path = realPath(_path); if (!file.startsWith(path)) continue;
var pcount = path.getNameCount(); res.append(file.toFile().getName()).append('\n');
}
if (files.containsKey(path)) return new MemoryFile(path.toString(), files.get(path), perms); return handles.put(new MemoryFile(new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ)));
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');
} }
for (var file : files.keySet()) { else throw new FilesystemException(ErrorReason.DOESNT_EXIST);
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(path.toString(), FSCode.DOESNT_EXIST); catch (FilesystemException e) { throw e.setPath(_path).setAction(ActionType.OPEN); }
} }
@Override public boolean create(String _path, EntryType type) {
@Override try {
public FileStat stat(String _path) { 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); var path = realPath(_path);
if (files.containsKey(path)) return new FileStat(mode, EntryType.FILE); if (files.containsKey(path)) return new FileStat(mode, EntryType.FILE);
else if (folders.contains(path)) return new FileStat(mode, EntryType.FOLDER); else if (folders.contains(path)) return new FileStat(mode, EntryType.FOLDER);
else return new FileStat(Mode.NONE, EntryType.NONE); else return new FileStat(Mode.NONE, EntryType.NONE);
} }
@Override public void close() throws FilesystemException {
handles.close();
}
public MemoryFilesystem put(String path, byte[] data) { public MemoryFilesystem put(String path, byte[] data) {
var _path = realPath(path); var _path = realPath(path);

View File

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

View File

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

View File

@@ -1,68 +1,69 @@
package me.topchetoeu.jscript.utils.filesystem; package me.topchetoeu.jscript.utils.filesystem;
import java.io.IOException; import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path; import java.nio.file.Path;
import me.topchetoeu.jscript.utils.filesystem.FilesystemException.FSCode;
public class PhysicalFilesystem implements Filesystem { public class PhysicalFilesystem implements Filesystem {
public final String root; public final String root;
private HandleManager handles = new HandleManager();
private void checkMode(Path path, Mode mode) { 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.readable && !Files.isReadable(path)) throw new FilesystemException(ErrorReason.NO_PERMISSION, "No read permissions");
if (mode.writable && !Files.isWritable(path)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_RW); if (mode.writable && !Files.isWritable(path)) throw new FilesystemException(ErrorReason.NO_PERMISSION, "No write permissions");
} }
private Path realPath(String path) { private Path realPath(String path) {
return Path.of(Paths.chroot(root, path)); return Path.of(Paths.chroot(root, path));
} }
@Override @Override public String normalize(String... paths) {
public String normalize(String... paths) {
return Paths.normalize(paths); return Paths.normalize(paths);
} }
@Override public File open(String _path, Mode perms) {
@Override
public File open(String _path, Mode perms) {
_path = normalize(_path);
var path = realPath(_path);
checkMode(path, perms);
try { try {
if (Files.isDirectory(path)) return new ListFile(_path, Files.list(path).map((v -> v.getFileName().toString()))); var path = realPath(normalize(_path));
else return new PhysicalFile(_path, path.toString(), perms); 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 boolean create(String _path, EntryType type) {
@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);
try { try {
switch (type) { var path = realPath(_path);
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); }
}
@Override try {
public FileStat stat(String _path) { 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); var path = realPath(_path);
if (!Files.exists(path)) return new FileStat(Mode.NONE, EntryType.NONE); 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 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) { public PhysicalFilesystem(String root) {
this.root = Paths.normalize(Path.of(root).toAbsolutePath().toString()); this.root = Paths.normalize(Path.of(root).toAbsolutePath().toString());

View File

@@ -4,7 +4,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import me.topchetoeu.jscript.common.Filename; 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; import me.topchetoeu.jscript.utils.permissions.PermissionsProvider;
public class RootFilesystem implements Filesystem { public class RootFilesystem implements Filesystem {
@@ -12,15 +12,29 @@ public class RootFilesystem implements Filesystem {
public final PermissionsProvider perms; public final PermissionsProvider perms;
private boolean canRead(String _path) { 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) { 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 { 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.readable && perms != null && !canRead(_path)) {
if (mode.writable && perms != null && !canWrite(_path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_RW); 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) { @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 { @Override public File open(String path, Mode perms) throws FilesystemException {
var filename = Filename.parse(path); try {
var protocol = protocols.get(filename.protocol); var filename = Filename.parse(path);
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST); var protocol = getProtocol(filename);
modeAllowed(filename.toString(), perms);
try { return protocol.open(filename.path, perms); } modeAllowed(filename.toString(), perms);
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); } 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 { @Override public boolean create(String path, EntryType type) throws FilesystemException {
var filename = Filename.parse(path); try {
var protocol = protocols.get(filename.protocol); var filename = Filename.parse(path);
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST); var protocol = getProtocol(filename);
modeAllowed(filename.toString(), Mode.READ_WRITE);
try { protocol.create(filename.path, type); } modeAllowed(filename.toString(), Mode.WRITE);
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); } return protocol.create(filename.path, type);
}
catch (FilesystemException e) { throw e.setPath(path).setAction(ActionType.CREATE); }
} }
@Override public FileStat stat(String path) throws FilesystemException { @Override public FileStat stat(String path) throws FilesystemException {
var filename = Filename.parse(path); try {
var protocol = protocols.get(filename.protocol); var filename = Filename.parse(path);
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST); var protocol = getProtocol(filename);
try { return protocol.stat(filename.path); } return protocol.stat(filename.path);
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); } }
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) { 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; package me.topchetoeu.jscript.utils.permissions;
import java.util.LinkedList;
public class Permission { 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 namespace;
public final String value; public final Matcher matcher;
public boolean match(Permission perm) { @Override public String toString() {
if (!Permission.match(namespace, perm.namespace, '.')) return false; return namespace;
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(String perm) { public Permission(String namespace, Matcher matcher) {
return match(new Permission(perm)); 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) { public Permission(String raw) {
var i = raw.indexOf(':'); this(raw, null);
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;
} }
} }

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; 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; import java.util.ArrayList;
public class PermissionsManager implements PermissionsProvider { 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 PermissionsProvider add(PermissionPredicate perm) {
public final ArrayList<Permission> denied = new ArrayList<>(); predicates.add(perm);
public PermissionsProvider add(Permission perm) {
allowed.add(perm);
return this; return this;
} }
public PermissionsProvider add(String perm) { public PermissionsProvider add(String perm) {
allowed.add(new Permission(perm)); predicates.add(new PermissionPredicate(perm));
return this; return this;
} }
@Override @Override public boolean hasPermission(Permission perm, String value) {
public boolean hasPermission(Permission perm, char delim) { for (var el : predicates) {
for (var el : denied) if (el.match(perm, delim)) return false; if (el.match(perm, value)) {
for (var el : allowed) if (el.match(perm, delim)) return true; if (el.denies) return false;
else return true;
}
}
return false; return false;
} }
@Override @Override public boolean hasPermission(Permission perm) {
public boolean hasPermission(Permission perm) { for (var el : predicates) {
for (var el : denied) if (el.match(perm)) return false; if (el.match(perm)) {
for (var el : allowed) if (el.match(perm)) return true; if (el.denies) return false;
else return true;
}
}
return false; 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 interface PermissionsProvider {
public static final Symbol ENV_KEY = new Symbol("Environment.perms"); 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, String value);
boolean hasPermission(Permission perm);
default boolean hasPermission(String perm, char delim) { default boolean hasPermission(Permission perm) {
return hasPermission(new Permission(perm), delim); 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) { public static PermissionsProvider get(Extensions exts) {
return new PermissionsProvider() { return (perm, value) -> {
@Override public boolean hasPermission(Permission perm) { if (exts.hasNotNull(ENV_KEY)) return ((PermissionsProvider)exts.get(ENV_KEY)).hasPermission(perm);
if (exts.hasNotNull(ENV_KEY)) return ((PermissionsProvider)exts.get(ENV_KEY)).hasPermission(perm); else return true;
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;
}
}; };
} }
} }