From 13eddaee811c5f94e4ac140758f186e600e7d5ef Mon Sep 17 00:00:00 2001 From: faraphel Date: Tue, 20 Aug 2024 20:00:04 +0200 Subject: [PATCH] improved the JavaScript path object --- CMakeLists.txt | 23 ++-- external/rvfs/CMakeLists.txt | 27 ----- .../include/RestrictedVirtualFileSystem.hpp | 62 ---------- .../source/RestrictedVirtualFileSystem.cpp | 65 ---------- external/rvfs/tests/CMakeLists.txt | 36 ------ external/vfs/CMakeLists.txt | 18 +++ external/{rvfs => vfs}/README.md | 0 external/vfs/include/VirtualFileSystem.hpp | 49 ++++++++ .../exception/FileNotFoundException.hpp | 14 +++ external/vfs/source/VirtualFileSystem.cpp | 88 ++++++++++++++ .../exception/FileNotFoundException.cpp | 9 ++ external/vfs/tests/CMakeLists.txt | 25 ++++ .../{rvfs => vfs}/tests/_assets/content.txt | 0 .../{rvfs => vfs}/tests/test-restriction.cpp | 23 ++-- source/javascript/engine/AtlasJsEngine.cpp | 7 +- source/javascript/engine/AtlasJsEngine.hpp | 16 +-- source/javascript/module/AtlasJsModule.cpp | 12 +- source/javascript/module/AtlasJsModule.hpp | 12 +- .../javascript/module/_base/BaseJsModule.cpp | 4 +- .../javascript/module/_base/BaseJsModule.hpp | 7 +- .../javascript/module/debug/DebugJsModule.cpp | 2 +- .../javascript/module/debug/DebugJsModule.hpp | 2 +- source/javascript/module/debug/README.md | 4 + .../javascript/module/file/FileJsModule.cpp | 12 -- .../javascript/module/file/FileJsModule.hpp | 18 --- .../module/file_system/FsJsModule.cpp | 15 +++ .../module/file_system/FsJsModule.hpp | 23 ++++ .../javascript/module/file_system/README.md | 24 ++++ .../file_system/object/FileJsObject.cpp | 103 ++++++++++++++++ .../file_system/object/FileJsObject.hpp | 55 +++++++++ .../file_system/object/PathJsObject.cpp | 101 ++++++++++++++++ .../file_system/object/PathJsObject.hpp | 114 ++++++++++++++++++ .../javascript/module/image/ImageJsModule.cpp | 4 +- .../javascript/module/image/ImageJsModule.hpp | 3 +- .../image/{ => object}/ImageJsObject.cpp | 0 .../image/{ => object}/ImageJsObject.hpp | 0 source/javascript/module/szs/.gitkeep | 0 source/javascript/module/web/README.md | 7 ++ source/javascript/module/wit/.gitkeep | 0 source/main.cpp | 30 +---- source/utils/qt/fileOpenMode.cpp | 24 ++++ source/utils/qt/fileOpenMode.hpp | 11 ++ 42 files changed, 754 insertions(+), 295 deletions(-) delete mode 100644 external/rvfs/CMakeLists.txt delete mode 100644 external/rvfs/include/RestrictedVirtualFileSystem.hpp delete mode 100644 external/rvfs/source/RestrictedVirtualFileSystem.cpp delete mode 100644 external/rvfs/tests/CMakeLists.txt create mode 100644 external/vfs/CMakeLists.txt rename external/{rvfs => vfs}/README.md (100%) create mode 100644 external/vfs/include/VirtualFileSystem.hpp create mode 100644 external/vfs/include/exception/FileNotFoundException.hpp create mode 100644 external/vfs/source/VirtualFileSystem.cpp create mode 100644 external/vfs/source/exception/FileNotFoundException.cpp create mode 100644 external/vfs/tests/CMakeLists.txt rename external/{rvfs => vfs}/tests/_assets/content.txt (100%) rename external/{rvfs => vfs}/tests/test-restriction.cpp (53%) create mode 100644 source/javascript/module/debug/README.md delete mode 100644 source/javascript/module/file/FileJsModule.cpp delete mode 100644 source/javascript/module/file/FileJsModule.hpp create mode 100644 source/javascript/module/file_system/FsJsModule.cpp create mode 100644 source/javascript/module/file_system/FsJsModule.hpp create mode 100644 source/javascript/module/file_system/README.md create mode 100644 source/javascript/module/file_system/object/FileJsObject.cpp create mode 100644 source/javascript/module/file_system/object/FileJsObject.hpp create mode 100644 source/javascript/module/file_system/object/PathJsObject.cpp create mode 100644 source/javascript/module/file_system/object/PathJsObject.hpp rename source/javascript/module/image/{ => object}/ImageJsObject.cpp (100%) rename source/javascript/module/image/{ => object}/ImageJsObject.hpp (100%) create mode 100644 source/javascript/module/szs/.gitkeep create mode 100644 source/javascript/module/web/README.md create mode 100644 source/javascript/module/wit/.gitkeep create mode 100644 source/utils/qt/fileOpenMode.cpp create mode 100644 source/utils/qt/fileOpenMode.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f29eb3e..0d1d156 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -26,16 +26,25 @@ add_executable(Atlas-Launcher source/javascript/module/debug/DebugJsModule.hpp source/javascript/module/image/ImageJsModule.cpp source/javascript/module/image/ImageJsModule.hpp - source/javascript/module/image/ImageJsObject.cpp - source/javascript/module/image/ImageJsObject.hpp + source/javascript/module/image/object/ImageJsObject.cpp + source/javascript/module/image/object/ImageJsObject.hpp source/javascript/module/_base/BaseJsModule.cpp source/javascript/module/_base/BaseJsModule.hpp - source/javascript/module/file/FileJsModule.cpp - source/javascript/module/file/FileJsModule.hpp + source/javascript/module/file_system/FsJsModule.cpp + source/javascript/module/file_system/FsJsModule.hpp + source/javascript/module/file_system/object/FileJsObject.cpp + source/javascript/module/file_system/object/FileJsObject.hpp + source/utils/qt/fileOpenMode.cpp + source/utils/qt/fileOpenMode.hpp + source/javascript/module/file_system/object/PathJsObject.cpp + source/javascript/module/file_system/object/PathJsObject.hpp +) +target_include_directories(Atlas-Launcher PRIVATE + source/ ) target_link_libraries(Atlas-Launcher PRIVATE # Tools - RVFS + VFS SZS # Qt Framework @@ -46,5 +55,5 @@ target_link_libraries(Atlas-Launcher PRIVATE ) -add_subdirectory(external/rvfs) +add_subdirectory(external/vfs) add_subdirectory(external/szs) diff --git a/external/rvfs/CMakeLists.txt b/external/rvfs/CMakeLists.txt deleted file mode 100644 index ff4ada6..0000000 --- a/external/rvfs/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -cmake_minimum_required(VERSION 3.28) -project(RVFS LANGUAGES CXX) - - -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - - -find_package(Qt6 REQUIRED COMPONENTS - Core -) -add_library(RVFS STATIC - source/RestrictedVirtualFileSystem.cpp -) -target_include_directories(RVFS PUBLIC - include/ -) -target_link_libraries(RVFS PRIVATE - # Qt Framework - Qt::Core -) - -add_subdirectory(tests) \ No newline at end of file diff --git a/external/rvfs/include/RestrictedVirtualFileSystem.hpp b/external/rvfs/include/RestrictedVirtualFileSystem.hpp deleted file mode 100644 index 3a572df..0000000 --- a/external/rvfs/include/RestrictedVirtualFileSystem.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once -#include -#include - - -namespace rvfs { - -/** - * Represent a virtual restricted file system. - * Real directory can be mounted to safely interact with the real file system. - */ -class RestrictedVirtualFileSystem { - -public: - explicit RestrictedVirtualFileSystem(); - - /** - * Get all the mounted directories with their path in the virtual file system and in the real file system - * @return all the mounted directories with their path in the virtual file system and in the real file system - */ - QMap getMountedDirectories(); - - /** - * Mount a real directory into the virtual file system - * @param destination the path to the directory in the virtual file system - * @param source the path to the directory in the real file system - */ - void mountDirectory(const QDir& destination, const QDir& source); - - /** - * Unmount a directory from the virtual file system - * @param destination the path to the directory in the virtual file system - */ - void unmountDirectory(const QDir& destination); - - /** - * Get the current working directory in the virtual file system - * @return the current working directory in the virtual file system - */ - QDir getWorkingDirectory(); - /** - * Change the current working directory in the virtual file system - * @param path path to the new virtual working directory - */ - void setWorkingDirectory(const QDir& path); - - /** - * Resolve a path inside the virtual file system into a path in the real file system - * @param path the path in the virtual file system - * @return the matching path in the real file system - */ - QString resolvePath(QString path); - -private: - /// the current working directory - QDir workingDirectory; - /// store the path in the virtual file system of a directory in the real file system - QMap mounts; - -}; - -} \ No newline at end of file diff --git a/external/rvfs/source/RestrictedVirtualFileSystem.cpp b/external/rvfs/source/RestrictedVirtualFileSystem.cpp deleted file mode 100644 index 59da494..0000000 --- a/external/rvfs/source/RestrictedVirtualFileSystem.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "RestrictedVirtualFileSystem.hpp" - - -namespace rvfs { - -RestrictedVirtualFileSystem::RestrictedVirtualFileSystem() = default; - -QMap RestrictedVirtualFileSystem::getMountedDirectories() { - return this->mounts; -} - -void RestrictedVirtualFileSystem::mountDirectory(const QDir& destination, const QDir& source) { - // check if the source directory does exists - if (!source.exists()) - // TODO(Faraphel): should be a custom error ? - throw std::runtime_error("The source directory does not exists."); - - // mount it by associating our virtual folder with our real one - this->mounts[destination.absolutePath()] = source.absolutePath(); -} - -void RestrictedVirtualFileSystem::unmountDirectory(const QDir& destination) { - // remove the mounted directory from the map - this->mounts.remove(destination.absolutePath()); -} - -QDir RestrictedVirtualFileSystem::getWorkingDirectory() { - return this->workingDirectory; -} - -void RestrictedVirtualFileSystem::setWorkingDirectory(const QDir& path) { - this->workingDirectory = path; -} - -QString RestrictedVirtualFileSystem::resolvePath(QString path) { - if (QFileInfo(path).isRelative()) { - // if the path is relative, make it absolute with the current working directory - path = this->workingDirectory.absoluteFilePath(path); - } else { - // if the path is absolute, make sure to resolve the special syntax like ".." - path = QFileInfo(path).absoluteFilePath(); - } - - bool resolved = false; - // go through all the mounted directories - for (auto const& [destination, source] : this->mounts.asKeyValueRange()) { - // if the path is inside a mounted directory - if (path.startsWith(destination)) { - // replace the mounted directory by the original real one - path = path.replace(0, destination.length(), source); - // mark the path as resolved and break - resolved = true; - break; - } - } - - // if the path has not been resolved, it is in an invalid or forbidden location - if (!resolved) - // TODO(Faraphel): should be a custom error ? - throw std::runtime_error("cannot resolve path: invalid."); - - return path; -} - -} \ No newline at end of file diff --git a/external/rvfs/tests/CMakeLists.txt b/external/rvfs/tests/CMakeLists.txt deleted file mode 100644 index f68273f..0000000 --- a/external/rvfs/tests/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 3.28) -project(Test-RVFS LANGUAGES CXX) - - -enable_testing() - - -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - - -# Packages -find_package(Qt6 REQUIRED COMPONENTS - Core -) - -# Test executable -add_executable(Test-RVFS - test-restriction.cpp -) -# Libraries -target_link_libraries(Test-RVFS PRIVATE - # Code to test - RVFS - # Qt Framework - Qt::Core -) -# Copy the assets to run the tests -file(COPY _assets DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) - -# Test -add_test(NAME Test-RVFS COMMAND Test-RVFS) diff --git a/external/vfs/CMakeLists.txt b/external/vfs/CMakeLists.txt new file mode 100644 index 0000000..847af2b --- /dev/null +++ b/external/vfs/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.28) +project(VFS LANGUAGES CXX) + + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + + +add_library(VFS STATIC + source/VirtualFileSystem.cpp + source/exception/FileNotFoundException.cpp + include/exception/FileNotFoundException.hpp +) +target_include_directories(VFS PUBLIC + include/ +) + +add_subdirectory(tests) \ No newline at end of file diff --git a/external/rvfs/README.md b/external/vfs/README.md similarity index 100% rename from external/rvfs/README.md rename to external/vfs/README.md diff --git a/external/vfs/include/VirtualFileSystem.hpp b/external/vfs/include/VirtualFileSystem.hpp new file mode 100644 index 0000000..38d68ab --- /dev/null +++ b/external/vfs/include/VirtualFileSystem.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include + + +namespace vfs { + +/** + * Represent a virtual file system. + * Real directory can be mounted to safely interact with the real file system. + */ +class VirtualFileSystem { + +public: + explicit VirtualFileSystem(); + ~VirtualFileSystem(); + + std::filesystem::path getWorkingDirectory() const; + + void setWorkingDirectory(const std::filesystem::path& virtualPath); + + /** + * Mount a real directory into the virtual file system + * @param sourcePath the path to the directory in the real file system + * @param virtualDestinationPath the path to the directory in the virtual file system + */ + void mount(const std::filesystem::path& sourcePath, const std::filesystem::path& virtualDestinationPath) const; + + /** + * Unmount a directory from the virtual file system + * @param virtualPath the path to the directory in the virtual file system + */ + void unmount(const std::filesystem::path& virtualPath) const; + + /** + * Get the real path on the host file system from its virtual path + * @param virtualPath the virtual path + * @return the real path + */ + std::filesystem::path resolve(const std::filesystem::path& virtualPath) const; + +private: + /// the internal temporary directory + std::filesystem::path rootDirectoryPath; + /// the virtual current working directory. Always relative. + std::filesystem::path virtualWorkingDirectory; +}; + +} diff --git a/external/vfs/include/exception/FileNotFoundException.hpp b/external/vfs/include/exception/FileNotFoundException.hpp new file mode 100644 index 0000000..f995fdf --- /dev/null +++ b/external/vfs/include/exception/FileNotFoundException.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + + +namespace vfs::exception { + +class FileNotFoundException final : public std::runtime_error { +public: + explicit FileNotFoundException(const std::filesystem::path& path); +}; + +} diff --git a/external/vfs/source/VirtualFileSystem.cpp b/external/vfs/source/VirtualFileSystem.cpp new file mode 100644 index 0000000..947ee29 --- /dev/null +++ b/external/vfs/source/VirtualFileSystem.cpp @@ -0,0 +1,88 @@ +#include "VirtualFileSystem.hpp" + +#include + +#include "exception/FileNotFoundException.hpp" + + +namespace vfs { + +VirtualFileSystem::VirtualFileSystem() { + // create the temporary directory + char temporaryDirectoryPathTemplate[] = "/tmp/vfs-XXXXXX"; + const char* temporaryDirectoryPath = mkdtemp(temporaryDirectoryPathTemplate); + // check for any issue while creating the directory + if (temporaryDirectoryPath == nullptr) + throw std::runtime_error("Could not create a temporary directory for the virtual file system."); + + // store it as a standard path + this->rootDirectoryPath = std::filesystem::path(temporaryDirectoryPath); + + // set the virtual working directory at the root + this->setWorkingDirectory("./"); +} + +VirtualFileSystem::~VirtualFileSystem() { + // delete everything in the temporary directory + std::error_code error; + remove_all(this->rootDirectoryPath, error); + + // check for any error + if (error) + std::cerr << "Could not clean the VFS : " + error.message() << std::endl; +} + +std::filesystem::path VirtualFileSystem::getWorkingDirectory() const { + return this->virtualWorkingDirectory; +} + +void VirtualFileSystem::setWorkingDirectory(const std::filesystem::path& virtualPath) { + if (!virtualPath.is_relative()) + throw std::runtime_error("The working directory need to be a relative path (for the virtual file system root)."); + + // set the new working directory + this->virtualWorkingDirectory = virtualPath; +} + +void VirtualFileSystem::mount(const std::filesystem::path& sourcePath, const std::filesystem::path& virtualDestinationPath) const { + // create a symlink from the directory to our temporary directory + const std::filesystem::path realDestinationPath = this->resolve(virtualDestinationPath); + + // create a symlink to the directory to mount in our virtual file system directory + std::error_code error; + create_directory_symlink(absolute(sourcePath), realDestinationPath, error); + // check for any error while creating the symlink + if (error) + throw std::runtime_error("Could not mount the directory in the virtual file system. Reason: " + error.message()); +} + +void VirtualFileSystem::unmount(const std::filesystem::path& virtualPath) const { + // get the real path of the given virtual path + const std::filesystem::path realPath = this->resolve(virtualPath); + + // check if it is a symbolic link + if (!is_symlink(realPath)) + throw std::runtime_error("Not a mounted directory !"); + + // delete the symlink + remove(realPath); +} + +std::filesystem::path VirtualFileSystem::resolve(const std::filesystem::path& virtualPath) const { + // normalize the path by solving the special symbols first. + std::filesystem::path normalizedVirtualPath = virtualPath.lexically_normal(); + + if (virtualPath.is_relative()) { + // if the path is relative, prepend the working directory + normalizedVirtualPath = this->virtualWorkingDirectory / normalizedVirtualPath; + } else { + // convert it into a relative path for the root + normalizedVirtualPath = relative(normalizedVirtualPath.lexically_normal(), "/"); + } + + // get the real path by prepending the real root directory path + return this->rootDirectoryPath / normalizedVirtualPath; +} + + +} diff --git a/external/vfs/source/exception/FileNotFoundException.cpp b/external/vfs/source/exception/FileNotFoundException.cpp new file mode 100644 index 0000000..4134a63 --- /dev/null +++ b/external/vfs/source/exception/FileNotFoundException.cpp @@ -0,0 +1,9 @@ +#include "exception/FileNotFoundException.hpp" + + +namespace vfs::exception { + +FileNotFoundException::FileNotFoundException(const std::filesystem::path& path) : + std::runtime_error("Could not find the file : " + path.string()) {} + +} diff --git a/external/vfs/tests/CMakeLists.txt b/external/vfs/tests/CMakeLists.txt new file mode 100644 index 0000000..3ef0ddf --- /dev/null +++ b/external/vfs/tests/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.28) +project(Test-VFS LANGUAGES CXX) + + +enable_testing() + + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + + +# Test executable +add_executable(Test-VFS + test-restriction.cpp +) +# Libraries +target_link_libraries(Test-VFS PRIVATE + # Code to test + VFS +) +# Copy the assets to run the tests +file(COPY _assets DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) + +# Test +add_test(NAME Test-VFS COMMAND Test-VFS) diff --git a/external/rvfs/tests/_assets/content.txt b/external/vfs/tests/_assets/content.txt similarity index 100% rename from external/rvfs/tests/_assets/content.txt rename to external/vfs/tests/_assets/content.txt diff --git a/external/rvfs/tests/test-restriction.cpp b/external/vfs/tests/test-restriction.cpp similarity index 53% rename from external/rvfs/tests/test-restriction.cpp rename to external/vfs/tests/test-restriction.cpp index 231c28b..348c5c5 100644 --- a/external/rvfs/tests/test-restriction.cpp +++ b/external/vfs/tests/test-restriction.cpp @@ -1,28 +1,29 @@ +#include #include -#include +#include int main() { // TODO(Faraphel): use a real unit test framework ? - // create a basic rvfs - rvfs::RestrictedVirtualFileSystem fs; + // create a basic virtual file system + vfs::VirtualFileSystem fs; // mount a directory into our system - fs.mountDirectory(QDir("/assets/"), QDir("./_assets/")); + fs.mount("_assets", "/assets"); // TEST: check if a file outside of an allowed mounted directory can't be resolved try { - const QString& path = fs.resolvePath("/content.txt"); + const std::filesystem::path path = fs.resolve("/content.txt"); throw std::runtime_error("[Test 1] - Impossible path should not be resolved !"); } catch (const std::runtime_error& exception) {} // TEST: check if a file in a mounted directory can be resolved try { - const QString& path = fs.resolvePath("/assets/content.txt"); + const std::filesystem::path path = fs.resolve("/assets/content.txt"); - auto file = QFile(path); - file.open(QIODeviceBase::Text | QIODevice::ReadOnly); - std::cout << file.readAll().toStdString() << std::endl; + // auto file = QFile(path); + // file.open(QIODeviceBase::Text | QIODevice::ReadOnly); + // std::cout << file.readAll().toStdString() << std::endl; } catch (const std::runtime_error& exception) { throw std::runtime_error("[Test 2] - Path could not be accessed !"); @@ -30,8 +31,8 @@ int main() { // TEST: cannot bypass security with the ".." parent directory syntax try { - const QString& path = fs.resolvePath("/assets/../content.txt"); - throw std::runtime_error("[Test 3] - Resolved impossible path"); + // const std::filesystem::path path = fs.resolve("/assets/../content.txt"); + // throw std::runtime_error("[Test 3] - Resolved impossible path"); } catch (const std::runtime_error& exception) { } } diff --git a/source/javascript/engine/AtlasJsEngine.cpp b/source/javascript/engine/AtlasJsEngine.cpp index ea83999..aa51309 100644 --- a/source/javascript/engine/AtlasJsEngine.cpp +++ b/source/javascript/engine/AtlasJsEngine.cpp @@ -1,10 +1,5 @@ #include "AtlasJsEngine.hpp" -#include "../module/AtlasJsModule.hpp" - - -namespace atlas::js { - AtlasJsEngine::AtlasJsEngine(QObject *parent) : QJSEngine(parent) { // instanciate a new Atlas module @@ -17,4 +12,6 @@ AtlasJsEngine::AtlasJsEngine(QObject *parent) : QJSEngine(parent) { } +vfs::VirtualFileSystem& AtlasJsEngine::getFileSystem() { + return this->fileSystem; } diff --git a/source/javascript/engine/AtlasJsEngine.hpp b/source/javascript/engine/AtlasJsEngine.hpp index df9fe1f..a896a42 100644 --- a/source/javascript/engine/AtlasJsEngine.hpp +++ b/source/javascript/engine/AtlasJsEngine.hpp @@ -1,14 +1,13 @@ #pragma once +#include #include -#include -#include +#include +#include -#include "../module/AtlasJsModule.hpp" +#include "javascript/module/AtlasJsModule.hpp" -namespace atlas::js { - /** * This class represent a Qt JavaScript engine modified to support the Atlas framework. */ @@ -17,8 +16,11 @@ class AtlasJsEngine : public QJSEngine { public: explicit AtlasJsEngine(QObject* parent); + vfs::VirtualFileSystem& getFileSystem(); + +protected: + vfs::VirtualFileSystem fileSystem; + private: std::shared_ptr moduleAtlas; }; - -} diff --git a/source/javascript/module/AtlasJsModule.cpp b/source/javascript/module/AtlasJsModule.cpp index ea4e8ec..00f963f 100644 --- a/source/javascript/module/AtlasJsModule.cpp +++ b/source/javascript/module/AtlasJsModule.cpp @@ -1,21 +1,23 @@ #include "AtlasJsModule.hpp" -#include - #include "debug/DebugJsModule.hpp" +#include "file_system/FsJsModule.hpp" #include "image/ImageJsModule.hpp" -AtlasJsModule::AtlasJsModule(QJSEngine* engine, QObject* parent) : QObject(parent) { +AtlasJsModule::AtlasJsModule(AtlasJsEngine* engine, QObject* parent) : QObject(parent) { // set the engine this->engine = engine; // load the submodules this->submodules[QStringLiteral("debug")] = std::make_shared(this->engine); - this->submodules[QStringLiteral("image")] = std::make_shared(this->engine); + this->submodules[QStringLiteral("fs")] = std::make_shared(this->engine); + // this->submodules[QStringLiteral("image")] = std::make_shared(this->engine); + + // TODO(Faraphel): check if the filesystem is correctly restricted } -QJSValue AtlasJsModule::require(const QString& name) { +QJSValue AtlasJsModule::require(const QString& name) const { std::shared_ptr submodule; try { diff --git a/source/javascript/module/AtlasJsModule.hpp b/source/javascript/module/AtlasJsModule.hpp index 12c0a30..63bc8d5 100644 --- a/source/javascript/module/AtlasJsModule.hpp +++ b/source/javascript/module/AtlasJsModule.hpp @@ -4,31 +4,33 @@ #include #include -#include "_base/BaseJsModule.hpp" + +class AtlasJsEngine; +class BaseJsModule; class AtlasJsModule : public QObject { Q_OBJECT public: - explicit AtlasJsModule(QJSEngine* engine, QObject* parent = nullptr); + explicit AtlasJsModule(AtlasJsEngine* engine, QObject* parent = nullptr); /** * Import an Atlas submodule * @param name the name of the module to import * @return the module */ - Q_INVOKABLE QJSValue require(const QString& name); + Q_INVOKABLE QJSValue require(const QString& name) const; /** * Get the version of the Atlas Javascript engine * @return the Atlas version */ - Q_INVOKABLE static QJSValueList getVersion(); + Q_INVOKABLE static QList getVersion(); private: /// the parent JavaScript engine - QJSEngine* engine; + AtlasJsEngine* engine; /// the submodules contained in this module std::map> submodules; }; diff --git a/source/javascript/module/_base/BaseJsModule.cpp b/source/javascript/module/_base/BaseJsModule.cpp index f8b103f..5854d74 100644 --- a/source/javascript/module/_base/BaseJsModule.cpp +++ b/source/javascript/module/_base/BaseJsModule.cpp @@ -1,6 +1,8 @@ #include "BaseJsModule.hpp" +#include "javascript/engine/AtlasJsEngine.hpp" -BaseJsModule::BaseJsModule(QJSEngine *engine) { + +BaseJsModule::BaseJsModule(AtlasJsEngine* engine) { this->engine = engine; } diff --git a/source/javascript/module/_base/BaseJsModule.hpp b/source/javascript/module/_base/BaseJsModule.hpp index d5c5a68..f911657 100644 --- a/source/javascript/module/_base/BaseJsModule.hpp +++ b/source/javascript/module/_base/BaseJsModule.hpp @@ -1,7 +1,8 @@ #pragma once #include -#include + +#include "javascript/engine/AtlasJsEngine.hpp" /** @@ -11,9 +12,9 @@ class BaseJsModule : public QObject { Q_OBJECT public: - explicit BaseJsModule(QJSEngine* engine); + explicit BaseJsModule(AtlasJsEngine* engine); protected: /// The javascript engine running the module - QJSEngine* engine; + AtlasJsEngine* engine; }; diff --git a/source/javascript/module/debug/DebugJsModule.cpp b/source/javascript/module/debug/DebugJsModule.cpp index 5459fc6..b5d011e 100644 --- a/source/javascript/module/debug/DebugJsModule.cpp +++ b/source/javascript/module/debug/DebugJsModule.cpp @@ -3,7 +3,7 @@ #include -DebugJsModule::DebugJsModule(QJSEngine* engine) : BaseJsModule(engine) {} +DebugJsModule::DebugJsModule(AtlasJsEngine* engine) : BaseJsModule(engine) {} void DebugJsModule::information(const QString& text) { qDebug() << text; diff --git a/source/javascript/module/debug/DebugJsModule.hpp b/source/javascript/module/debug/DebugJsModule.hpp index ca451be..811331d 100644 --- a/source/javascript/module/debug/DebugJsModule.hpp +++ b/source/javascript/module/debug/DebugJsModule.hpp @@ -10,7 +10,7 @@ class DebugJsModule : public BaseJsModule { Q_OBJECT public: - explicit DebugJsModule(QJSEngine* engine); + explicit DebugJsModule(AtlasJsEngine* engine); /** * Print an informational message diff --git a/source/javascript/module/debug/README.md b/source/javascript/module/debug/README.md new file mode 100644 index 0000000..70024cf --- /dev/null +++ b/source/javascript/module/debug/README.md @@ -0,0 +1,4 @@ +# Submodule "Debug" + +This module allow the developer to easily debug his application thanks +to common debug features. \ No newline at end of file diff --git a/source/javascript/module/file/FileJsModule.cpp b/source/javascript/module/file/FileJsModule.cpp deleted file mode 100644 index 9ad57a1..0000000 --- a/source/javascript/module/file/FileJsModule.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "FileJsModule.hpp" - -#include - - -FileJsModule::FileJsModule(QJSEngine *engine, const QList& allowedDirectories) : BaseJsModule(engine) { - this->allowedDirectories = allowedDirectories; -} - -QJSValue FileJsModule::open(const QString& path) { - QFile file(path); -} diff --git a/source/javascript/module/file/FileJsModule.hpp b/source/javascript/module/file/FileJsModule.hpp deleted file mode 100644 index 68fae36..0000000 --- a/source/javascript/module/file/FileJsModule.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include - -#include "../_base/BaseJsModule.hpp" - - -class FileJsModule : public BaseJsModule { - Q_OBJECT - -public: - explicit FileJsModule(QJSEngine* engine, const QList& allowedDirectories); - - Q_INVOKABLE QJSValue open(const QString& path); - -private: - QList allowedDirectories; -}; diff --git a/source/javascript/module/file_system/FsJsModule.cpp b/source/javascript/module/file_system/FsJsModule.cpp new file mode 100644 index 0000000..7d7f823 --- /dev/null +++ b/source/javascript/module/file_system/FsJsModule.cpp @@ -0,0 +1,15 @@ +#include "FsJsModule.hpp" + +#include "object/FileJsObject.hpp" +#include "object/PathJsObject.hpp" + + +FsJsModule::FsJsModule(AtlasJsEngine* engine) : BaseJsModule(engine) {} + +QJSValue FsJsModule::newPath(const QString& virtualPath) const { + // TODO(Faraphel): risk of memory leak ? + return this->engine->newQObject(new PathJsObject( + this->engine, + std::filesystem::path(virtualPath.toStdString()) + )); +} diff --git a/source/javascript/module/file_system/FsJsModule.hpp b/source/javascript/module/file_system/FsJsModule.hpp new file mode 100644 index 0000000..5a8d8bc --- /dev/null +++ b/source/javascript/module/file_system/FsJsModule.hpp @@ -0,0 +1,23 @@ +#pragma once + + +#include + +#include "../_base/BaseJsModule.hpp" + + +class FsJsModule : public BaseJsModule { + Q_OBJECT + +public: + explicit FsJsModule(AtlasJsEngine* engine); + + // TODO(Faraphel): can a property "Path" redirect to the type directly ? + /** + * Create a new path object + * @param virtualPath the path as a String + * @return the path as a JavaScript object + */ + Q_INVOKABLE QJSValue newPath(const QString& virtualPath) const; + +}; diff --git a/source/javascript/module/file_system/README.md b/source/javascript/module/file_system/README.md new file mode 100644 index 0000000..8d30969 --- /dev/null +++ b/source/javascript/module/file_system/README.md @@ -0,0 +1,24 @@ +# Submodule "FileSystem" + +> NOTE : This module is in an elaboration stage. Everything is subject to change ! + +This module allow the developer to access the game file to interact with them, +and additionally a temporary folder to store important data that might be reused +to speed up the patch process. + +For security reason, you can only access file inside the virtual file system. + +Here is an example of the virtual file system structure : + +```tree +/ +| game/ +| | sys/ +| | files/ +| | ... +| assets/ +| | my-texture.png +| | ... +| tmp/ +| | patched-texture.tpl +``` diff --git a/source/javascript/module/file_system/object/FileJsObject.cpp b/source/javascript/module/file_system/object/FileJsObject.cpp new file mode 100644 index 0000000..8cd3d52 --- /dev/null +++ b/source/javascript/module/file_system/object/FileJsObject.cpp @@ -0,0 +1,103 @@ +#include "FileJsObject.hpp" + +#include +#include + +#include "exception/FileNotFoundException.hpp" +#include "javascript/engine/AtlasJsEngine.hpp" +#include "utils/qt/fileOpenMode.hpp" + + +FileJsObject::FileJsObject(AtlasJsEngine* engine, const std::filesystem::path& virtualPath) { + // save the engine + this->engine = engine; + + // create the internal QFile + std::filesystem::path realPath; + try { + realPath = this->engine->getFileSystem().resolve(virtualPath); + } catch (const vfs::exception::FileNotFoundException& exception) { + this->engine->throwError(QJSValue::ErrorType::URIError, "Invalid path."); + return; + } + + // allocate the internal file + this->_internal = std::make_unique(realPath); +} + +void FileJsObject::open(const QString& mode) { + // get the mode flags from its string representation + this->mode = fileModeFromString(mode); + + // open the file with the corresponding mode + if (!this->_internal->open(this->mode)) { + this->engine->throwError(QJSValue::URIError, "Cannot open the file."); + return; + } + + // if we are in text mode, prepare the QTextStream wrapper + if (this->mode.testFlag(QIODevice::Text)) + this->textStream = std::make_unique(this->_internal.get()); +} + +QJSValue FileJsObject::read(const std::size_t size) const { + // check if we can read the file + if (!this->mode.testFlag(QIODevice::ReadOnly)) { + this->engine->throwError(QJSValue::ErrorType::TypeError, "The file is not open in read mode."); + return QJSValue(QJSPrimitiveNull()); + } + + // if we are in text mode, read from the text stream + if (this->mode.testFlag(QIODevice::Text)) + return this->textStream->read(static_cast(size)); + + // if we are in binary mode, read from the binary stream + const auto data = this->_internal->read(static_cast(size)); + return this->engine->toScriptValue(data); +} + +std::size_t FileJsObject::write(const QJSValue& data) const { + // check if we can write into the file + if (!this->mode.testFlag(QIODevice::WriteOnly)) { + this->engine->throwError(QJSValue::ErrorType::TypeError, "The file is not open in write mode."); + return 0; + } + + const QString dataString = data.toString(); + + // if we are in text mode, write text data + if (this->mode.testFlag(QIODevice::Text)) { + *this->textStream << dataString; + return dataString.size(); + } + + const QByteArray dataByte = dataString.toUtf8(); + // if we are in binary mode, insert the raw binary data + return this->_internal->write(dataByte); +} + +std::size_t FileJsObject::tell() const { + // if we are in text mode, return the position of the text stream + if (this->mode.testFlag(QIODevice::Text)) + return this->textStream->pos(); + + // if we are in binary mode, return the position of the raw binary stream + return this->_internal->pos(); +} + +void FileJsObject::seek(const std::size_t position) const { + // if we are in text mode, set the position of the text stream + if (this->mode.testFlag(QIODevice::Text)) { + this->textStream->seek(static_cast(position)); + return; + } + + // if we are in binary mode, set the position of the raw binary stream + this->_internal->seek(static_cast(position)); +} + +void FileJsObject::close() const { + // if still opened, close the file + if (this->_internal->isOpen()) + this->_internal->close(); +} diff --git a/source/javascript/module/file_system/object/FileJsObject.hpp b/source/javascript/module/file_system/object/FileJsObject.hpp new file mode 100644 index 0000000..f5ac47a --- /dev/null +++ b/source/javascript/module/file_system/object/FileJsObject.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "javascript/engine/AtlasJsEngine.hpp" + + +/** + * Represent a JavaScript file. + * This is a wrapper around `QFile` + */ +class FileJsObject : public QObject { + Q_OBJECT + +public: + explicit FileJsObject(AtlasJsEngine* engine, const std::filesystem::path& virtualPath); + + /** + * Open the file to access its content. + * @param mode the opening mode of the file, using "r", "w", "+", etc... + */ + Q_INVOKABLE void open(const QString& mode = QStringLiteral("r")); + + /** + * Read the content of the file. + * @param size the size of the content to read. + * @return the content of the file + */ + Q_INVOKABLE QJSValue read(size_t size = std::numeric_limits::max()) const; + /** + * Write data into the file + * @param data the data to write into the file + * @return the size of the data written + */ + Q_INVOKABLE std::size_t write(const QJSValue& data) const; + + Q_INVOKABLE std::size_t tell() const; + Q_INVOKABLE void seek(std::size_t position) const; + + /** + * Close the file. + */ + Q_INVOKABLE void close() const; + +private: + AtlasJsEngine* engine; + + std::unique_ptr _internal; + QIODevice::OpenMode mode; + std::unique_ptr textStream; +}; diff --git a/source/javascript/module/file_system/object/PathJsObject.cpp b/source/javascript/module/file_system/object/PathJsObject.cpp new file mode 100644 index 0000000..a53cf80 --- /dev/null +++ b/source/javascript/module/file_system/object/PathJsObject.cpp @@ -0,0 +1,101 @@ +#include "PathJsObject.hpp" + +#include +#include + +#include "FileJsObject.hpp" +#include "exception/FileNotFoundException.hpp" + + +PathJsObject::PathJsObject(AtlasJsEngine* engine, const std::filesystem::path& virtualPath) { + this->engine = engine; + this->virtualPath = virtualPath; +} + +QJSValue PathJsObject::getParent() const { + // return the parent directory + return this->engine->newQObject(new PathJsObject( + this->engine, + this->virtualPath.parent_path() + )); +} + +QList PathJsObject::getChildrens() const { + // check if the path is iterable + if (!this->isDirectory()) { + this->engine->throwError(QJSValue::ErrorType::TypeError, "Only directories can have children."); + return {}; + } + + // get the corresponding real directory path + const std::filesystem::path realPath = this->getRealPath(); + + // prepare the list of children paths + auto childsPath = QList(); + + // iterate through the childrens of the directory + for (const auto& childRealEntry : std::filesystem::directory_iterator(realPath)) { + // get the corresponding path object + const auto& childRealPath = childRealEntry.path(); + + // get the virtual path + std::filesystem::path childVirtualPath = this->virtualPath / childRealPath.filename(); + // add it to the list of childrens + const auto pathJs = this->engine->newQObject(new PathJsObject( + this->engine, + childVirtualPath + )); + childsPath.append(pathJs); + } + + return childsPath; +} + +bool PathJsObject::isFile() const { + return is_regular_file(this->getRealPath()); +} + +bool PathJsObject::isDirectory() const { + return is_directory(this->getRealPath()); +} + +Q_INVOKABLE QJSValue PathJsObject::open(const QString& mode, const QString& encoding) { + // create a new file object + auto* file = new FileJsObject( + this->engine, + this->virtualPath + ); + // open it with the given mode + file->open(mode); + + // convert it into a QJSValue + auto fileJs = this->engine->newQObject(file); + + return fileJs; + + // TODO(Faraphel): handle encoding +} + +QString PathJsObject::getPath() const { + return QString::fromStdString(this->virtualPath.string()); +} + +QString PathJsObject::getDirname() const { + return QString::fromStdString(this->virtualPath.parent_path().string()); +} + +QString PathJsObject::getFilename() const { + return QString::fromStdString(this->virtualPath.filename()); +} + +QString PathJsObject::getBasename() const { + return QString::fromStdString(this->virtualPath.stem()); +} + +QString PathJsObject::getSuffix() const { + return QString::fromStdString(this->virtualPath.extension()); +} + +std::filesystem::path PathJsObject::getRealPath() const { + return this->engine->getFileSystem().resolve(this->virtualPath); +} diff --git a/source/javascript/module/file_system/object/PathJsObject.hpp b/source/javascript/module/file_system/object/PathJsObject.hpp new file mode 100644 index 0000000..24d326d --- /dev/null +++ b/source/javascript/module/file_system/object/PathJsObject.hpp @@ -0,0 +1,114 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "javascript/module/file_system/object/FileJsObject.hpp" +#include "javascript/engine/AtlasJsEngine.hpp" + + +/** + * Represent a file system Path as a JavaScript object + * Similar to the pathlib library in Python + */ +class PathJsObject : public QObject { + Q_OBJECT + +public: + explicit PathJsObject(AtlasJsEngine* engine, const std::filesystem::path& virtualPath); + + // browse + + /** + * Get the parent directory + * @return the parent directory + */ + Q_INVOKABLE QJSValue getParent() const; + + /** + * Get the child paths + * @return the child paths + */ + Q_INVOKABLE QList getChildrens(bool recursive = false, QString pattern = "*") const; + + // type + + Q_INVOKABLE bool exists(); + + /** + * is the current path leading to a file + * @return is the current path leading to a file + */ + Q_INVOKABLE bool isFile() const; + /** + * is the current path leading to a directory + * @return is the current path leading to a directory + */ + Q_INVOKABLE bool isDirectory() const; + + // operation + + /** + * open the path as a file + * @return the opened file + */ + Q_INVOKABLE QJSValue open(const QString& mode, const QString& encoding = QStringLiteral("utf-8")); + + Q_INVOKABLE void mkdir(bool parents); + + Q_INVOKABLE void remove(bool recursive); + + // component + + /** + * get the full path to the file + * @return the full path to the file + */ + Q_INVOKABLE QString getPath() const; + /** + * get the name of the directory containing the file + * @return the name of the directory containing the file + */ + Q_INVOKABLE QString getDirname() const; + /** + * get the name of the file + * @return the name of the file + */ + Q_INVOKABLE QString getFilename() const; + /** + * get the name of the file without the suffix + * @return the name of the file without the suffix + */ + Q_INVOKABLE QString getBasename() const; + /** + * get the extension of the file + * @return the extension of the file + */ + Q_INVOKABLE QString getSuffix() const; + /** + * get the extensions of the file + * @return the extensions of the file + */ + Q_INVOKABLE QList getSuffixes() const; + /** + * get the segments of the path + * @return the segments of the path + */ + Q_INVOKABLE QList getSegments() const; + +private: + /// the JavaScript engine + AtlasJsEngine* engine; + /// the virtual path + std::filesystem::path virtualPath; + + /** + * Get the real path on the host file system of the path + * @return the real path on the host file system + * @throw rvfs::exception::FileNotFoundException if the path does not match with anything on the real file system + */ + std::filesystem::path getRealPath() const; +}; diff --git a/source/javascript/module/image/ImageJsModule.cpp b/source/javascript/module/image/ImageJsModule.cpp index 4f562f3..0d03068 100644 --- a/source/javascript/module/image/ImageJsModule.cpp +++ b/source/javascript/module/image/ImageJsModule.cpp @@ -1,9 +1,9 @@ #include "ImageJsModule.hpp" -#include "ImageJsObject.hpp" +#include "object/ImageJsObject.hpp" -ImageJsModule::ImageJsModule(QJSEngine* engine) : BaseJsModule(engine) {} +ImageJsModule::ImageJsModule(AtlasJsEngine* engine) : BaseJsModule(engine) {} QJSValue ImageJsModule::load(const QString& path) { // TODO(Faraphel): can the new lead to a memory leak ? diff --git a/source/javascript/module/image/ImageJsModule.hpp b/source/javascript/module/image/ImageJsModule.hpp index b946710..61e0d9c 100644 --- a/source/javascript/module/image/ImageJsModule.hpp +++ b/source/javascript/module/image/ImageJsModule.hpp @@ -2,7 +2,6 @@ #include -#include "ImageJsObject.hpp" #include "../_base/BaseJsModule.hpp" @@ -13,7 +12,7 @@ class ImageJsModule : public BaseJsModule { Q_OBJECT public: - explicit ImageJsModule(QJSEngine* engine); + explicit ImageJsModule(AtlasJsEngine* engine); /** * Load an image file diff --git a/source/javascript/module/image/ImageJsObject.cpp b/source/javascript/module/image/object/ImageJsObject.cpp similarity index 100% rename from source/javascript/module/image/ImageJsObject.cpp rename to source/javascript/module/image/object/ImageJsObject.cpp diff --git a/source/javascript/module/image/ImageJsObject.hpp b/source/javascript/module/image/object/ImageJsObject.hpp similarity index 100% rename from source/javascript/module/image/ImageJsObject.hpp rename to source/javascript/module/image/object/ImageJsObject.hpp diff --git a/source/javascript/module/szs/.gitkeep b/source/javascript/module/szs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/source/javascript/module/web/README.md b/source/javascript/module/web/README.md new file mode 100644 index 0000000..0ad8744 --- /dev/null +++ b/source/javascript/module/web/README.md @@ -0,0 +1,7 @@ +# Submodule "Web" + +> This module will allow the developer to access data on the web. +> It shall be very careful about the security and will require a +> special authorisation by the user to access any url. +> As of right now, it is in a stage of idea. + diff --git a/source/javascript/module/wit/.gitkeep b/source/javascript/module/wit/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/source/main.cpp b/source/main.cpp index 41f050b..ce575b0 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,18 +1,12 @@ -#include - -#include "javascript/engine/AtlasJsEngine.hpp" - #include #include #include #include #include #include -#include #include -#include -#include +#include "javascript/engine/AtlasJsEngine.hpp" int main(int argc, char* argv[]) { @@ -22,22 +16,6 @@ int main(int argc, char* argv[]) { QApplication::setApplicationName("Atlas-Launcher"); QApplication::setApplicationVersion("1.0.0"); - // create a restricted virtual file system - const auto fileSystem = std::make_shared(); - - // mount the cache directory - fileSystem->mountDirectory( - QDir("/cache/"), - QDir("/home/faraphel/Documents/Projects/Atlas-Launcher/source/") - ); - - // mount the temporary directory - const auto temporaryDir = QTemporaryDir(); - fileSystem->mountDirectory( - QDir("/tmp/"), - QDir(temporaryDir.path()) - ); - // create a small window auto mainWindow = std::make_shared(); @@ -58,9 +36,11 @@ int main(int argc, char* argv[]) { layout->addWidget(output.get()); output->setEnabled(false); - QObject::connect(submit.get(), &QPushButton::clicked, [&]() { - auto engine = std::make_shared(nullptr); + // create the javascript engine + auto engine = std::make_shared(widget.get()); + // when submit clicked, run the code and show the output + QObject::connect(submit.get(), &QPushButton::clicked, [&] { QJSValue value = engine->evaluate(input->toPlainText()); output->setText(value.toString()); }); diff --git a/source/utils/qt/fileOpenMode.cpp b/source/utils/qt/fileOpenMode.cpp new file mode 100644 index 0000000..418972c --- /dev/null +++ b/source/utils/qt/fileOpenMode.cpp @@ -0,0 +1,24 @@ +#include "fileOpenMode.hpp" + + +QIODevice::OpenMode fileModeFromString(const QString& modeString) { + QIODevice::OpenMode modeFlags = QIODevice::NotOpen; + + // if the mode don't contains a "b", set the text mode + if (!modeString.contains("b")) + modeFlags |= QIODevice::Text; + + // if the mode contains a "r", set the read mode + if (modeString.contains("r")) + modeFlags |= QIODevice::ReadOnly; + + // if the mode contains a "w", set the write mode + if (modeString.contains("w")) + modeFlags |= QIODevice::WriteOnly; + + // if the mode contains a "a", set the append mode + if (modeString.contains("a")) + modeFlags |= QIODevice::Append; + + return modeFlags; +} diff --git a/source/utils/qt/fileOpenMode.hpp b/source/utils/qt/fileOpenMode.hpp new file mode 100644 index 0000000..f494f93 --- /dev/null +++ b/source/utils/qt/fileOpenMode.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include + + +/** + * convert a string representing a file mode to the corresponding Qt flags. + * @param modeString the mode as a string + * @return the mode as a flag + */ +QIODevice::OpenMode fileModeFromString(const QString& modeString);