From 26404168f5cd663c363397f6c2fd649f70d5f1de Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 25 Jan 2026 21:59:47 -0500 Subject: [PATCH] Add script identification, require permission to r/w external files --- docsrc/manual/scripting-capabilities.rst | 4 + include/config.h | 1 + include/core/utility.h | 1 + include/scripting.h | 52 ++++++++- include/ui/message.h | 1 + src/config.cpp | 8 +- src/core/utility.cpp | 9 ++ src/scriptapi/apiutility.cpp | 17 ++- src/scriptapi/scripting.cpp | 130 ++++++++++++++++------- 9 files changed, 175 insertions(+), 48 deletions(-) diff --git a/docsrc/manual/scripting-capabilities.rst b/docsrc/manual/scripting-capabilities.rst index c21f3ea6..7c5452b1 100644 --- a/docsrc/manual/scripting-capabilities.rst +++ b/docsrc/manual/scripting-capabilities.rst @@ -2062,6 +2062,8 @@ All utility functions are callable via the global ``utility`` object. Reads the contents of the file specified by ``path``. + If ``path`` refers to a file outside the project directory the user will be warned about this and may choose to cancel the read. + :param path: The path of the file to read. The path may either be relative to the project directory or absolute. :type path: string :returns: If reading the file was successful ``content`` will contain the contents of the file and ``error`` will be empty. Otherwise ``content`` will be empty and ``error`` will contain an error message. @@ -2071,6 +2073,8 @@ All utility functions are callable via the global ``utility`` object. Writes ``content`` to the file specified by ``path``. The file is created if it does not already exist. + If ``path`` refers to a file outside the project directory the user will be warned about this and may choose to cancel the write. + :param path: The path of the file to write. The path to an existing file may either be relative to the project directory or absolute. If the file does not already exist the path must be absolute. :type path: string :param content: The text to write to the file. diff --git a/include/config.h b/include/config.h index 8e363914..4907014f 100644 --- a/include/config.h +++ b/include/config.h @@ -150,6 +150,7 @@ public: QFont applicationFont; QFont mapListFont; int imageExportColorSpaceId; + QMap trustedScriptHashes; protected: virtual void parseConfigKeyValue(QString key, QString value) override; diff --git a/include/core/utility.h b/include/core/utility.h index 07b52a70..ccb3cf24 100644 --- a/include/core/utility.h +++ b/include/core/utility.h @@ -20,6 +20,7 @@ namespace Util { void show(QWidget *w); QColorSpace toColorSpace(int colorSpaceInt); QString mkpath(const QString& dirPath); + QString getFileHash(const QString &filepath); } #endif // UTILITY_H diff --git a/include/scripting.h b/include/scripting.h index 40903c09..e98cdccf 100644 --- a/include/scripting.h +++ b/include/scripting.h @@ -3,7 +3,10 @@ #define SCRIPTING_H #include +#include +#include #include "scriptutility.h" +#include "utility.h" class Block; class Tile; @@ -73,17 +76,58 @@ public: static const QImage * getImage(const QString &filepath, bool useCache); static QJSValue dialogInput(QJSValue input, bool selectedOk); static QJSValue fileResponse(const QString &s, bool isError); + static bool checkFilePermissions(const QString &filepath); private: MainWindow *mainWindow; QJSEngine *engine; - QStringList filepaths; - QList modules; + + class Script { + public: + Script() {}; + + QString filepath() const { return m_filepath; } + QString fileName() const { + QFileInfo fileInfo(m_filepath); + return fileInfo.fileName(); + } + void setFilepath(const QString &filepath) { m_filepath = filepath; } + + QJSValue module() const { return m_module; } + void setModule(const QJSValue &module) { m_module = module; } + + QString hash() { + if (m_hash.isEmpty()) { + // We won't need to check whether most scripts are trusted, + // so only calculate the hash when it's first requested. + m_hash = Util::getFileHash(m_filepath); + } + return m_hash; + } + private: + QString m_filepath; + QJSValue m_module; + QString m_hash; + }; + QList> scripts; + + // Tracks the scripts that are currently being evaluated. + // This is useful for identifying which script is responsible + // for calling a function at any point in time. + // Because executing a callback/action in one script may trigger + // a callback/action in a different script we may need to keep + // track of multiple scripts executing at once. + QStack> scriptExecutionStack; + QMap imageCache; ScriptUtility *scriptUtility; - void loadModules(const QStringList &moduleFiles); - void invokeCallback(CallbackType type, QJSValueList args); + void loadScript(const QString &filepath); + void invokeCallback(CallbackType type, const QJSValueList &args); + void invokeCallback(QSharedPointer script, CallbackType type, const QJSValueList &args); + bool invokeAction(QSharedPointer script, const QString &functionName); + QSharedPointer