Android: Fix for URLs with local authorities

The file URLs provided by the native Android can use the "content://" or
"assetes://" scheme. That was already handled by urlToLocalFileOrQrc(),
but we cannot assume that authority is empty. Some authorities still
point to local host, like:
- com.android.externalstorage.documents;
- com.android.providers.downloads.documents;
- com.android.providers.media.documents;

This commit treats URLs with the above mentioned authorities as local.

Fixes: QTBUG-105110
Pick-to: 6.3 6.4
Change-Id: I9c080305547e5cd2c2d1be489f59d185a5d49658
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
Bartlomiej Moskal 2022-07-21 11:49:07 +02:00
parent fc683799fe
commit b65a5a9d06
2 changed files with 79 additions and 17 deletions

View File

@ -29,6 +29,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;
@ -469,6 +472,17 @@ bool QQmlFile::isSynchronous(const QString &url)
return false;
}
#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
/*!
Returns true if \a url is a local file that can be opened with QFile.
@ -490,18 +504,18 @@ bool QQmlFile::isLocalFile(const QUrl &url)
return url.authority().isEmpty();
#if defined(Q_OS_ANDROID)
if ((scheme.length() == 6
if (scheme.length() == 6
&& scheme.startsWith(QLatin1String(assets_string), Qt::CaseInsensitive))
|| (scheme.length() == 7
&& scheme.startsWith(QLatin1String(content_string), Qt::CaseInsensitive))) {
return url.authority().isEmpty();
}
if (scheme.length() == 7
&& scheme.startsWith(QLatin1String(content_string), Qt::CaseInsensitive))
return hasLocalContentAuthority(url);
#endif
return false;
}
static bool hasSchemeAndNoAuthority(const QString &url, const char *scheme, qsizetype schemeLength)
static bool hasScheme(const QString &url, const char *scheme, qsizetype schemeLength)
{
const qsizetype urlLength = url.length();
@ -514,19 +528,41 @@ static bool hasSchemeAndNoAuthority(const QString &url, const char *scheme, qsiz
if (url[schemeLength] != QLatin1Char(':'))
return false;
return true;
}
static qsizetype authorityOffset(const QString &url, qsizetype schemeLength)
{
const qsizetype urlLength = url.length();
if (urlLength < schemeLength + 3)
return true;
return -1;
const QLatin1Char slash('/');
if (url[schemeLength + 1] == slash && url[schemeLength + 2] == slash) {
// Exactly two slashes denote an authority. We don't want that.
// Exactly two slashes denote an authority.
if (urlLength < schemeLength + 4 || url[schemeLength + 3] != slash)
return false;
return schemeLength + 3;
}
return true;
return -1;
}
#if defined(Q_OS_ANDROID)
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 true if \a url is a local file that can be opened with QFile.
@ -553,14 +589,17 @@ bool QQmlFile::isLocalFile(const QString &url)
}
case 'q':
case 'Q':
return hasSchemeAndNoAuthority(url, qrc_string, strlen(qrc_string));
return hasScheme(url, qrc_string, strlen(qrc_string))
&& authorityOffset(url, strlen(qrc_string)) == -1;
#if defined(Q_OS_ANDROID)
case 'a':
case 'A':
return hasSchemeAndNoAuthority(url, assets_string, strlen(assets_string));
return hasScheme(url, assets_string, strlen(assets_string))
&& authorityOffset(url, strlen(assets_string)) == -1;
case 'c':
case 'C':
return hasSchemeAndNoAuthority(url, content_string, strlen(content_string));
return hasScheme(url, content_string, strlen(content_string))
&& hasLocalContentAuthority(url, strlen(content_string));
#endif
default:
break;
@ -584,10 +623,12 @@ QString QQmlFile::urlToLocalFileOrQrc(const QUrl& url)
#if defined(Q_OS_ANDROID)
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)
return url.authority().isEmpty() ? url.toString() : QString();
if (url.scheme().compare(QLatin1String("content"), Qt::CaseInsensitive) == 0) {
if (hasLocalContentAuthority(url))
return url.toString();
return QString();
}
#endif
return url.toLocalFile();
}
@ -646,8 +687,8 @@ QString QQmlFile::urlToLocalFileOrQrc(const QString& url)
#if defined(Q_OS_ANDROID)
if (url.startsWith(QLatin1String("assets:"), Qt::CaseInsensitive))
return isDoubleSlashed(url, strlen("assets:")) ? QString() : url;
if (url.startsWith(QLatin1String("content:"), Qt::CaseInsensitive))
return isDoubleSlashed(url, strlen("content:")) ? QString() : url;
if (hasScheme(url, content_string, strlen(content_string)))
return hasLocalContentAuthority(url, strlen(content_string)) ? url : QString();
#endif
return toLocalFile(url);

View File

@ -118,6 +118,27 @@ void tst_qqmlfile::urlData()
QTest::addRow("file:content 1 slash") << QStringLiteral("file:content:/foo/bar") << true << QStringLiteral("content:/foo/bar");
QTest::addRow("file:content 2 slashes") << QStringLiteral("file:content://foo/bar") << true << QStringLiteral("content://foo/bar");
QTest::addRow("file:content 3 slashes") << QStringLiteral("file:content:///foo/bar") << true << QStringLiteral("content:///foo/bar");
const QString contentExternalstoragePath = hasAssetsAndContent ?
QStringLiteral("content://com.android.externalstorage.documents/foo") : invalid;
const QString contentDownloadsPath = hasAssetsAndContent ?
QStringLiteral("content://com.android.providers.downloads.documents/foo") : invalid;
const QString contentMediaPath = hasAssetsAndContent ?
QStringLiteral("content://com.android.providers.media.documents") : invalid;
QTest::addRow("content externalstorage") << QStringLiteral("content://com.android.externalstorage.documents/foo")
<< hasAssetsAndContent << contentExternalstoragePath;
QTest::addRow("content downloads documents") << QStringLiteral("content://com.android.providers.downloads.documents/foo")
<< hasAssetsAndContent << contentDownloadsPath;
QTest::addRow("content media documents") << QStringLiteral("content://com.android.providers.media.documents")
<< hasAssetsAndContent << contentMediaPath;
QTest::addRow("assets externalstorage") << QStringLiteral("assets://com.android.externalstorage.documents/foo")
<< false << invalid;
QTest::addRow("assets downloads documents") << QStringLiteral("assets://com.android.providers.downloads.documents/foo")
<< false << invalid;
QTest::addRow("assets media documents") << QStringLiteral("assets://com.android.providers.media.documents")
<< false << invalid;
}
void tst_qqmlfile::isLocalFile_data()