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(); _registeredHandlers[entityID] = RegisteredEventHandlers();
} }
CallbackList& handlersForEvent = _registeredHandlers[entityID][eventName]; 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(). 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 // call the associated JS function, if it exists
if (timerData.function.isValid()) { 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 // make sure the timer stops when the script does
connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop); connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);
CallbackData timerData = {function, currentEntityIdentifier}; CallbackData timerData = {function, currentEntityIdentifier, currentSandboxURL};
_timerFunctionMap.insert(newTimer, timerData); _timerFunctionMap.insert(newTimer, timerData);
newTimer->start(intervalMS); newTimer->start(intervalMS);
@ -885,19 +885,49 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
return; // bail early return; // bail early
} }
QList<QUrl> urls; 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) { for (QString file : includeFiles) {
QUrl thisURL { resolvePath(file) }; QUrl thisURL { resolvePath(file) };
if (!_includedURLs.contains(thisURL)) { if (!_includedURLs.contains(thisURL)) {
urls.append(thisURL); if (!currentSandboxURL.isEmpty() && (thisURL.scheme() == "file") &&
_includedURLs << thisURL; (
} (currentSandboxURL.scheme() != "file") ||
else { (
!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; qCDebug(scriptengine) << "Script.include() ignoring previously included url:" << thisURL;
} }
} }
BatchLoader* loader = new BatchLoader(urls); BatchLoader* loader = new BatchLoader(urls);
EntityItemID capturedEntityIdentifier = currentEntityIdentifier; EntityItemID capturedEntityIdentifier = currentEntityIdentifier;
QUrl capturedSandboxURL = currentSandboxURL;
auto evaluateScripts = [=](const QMap<QUrl, QString>& data) { auto evaluateScripts = [=](const QMap<QUrl, QString>& data) {
auto parentURL = _parentURL; auto parentURL = _parentURL;
@ -912,13 +942,13 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
auto operation = [&]() { auto operation = [&]() {
evaluate(contents, url.toString()); evaluate(contents, url.toString());
}; };
doWithEnvironment(capturedEntityIdentifier, operation); doWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, operation);
} }
} }
_parentURL = parentURL; _parentURL = parentURL;
if (callback.isFunction()) { if (callback.isFunction()) {
callWithEnvironment(capturedEntityIdentifier, QScriptValue(callback), QScriptValue(), QScriptValueList()); callWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, QScriptValue(callback), QScriptValue(), QScriptValueList());
} }
loader->deleteLater(); loader->deleteLater();
@ -996,10 +1026,11 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin
CallbackList handlersForEvent = handlersOnEntity[eventName]; CallbackList handlersForEvent = handlersOnEntity[eventName];
if (!handlersForEvent.isEmpty()) { if (!handlersForEvent.isEmpty()) {
for (int i = 0; i < handlersForEvent.count(); ++i) { 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. // 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. // 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(); lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch();
} }
QScriptValue entityScriptConstructor, entityScriptObject; QScriptValue entityScriptConstructor, entityScriptObject;
QUrl sandboxURL = currentSandboxURL.isEmpty() ? scriptOrURL : currentSandboxURL;
auto initialization = [&]{ auto initialization = [&]{
entityScriptConstructor = evaluate(contents, fileName); entityScriptConstructor = evaluate(contents, fileName);
entityScriptObject = entityScriptConstructor.construct(); entityScriptObject = entityScriptConstructor.construct();
}; };
doWithEnvironment(entityID, initialization); doWithEnvironment(entityID, sandboxURL, initialization);
EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified }; EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified, sandboxURL };
_entityScripts[entityID] = newDetails; _entityScripts[entityID] = newDetails;
if (isURL) { if (isURL) {
setParentURL(""); setParentURL("");
@ -1201,9 +1233,11 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) {
// Even if entityID is supplied as currentEntityIdentifier, this still documents the source // 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 // of the code being executed (e.g., if we ever sandbox different entity scripts, or provide different
// global values for different entity scripts). // 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; EntityItemID oldIdentifier = currentEntityIdentifier;
QUrl oldSandboxURL = currentSandboxURL;
currentEntityIdentifier = entityID; currentEntityIdentifier = entityID;
currentSandboxURL = sandboxURL;
#if DEBUG_CURRENT_ENTITY #if DEBUG_CURRENT_ENTITY
QScriptValue oldData = this->globalObject().property("debugEntityID"); QScriptValue oldData = this->globalObject().property("debugEntityID");
@ -1215,12 +1249,13 @@ void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, std::function
#endif #endif
currentEntityIdentifier = oldIdentifier; 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 = [&]() { auto operation = [&]() {
function.call(thisObject, args); function.call(thisObject, args);
}; };
doWithEnvironment(entityID, operation); doWithEnvironment(entityID, sandboxURL, operation);
} }
void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params) { 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; QScriptValueList args;
args << entityID.toScriptValue(this); args << entityID.toScriptValue(this);
args << qScriptValueFromSequence(this, params); 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; QScriptValueList args;
args << entityID.toScriptValue(this); args << entityID.toScriptValue(this);
args << event.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 << entityID.toScriptValue(this);
args << otherID.toScriptValue(this); args << otherID.toScriptValue(this);
args << collisionToScriptValue(this, collision); 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: public:
QScriptValue function; QScriptValue function;
EntityItemID definingEntityIdentifier; EntityItemID definingEntityIdentifier;
QUrl definingSandboxURL;
}; };
typedef QList<CallbackData> CallbackList; typedef QList<CallbackData> CallbackList;
@ -57,6 +58,7 @@ public:
QString scriptText; QString scriptText;
QScriptValue scriptObject; QScriptValue scriptObject;
int64_t lastModified; int64_t lastModified;
QUrl definingSandboxURL;
}; };
class ScriptEngine : public QScriptEngine, public ScriptUser, public EntitiesScriptEngineProvider { 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); 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. 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); QUrl currentSandboxURL {}; // The toplevel url string for the entity script that loaded the code being executed, else empty.
void callWithEnvironment(const EntityItemID& entityID, QScriptValue function, QScriptValue thisObject, QScriptValueList args); 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; friend class ScriptEngines;
static std::atomic<bool> _stoppingAllScripts; static std::atomic<bool> _stoppingAllScripts;