Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qssggltfresourceresolver.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:critical reason:data-parser
4
6
7#include <QtCore/qdir.h>
8#include <QtCore/qfile.h>
9#include <QtCore/qurl.h>
10
12
13/*!
14 \namespace QSSGGltfResourceResolver
15 \internal
16
17 Resolves glTF URIs: data: URIs (base64 or percent-encoded), and relative
18 or absolute file paths, including Qt resource (":/...") paths.
19*/
20namespace QSSGGltfResourceResolver {
21
22/*!
23 \internal
24
25 Returns whether \a uri is a data: URI.
26*/
27bool isDataUri(QStringView uri)
28{
29 return uri.startsWith(QLatin1String("data:"), Qt::CaseInsensitive);
30}
31
32/*!
33 \internal
34
35 Decodes the payload of the data: URI \a uri. Returns an empty array and
36 sets \a errorMessage on failure.
37*/
38QByteArray decodeDataUri(QStringView uri, QString *errorMessage)
39{
40 // data:[<mediatype>][;base64],<payload>
41 const qsizetype comma = uri.indexOf(u',');
42 if (!isDataUri(uri) || comma < 0) {
43 if (errorMessage)
44 *errorMessage = QStringLiteral("Malformed data URI");
45 return {};
46 }
47
48 const QStringView header = uri.mid(0, comma);
49 const QStringView payload = uri.mid(comma + 1);
50
51 if (header.endsWith(QLatin1String(";base64"), Qt::CaseInsensitive)) {
52 auto result = QByteArray::fromBase64Encoding(payload.toLatin1());
53 if (!result) {
54 if (errorMessage)
55 *errorMessage = QStringLiteral("Invalid base64 payload in data URI");
56 return {};
57 }
58 return result.decoded;
59 }
60
61 // Non-base64 data URIs carry percent-encoded octets. Decoding has to stay
62 // in QByteArray: going through QString would replace every octet sequence
63 // that is not valid UTF-8 with U+FFFD, changing both the contents and the
64 // length of what is meant to be arbitrary binary data.
65 return QByteArray::fromPercentEncoding(payload.toLatin1());
66}
67
68/*!
69 \internal
70
71 Resolves the relative file URI \a uri against \a baseDir to a local or qrc
72 path. The URI is percent-decoded as required by the glTF specification.
73
74 A URI that would reach outside \a baseDir is rejected, which returns an
75 empty string. The specification only allows relative references here, so
76 an absolute path or one climbing out with ".." is malformed rather than
77 merely unusual, and refusing it keeps a hostile asset from naming an
78 arbitrary file for the application to load and read back.
79*/
80QString resolveFilePath(QStringView uri, const QString &baseDir)
81{
82 // glTF URIs are percent-encoded relative URI references
83 const QString decoded = QUrl::fromPercentEncoding(uri.toUtf8());
84 if (decoded.isEmpty())
85 return {};
86 if (QDir::isAbsolutePath(decoded) || decoded.startsWith(QLatin1String(":/")))
87 return {};
88 if (baseDir.isEmpty())
89 return QDir::cleanPath(decoded).startsWith(QLatin1String("..")) ? QString() : decoded;
90
91 const QString base = QDir::cleanPath(baseDir);
92 const QString resolved = QDir::cleanPath(base + QLatin1Char('/') + decoded);
93 if (resolved != base && !resolved.startsWith(base + QLatin1Char('/')))
94 return {};
95 return resolved;
96}
97
98/*!
99 \internal
100
101 Loads the contents referenced by the glTF URI \a uri: data: URIs are
102 decoded, anything else is treated as a file path relative to \a baseDir.
103 Sets \a errorMessage on failure.
104*/
105QByteArray loadUri(QStringView uri, const QString &baseDir, QString *errorMessage)
106{
107 if (isDataUri(uri))
108 return decodeDataUri(uri, errorMessage);
109
110 const QString path = resolveFilePath(uri, baseDir);
111 if (path.isEmpty()) {
112 if (errorMessage)
113 *errorMessage = QStringLiteral("Refusing to resolve '%1' outside the asset directory").arg(uri);
114 return {};
115 }
116
117 QFile file(path);
118 if (!file.open(QIODevice::ReadOnly)) {
119 if (errorMessage)
120 *errorMessage = QStringLiteral("Failed to open '%1': %2").arg(path, file.errorString());
121 return {};
122 }
123 return file.readAll();
124}
125
126} // namespace QSSGGltfResourceResolver
127
128QT_END_NAMESPACE
QByteArray decodeDataUri(QStringView uri, QString *errorMessage)
QByteArray loadUri(QStringView uri, const QString &baseDir, QString *errorMessage)
QString resolveFilePath(QStringView uri, const QString &baseDir)
Combined button and popup list for selecting options.