Disable untrusted scripts on loading files

This commit is contained in:
Sampo Niskanen 2015-01-06 19:25:13 +02:00
parent a39a3fce15
commit a1f6782195
16 changed files with 554 additions and 206 deletions

View File

@ -386,6 +386,7 @@ simedtdlg.but.ttip.resettodefault = Reset the time step to its default value (
simedtdlg.border.SimExt = Simulation extensions simedtdlg.border.SimExt = Simulation extensions
simedtdlg.SimExt.desc = <html><i>Simulation extensions</i> enable advanced features and custom functionality during flight simulations. You can for example do hardware-in-the-loop testing with them. simedtdlg.SimExt.desc = <html><i>Simulation extensions</i> enable advanced features and custom functionality during flight simulations. You can for example do hardware-in-the-loop testing with them.
simedtdlg.SimExt.noExtensions = No simulation extensions defined simedtdlg.SimExt.noExtensions = No simulation extensions defined
simedtdlg.SimExt.add = Add extension
simedtdlg.lbl.Noflightdata = No flight data available. simedtdlg.lbl.Noflightdata = No flight data available.
simedtdlg.lbl.runsimfirst = Please run the simulation first. simedtdlg.lbl.runsimfirst = Please run the simulation first.
simedtdlg.chart.Simflight = Simulated flight simedtdlg.chart.Simflight = Simulated flight
@ -408,6 +409,15 @@ SimulationExtension.javacode.className = Fully-qualified Java class name:
SimulationExtension.scripting.name = {language} script SimulationExtension.scripting.name = {language} script
SimulationExtension.scripting.desc = Extend OpenRocket simulations by custom scripts. SimulationExtension.scripting.desc = Extend OpenRocket simulations by custom scripts.
SimulationExtension.scripting.language.label = Language: SimulationExtension.scripting.language.label = Language:
SimulationExtension.scripting.warning.disabled = Untrusted scripts have been disabled. You need to manually enable them.
SimulationExtension.scripting.text.enabled = Enable script
SimulationExtension.scripting.text.enabled.ttip = The script is run only when enabled.
SimulationExtension.scripting.text.trusted = Trust this script on this computer
SimulationExtension.scripting.text.trusted.msg = Untrusted scripts are disabled when loading the document
SimulationExtension.scripting.text.trusted.clear = Clear trusted scripts
SimulationExtension.scripting.text.trusted.clear.ttip = Clear the trusted status of all scripts on this computer
SimulationExtension.scripting.text.trusted.cleared = All scripts are now untrusted on this computer.
SimulationExtension.scripting.text.trusted.cleared.title = Cleared
SimulationEditDialog.btn.plot = Plot SimulationEditDialog.btn.plot = Plot
SimulationEditDialog.btn.export = Export SimulationEditDialog.btn.export = Export

View File

@ -3,6 +3,9 @@ package net.sf.openrocket.simulation.extension;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.l10n.Translator; import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.simulation.FlightDataType; import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.util.AbstractChangeSource; import net.sf.openrocket.util.AbstractChangeSource;
@ -42,7 +45,9 @@ public abstract class AbstractSimulationExtension extends AbstractChangeSource i
} }
/** /**
* By default, returns the canonical name of this class. * {@inheritDoc}
* <p>
* By default, this method returns the canonical name of this class.
*/ */
@Override @Override
public String getId() { public String getId() {
@ -50,7 +55,9 @@ public abstract class AbstractSimulationExtension extends AbstractChangeSource i
} }
/** /**
* By default, returns the name provided to the constructor. * {@inheritDoc}
* <p>
* By default, this method returns the name provided to the constructor.
*/ */
@Override @Override
public String getName() { public String getName() {
@ -58,7 +65,9 @@ public abstract class AbstractSimulationExtension extends AbstractChangeSource i
} }
/** /**
* By default, returns null. * {@inheritDoc}
* <p>
* By default, this method returns null.
*/ */
@Override @Override
public String getDescription() { public String getDescription() {
@ -66,7 +75,9 @@ public abstract class AbstractSimulationExtension extends AbstractChangeSource i
} }
/** /**
* By default, returns an empty list. * {@inheritDoc}
* <p>
* By default, this method returns an empty list.
*/ */
@Override @Override
public List<FlightDataType> getFlightDataTypes() { public List<FlightDataType> getFlightDataTypes() {
@ -74,7 +85,18 @@ public abstract class AbstractSimulationExtension extends AbstractChangeSource i
} }
/** /**
* By default, returns a new object obtained by calling Object.clone(). * {@inheritDoc}
* <p>
* By default, this method does nothing.
*/
@Override
public void documentLoaded(OpenRocketDocument document, Simulation simulation, WarningSet warnings) {
}
/**
* By default, returns a new object obtained by calling Object.clone() and
* cloning the config object.
*/ */
@Override @Override
public SimulationExtension clone() { public SimulationExtension clone() {

View File

@ -2,6 +2,9 @@ package net.sf.openrocket.simulation.extension;
import java.util.List; import java.util.List;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.simulation.FlightDataType; import net.sf.openrocket.simulation.FlightDataType;
import net.sf.openrocket.simulation.SimulationConditions; import net.sf.openrocket.simulation.SimulationConditions;
import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.simulation.exception.SimulationException;
@ -35,6 +38,16 @@ public interface SimulationExtension {
*/ */
public String getDescription(); public String getDescription();
/**
* Called once for each simulation this extension is attached to when loading a document.
* This may perform necessary changes to the document at load time.
*
* @param document the loaded document
* @param simulation the simulation this extension is attached to
* @param warnings the document loading warnings
*/
public void documentLoaded(OpenRocketDocument document, Simulation simulation, WarningSet warnings);
/** /**
* Initialize this simulation extension for running within a simulation. * Initialize this simulation extension for running within a simulation.
* This method is called before running a simulation. It can either modify * This method is called before running a simulation. It can either modify

View File

@ -5,18 +5,30 @@ import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager; import javax.script.ScriptEngineManager;
import javax.script.ScriptException; import javax.script.ScriptException;
import net.sf.openrocket.aerodynamics.Warning;
import net.sf.openrocket.aerodynamics.WarningSet;
import net.sf.openrocket.document.OpenRocketDocument;
import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.l10n.L10N; import net.sf.openrocket.l10n.L10N;
import net.sf.openrocket.simulation.SimulationConditions; import net.sf.openrocket.simulation.SimulationConditions;
import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.extension.AbstractSimulationExtension; import net.sf.openrocket.simulation.extension.AbstractSimulationExtension;
import net.sf.openrocket.simulation.listeners.SimulationListener; import net.sf.openrocket.simulation.listeners.SimulationListener;
import com.google.inject.Inject;
public class ScriptingExtension extends AbstractSimulationExtension { public class ScriptingExtension extends AbstractSimulationExtension {
private static final String DEFAULT_LANGUAGE = "JavaScript"; private static final String DEFAULT_LANGUAGE = "JavaScript";
@Inject
private ScriptingUtil util;
public ScriptingExtension() { public ScriptingExtension() {
setLanguage(DEFAULT_LANGUAGE); setLanguage(DEFAULT_LANGUAGE);
setScript("");
setEnabled(true);
} }
@Override @Override
@ -31,9 +43,25 @@ public class ScriptingExtension extends AbstractSimulationExtension {
return trans.get("SimulationExtension.scripting.desc"); return trans.get("SimulationExtension.scripting.desc");
} }
@Override
public void documentLoaded(OpenRocketDocument document, Simulation simulation, WarningSet warnings) {
/*
* Scripts that the user has not explicitly indicated as trusted are disabled
* when loading from a file. This is to prevent trojans.
*/
if (isEnabled()) {
if (!util.isTrustedScript(getLanguage(), getScript())) {
setEnabled(false);
warnings.add(Warning.fromString(trans.get("SimulationExtension.scripting.warning.disabled")));
}
}
}
@Override @Override
public void initialize(SimulationConditions conditions) throws SimulationException { public void initialize(SimulationConditions conditions) throws SimulationException {
conditions.getSimulationListenerList().add(getListener()); if (isEnabled()) {
conditions.getSimulationListenerList().add(getListener());
}
} }
@ -53,6 +81,14 @@ public class ScriptingExtension extends AbstractSimulationExtension {
config.put("language", language); config.put("language", language);
} }
public boolean isEnabled() {
return config.getBoolean("enabled", false);
}
public void setEnabled(boolean enabled) {
config.put("enabled", enabled);
}
SimulationListener getListener() throws SimulationException { SimulationListener getListener() throws SimulationException {
ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngineManager manager = new ScriptEngineManager();

View File

@ -18,6 +18,7 @@ import net.sf.openrocket.simulation.FlightEvent;
import net.sf.openrocket.simulation.MassData; import net.sf.openrocket.simulation.MassData;
import net.sf.openrocket.simulation.SimulationStatus; import net.sf.openrocket.simulation.SimulationStatus;
import net.sf.openrocket.simulation.exception.SimulationException; import net.sf.openrocket.simulation.exception.SimulationException;
import net.sf.openrocket.simulation.exception.SimulationListenerException;
import net.sf.openrocket.simulation.listeners.SimulationComputationListener; import net.sf.openrocket.simulation.listeners.SimulationComputationListener;
import net.sf.openrocket.simulation.listeners.SimulationEventListener; import net.sf.openrocket.simulation.listeners.SimulationEventListener;
import net.sf.openrocket.simulation.listeners.SimulationListener; import net.sf.openrocket.simulation.listeners.SimulationListener;
@ -68,25 +69,25 @@ public class ScriptingSimulationListener implements SimulationListener, Simulati
@Override @Override
public void startSimulation(SimulationStatus status) throws SimulationException { public void startSimulation(SimulationStatus status) throws SimulationException {
invoke(null, "startSimulation", status); invoke(Void.class, null, "startSimulation", status);
} }
@Override @Override
public void endSimulation(SimulationStatus status, SimulationException exception) { public void endSimulation(SimulationStatus status, SimulationException exception) {
try { try {
invoke(null, "endSimulation", status, exception); invoke(Void.class, null, "endSimulation", status, exception);
} catch (SimulationException e) { } catch (SimulationException e) {
} }
} }
@Override @Override
public boolean preStep(SimulationStatus status) throws SimulationException { public boolean preStep(SimulationStatus status) throws SimulationException {
return invoke(true, "preStep", status); return invoke(Boolean.class, true, "preStep", status);
} }
@Override @Override
public void postStep(SimulationStatus status) throws SimulationException { public void postStep(SimulationStatus status) throws SimulationException {
invoke(null, "postStep", status); invoke(Void.class, null, "postStep", status);
} }
@ -95,22 +96,22 @@ public class ScriptingSimulationListener implements SimulationListener, Simulati
@Override @Override
public boolean addFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException { public boolean addFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException {
return invoke(true, "addFlightEvent", status, event); return invoke(Boolean.class, true, "addFlightEvent", status, event);
} }
@Override @Override
public boolean handleFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException { public boolean handleFlightEvent(SimulationStatus status, FlightEvent event) throws SimulationException {
return invoke(true, "handleFlightEvent", status, event); return invoke(Boolean.class, true, "handleFlightEvent", status, event);
} }
@Override @Override
public boolean motorIgnition(SimulationStatus status, MotorId motorId, MotorMount mount, MotorInstance instance) throws SimulationException { public boolean motorIgnition(SimulationStatus status, MotorId motorId, MotorMount mount, MotorInstance instance) throws SimulationException {
return invoke(true, "motorIgnition", status, motorId, mount, instance); return invoke(Boolean.class, true, "motorIgnition", status, motorId, mount, instance);
} }
@Override @Override
public boolean recoveryDeviceDeployment(SimulationStatus status, RecoveryDevice recoveryDevice) throws SimulationException { public boolean recoveryDeviceDeployment(SimulationStatus status, RecoveryDevice recoveryDevice) throws SimulationException {
return invoke(true, "recoveryDeviceDeployment", status, recoveryDevice); return invoke(Boolean.class, true, "recoveryDeviceDeployment", status, recoveryDevice);
} }
@ -119,90 +120,99 @@ public class ScriptingSimulationListener implements SimulationListener, Simulati
@Override @Override
public AccelerationData preAccelerationCalculation(SimulationStatus status) throws SimulationException { public AccelerationData preAccelerationCalculation(SimulationStatus status) throws SimulationException {
return invoke(null, "preAccelerationCalculation", status); return invoke(AccelerationData.class, null, "preAccelerationCalculation", status);
} }
@Override @Override
public AerodynamicForces preAerodynamicCalculation(SimulationStatus status) throws SimulationException { public AerodynamicForces preAerodynamicCalculation(SimulationStatus status) throws SimulationException {
return invoke(null, "preAerodynamicCalculation", status); return invoke(AerodynamicForces.class, null, "preAerodynamicCalculation", status);
} }
@Override @Override
public AtmosphericConditions preAtmosphericModel(SimulationStatus status) throws SimulationException { public AtmosphericConditions preAtmosphericModel(SimulationStatus status) throws SimulationException {
return invoke(null, "preAtmosphericModel", status); return invoke(AtmosphericConditions.class, null, "preAtmosphericModel", status);
} }
@Override @Override
public FlightConditions preFlightConditions(SimulationStatus status) throws SimulationException { public FlightConditions preFlightConditions(SimulationStatus status) throws SimulationException {
return invoke(null, "preFlightConditions", status); return invoke(FlightConditions.class, null, "preFlightConditions", status);
} }
@Override @Override
public double preGravityModel(SimulationStatus status) throws SimulationException { public double preGravityModel(SimulationStatus status) throws SimulationException {
return invoke(Double.NaN, "preGravityModel", status); return invoke(Double.class, Double.NaN, "preGravityModel", status);
} }
@Override @Override
public MassData preMassCalculation(SimulationStatus status) throws SimulationException { public MassData preMassCalculation(SimulationStatus status) throws SimulationException {
return invoke(null, "preMassCalculation", status); return invoke(MassData.class, null, "preMassCalculation", status);
} }
@Override @Override
public double preSimpleThrustCalculation(SimulationStatus status) throws SimulationException { public double preSimpleThrustCalculation(SimulationStatus status) throws SimulationException {
return invoke(Double.NaN, "preSimpleThrustCalculation", status); return invoke(Double.class, Double.NaN, "preSimpleThrustCalculation", status);
} }
@Override @Override
public Coordinate preWindModel(SimulationStatus status) throws SimulationException { public Coordinate preWindModel(SimulationStatus status) throws SimulationException {
return invoke(null, "preWindModel", status); return invoke(Coordinate.class, null, "preWindModel", status);
} }
@Override @Override
public AccelerationData postAccelerationCalculation(SimulationStatus status, AccelerationData acceleration) throws SimulationException { public AccelerationData postAccelerationCalculation(SimulationStatus status, AccelerationData acceleration) throws SimulationException {
return invoke(null, "postAccelerationCalculation", status, acceleration); return invoke(AccelerationData.class, null, "postAccelerationCalculation", status, acceleration);
} }
@Override @Override
public AerodynamicForces postAerodynamicCalculation(SimulationStatus status, AerodynamicForces forces) throws SimulationException { public AerodynamicForces postAerodynamicCalculation(SimulationStatus status, AerodynamicForces forces) throws SimulationException {
return invoke(null, "postAerodynamicCalculation", status, forces); return invoke(AerodynamicForces.class, null, "postAerodynamicCalculation", status, forces);
} }
@Override @Override
public AtmosphericConditions postAtmosphericModel(SimulationStatus status, AtmosphericConditions atmosphericConditions) throws SimulationException { public AtmosphericConditions postAtmosphericModel(SimulationStatus status, AtmosphericConditions atmosphericConditions) throws SimulationException {
return invoke(null, "postAtmosphericModel", status, atmosphericConditions); return invoke(AtmosphericConditions.class, null, "postAtmosphericModel", status, atmosphericConditions);
} }
@Override @Override
public FlightConditions postFlightConditions(SimulationStatus status, FlightConditions flightConditions) throws SimulationException { public FlightConditions postFlightConditions(SimulationStatus status, FlightConditions flightConditions) throws SimulationException {
return invoke(null, "postFlightConditions", status, flightConditions); return invoke(FlightConditions.class, null, "postFlightConditions", status, flightConditions);
} }
@Override @Override
public double postGravityModel(SimulationStatus status, double gravity) throws SimulationException { public double postGravityModel(SimulationStatus status, double gravity) throws SimulationException {
return invoke(Double.NaN, "postGravityModel", status, gravity); return invoke(Double.class, Double.NaN, "postGravityModel", status, gravity);
} }
@Override @Override
public MassData postMassCalculation(SimulationStatus status, MassData massData) throws SimulationException { public MassData postMassCalculation(SimulationStatus status, MassData massData) throws SimulationException {
return invoke(null, "postMassCalculation", status, massData); return invoke(MassData.class, null, "postMassCalculation", status, massData);
} }
@Override @Override
public double postSimpleThrustCalculation(SimulationStatus status, double thrust) throws SimulationException { public double postSimpleThrustCalculation(SimulationStatus status, double thrust) throws SimulationException {
return invoke(Double.NaN, "postSimpleThrustCalculation", status, thrust); return invoke(Double.class, Double.NaN, "postSimpleThrustCalculation", status, thrust);
} }
@Override @Override
public Coordinate postWindModel(SimulationStatus status, Coordinate wind) throws SimulationException { public Coordinate postWindModel(SimulationStatus status, Coordinate wind) throws SimulationException {
return invoke(null, "postWindModel", status, wind); return invoke(Coordinate.class, null, "postWindModel", status, wind);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T> T invoke(T def, String method, Object... args) throws SimulationException { private <T> T invoke(Class<T> retType, T def, String method, Object... args) throws SimulationException {
try { try {
if (!missing.contains(method)) { if (!missing.contains(method)) {
return (T) invocable.invokeFunction(method, args); Object o = invocable.invokeFunction(method, args);
if (o == null) {
// Use default/null if function returns nothing
return def;
} else if (!o.getClass().equals(retType)) {
throw new SimulationListenerException("Custom script function " + method + " returned type " +
o.getClass().getSimpleName() + ", expected " + retType.getSimpleName());
} else {
return (T) o;
}
} }
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
missing.add(method); missing.add(method);

View File

@ -0,0 +1,150 @@
package net.sf.openrocket.simulation.extension.impl;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;
import java.util.prefs.BackingStoreException;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import net.sf.openrocket.startup.Preferences;
import net.sf.openrocket.util.ArrayList;
import net.sf.openrocket.util.BugException;
import com.google.inject.Inject;
/**
* Utility class used by the scripting extension and its configurator.
*/
public class ScriptingUtil {
static final String NODE_ID = ScriptingExtension.class.getCanonicalName();
/** The name to be chosen from a list of alternatives. If not found, will use the default name. */
private static final List<String> PREFERRED_LANGUAGE_NAMES = Arrays.asList("JavaScript");
@Inject
Preferences prefs;
/**
* Return the preferred internal language name based on a script language name.
*
* @return the preferred language name, or null if the language is not supported.
*/
public String getLanguage(String language) {
if (language == null) {
return null;
}
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName(language);
if (engine == null) {
return null;
}
return getLanguage(engine.getFactory());
}
public List<String> getLanguages() {
List<String> langs = new ArrayList<String>();
ScriptEngineManager manager = new ScriptEngineManager();
for (ScriptEngineFactory factory : manager.getEngineFactories()) {
langs.add(getLanguage(factory));
}
return langs;
}
private String getLanguage(ScriptEngineFactory factory) {
for (String name : factory.getNames()) {
if (PREFERRED_LANGUAGE_NAMES.contains(name)) {
return name;
}
}
return factory.getLanguageName();
}
/**
* Test whether the user has indicated this script to be trusted,
* or if it is an internally trusted script.
*/
public boolean isTrustedScript(String language, String script) {
if (language == null || script == null) {
return false;
}
script = normalize(script);
if (script.length() == 0) {
return true;
}
String hash = hash(language, script);
return prefs.getNode(NODE_ID).getBoolean(hash, false);
}
/**
* Mark a script as trusted.
*/
public void setTrustedScript(String language, String script, boolean trusted) {
script = normalize(script);
String hash = hash(language, script);
if (trusted) {
prefs.getNode(NODE_ID).putBoolean(hash, true);
} else {
prefs.getNode(NODE_ID).remove(hash);
}
}
/**
* Clear all trusted scripts.
*/
public void clearTrustedScripts() {
try {
prefs.getNode(NODE_ID).clear();
} catch (BackingStoreException e) {
throw new BugException(e);
}
}
static String normalize(String script) {
return script.replaceAll("\r", "").trim();
}
static String hash(String language, String script) {
/*
* NOTE: Hash length must be max 80 chars, the max length of a key in a Properties object.
*/
String output;
MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA-256");
digest.update(language.getBytes("UTF-8"));
digest.update((byte) '|');
byte[] hash = digest.digest(script.getBytes("UTF-8"));
BigInteger bigInt = new BigInteger(1, hash);
output = bigInt.toString(16);
while (output.length() < 64) {
output = "0" + output;
}
} catch (NoSuchAlgorithmException e) {
throw new BugException("JRE does not support SHA-256 hash algorithm", e);
} catch (UnsupportedEncodingException e) {
throw new BugException(e);
}
return digest.getAlgorithm() + ":" + output;
}
}

View File

@ -0,0 +1,88 @@
package net.sf.openrocket.simulation.extension.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import net.sf.openrocket.startup.MockPreferences;
import org.junit.Before;
import org.junit.Test;
public class TestScriptingUtil {
private static final String HASH_JavaScript_foobar = "SHA-256:8f06133e0235d239355b5ca8ca0b43dece803c29b2a563222519d982abd3fc43";
private ScriptingUtil util;
@Before
public void setup() {
util = new ScriptingUtil();
util.prefs = new MockPreferences();
}
/*
* Note: This class assumes that the JRE supports JavaScript scripting.
*/
@Test
public void testGetLanguage() {
assertEquals(null, util.getLanguage(null));
assertEquals(null, util.getLanguage(""));
assertEquals(null, util.getLanguage("foobar"));
assertEquals("JavaScript", util.getLanguage("JavaScript"));
assertEquals("JavaScript", util.getLanguage("javascript"));
assertEquals("JavaScript", util.getLanguage("ECMAScript"));
assertEquals("JavaScript", util.getLanguage("js"));
}
@Test
public void testGetLanguages() {
assertTrue(util.getLanguages().size() >= 1);
assertTrue(util.getLanguages().contains("JavaScript"));
}
@Test
public void testIsTrustedScript() {
util.setTrustedScript("JavaScript", "foobar", true);
assertTrue(util.isTrustedScript("JavaScript", "foobar"));
assertTrue(util.isTrustedScript("JavaScript", " \n foobar \n\t\r"));
assertFalse(util.isTrustedScript("JavaScript", "foo\nbar"));
assertFalse(util.isTrustedScript("Javascript", "foobar"));
// Empty script is always considered trusted
assertFalse(util.isTrustedScript("foo", null));
assertTrue(util.isTrustedScript("foo", ""));
assertTrue(util.isTrustedScript("foo", " \n\r\t "));
}
@Test
public void testSetTrustedScript() {
util.setTrustedScript("JavaScript", " \n foobar \n\r ", true);
assertTrue(util.prefs.getNode(ScriptingUtil.NODE_ID).getBoolean(HASH_JavaScript_foobar, false));
util.setTrustedScript("JavaScript", " foobar ", false);
assertTrue(util.prefs.getNode(ScriptingUtil.NODE_ID).getBoolean(HASH_JavaScript_foobar, true));
assertFalse(util.prefs.getNode(ScriptingUtil.NODE_ID).getBoolean(HASH_JavaScript_foobar, false));
}
@Test
public void testClearTrustedScripts() {
util.setTrustedScript("JavaScript", "foobar", true);
assertTrue(util.isTrustedScript("JavaScript", "foobar"));
util.clearTrustedScripts();
assertFalse(util.isTrustedScript("JavaScript", "foobar"));
}
@Test
public void testNormalize() {
assertEquals("foo", ScriptingUtil.normalize("foo"));
assertEquals("foo bar", ScriptingUtil.normalize(" \n\r\t foo \r bar \n\t\r "));
}
@Test
public void testHash() {
assertEquals("SHA-256:12e6a78889b96a16d305b8e4af81119545f89eccba5fb37cc3a1ec2c53eab514", ScriptingUtil.hash("JS", ""));
assertEquals("SHA-256:000753e5deb2d8fa80e602ca03bcdb8e12a6b14b2b4a4d0abecdc976ad26e3ef", ScriptingUtil.hash("foo", "1165"));
assertEquals(HASH_JavaScript_foobar, ScriptingUtil.hash("JavaScript", "foobar"));
}
}

View File

@ -92,6 +92,8 @@ public abstract class Preferences {
public abstract void putString(String directory, String key, String value); public abstract void putString(String directory, String key, String value);
public abstract java.util.prefs.Preferences getNode(String nodeName);
/* /*
* ****************************************************************************************** * ******************************************************************************************
*/ */

View File

@ -1,53 +0,0 @@
package net.sf.openrocket.util;
import java.util.Arrays;
import java.util.List;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
public class ScriptingUtil {
/** The name to be chosen from a list of alternatives. If not found, will use the default name. */
private static final List<String> PREFERRED_LANGUAGE_NAMES = Arrays.asList("JavaScript");
/**
* Return the preferred internal language name based on a script language name.
*
* @return the preferred language name, or null if the language is not supported.
*/
public static String getLanguage(String language) {
if (language == null) {
return null;
}
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName(language);
if (engine == null) {
return null;
}
return getLanguage(engine.getFactory());
}
public static List<String> getLanguages() {
List<String> langs = new ArrayList<String>();
ScriptEngineManager manager = new ScriptEngineManager();
for (ScriptEngineFactory factory : manager.getEngineFactories()) {
langs.add(getLanguage(factory));
}
return langs;
}
private static String getLanguage(ScriptEngineFactory factory) {
for (String name : factory.getNames()) {
if (PREFERRED_LANGUAGE_NAMES.contains(name)) {
return name;
}
}
return factory.getLanguageName();
}
}

View File

@ -151,5 +151,11 @@ public class ServicesForTesting extends AbstractModule {
return null; return null;
} }
@Override
public java.util.prefs.Preferences getNode(String nodeName) {
// TODO Auto-generated method stub
return null;
}
} }
} }

View File

@ -0,0 +1,108 @@
package net.sf.openrocket.startup;
import java.util.Set;
import java.util.prefs.BackingStoreException;
import net.sf.openrocket.material.Material;
import net.sf.openrocket.preset.ComponentPreset;
import net.sf.openrocket.preset.ComponentPreset.Type;
import net.sf.openrocket.util.BugException;
public class MockPreferences extends Preferences {
private final String NODENAME = "OpenRocket-test-mock";
private final java.util.prefs.Preferences NODE;
public MockPreferences() {
java.util.prefs.Preferences root = java.util.prefs.Preferences.userRoot();
try {
if (root.nodeExists(NODENAME)) {
root.node(NODENAME).removeNode();
}
} catch (BackingStoreException e) {
throw new BugException("Unable to clear preference node", e);
}
NODE = root.node(NODENAME);
}
@Override
public boolean getBoolean(String key, boolean def) {
return NODE.getBoolean(key, def);
}
@Override
public void putBoolean(String key, boolean value) {
NODE.putBoolean(key, value);
}
@Override
public int getInt(String key, int def) {
return NODE.getInt(key, def);
}
@Override
public void putInt(String key, int value) {
NODE.putInt(key, value);
}
@Override
public double getDouble(String key, double def) {
return NODE.getDouble(key, def);
}
@Override
public void putDouble(String key, double value) {
NODE.putDouble(key, value);
}
@Override
public String getString(String key, String def) {
return NODE.get(key, def);
}
@Override
public void putString(String key, String value) {
NODE.put(key, value);
}
@Override
public String getString(String directory, String key, String def) {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void putString(String directory, String key, String value) {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public java.util.prefs.Preferences getNode(String nodeName) {
return NODE.node(nodeName);
}
@Override
public void addUserMaterial(Material m) {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public Set<Material> getUserMaterials() {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void removeUserMaterial(Material m) {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void setComponentFavorite(ComponentPreset preset, Type type, boolean favorite) {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public Set<String> getComponentFavorites(Type type) {
throw new UnsupportedOperationException("Not yet implemented");
}
}

View File

@ -1,32 +0,0 @@
package net.sf.openrocket.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
public class TestScriptingUtil {
/*
* Note: This class assumes that the JRE supports JavaScript scripting.
*/
@Test
public void testGetLanguage() {
assertEquals(null, ScriptingUtil.getLanguage(null));
assertEquals(null, ScriptingUtil.getLanguage(""));
assertEquals(null, ScriptingUtil.getLanguage("foobar"));
assertEquals("JavaScript", ScriptingUtil.getLanguage("JavaScript"));
assertEquals("JavaScript", ScriptingUtil.getLanguage("javascript"));
assertEquals("JavaScript", ScriptingUtil.getLanguage("ECMAScript"));
assertEquals("JavaScript", ScriptingUtil.getLanguage("js"));
}
@Test
public void testGetLanguages() {
assertTrue(ScriptingUtil.getLanguages().size() >= 1);
assertTrue(ScriptingUtil.getLanguages().contains("JavaScript"));
}
}

View File

@ -186,7 +186,7 @@ class SimulationOptionsPanel extends JPanel {
sub.add(desc, "aligny 0, hmin 100lp, growx, wrap para"); sub.add(desc, "aligny 0, hmin 100lp, growx, wrap para");
final JButton addExtension = new JButton("Add extension"); final JButton addExtension = new JButton(trans.get("simedtdlg.SimExt.add"));
final JPopupMenu menu = getExtensionMenu(); final JPopupMenu menu = getExtensionMenu();
addExtension.addActionListener(new ActionListener() { addExtension.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) { public void actionPerformed(ActionEvent ev) {
@ -223,6 +223,10 @@ class SimulationOptionsPanel extends JPanel {
SimulationExtension e = provider.getInstance(id); SimulationExtension e = provider.getInstance(id);
simulation.getSimulationExtensions().add(e); simulation.getSimulationExtensions().add(e);
updateCurrentExtensions(); updateCurrentExtensions();
SwingSimulationExtensionConfigurator configurator = findConfigurator(e);
if (configurator != null) {
configurator.configure(e, simulation, SwingUtilities.windowForComponent(SimulationOptionsPanel.this));
}
} }
}); });
menu.add(item); menu.add(item);
@ -344,86 +348,17 @@ class SimulationOptionsPanel extends JPanel {
this.add(button, "right"); this.add(button, "right");
} }
private SwingSimulationExtensionConfigurator findConfigurator(SimulationExtension extension) {
Set<SwingSimulationExtensionConfigurator> configurators = Application.getInjector().getInstance(new Key<Set<SwingSimulationExtensionConfigurator>>() {
});
for (SwingSimulationExtensionConfigurator c : configurators) {
if (c.support(extension)) {
return c;
}
}
return null;
}
} }
// private SwingSimulationExtensionConfigurator findConfigurator(SimulationExtension extension) {
// Set<SwingSimulationExtensionConfigurator> configurators = Application.getInjector().getInstance(new Key<Set<SwingSimulationExtensionConfigurator>>() {
// private class ExtensionListModel extends AbstractListModel { });
// @Override for (SwingSimulationExtensionConfigurator c : configurators) {
// public SimulationExtensionConfiguration getElementAt(int index) { if (c.support(extension)) {
// if (index < 0 || index >= getSize()) return c;
// return null; }
// return simulation.getSimulationExtensions().get(index); }
// } return null;
// }
// @Override
// public int getSize() {
// return simulation.getSimulationExtensions().size();
// }
// }
//
//
// private class ExtensionCellRenderer extends JPanel implements ListCellRenderer {
// private JLabel label;
//
// public ExtensionCellRenderer() {
// super(new MigLayout("fill"));
// label = new JLabel();
//
// }
//
// @Override
// public Component getListCellRendererComponent(JList list, Object value,
// int index, boolean isSelected, boolean cellHasFocus) {
// SimulationExtensionConfiguration config = (SimulationExtensionConfiguration) value;
//
//
//
// String s = value.toString();
// setText(s);
//
// // Attempt instantiating, catch any exceptions
// Exception ex = null;
// try {
// Class<?> c = Class.forName(s);
// @SuppressWarnings("unused")
// SimulationListener l = (SimulationListener) c.newInstance();
// } catch (Exception e) {
// ex = e;
// }
//
// if (ex == null) {
// setIcon(Icons.SIMULATION_LISTENER_OK);
// //// Listener instantiated successfully.
// setToolTipText("Listener instantiated successfully.");
// } else {
// setIcon(Icons.SIMULATION_LISTENER_ERROR);
// //// <html>Unable to instantiate listener due to exception:<br>
// setToolTipText("<html>Unable to instantiate listener due to exception:<br>" +
// ex.toString());
// }
//
// if (isSelected) {
// setBackground(list.getSelectionBackground());
// setForeground(list.getSelectionForeground());
// } else {
// setBackground(list.getBackground());
// setForeground(list.getForeground());
// }
// setOpaque(true);
// return this;
// }
// }
} }

View File

@ -45,8 +45,8 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
for (String lang : new String[] { "en", "de", "es", "fr", "it", "ru", "cs", "pl", "ja", "pt", "tr" }) { for (String lang : new String[] { "en", "de", "es", "fr", "it", "ru", "cs", "pl", "ja", "pt", "tr" }) {
list.add(new Locale(lang)); list.add(new Locale(lang));
} }
list.add(new Locale("zh","CN")); list.add(new Locale("zh", "CN"));
list.add(new Locale("uk","UA")); list.add(new Locale("uk", "UA"));
SUPPORTED_LOCALES = Collections.unmodifiableList(list); SUPPORTED_LOCALES = Collections.unmodifiableList(list);
} }
@ -200,6 +200,7 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
* @param nodeName the node name * @param nodeName the node name
* @return the preferences object for that node * @return the preferences object for that node
*/ */
@Override
public Preferences getNode(String nodeName) { public Preferences getNode(String nodeName) {
return PREFNODE.node(nodeName); return PREFNODE.node(nodeName);
} }
@ -418,11 +419,11 @@ public class SwingPreferences extends net.sf.openrocket.startup.Preferences {
public boolean computeFlightInBackground() { public boolean computeFlightInBackground() {
return PREFNODE.getBoolean("backgroundFlight", true); return PREFNODE.getBoolean("backgroundFlight", true);
} }
public void setComputeFlightInBackground(boolean b) { public void setComputeFlightInBackground(boolean b) {
PREFNODE.putBoolean("backgroundFlight", b); PREFNODE.putBoolean("backgroundFlight", b);
} }
public Simulation getBackgroundSimulation(Rocket rocket) { public Simulation getBackgroundSimulation(Rocket rocket) {
Simulation s = new Simulation(rocket); Simulation s = new Simulation(rocket);
SimulationOptions cond = s.getOptions(); SimulationOptions cond = s.getOptions();

View File

@ -57,6 +57,7 @@ public abstract class AbstractSwingSimulationExtensionConfigurator<E extends Sim
dialog.add(panel); dialog.add(panel);
GUIUtil.setDisposableDialogOptions(dialog, close); GUIUtil.setDisposableDialogOptions(dialog, close);
dialog.setVisible(true); dialog.setVisible(true);
close();
GUIUtil.setNullModels(dialog); GUIUtil.setNullModels(dialog);
dialog = null; dialog = null;
} }
@ -75,6 +76,13 @@ public abstract class AbstractSwingSimulationExtensionConfigurator<E extends Sim
return dialog; return dialog;
} }
/**
* Called when the default dialog is closed. By default does nothing.
*/
protected void close() {
}
protected abstract JComponent getConfigurationComponent(E extension, Simulation simulation, JPanel panel); protected abstract JComponent getConfigurationComponent(E extension, Simulation simulation, JPanel panel);
} }

View File

@ -9,27 +9,36 @@ import java.util.Set;
import javax.script.ScriptEngine; import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager; import javax.script.ScriptEngineManager;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox; import javax.swing.JComboBox;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JPanel; import javax.swing.JPanel;
import net.sf.openrocket.document.Simulation; import net.sf.openrocket.document.Simulation;
import net.sf.openrocket.gui.adaptors.BooleanModel;
import net.sf.openrocket.gui.components.StyledLabel; import net.sf.openrocket.gui.components.StyledLabel;
import net.sf.openrocket.gui.components.StyledLabel.Style; import net.sf.openrocket.gui.components.StyledLabel.Style;
import net.sf.openrocket.plugin.Plugin; import net.sf.openrocket.plugin.Plugin;
import net.sf.openrocket.simulation.extension.AbstractSwingSimulationExtensionConfigurator; import net.sf.openrocket.simulation.extension.AbstractSwingSimulationExtensionConfigurator;
import net.sf.openrocket.util.ScriptingUtil;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
import org.fife.ui.rsyntaxtextarea.TokenMakerFactory; import org.fife.ui.rsyntaxtextarea.TokenMakerFactory;
import org.fife.ui.rtextarea.RTextScrollPane; import org.fife.ui.rtextarea.RTextScrollPane;
import com.google.inject.Inject;
@Plugin @Plugin
public class ScriptingConfigurator extends AbstractSwingSimulationExtensionConfigurator<ScriptingExtension> { public class ScriptingConfigurator extends AbstractSwingSimulationExtensionConfigurator<ScriptingExtension> {
@Inject
private ScriptingUtil util;
private JComboBox languageSelector; private JComboBox languageSelector;
private RSyntaxTextArea text; private RSyntaxTextArea text;
private JCheckBox trusted;
private ScriptingExtension extension; private ScriptingExtension extension;
private Simulation simulation; private Simulation simulation;
@ -43,9 +52,9 @@ public class ScriptingConfigurator extends AbstractSwingSimulationExtensionConfi
this.extension = extension; this.extension = extension;
this.simulation = simulation; this.simulation = simulation;
panel.add(new StyledLabel(trans.get("SimulationExtension.scripting.language.label"), Style.BOLD), ""); panel.add(new StyledLabel(trans.get("SimulationExtension.scripting.language.label"), Style.BOLD), "spanx, split");
String[] languages = ScriptingUtil.getLanguages().toArray(new String[0]); String[] languages = util.getLanguages().toArray(new String[0]);
languageSelector = new JComboBox(languages); languageSelector = new JComboBox(languages);
languageSelector.setEditable(false); languageSelector.setEditable(false);
languageSelector.addActionListener(new ActionListener() { languageSelector.addActionListener(new ActionListener() {
@ -79,13 +88,48 @@ public class ScriptingConfigurator extends AbstractSwingSimulationExtensionConfi
}); });
RTextScrollPane scroll = new RTextScrollPane(text); RTextScrollPane scroll = new RTextScrollPane(text);
panel.add(scroll, "spanx, grow"); panel.add(scroll, "spanx, grow, wrap para");
setLanguage(ScriptingUtil.getLanguage(extension.getLanguage()));
BooleanModel enabled = new BooleanModel(extension, "Enabled");
JCheckBox check = new JCheckBox(enabled);
check.setText(trans.get("SimulationExtension.scripting.text.enabled"));
check.setToolTipText(trans.get("SimulationExtension.scripting.text.enabled.ttip"));
panel.add(check, "spanx, wrap rel");
trusted = new JCheckBox(trans.get("SimulationExtension.scripting.text.trusted"));
trusted.setSelected(util.isTrustedScript(extension.getLanguage(), extension.getScript()));
panel.add(trusted, "spanx, split");
panel.add(new JPanel(), "growx");
JButton button = new JButton(trans.get("SimulationExtension.scripting.text.trusted.clear"));
button.setToolTipText(trans.get("SimulationExtension.scripting.text.trusted.clear.ttip"));
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
util.clearTrustedScripts();
JOptionPane.showMessageDialog(getDialog(), trans.get("SimulationExtension.scripting.text.trusted.cleared"),
trans.get("SimulationExtension.scripting.text.trusted.cleared.title"), JOptionPane.INFORMATION_MESSAGE);
}
});
panel.add(button, "wrap rel");
StyledLabel label = new StyledLabel(trans.get("SimulationExtension.scripting.text.trusted.msg"), -1, Style.ITALIC);
panel.add(label);
setLanguage(util.getLanguage(extension.getLanguage()));
return panel; return panel;
} }
@Override
protected void close() {
util.setTrustedScript(extension.getLanguage(), extension.getScript(), trusted.isSelected());
}
private void setLanguage(String language) { private void setLanguage(String language) {
if (language == null) { if (language == null) {
language = ""; language = "";