Revision control
Copy as Markdown
// 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