Revision control

++ // Copyright (C) 2025 Qt Group
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++ #include "qwasmlocalfileengine_p.h"
++ #include <QtCore/QDebug>
++ #include <QtCore/QUrl>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++
++ // Custom URL scheme for files handled by this file engine. A complete file URL can
++ // look like
++ //
++ // "weblocalfile:/n/file.ext"
++ //
++ // where n is a counter to ensure uniqueness, needed since web platform gives us the file name only
++ // and does not provide the file path.
++ //
++ // The scheme may be visible to end users if the application displays it, so
++ // we avoid using "wasm" and instead say "web", which should be more recognizable.
++ static constexpr QLatin1StringView wasmlocalfileScheme = "weblocalfile"_L1;
++
++ // Instantiate the engine, the system will pick it up automatically
++ // Never destroy it to avoid problems with static destruction.
++ // The OS will reclaim the memory anyway,
++ // TODO: cleanup on QApplication destruction and re-init on QApplication re-creation.
++ static QWasmFileEngineHandler *singleton = new QWasmFileEngineHandler();
++
++ QWasmFileEngineHandler::QWasmFileEngineHandler()
++ {
++ }
++
++ QWasmFileEngineHandler::~QWasmFileEngineHandler()
++ {
++ }
++
++ std::unique_ptr<QAbstractFileEngine> QWasmFileEngineHandler::create(const QString &fileName) const
++ {
++ if (!QWasmFileEngineHandler::isWasmFileName(fileName))
++ return {};
++
++ // Check if it's a File or FileSystemFileHandle
++ if (singleton->m_files.contains(fileName)) {
++ qstdweb::File file = singleton->m_files.value(fileName);
++ return std::make_unique<QWasmFileEngine>(fileName, file);
++ } else if (singleton->m_fileSystemFiles.contains(fileName)) {
++ qstdweb::FileSystemFileHandle file = singleton->m_fileSystemFiles.value(fileName);
++ return std::make_unique<QWasmFileEngine>(fileName, file);
++ }
++
++ // Not an error, this function will be called with partial paths like "weblocalfile:/1/"
++ return {};
++ }
++
++ // Check if this is a wasm filename by checking the URL scheme.
++ bool QWasmFileEngineHandler::isWasmFileName(const QString& fileName)
++ {
++ return QUrl(fileName).scheme() == wasmlocalfileScheme;
++ }
++
++ // Creates a wasm filename using the custom URL scheme and a counter.
++ QString QWasmFileEngineHandler::makeWasmFileName(const QString &nativeFileName)
++ {
++ static std::atomic<uint64_t> sid = 0;
++ const uint64_t id = ++sid;
++ return wasmlocalfileScheme + QStringLiteral(":/%1/%2").arg(id).arg(nativeFileName);
++ }
++
++ // Extracts the native filename from the custom URL (removes scheme and counter).
++ QString QWasmFileEngineHandler::nativeFileName(const QString &wasmFileName)
++ {
++ QUrl url(wasmFileName);
++ if (url.scheme() == wasmlocalfileScheme) {
++ QString path = url.path();
++ // Path is "/n/filename", find the second '/' and extract the filename
++ const qsizetype idx = path.indexOf(u'/', 1);
++ if (idx != -1)
++ return path.mid(idx + 1);
++ }
++ return wasmFileName;
++ }
++
++ // Adds a File to the set of open files. Returns a prefixed wasm file name.
++ QString QWasmFileEngineHandler::addFile(qstdweb::File file)
++ {
++ QString nativeFileName = QString::fromStdString(file.name());
++ QString wasmFileName = makeWasmFileName(nativeFileName);
++ singleton->m_files.insert(wasmFileName, file);
++ return wasmFileName;
++ }
++
++ // Adds a FileSystemFileHandle to the set of open files. Returns a prefixed wasm file name.
++ QString QWasmFileEngineHandler::addFile(qstdweb::FileSystemFileHandle file)
++ {
++ QString nativeFileName = QString::fromStdString(file.name());
++ QString wasmFileName = makeWasmFileName(nativeFileName);
++ singleton->m_fileSystemFiles.insert(wasmFileName, file);
++ return wasmFileName;
++ }
++
++ // Removes a File or FileSystemFileHandle from the set of open file handlers
++ void QWasmFileEngineHandler::removeFile(const QString fileName)
++ {
++ singleton->m_files.remove(fileName);
++ singleton->m_fileSystemFiles.remove(fileName);
++ }
++
++ qstdweb::File QWasmFileEngineHandler::getFile(const QString fileName)
++ {
++ return singleton->m_files.value(fileName);
++ }
++
++ qstdweb::FileSystemFileHandle QWasmFileEngineHandler::getFileSystemFile(const QString fileName)
++ {
++ return singleton->m_fileSystemFiles.value(fileName);
++ }
++
++ /*!
++ \class QWasmFileEngine
++ \brief The QWasmFileEngine class provides a QAbstractFileEngine
++ for files that has the prefix ':weblocalfile/'.
++ */
++
++ // Constructs a QWasmFileEngine with a File for read-only access
++ QWasmFileEngine::QWasmFileEngine(const QString &fileName, qstdweb::File file)
++ : m_fileName(fileName),
++ m_blobDevice(std::make_unique<qstdweb::BlobIODevice>(file.slice(0, file.size())))
++ {
++ // TODO use m_blobDevice in unbuffered mode? if there is already a buffer higher up.
++ }
++
++ // Constructs a QWasmFileEngine with a FileSystemFileHandle for read-write access
++ QWasmFileEngine::QWasmFileEngine(const QString &fileName, qstdweb::FileSystemFileHandle file)
++ : m_fileName(fileName),
++ m_fileDevice(std::make_unique<qstdweb::FileSystemFileIODevice>(file))
++ {
++
++ }
++
++ QWasmFileEngine::~QWasmFileEngine()
++ {
++ close();
++ }
++
++ bool QWasmFileEngine::open(QIODevice::OpenMode openMode, std::optional<QFile::Permissions> permissions)
++ {
++ Q_UNUSED(permissions);
++
++ // Warn on missing asyncify/JSPI and fail the open
++ if (!qstdweb::canBlockCallingThread()) {
++ const QString message =
++ QStringLiteral("Accessing local file contents on Qt for WebAssembly "
++ "requires JSPI or asyncify to be enabled.");
++ qWarning().noquote() << message;
++ setError(QFile::OpenError, message);
++ return false;
++ }
++
++ bool opened = false;
++ if (m_fileDevice)
++ opened = m_fileDevice->open(openMode);
++ else if (m_blobDevice)
++ opened = m_blobDevice->open(openMode);
++
++ if (opened)
++ m_openMode = openMode;
++ return opened;
++ }
++
++ bool QWasmFileEngine::close()
++ {
++ if (m_openMode == QIODevice::NotOpen)
++ return false;
++
++ bool success = true;
++ if (m_fileDevice) {
++ m_fileDevice->close();
++ }
++ if (m_blobDevice) {
++ m_blobDevice->close();
++ }
++
++ m_openMode = QIODevice::NotOpen;
++ return success;
++ }
++
++ bool QWasmFileEngine::flush()
++ {
++ return true;
++ }
++
++ bool QWasmFileEngine::syncToDisk()
++ {
++ return true;
++ }
++
++ qint64 QWasmFileEngine::size() const
++ {
++ if (m_fileDevice)
++ return m_fileDevice->size();
++ if (m_blobDevice)
++ return m_blobDevice->size();
++ return 0;
++ }
++
++ qint64 QWasmFileEngine::pos() const
++ {
++ if (m_fileDevice)
++ return m_fileDevice->pos();
++ if (m_blobDevice)
++ return m_blobDevice->pos();
++ return 0;
++ }
++
++ bool QWasmFileEngine::seek(qint64 pos)
++ {
++ if (m_fileDevice)
++ return m_fileDevice->seek(pos);
++ if (m_blobDevice)
++ return m_blobDevice->seek(pos);
++ return false;
++ }
++
++ bool QWasmFileEngine::isSequential() const
++ {
++ return false;
++ }
++
++ bool QWasmFileEngine::remove()
++ {
++ return false;
++ }
++
++ bool QWasmFileEngine::copy(const QString &newName)
++ {
++ Q_UNUSED(newName);
++ return false;
++ }
++
++ bool QWasmFileEngine::rename(const QString &newName)
++ {
++ Q_UNUSED(newName);
++ return false;
++ }
++
++ bool QWasmFileEngine::renameOverwrite(const QString &newName)
++ {
++ Q_UNUSED(newName);
++ return false;
++ }
++
++ bool QWasmFileEngine::link(const QString &newName)
++ {
++ Q_UNUSED(newName);
++ return false;
++ }
++
++ bool QWasmFileEngine::mkdir(const QString &dirName, bool createParentDirectories,
++ std::optional<QFile::Permissions> permissions) const
++ {
++ Q_UNUSED(dirName);
++ Q_UNUSED(createParentDirectories);
++ Q_UNUSED(permissions);
++ return false;
++ }
++
++ bool QWasmFileEngine::rmdir(const QString &dirName, bool recurseParentDirectories) const
++ {
++ Q_UNUSED(dirName);
++ Q_UNUSED(recurseParentDirectories);
++ return false;
++ }
++
++ bool QWasmFileEngine::setSize(qint64 size)
++ {
++ Q_UNUSED(size);
++ return false;
++ }
++
++ bool QWasmFileEngine::caseSensitive() const
++ {
++ return true;
++ }
++
++ bool QWasmFileEngine::isRelativePath() const
++ {
++ return false;
++ }
++
++
++ QAbstractFileEngine::FileFlags QWasmFileEngine::fileFlags(FileFlags type) const
++ {
++ return type & (QAbstractFileEngine::FileFlag::ExistsFlag |
++ QAbstractFileEngine::FileFlag::FileType |
++ QAbstractFileEngine::FileFlag::ReadOwnerPerm |
++ QAbstractFileEngine::FileFlag::WriteOwnerPerm |
++ QAbstractFileEngine::FileFlag::ReadUserPerm |
++ QAbstractFileEngine::FileFlag::WriteUserPerm);
++ }
++
++ bool QWasmFileEngine::setPermissions(uint perms)
++ {
++ Q_UNUSED(perms);
++ return false;
++ }
++
++ QByteArray QWasmFileEngine::id() const
++ {
++ return {};
++ }
++
++ QString QWasmFileEngine::fileName(FileName file) const
++ {
++ switch (file) {
++ case DefaultName:
++ case AbsoluteName:
++ case CanonicalName:
++ return m_fileName;
++ case BaseName: {
++ QString native = QWasmFileEngineHandler::nativeFileName(m_fileName);
++ QFileInfo info(native);
++ return info.fileName();
++ }
++ case PathName:
++ case AbsolutePathName:
++ case CanonicalPathName: {
++ QString native = QWasmFileEngineHandler::nativeFileName(m_fileName);
++ QFileInfo info(native);
++ QString path = info.path();
++ return path.isEmpty() ? "."_L1 : path;
++ }
++ default:
++ return QString();
++ }
++ }
++
++ uint QWasmFileEngine::ownerId(FileOwner) const
++ {
++ return 0;
++ }
++
++ QString QWasmFileEngine::owner(FileOwner) const
++ {
++ return {};
++ }
++
++ bool QWasmFileEngine::setFileTime(const QDateTime &newDate, QFile::FileTime time)
++ {
++ Q_UNUSED(newDate);
++ Q_UNUSED(time);
++ return false;
++ }
++
++ QDateTime QWasmFileEngine::fileTime(QFile::FileTime time) const
++ {
++ Q_UNUSED(time);
++ return {};
++ }
++
++ void QWasmFileEngine::setFileName(const QString &file)
++ {
++ if (m_fileName == file)
++ return;
++ close();
++ m_fileName = file;
++ }
++
++ int QWasmFileEngine::handle() const
++ {
++ return -1;
++ }
++
++ QAbstractFileEngine::TriStateResult QWasmFileEngine::cloneTo(QAbstractFileEngine *target)
++ {
++ Q_UNUSED(target);
++ return QAbstractFileEngine::TriStateResult::NotSupported;
++ }
++
++ QAbstractFileEngine::IteratorUniquePtr QWasmFileEngine::beginEntryList(const QString &path, QDirListing::IteratorFlags filters,
++ const QStringList &filterNames)
++ {
++ Q_UNUSED(path);
++ Q_UNUSED(filters);
++ Q_UNUSED(filterNames);
++ return nullptr;
++ }
++
++ qint64 QWasmFileEngine::read(char *data, qint64 maxlen)
++ {
++ if (!(m_openMode & QIODevice::ReadOnly))
++ return -1;
++
++ if (m_fileDevice)
++ return m_fileDevice->read(data, maxlen);
++ if (m_blobDevice)
++ return m_blobDevice->read(data, maxlen);
++ return -1;
++ }
++
++ qint64 QWasmFileEngine::readLine(char *data, qint64 maxlen)
++ {
++ if (!(m_openMode & QIODevice::ReadOnly))
++ return -1;
++
++ if (m_fileDevice)
++ return m_fileDevice->readLine(data, maxlen);
++ if (m_blobDevice)
++ return m_blobDevice->readLine(data, maxlen);
++ return -1;
++ }
++
++ qint64 QWasmFileEngine::write(const char *data, qint64 len)
++ {
++ if (!(m_openMode & QIODevice::WriteOnly))
++ return -1;
++
++ if (m_fileDevice)
++ return m_fileDevice->write(data, len);
++ return -1;
++ }
++
++ bool QWasmFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output)
++ {
++ Q_UNUSED(extension);
++ Q_UNUSED(option);
++ Q_UNUSED(output);
++ return false;
++ }
++
++ bool QWasmFileEngine::supportsExtension(Extension extension) const
++ {
++ Q_UNUSED(extension);
++ return false;
++ }
++
++ QT_END_NAMESPACE