diff options
Diffstat (limited to 'src/qml/qml/qqmlfile.cpp')
-rw-r--r-- | src/qml/qml/qqmlfile.cpp | 418 |
1 files changed, 275 insertions, 143 deletions
diff --git a/src/qml/qml/qqmlfile.cpp b/src/qml/qml/qqmlfile.cpp index 4db8981975..bac70e69bb 100644 --- a/src/qml/qml/qqmlfile.cpp +++ b/src/qml/qml/qqmlfile.cpp @@ -1,41 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtQml module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qqmlfile.h" @@ -46,18 +10,27 @@ #include <private/qqmlengine_p.h> #include <private/qqmlglobal_p.h> -/*! -\class QQmlFile -\brief The QQmlFile class gives access to local and remote files. +QT_BEGIN_NAMESPACE -\internal +/*! + \class QQmlFile + \inmodule QtQml + \since 5.0 + \brief The QQmlFile class provides static utility methods to categorize URLs. -Supports file:// and qrc:/ uris and whatever QNetworkAccessManager supports. + QQmlFile provides some static utility methods to categorize URLs + and file names the way \l{QQmlEngine} does when loading content from them. */ -#define QQMLFILE_MAX_REDIRECT_RECURSION 16 +/*! + \internal -QT_BEGIN_NAMESPACE + \enum QQmlFile::Status + \value Null + \value Ready + \value Error + \value Loading + */ static char qrc_string[] = "qrc"; static char file_string[] = "file"; @@ -65,6 +38,9 @@ static char file_string[] = "file"; #if defined(Q_OS_ANDROID) static char assets_string[] = "assets"; static char content_string[] = "content"; +static char authority_externalstorage[] = "com.android.externalstorage.documents"; +static char authority_downloads_documents[] = "com.android.providers.downloads.documents"; +static char authority_media_documents[] = "com.android.providers.media.documents"; #endif class QQmlFilePrivate; @@ -97,7 +73,6 @@ private: QQmlEngine *m_engine; QQmlFilePrivate *m_p; - int m_redirectCount; QNetworkReply *m_reply; }; #endif @@ -132,7 +107,7 @@ int QQmlFileNetworkReply::replyFinishedIndex = -1; int QQmlFileNetworkReply::replyDownloadProgressIndex = -1; QQmlFileNetworkReply::QQmlFileNetworkReply(QQmlEngine *e, QQmlFilePrivate *p, const QUrl &url) -: m_engine(e), m_p(p), m_redirectCount(0), m_reply(nullptr) +: m_engine(e), m_p(p), m_reply(nullptr) { if (finishedIndex == -1) { finishedIndex = QMetaMethod::fromSignal(&QQmlFileNetworkReply::finished).methodIndex(); @@ -166,27 +141,6 @@ QQmlFileNetworkReply::~QQmlFileNetworkReply() void QQmlFileNetworkReply::networkFinished() { - ++m_redirectCount; - if (m_redirectCount < QQMLFILE_MAX_REDIRECT_RECURSION) { - QVariant redirect = m_reply->attribute(QNetworkRequest::RedirectionTargetAttribute); - if (redirect.isValid()) { - QUrl url = m_reply->url().resolved(redirect.toUrl()); - - QNetworkRequest req(url); - req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true); - - m_reply->deleteLater(); - m_reply = m_engine->networkAccessManager()->get(req); - - QMetaObject::connect(m_reply, replyFinishedIndex, - this, networkFinishedIndex); - QMetaObject::connect(m_reply, replyDownloadProgressIndex, - this, networkDownloadProgressIndex); - - return; - } - } - if (m_reply->error()) { m_p->errorString = m_reply->errorString(); m_p->error = QQmlFilePrivate::Network; @@ -216,22 +170,36 @@ QQmlFilePrivate::QQmlFilePrivate() { } +/*! + \internal + */ QQmlFile::QQmlFile() : d(new QQmlFilePrivate) { } -QQmlFile::QQmlFile(QQmlEngine *e, const QUrl &url) +/*! + \internal + Constructs a QQmlFile for content at \a url, using \a engine to retrieve it. + */ +QQmlFile::QQmlFile(QQmlEngine *engine, const QUrl &url) : d(new QQmlFilePrivate) { - load(e, url); + load(engine, url); } -QQmlFile::QQmlFile(QQmlEngine *e, const QString &url) - : QQmlFile(e, QUrl(url)) +/*! + \internal + Constructs a QQmlFile for content at \a url, using \a engine to retrieve it. + */ +QQmlFile::QQmlFile(QQmlEngine *engine, const QString &url) + : QQmlFile(engine, QUrl(url)) { } +/*! + \internal + */ QQmlFile::~QQmlFile() { #if QT_CONFIG(qml_network) @@ -241,26 +209,41 @@ QQmlFile::~QQmlFile() d = nullptr; } +/*! + \internal + */ bool QQmlFile::isNull() const { return status() == Null; } +/*! + \internal + */ bool QQmlFile::isReady() const { return status() == Ready; } +/*! + \internal + */ bool QQmlFile::isError() const { return status() == Error; } +/*! + \internal + */ bool QQmlFile::isLoading() const { return status() == Loading; } +/*! + \internal + */ QUrl QQmlFile::url() const { if (!d->urlString.isEmpty()) { @@ -270,6 +253,9 @@ QUrl QQmlFile::url() const return d->url; } +/*! + \internal + */ QQmlFile::Status QQmlFile::status() const { if (d->url.isEmpty() && d->urlString.isEmpty()) @@ -284,6 +270,9 @@ QQmlFile::Status QQmlFile::status() const return Ready; } +/*! + \internal + */ QString QQmlFile::error() const { switch (d->error) { @@ -297,21 +286,34 @@ QString QQmlFile::error() const } } +/*! + \internal + */ qint64 QQmlFile::size() const { return d->data.size(); } +/*! + \internal + */ const char *QQmlFile::data() const { return d->data.constData(); } +/*! + \internal + */ QByteArray QQmlFile::dataByteArray() const { return d->data; } +/*! + \internal + Loads content at \a url using \a engine. + */ void QQmlFile::load(QQmlEngine *engine, const QUrl &url) { Q_ASSERT(engine); @@ -342,6 +344,10 @@ void QQmlFile::load(QQmlEngine *engine, const QUrl &url) } } +/*! + \internal + Loads content at \a url using \a engine. + */ void QQmlFile::load(QQmlEngine *engine, const QString &url) { Q_ASSERT(engine); @@ -376,6 +382,9 @@ void QQmlFile::load(QQmlEngine *engine, const QString &url) } } +/*! + \internal + */ void QQmlFile::clear() { d->url = QUrl(); @@ -384,12 +393,22 @@ void QQmlFile::clear() d->error = QQmlFilePrivate::None; } -void QQmlFile::clear(QObject *) +/*! + \internal + Redirects to the other clear() overload, ignoring \a object. + */ +void QQmlFile::clear(QObject *object) { + Q_UNUSED(object); clear(); } #if QT_CONFIG(qml_network) + +/*! + \internal + Connects \a method of \a object to the internal \c{finished} signal. + */ bool QQmlFile::connectFinished(QObject *object, const char *method) { if (!d || !d->reply) { @@ -397,10 +416,13 @@ bool QQmlFile::connectFinished(QObject *object, const char *method) return false; } - return QObject::connect(d->reply, SIGNAL(finished()), - object, method); + return QObject::connect(d->reply, SIGNAL(finished()), object, method); } +/*! + \internal + Connects \a method of \a object to the internal \c{finished} signal. + */ bool QQmlFile::connectFinished(QObject *object, int method) { if (!d || !d->reply) { @@ -412,6 +434,10 @@ bool QQmlFile::connectFinished(QObject *object, int method) object, method); } +/*! + \internal + Connects \a method of \a object to the internal \c{downloadProgress} signal. + */ bool QQmlFile::connectDownloadProgress(QObject *object, const char *method) { if (!d || !d->reply) { @@ -423,6 +449,10 @@ bool QQmlFile::connectDownloadProgress(QObject *object, const char *method) object, method); } +/*! + \internal + Connects \a method of \a object to the internal \c{downloadProgress} signal. + */ bool QQmlFile::connectDownloadProgress(QObject *object, int method) { if (!d || !d->reply) { @@ -436,18 +466,21 @@ bool QQmlFile::connectDownloadProgress(QObject *object, int method) #endif /*! -Returns true if QQmlFile will open \a url synchronously. + \internal -Synchronous urls have a qrc:/ or file:// scheme. + Returns \c true if QQmlFile will open \a url synchronously. + Otherwise returns \c false. Synchronous urls have a \c{qrc:} or \c{file:} + scheme. -\note On Android, urls with assets:/ scheme are also considered synchronous. + \note On Android, urls with \c{assets:} or \c{content:} scheme are also + considered synchronous. */ bool QQmlFile::isSynchronous(const QUrl &url) { QString scheme = url.scheme(); - if ((scheme.length() == 4 && 0 == scheme.compare(QLatin1String(file_string), Qt::CaseInsensitive)) || - (scheme.length() == 3 && 0 == scheme.compare(QLatin1String(qrc_string), Qt::CaseInsensitive))) { + if ((scheme.size() == 4 && 0 == scheme.compare(QLatin1String(file_string), Qt::CaseInsensitive)) || + (scheme.size() == 3 && 0 == scheme.compare(QLatin1String(qrc_string), Qt::CaseInsensitive))) { return true; #if defined(Q_OS_ANDROID) @@ -463,28 +496,31 @@ bool QQmlFile::isSynchronous(const QUrl &url) } /*! -Returns true if QQmlFile will open \a url synchronously. + \internal -Synchronous urls have a qrc:/ or file:// scheme. + Returns \c true if QQmlFile will open \a url synchronously. + Otherwise returns \c false. Synchronous urls have a \c{qrc:} or \c{file:} + scheme. -\note On Android, urls with assets:/ scheme are also considered synchronous. + \note On Android, urls with \c{assets:} or \c{content:} scheme are also + considered synchronous. */ bool QQmlFile::isSynchronous(const QString &url) { - if (url.length() < 5 /* qrc:/ */) + if (url.size() < 5 /* qrc:/ */) return false; QChar f = url[0]; if (f == QLatin1Char('f') || f == QLatin1Char('F')) { - return url.length() >= 7 /* file:// */ && + return url.size() >= 7 /* file:// */ && url.startsWith(QLatin1String(file_string), Qt::CaseInsensitive) && url[4] == QLatin1Char(':') && url[5] == QLatin1Char('/') && url[6] == QLatin1Char('/'); } else if (f == QLatin1Char('q') || f == QLatin1Char('Q')) { - return url.length() >= 5 /* qrc:/ */ && + return url.size() >= 5 /* qrc:/ */ && url.startsWith(QLatin1String(qrc_string), Qt::CaseInsensitive) && url[3] == QLatin1Char(':') && url[4] == QLatin1Char('/'); @@ -505,76 +541,149 @@ bool QQmlFile::isSynchronous(const QString &url) return false; } -/*! -Returns true if \a url is a local file that can be opened with QFile. +#if defined(Q_OS_ANDROID) +static bool hasLocalContentAuthority(const QUrl &url) +{ + const QString authority = url.authority(); + return authority.isEmpty() + || authority == QLatin1String(authority_externalstorage) + || authority == QLatin1String(authority_downloads_documents) + || authority == QLatin1String(authority_media_documents); +} +#endif -Local file urls have either a qrc:/ or file:// scheme. +/*! + Returns \c true if \a url is a local file that can be opened with \l{QFile}. + Otherwise returns \c false. Local file urls have either a \c{qrc:} or + \c{file:} scheme. -\note On Android, urls with assets:/ scheme are also considered local files. + \note On Android, urls with \c{assets:} or \c{content:} scheme are also + considered local files. */ bool QQmlFile::isLocalFile(const QUrl &url) { QString scheme = url.scheme(); - if ((scheme.length() == 4 && 0 == scheme.compare(QLatin1String(file_string), Qt::CaseInsensitive)) || - (scheme.length() == 3 && 0 == scheme.compare(QLatin1String(qrc_string), Qt::CaseInsensitive))) { + // file: URLs with two slashes following the scheme can be interpreted as local files + // where the slashes are part of the path. Therefore, disregard the authority. + // See QUrl::toLocalFile(). + if (scheme.size() == 4 && scheme.startsWith(QLatin1String(file_string), Qt::CaseInsensitive)) return true; + if (scheme.size() == 3 && scheme.startsWith(QLatin1String(qrc_string), Qt::CaseInsensitive)) + return url.authority().isEmpty(); + #if defined(Q_OS_ANDROID) - } else if (scheme.length() == 6 && 0 == scheme.compare(QLatin1String(assets_string), Qt::CaseInsensitive)) { - return true; + if (scheme.length() == 6 + && scheme.startsWith(QLatin1String(assets_string), Qt::CaseInsensitive)) + return url.authority().isEmpty(); + if (scheme.length() == 7 + && scheme.startsWith(QLatin1String(content_string), Qt::CaseInsensitive)) + return hasLocalContentAuthority(url); #endif - } else { - return false; - } + return false; } -/*! -Returns true if \a url is a local file that can be opened with QFile. - -Local file urls have either a qrc:/ or file:// scheme. - -\note On Android, urls with assets:/ scheme are also considered local files. -*/ -bool QQmlFile::isLocalFile(const QString &url) +static bool hasScheme(const QString &url, const char *scheme, qsizetype schemeLength) { - if (url.length() < 5 /* qrc:/ */) + const qsizetype urlLength = url.size(); + + if (urlLength < schemeLength + 1) return false; - QChar f = url[0]; + if (!url.startsWith(QLatin1String(scheme, scheme + schemeLength), Qt::CaseInsensitive)) + return false; - if (f == QLatin1Char('f') || f == QLatin1Char('F')) { + if (url[schemeLength] != QLatin1Char(':')) + return false; - return url.length() >= 7 /* file:// */ && - url.startsWith(QLatin1String(file_string), Qt::CaseInsensitive) && - url[4] == QLatin1Char(':') && url[5] == QLatin1Char('/') && url[6] == QLatin1Char('/'); + return true; +} - } else if (f == QLatin1Char('q') || f == QLatin1Char('Q')) { +static qsizetype authorityOffset(const QString &url, qsizetype schemeLength) +{ + const qsizetype urlLength = url.size(); - return url.length() >= 5 /* qrc:/ */ && - url.startsWith(QLatin1String(qrc_string), Qt::CaseInsensitive) && - url[3] == QLatin1Char(':') && url[4] == QLatin1Char('/'); + if (urlLength < schemeLength + 3) + return -1; + const QLatin1Char slash('/'); + if (url[schemeLength + 1] == slash && url[schemeLength + 2] == slash) { + // Exactly two slashes denote an authority. + if (urlLength < schemeLength + 4 || url[schemeLength + 3] != slash) + return schemeLength + 3; } + + return -1; +} + #if defined(Q_OS_ANDROID) - else if (f == QLatin1Char('a') || f == QLatin1Char('A')) { - return url.length() >= 8 /* assets:/ */ && - url.startsWith(QLatin1String(assets_string), Qt::CaseInsensitive) && - url[6] == QLatin1Char(':') && url[7] == QLatin1Char('/'); - } else if (f == QLatin1Char('c') || f == QLatin1Char('C')) { - return url.length() >= 9 /* content:/ */ && - url.startsWith(QLatin1String(content_string), Qt::CaseInsensitive) && - url[7] == QLatin1Char(':') && url[8] == QLatin1Char('/'); +static bool hasLocalContentAuthority(const QString &url, qsizetype schemeLength) +{ + const qsizetype offset = authorityOffset(url, schemeLength); + if (offset == -1) + return true; // no authority is a local authority. + + const QString authorityAndPath = url.sliced(offset); + return authorityAndPath.startsWith(QLatin1String(authority_externalstorage)) + || authorityAndPath.startsWith(QLatin1String(authority_downloads_documents)) + || authorityAndPath.startsWith(QLatin1String(authority_media_documents)); +} + +#endif + +/*! + Returns \c true if \a url is a local file that can be opened with \l{QFile}. + Otherwise returns \c false. Local file urls have either a \c{qrc:} or + \c{file:} scheme. + + \note On Android, urls with \c{assets:} or \c{content:} scheme are also considered + local files. +*/ +bool QQmlFile::isLocalFile(const QString &url) +{ + if (url.size() < 4 /* qrc: */) + return false; + + switch (url[0].toLatin1()) { + case 'f': + case 'F': { + // file: URLs with two slashes following the scheme can be interpreted as local files + // where the slashes are part of the path. Therefore, disregard the authority. + // See QUrl::toLocalFile(). + const qsizetype fileLength = strlen(file_string); + return url.startsWith(QLatin1String(file_string, file_string + fileLength), + Qt::CaseInsensitive) + && url.size() > fileLength + && url[fileLength] == QLatin1Char(':'); } + case 'q': + case 'Q': + return hasScheme(url, qrc_string, strlen(qrc_string)) + && authorityOffset(url, strlen(qrc_string)) == -1; +#if defined(Q_OS_ANDROID) + case 'a': + case 'A': + return hasScheme(url, assets_string, strlen(assets_string)) + && authorityOffset(url, strlen(assets_string)) == -1; + case 'c': + case 'C': + return hasScheme(url, content_string, strlen(content_string)) + && hasLocalContentAuthority(url, strlen(content_string)); #endif + default: + break; + } return false; } /*! -If \a url is a local file returns a path suitable for passing to QFile. Otherwise returns an -empty string. + If \a url is a local file returns a path suitable for passing to \l{QFile}. + Otherwise returns an empty string. + + \sa isLocalFile */ QString QQmlFile::urlToLocalFileOrQrc(const QUrl& url) { @@ -585,15 +694,14 @@ QString QQmlFile::urlToLocalFileOrQrc(const QUrl& url) } #if defined(Q_OS_ANDROID) - else if (url.scheme().compare(QLatin1String("assets"), Qt::CaseInsensitive) == 0) { - if (url.authority().isEmpty()) + if (url.scheme().compare(QLatin1String("assets"), Qt::CaseInsensitive) == 0) + return url.authority().isEmpty() ? url.toString() : QString(); + if (url.scheme().compare(QLatin1String("content"), Qt::CaseInsensitive) == 0) { + if (hasLocalContentAuthority(url)) return url.toString(); return QString(); - } else if (url.scheme().compare(QLatin1String("content"), Qt::CaseInsensitive) == 0) { - return url.toString(); } #endif - return url.toLocalFile(); } @@ -603,35 +711,59 @@ static QString toLocalFile(const QString &url) if (!file.isLocalFile()) return QString(); - //XXX TODO: handle windows hostnames: "//servername/path/to/file.txt" + // QUrl::toLocalFile() interprets two slashes as part of the path. + // Therefore windows hostnames like "//servername/path/to/file.txt" are preserved. return file.toLocalFile(); } +static bool isDoubleSlashed(const QString &url, qsizetype offset) +{ + const qsizetype urlLength = url.size(); + if (urlLength < offset + 2) + return false; + + const QLatin1Char slash('/'); + if (url[offset] != slash || url[offset + 1] != slash) + return false; + + if (urlLength < offset + 3) + return true; + + return url[offset + 2] != slash; +} + /*! -If \a url is a local file returns a path suitable for passing to QFile. Otherwise returns an -empty string. + If \a url is a local file returns a path suitable for passing to \l{QFile}. + Otherwise returns an empty string. + + \sa isLocalFile */ QString QQmlFile::urlToLocalFileOrQrc(const QString& url) { if (url.startsWith(QLatin1String("qrc://"), Qt::CaseInsensitive)) { - if (url.length() > 6) - return QLatin1Char(':') + QStringView{url}.mid(6); - return QString(); + // Exactly two slashes are bad because that's a URL authority. + // One slash is fine and >= 3 slashes are file. + if (url.size() == 6 || url[6] != QLatin1Char('/')) { + Q_ASSERT(isDoubleSlashed(url, strlen("qrc:"))); + return QString(); + } + Q_ASSERT(!isDoubleSlashed(url, strlen("qrc:"))); + return QLatin1Char(':') + QStringView{url}.mid(6); } if (url.startsWith(QLatin1String("qrc:"), Qt::CaseInsensitive)) { - if (url.length() > 4) + Q_ASSERT(!isDoubleSlashed(url, strlen("qrc:"))); + if (url.size() > 4) return QLatin1Char(':') + QStringView{url}.mid(4); - return QString(); + return QStringLiteral(":"); } #if defined(Q_OS_ANDROID) - else if (url.startsWith(QLatin1String("assets:"), Qt::CaseInsensitive)) { - return url; - } else if (url.startsWith(QLatin1String("content:"), Qt::CaseInsensitive)) { - return url; - } + if (url.startsWith(QLatin1String("assets:"), Qt::CaseInsensitive)) + return isDoubleSlashed(url, strlen("assets:")) ? QString() : url; + if (hasScheme(url, content_string, strlen(content_string))) + return hasLocalContentAuthority(url, strlen(content_string)) ? url : QString(); #endif return toLocalFile(url); |