* remove "sandbox" feature which isn't used and probably wouldn't work that well anyhow
This commit is contained in:
parent
dc6cc5e9c1
commit
86cb93c040
|
@ -14,7 +14,6 @@ import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.security.AccessController;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.MissingResourceException;
|
import java.util.MissingResourceException;
|
||||||
|
@ -27,7 +26,6 @@ import javax.script.SimpleBindings;
|
||||||
import net.sourceforge.filebot.Analytics;
|
import net.sourceforge.filebot.Analytics;
|
||||||
import net.sourceforge.filebot.MediaTypes;
|
import net.sourceforge.filebot.MediaTypes;
|
||||||
import net.sourceforge.filebot.StandardRenameAction;
|
import net.sourceforge.filebot.StandardRenameAction;
|
||||||
import net.sourceforge.filebot.cli.ScriptShell.Script;
|
|
||||||
import net.sourceforge.filebot.cli.ScriptShell.ScriptProvider;
|
import net.sourceforge.filebot.cli.ScriptShell.ScriptProvider;
|
||||||
import net.sourceforge.filebot.web.CachedResource;
|
import net.sourceforge.filebot.web.CachedResource;
|
||||||
|
|
||||||
|
@ -88,7 +86,7 @@ public class ArgumentProcessor {
|
||||||
Bindings bindings = new SimpleBindings();
|
Bindings bindings = new SimpleBindings();
|
||||||
bindings.put("args", args.getFiles(false));
|
bindings.put("args", args.getFiles(false));
|
||||||
|
|
||||||
DefaultScriptProvider scriptProvider = new DefaultScriptProvider(args.trustScript);
|
DefaultScriptProvider scriptProvider = new DefaultScriptProvider();
|
||||||
URI script = scriptProvider.getScriptLocation(args.script);
|
URI script = scriptProvider.getScriptLocation(args.script);
|
||||||
|
|
||||||
if (!scriptProvider.isInlineScheme(script.getScheme())) {
|
if (!scriptProvider.isInlineScheme(script.getScheme())) {
|
||||||
|
@ -105,7 +103,7 @@ public class ArgumentProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ScriptShell shell = new ScriptShell(cli, args, AccessController.getContext(), scriptProvider);
|
ScriptShell shell = new ScriptShell(scriptProvider, args.bindings);
|
||||||
shell.runScript(script, bindings);
|
shell.runScript(script, bindings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,14 +122,8 @@ public class ArgumentProcessor {
|
||||||
|
|
||||||
public static class DefaultScriptProvider implements ScriptProvider {
|
public static class DefaultScriptProvider implements ScriptProvider {
|
||||||
|
|
||||||
private final boolean trustRemoteScript;
|
|
||||||
|
|
||||||
private URI baseScheme;
|
private URI baseScheme;
|
||||||
|
|
||||||
public DefaultScriptProvider(boolean trustRemoteScript) {
|
|
||||||
this.trustRemoteScript = trustRemoteScript;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBaseScheme(URI baseScheme) {
|
public void setBaseScheme(URI baseScheme) {
|
||||||
this.baseScheme = baseScheme;
|
this.baseScheme = baseScheme;
|
||||||
}
|
}
|
||||||
|
@ -145,19 +137,14 @@ public class ArgumentProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInlineScheme(String scheme) {
|
public boolean isInlineScheme(String scheme) {
|
||||||
return "g".equals(scheme) || "system".equals(scheme);
|
return "g".equals(scheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public URI getScriptLocation(String input) throws Exception {
|
public URI getScriptLocation(String input) throws Exception {
|
||||||
try {
|
try {
|
||||||
return new URL(input).toURI();
|
return new URL(input).toURI();
|
||||||
} catch (Exception _) {
|
} catch (Exception e) {
|
||||||
// system:in
|
|
||||||
if (input.equals("system:in")) {
|
|
||||||
return new URI("system", "in", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// g:println 'hello world'
|
// g:println 'hello world'
|
||||||
if (input.startsWith("g:")) {
|
if (input.startsWith("g:")) {
|
||||||
return new URI("g", input.substring(2), null);
|
return new URI("g", input.substring(2), null);
|
||||||
|
@ -185,31 +172,18 @@ public class ArgumentProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Script fetchScript(URI uri) throws IOException {
|
public String fetchScript(URI uri) throws IOException {
|
||||||
if (uri.getScheme().equals("file")) {
|
if (uri.getScheme().equals("file")) {
|
||||||
return new Script(readAll(new InputStreamReader(new FileInputStream(new File(uri)), "UTF-8")), true);
|
return readAll(new InputStreamReader(new FileInputStream(new File(uri)), "UTF-8"));
|
||||||
}
|
|
||||||
|
|
||||||
if (uri.getScheme().equals("system")) {
|
|
||||||
return new Script(readAll(new InputStreamReader(System.in)), true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uri.getScheme().equals("g")) {
|
if (uri.getScheme().equals("g")) {
|
||||||
return new Script(uri.getSchemeSpecificPart(), true);
|
return uri.getSchemeSpecificPart();
|
||||||
}
|
}
|
||||||
|
|
||||||
// remote script
|
// remote script
|
||||||
String url;
|
|
||||||
boolean trusted;
|
|
||||||
|
|
||||||
String resolver = getResourceTemplate(uri.getScheme());
|
String resolver = getResourceTemplate(uri.getScheme());
|
||||||
if (resolver != null) {
|
String url = (resolver != null) ? String.format(resolver, uri.getSchemeSpecificPart()) : uri.toString();
|
||||||
url = String.format(resolver, uri.getSchemeSpecificPart());
|
|
||||||
trusted = true;
|
|
||||||
} else {
|
|
||||||
url = uri.toString();
|
|
||||||
trusted = trustRemoteScript;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch remote script only if modified
|
// fetch remote script only if modified
|
||||||
CachedResource<String> script = new CachedResource<String>(url, String.class, CachedResource.ONE_DAY) {
|
CachedResource<String> script = new CachedResource<String>(url, String.class, CachedResource.ONE_DAY) {
|
||||||
|
@ -219,7 +193,7 @@ public class ArgumentProcessor {
|
||||||
return Charset.forName("UTF-8").decode(data).toString();
|
return Charset.forName("UTF-8").decode(data).toString();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return new Script(script.get(), trusted);
|
return script.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,8 @@ import java.io.IOException;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.security.AccessController;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ -164,10 +164,10 @@ public class GroovyPad extends JFrame {
|
||||||
|
|
||||||
protected ScriptShell createScriptShell() {
|
protected ScriptShell createScriptShell() {
|
||||||
try {
|
try {
|
||||||
DefaultScriptProvider scriptProvider = new DefaultScriptProvider(true);
|
DefaultScriptProvider scriptProvider = new DefaultScriptProvider();
|
||||||
scriptProvider.setBaseScheme(new URI("fn", "%s", null));
|
scriptProvider.setBaseScheme(new URI("fn", "%s", null));
|
||||||
|
|
||||||
return new ScriptShell(new CmdlineOperations(), ArgumentBean.parse(new String[0]), AccessController.getContext(), scriptProvider);
|
return new ScriptShell(scriptProvider, new HashMap<String, Object>());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
@ -232,15 +232,15 @@ public class GroovyPad extends JFrame {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
result = shell.evaluate(script, new SimpleBindings(), true);
|
result = shell.evaluate(script, new SimpleBindings());
|
||||||
|
|
||||||
// print result and make sure to flush Groovy output
|
// print result and make sure to flush Groovy output
|
||||||
SimpleBindings binding = new SimpleBindings();
|
SimpleBindings binding = new SimpleBindings();
|
||||||
binding.put("result", result);
|
binding.put("result", result);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
shell.evaluate("print('Result: '); println(result);", binding, true);
|
shell.evaluate("print('Result: '); println(result);", binding);
|
||||||
} else {
|
} else {
|
||||||
shell.evaluate("println();", binding, true);
|
shell.evaluate("println();", binding);
|
||||||
}
|
}
|
||||||
} catch (ScriptException e) {
|
} catch (ScriptException e) {
|
||||||
while (e.getCause() instanceof ScriptException) {
|
while (e.getCause() instanceof ScriptException) {
|
||||||
|
|
|
@ -2,35 +2,18 @@ package net.sourceforge.filebot.cli;
|
||||||
|
|
||||||
import groovy.lang.GroovyClassLoader;
|
import groovy.lang.GroovyClassLoader;
|
||||||
|
|
||||||
import java.awt.AWTPermission;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FilePermission;
|
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.lang.management.ManagementPermission;
|
|
||||||
import java.lang.reflect.ReflectPermission;
|
|
||||||
import java.net.SocketPermission;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.security.AccessControlContext;
|
|
||||||
import java.security.AccessController;
|
|
||||||
import java.security.Permissions;
|
|
||||||
import java.security.PrivilegedActionException;
|
|
||||||
import java.security.PrivilegedExceptionAction;
|
|
||||||
import java.security.ProtectionDomain;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.PropertyPermission;
|
|
||||||
import java.util.ResourceBundle;
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
import javax.script.Bindings;
|
import javax.script.Bindings;
|
||||||
import javax.script.ScriptContext;
|
import javax.script.ScriptContext;
|
||||||
import javax.script.ScriptEngine;
|
import javax.script.ScriptEngine;
|
||||||
import javax.script.ScriptException;
|
import javax.script.ScriptException;
|
||||||
import javax.script.SimpleBindings;
|
|
||||||
import javax.script.SimpleScriptContext;
|
import javax.script.SimpleScriptContext;
|
||||||
|
|
||||||
import net.sourceforge.filebot.format.ExpressionFormat;
|
import net.sourceforge.filebot.format.ExpressionFormat;
|
||||||
import net.sourceforge.filebot.format.PrivilegedInvocation;
|
|
||||||
|
|
||||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||||
import org.codehaus.groovy.control.customizers.ImportCustomizer;
|
import org.codehaus.groovy.control.customizers.ImportCustomizer;
|
||||||
|
@ -42,16 +25,26 @@ public class ScriptShell {
|
||||||
private final ScriptEngine engine;
|
private final ScriptEngine engine;
|
||||||
private final ScriptProvider scriptProvider;
|
private final ScriptProvider scriptProvider;
|
||||||
|
|
||||||
public ScriptShell(CmdlineInterface cli, ArgumentBean args, AccessControlContext acc, ScriptProvider scriptProvider) throws ScriptException {
|
public ScriptShell(ScriptProvider scriptProvider, Map<String, ?> globals) throws ScriptException {
|
||||||
this.engine = createScriptEngine();
|
this.engine = createScriptEngine();
|
||||||
this.scriptProvider = scriptProvider;
|
this.scriptProvider = scriptProvider;
|
||||||
|
|
||||||
|
// setup bindings
|
||||||
|
Bindings bindings = engine.createBindings();
|
||||||
|
bindings.putAll(globals);
|
||||||
|
|
||||||
|
// bind API objects
|
||||||
|
// TODO remove
|
||||||
|
bindings.put("_cli", new CmdlineOperations());
|
||||||
|
bindings.put("_shell", this);
|
||||||
|
|
||||||
// setup script context
|
// setup script context
|
||||||
ScriptContext context = new SimpleScriptContext();
|
ScriptContext context = new SimpleScriptContext();
|
||||||
context.setBindings(initializeBindings(cli, args, acc), ScriptContext.GLOBAL_SCOPE);
|
context.setBindings(bindings, ScriptContext.GLOBAL_SCOPE);
|
||||||
engine.setContext(context);
|
engine.setContext(context);
|
||||||
|
|
||||||
// import additional functions into the shell environment
|
// import additional functions into the shell environment
|
||||||
|
// TODO remove
|
||||||
engine.eval(new InputStreamReader(ExpressionFormat.class.getResourceAsStream("ExpressionFormat.lib.groovy")));
|
engine.eval(new InputStreamReader(ExpressionFormat.class.getResourceAsStream("ExpressionFormat.lib.groovy")));
|
||||||
engine.eval(new InputStreamReader(ScriptShell.class.getResourceAsStream("ScriptShell.lib.groovy")));
|
engine.eval(new InputStreamReader(ScriptShell.class.getResourceAsStream("ScriptShell.lib.groovy")));
|
||||||
}
|
}
|
||||||
|
@ -74,22 +67,22 @@ public class ScriptShell {
|
||||||
return new GroovyScriptEngineImpl(classLoader);
|
return new GroovyScriptEngineImpl(classLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Object evaluate(final String script, final Bindings bindings) throws Throwable {
|
||||||
|
try {
|
||||||
|
return engine.eval(script, bindings);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
while (e.getClass() == ScriptException.class && e.getCause() != null) {
|
||||||
|
e = e.getCause();
|
||||||
|
}
|
||||||
|
throw StackTraceUtils.deepSanitize(e); // make Groovy stacktrace human-readable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static interface ScriptProvider {
|
public static interface ScriptProvider {
|
||||||
|
|
||||||
public URI getScriptLocation(String input) throws Exception;
|
public URI getScriptLocation(String input) throws Exception;
|
||||||
|
|
||||||
public Script fetchScript(URI uri) throws Exception;
|
public String fetchScript(URI uri) throws Exception;
|
||||||
}
|
|
||||||
|
|
||||||
public static class Script {
|
|
||||||
|
|
||||||
public final String code;
|
|
||||||
public final boolean trusted;
|
|
||||||
|
|
||||||
public Script(String code, boolean trusted) {
|
|
||||||
this.code = code;
|
|
||||||
this.trusted = trusted;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object runScript(String input, Bindings bindings) throws Throwable {
|
public Object runScript(String input, Bindings bindings) throws Throwable {
|
||||||
|
@ -97,90 +90,7 @@ public class ScriptShell {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object runScript(URI resource, Bindings bindings) throws Throwable {
|
public Object runScript(URI resource, Bindings bindings) throws Throwable {
|
||||||
Script script = scriptProvider.fetchScript(resource);
|
return evaluate(scriptProvider.fetchScript(resource), bindings);
|
||||||
return evaluate(script.code, bindings, script.trusted);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object evaluate(final String script, final Bindings bindings, boolean trustScript) throws Throwable {
|
|
||||||
try {
|
|
||||||
if (trustScript) {
|
|
||||||
return engine.eval(script, bindings);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object run() throws ScriptException {
|
|
||||||
return engine.eval(script, bindings);
|
|
||||||
}
|
|
||||||
}, getSandboxAccessControlContext());
|
|
||||||
} catch (PrivilegedActionException e) {
|
|
||||||
throw e.getException();
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
while (e.getClass() == ScriptException.class && e.getCause() != null) {
|
|
||||||
e = e.getCause();
|
|
||||||
}
|
|
||||||
throw StackTraceUtils.deepSanitize(e); // make Groovy stack human-readable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Bindings initializeBindings(CmdlineInterface cli, ArgumentBean args, AccessControlContext acc) {
|
|
||||||
Bindings bindings = new SimpleBindings();
|
|
||||||
|
|
||||||
// bind external parameters
|
|
||||||
if (args.bindings != null) {
|
|
||||||
for (Entry<String, String> it : args.bindings.entrySet()) {
|
|
||||||
bindings.put(it.getKey(), it.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// bind API objects
|
|
||||||
bindings.put("_cli", PrivilegedInvocation.newProxy(CmdlineInterface.class, cli, acc));
|
|
||||||
bindings.put("_script", args.script);
|
|
||||||
bindings.put("_args", args);
|
|
||||||
bindings.put("_shell", this);
|
|
||||||
|
|
||||||
Map<String, String> defines = new LinkedHashMap<String, String>();
|
|
||||||
if (args.bindings != null) {
|
|
||||||
for (Entry<String, String> it : args.bindings.entrySet()) {
|
|
||||||
defines.put(it.getKey(), it.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bindings.put("_def", defines);
|
|
||||||
|
|
||||||
return bindings;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected AccessControlContext getSandboxAccessControlContext() {
|
|
||||||
Permissions permissions = new Permissions();
|
|
||||||
|
|
||||||
permissions.add(new RuntimePermission("createClassLoader"));
|
|
||||||
permissions.add(new RuntimePermission("accessClassInPackage.*"));
|
|
||||||
permissions.add(new RuntimePermission("modifyThread"));
|
|
||||||
permissions.add(new FilePermission("<<ALL FILES>>", "read"));
|
|
||||||
permissions.add(new SocketPermission("*", "connect"));
|
|
||||||
permissions.add(new PropertyPermission("*", "read"));
|
|
||||||
permissions.add(new RuntimePermission("getenv.*"));
|
|
||||||
permissions.add(new RuntimePermission("getFileSystemAttributes"));
|
|
||||||
permissions.add(new ManagementPermission("monitor"));
|
|
||||||
|
|
||||||
// write permissions for temp and cache folders
|
|
||||||
permissions.add(new FilePermission(new File(System.getProperty("ehcache.disk.store.dir")).getAbsolutePath() + File.separator + "-", "write, delete"));
|
|
||||||
permissions.add(new FilePermission(new File(System.getProperty("java.io.tmpdir")).getAbsolutePath() + File.separator + "-", "write, delete"));
|
|
||||||
|
|
||||||
// AWT / Swing permissions
|
|
||||||
permissions.add(new AWTPermission("accessEventQueue"));
|
|
||||||
permissions.add(new AWTPermission("toolkitModality"));
|
|
||||||
permissions.add(new AWTPermission("showWindowWithoutWarningBanner"));
|
|
||||||
|
|
||||||
// this is probably a security problem but nevermind
|
|
||||||
permissions.add(new RuntimePermission("accessDeclaredMembers"));
|
|
||||||
permissions.add(new ReflectPermission("suppressAccessChecks"));
|
|
||||||
permissions.add(new RuntimePermission("modifyThread"));
|
|
||||||
|
|
||||||
return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, permissions) });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
scriptBaseClass: net.sourceforge.filebot.cli.ScriptShellBaseClass
|
scriptBaseClass: net.sourceforge.filebot.cli.ScriptShellBaseClass
|
||||||
starImport: net.sourceforge.filebot, net.sourceforge.filebot.util, net.sourceforge.filebot.web, net.sourceforge.filebot.media, net.sourceforge.filebot.mediainfo, net.sourceforge.filebot.hash, net.sourceforge.filebot.similarity, groovy.io, groovy.xml, groovy.json, org.jsoup, java.nio.file, java.nio.file.attribute, java.util.regex
|
starImport: net.sourceforge.filebot, net.sourceforge.filebot.hash, net.sourceforge.filebot.media, net.sourceforge.filebot.mediainfo, net.sourceforge.filebot.similarity, net.sourceforge.filebot.subtitle, net.sourceforge.filebot.torrent, net.sourceforge.filebot.web, net.sourceforge.filebot.util, groovy.io, groovy.xml, groovy.json, org.jsoup, java.nio.file, java.nio.file.attribute, java.util.regex
|
||||||
starStaticImport: net.sourceforge.filebot.WebServices, net.sourceforge.filebot.media.MediaDetection, java.nio.file.Files
|
starStaticImport: net.sourceforge.filebot.WebServices, net.sourceforge.filebot.media.MediaDetection, java.nio.file.Files
|
|
@ -1,10 +1,12 @@
|
||||||
package net.sourceforge.filebot.cli;
|
package net.sourceforge.filebot.cli;
|
||||||
|
|
||||||
|
import static net.sourceforge.filebot.Settings.*;
|
||||||
import static net.sourceforge.filebot.cli.CLILogging.*;
|
import static net.sourceforge.filebot.cli.CLILogging.*;
|
||||||
import groovy.lang.Closure;
|
import groovy.lang.Closure;
|
||||||
import groovy.lang.Script;
|
import groovy.lang.Script;
|
||||||
|
|
||||||
import java.io.Console;
|
import java.io.Console;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import net.sourceforge.filebot.MediaTypes;
|
import net.sourceforge.filebot.MediaTypes;
|
||||||
|
@ -30,6 +32,16 @@ public abstract class ScriptShellBaseClass extends Script {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// define global variable: _args
|
||||||
|
public ArgumentBean get_args() {
|
||||||
|
return getApplicationArguments();
|
||||||
|
}
|
||||||
|
|
||||||
|
// define global variable: _def
|
||||||
|
public Map<String, String> get_def() {
|
||||||
|
return getApplicationArguments().bindings;
|
||||||
|
}
|
||||||
|
|
||||||
// define global variable: _system
|
// define global variable: _system
|
||||||
public AssociativeScriptObject get_system() {
|
public AssociativeScriptObject get_system() {
|
||||||
return new AssociativeScriptObject(System.getProperties());
|
return new AssociativeScriptObject(System.getProperties());
|
||||||
|
|
Loading…
Reference in New Issue