Merge pull request #7571 from howard-stearns/sandbox-entity-includes

Sandbox entity includes
This commit is contained in:
Brad Hefta-Gaub 2016-04-06 19:11:25 -07:00
commit cea622f6f1
2 changed files with 59 additions and 21 deletions

View file

@ -578,7 +578,7 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
_registeredHandlers[entityID] = RegisteredEventHandlers();
}
CallbackList& handlersForEvent = _registeredHandlers[entityID][eventName];
CallbackData handlerData = {handler, currentEntityIdentifier};
CallbackData handlerData = {handler, currentEntityIdentifier, currentSandboxURL};
handlersForEvent << handlerData; // Note that the same handler can be added many times. See removeEntityEventHandler().
}
@ -795,7 +795,7 @@ void ScriptEngine::timerFired() {
// call the associated JS function, if it exists
if (timerData.function.isValid()) {
callWithEnvironment(timerData.definingEntityIdentifier, timerData.function, timerData.function, QScriptValueList());
callWithEnvironment(timerData.definingEntityIdentifier, timerData.definingSandboxURL, timerData.function, timerData.function, QScriptValueList());
}
}
@ -810,7 +810,7 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int
// make sure the timer stops when the script does
connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);
CallbackData timerData = {function, currentEntityIdentifier};
CallbackData timerData = {function, currentEntityIdentifier, currentSandboxURL};
_timerFunctionMap.insert(newTimer, timerData);
newTimer->start(intervalMS);
@ -885,19 +885,49 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
return; // bail early
}
QList<QUrl> urls;
bool knowsSensitivity = false;
Qt::CaseSensitivity sensitivity;
auto getSensitivity = [&]() {
if (!knowsSensitivity) {
QString path = currentSandboxURL.path();
QFileInfo upperFI(path.toUpper());
QFileInfo lowerFI(path.toLower());
sensitivity = (upperFI == lowerFI) ? Qt::CaseInsensitive : Qt::CaseSensitive;
knowsSensitivity = true;
}
return sensitivity;
};
// Guard against meaningless query and fragment parts.
// Do NOT use PreferLocalFile as its behavior is unpredictable (e.g., on defaultScriptsLocation())
const auto strippingFlags = QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment;
for (QString file : includeFiles) {
QUrl thisURL { resolvePath(file) };
if (!_includedURLs.contains(thisURL)) {
urls.append(thisURL);
_includedURLs << thisURL;
}
else {
if (!currentSandboxURL.isEmpty() && (thisURL.scheme() == "file") &&
(
(currentSandboxURL.scheme() != "file") ||
(
!thisURL.toString(strippingFlags).startsWith(defaultScriptsLocation().toString(), getSensitivity()) &&
!thisURL.toString(strippingFlags).startsWith(currentSandboxURL.toString(strippingFlags), getSensitivity())
)
)
) {
qCWarning(scriptengine) << "Script.include() ignoring file path" << thisURL << "outside of original entity script" << currentSandboxURL;
} else {
// We could also check here for CORS, but we don't yet.
// It turns out that QUrl.resolve will not change hosts and copy authority, so we don't need to check that here.
urls.append(thisURL);
_includedURLs << thisURL;
}
} else {
qCDebug(scriptengine) << "Script.include() ignoring previously included url:" << thisURL;
}
}
BatchLoader* loader = new BatchLoader(urls);
EntityItemID capturedEntityIdentifier = currentEntityIdentifier;
QUrl capturedSandboxURL = currentSandboxURL;
auto evaluateScripts = [=](const QMap<QUrl, QString>& data) {
auto parentURL = _parentURL;
@ -912,13 +942,13 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
auto operation = [&]() {
evaluate(contents, url.toString());
};
doWithEnvironment(capturedEntityIdentifier, operation);
doWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, operation);
}
}
_parentURL = parentURL;
if (callback.isFunction()) {
callWithEnvironment(capturedEntityIdentifier, QScriptValue(callback), QScriptValue(), QScriptValueList());
callWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, QScriptValue(callback), QScriptValue(), QScriptValueList());
}
loader->deleteLater();
@ -996,10 +1026,11 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin
CallbackList handlersForEvent = handlersOnEntity[eventName];
if (!handlersForEvent.isEmpty()) {
for (int i = 0; i < handlersForEvent.count(); ++i) {
// handlersForEvent[i] can tonain many handlers that may have each been added by different interface or entity scripts,
// handlersForEvent[i] can contain many handlers that may have each been added by different interface or entity scripts,
// and the entity scripts may be for entities other than the one this is a handler for.
// Fortunately, the definingEntityIdentifier captured the entity script id (if any) when the handler was added.
callWithEnvironment(handlersForEvent[i].definingEntityIdentifier, handlersForEvent[i].function, QScriptValue(), eventHandlerArgs);
CallbackData& handler = handlersForEvent[i];
callWithEnvironment(handler.definingEntityIdentifier, handler.definingSandboxURL, handler.function, QScriptValue(), eventHandlerArgs);
}
}
}
@ -1106,13 +1137,14 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch();
}
QScriptValue entityScriptConstructor, entityScriptObject;
QUrl sandboxURL = currentSandboxURL.isEmpty() ? scriptOrURL : currentSandboxURL;
auto initialization = [&]{
entityScriptConstructor = evaluate(contents, fileName);
entityScriptObject = entityScriptConstructor.construct();
};
doWithEnvironment(entityID, initialization);
doWithEnvironment(entityID, sandboxURL, initialization);
EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified };
EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified, sandboxURL };
_entityScripts[entityID] = newDetails;
if (isURL) {
setParentURL("");
@ -1201,9 +1233,11 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) {
// Even if entityID is supplied as currentEntityIdentifier, this still documents the source
// of the code being executed (e.g., if we ever sandbox different entity scripts, or provide different
// global values for different entity scripts).
void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, std::function<void()> operation) {
void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function<void()> operation) {
EntityItemID oldIdentifier = currentEntityIdentifier;
QUrl oldSandboxURL = currentSandboxURL;
currentEntityIdentifier = entityID;
currentSandboxURL = sandboxURL;
#if DEBUG_CURRENT_ENTITY
QScriptValue oldData = this->globalObject().property("debugEntityID");
@ -1215,12 +1249,13 @@ void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, std::function
#endif
currentEntityIdentifier = oldIdentifier;
currentSandboxURL = oldSandboxURL;
}
void ScriptEngine::callWithEnvironment(const EntityItemID& entityID, QScriptValue function, QScriptValue thisObject, QScriptValueList args) {
void ScriptEngine::callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args) {
auto operation = [&]() {
function.call(thisObject, args);
};
doWithEnvironment(entityID, operation);
doWithEnvironment(entityID, sandboxURL, operation);
}
void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params) {
@ -1249,7 +1284,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
QScriptValueList args;
args << entityID.toScriptValue(this);
args << qScriptValueFromSequence(this, params);
callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args);
callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args);
}
}
@ -1281,7 +1316,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
QScriptValueList args;
args << entityID.toScriptValue(this);
args << event.toScriptValue(this);
callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args);
callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args);
}
}
}
@ -1315,7 +1350,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS
args << entityID.toScriptValue(this);
args << otherID.toScriptValue(this);
args << collisionToScriptValue(this, collision);
callWithEnvironment(entityID, entityScript.property(methodName), entityScript, args);
callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args);
}
}
}

View file

@ -47,6 +47,7 @@ class CallbackData {
public:
QScriptValue function;
EntityItemID definingEntityIdentifier;
QUrl definingSandboxURL;
};
typedef QList<CallbackData> CallbackList;
@ -57,6 +58,7 @@ public:
QString scriptText;
QScriptValue scriptObject;
int64_t lastModified;
QUrl definingSandboxURL;
};
class ScriptEngine : public QScriptEngine, public ScriptUser, public EntitiesScriptEngineProvider {
@ -214,8 +216,9 @@ protected:
Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success);
EntityItemID currentEntityIdentifier {}; // Contains the defining entity script entity id during execution, if any. Empty for interface script execution.
void doWithEnvironment(const EntityItemID& entityID, std::function<void()> operation);
void callWithEnvironment(const EntityItemID& entityID, QScriptValue function, QScriptValue thisObject, QScriptValueList args);
QUrl currentSandboxURL {}; // The toplevel url string for the entity script that loaded the code being executed, else empty.
void doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function<void()> operation);
void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args);
friend class ScriptEngines;
static std::atomic<bool> _stoppingAllScripts;