Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Toggle main menu visibility
Loading...
Searching...
No Matches
qfileselector.cpp
Go to the documentation of this file.
1
// Copyright (C) 2013 BlackBerry Limited. All rights reserved.
2
// Copyright (C) 2016 Intel Corporation.
3
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
// Qt-Security score:critical reason:file-selection
5
6
#
include
"qfileselector.h"
7
#
include
"qfileselector_p.h"
8
9
#
include
<
QtCore
/
QFile
>
10
#
include
<
QtCore
/
QDir
>
11
#
include
<
QtCore
/
QMutex
>
12
#
include
<
QtCore
/
private
/
qlocking_p
.
h
>
13
#
include
<
QtCore
/
QUrl
>
14
#
include
<
QtCore
/
QFileInfo
>
15
#
include
<
QtCore
/
QLocale
>
16
#
include
<
QtCore
/
QDebug
>
17
18
#
include
<
algorithm
>
19
20
QT_BEGIN_NAMESPACE
21
22
using
namespace
Qt::StringLiterals;
23
24
//Environment variable to allow tooling full control of file selectors
25
static
const
char
env_override
[] =
"QT_NO_BUILTIN_SELECTORS"
;
26
27
Q_GLOBAL_STATIC
(
QFileSelectorSharedData
,
sharedData
);
28
Q_CONSTINIT
static
QBasicMutex sharedDataMutex;
29
30
QFileSelectorPrivate::QFileSelectorPrivate()
31
: QObjectPrivate()
32
{
33
}
34
35
/*!
36
\class QFileSelector
37
\inmodule QtCore
38
\brief QFileSelector provides a convenient way of selecting file variants.
39
\since 5.2
40
41
QFileSelector is a convenience for selecting file variants based on platform or device
42
characteristics. This allows you to develop and deploy one codebase containing all the
43
different variants more easily in some circumstances, such as when the correct variant cannot
44
be determined during the deploy step.
45
46
\section1 Using QFileSelector
47
48
If you always use the same file you do not need to use QFileSelector.
49
50
Consider the following example usage, where you want to use different settings files on
51
different locales. You might select code between locales like this:
52
53
\snippet code/src_corelib_io_qfileselector.cpp 0
54
55
Similarly, if you want to pick a different data file based on target platform,
56
your code might look something like this:
57
\snippet code/src_corelib_io_qfileselector.cpp 1
58
59
QFileSelector provides a convenient alternative to writing such boilerplate code, and in the
60
latter case it allows you to start using an platform-specific configuration without a recompile.
61
QFileSelector also allows for chaining of multiple selectors in a convenient way, for example
62
selecting a different file only on certain combinations of platform and locale. For example, to
63
select based on platform and/or locale, the code is as follows:
64
65
\snippet code/src_corelib_io_qfileselector.cpp 2
66
67
The files to be selected are placed in directories named with a \c'+' and a selector name. In the above
68
example you could have the platform configurations selected by placing them in the following locations:
69
\snippet code/src_corelib_io_qfileselector.cpp 3
70
71
To find selected files, QFileSelector looks in the same directory as the base file. If there are
72
any directories of the form +<selector> with an active selector, QFileSelector will prefer a file
73
with the same file name from that directory over the base file. These directories can be nested to
74
check against multiple selectors, for example:
75
\snippet code/src_corelib_io_qfileselector.cpp 4
76
With those files available, you would select a different file on the android platform,
77
but only if the locale was en_GB.
78
79
For error handling in the case no valid selectors are present, it is recommended to have a default or
80
error-handling file in the base file location even if you expect selectors to be present for all
81
deployments.
82
83
In a future version, some may be marked as deploy-time static and be moved during the
84
deployment step as an optimization. As selectors come with a performance cost, it is
85
recommended to avoid their use in circumstances involving performance-critical code.
86
87
\section1 Adding Selectors
88
89
Selectors normally available are
90
\list
91
\li platform, any of the following strings which match the platform the application is running
92
on (list not exhaustive): android, harmonyos, ios, osx, darwin, mac, macos, linux, qnx,
93
unix, windows. On Linux, if it can be determined, the name of the distribution too, like
94
debian, fedora or opensuse.
95
\li locale, same as QLocale().name().
96
\endlist
97
98
Further selectors will be added from the \c QT_FILE_SELECTORS environment variable, which
99
when set should be a set of comma separated selectors. Note that this variable will only be
100
read once; selectors may not update if the variable changes while the application is running.
101
The initial set of selectors are evaluated only once, on first use.
102
103
You can also add extra selectors at runtime for custom behavior. These will be used in any
104
future calls to select(). If the extra selectors list has been changed, calls to select() will
105
use the new list and may return differently.
106
107
\section1 Conflict Resolution when Multiple Selectors Apply
108
109
When multiple selectors could be applied to the same file, the first matching selector is chosen.
110
The order selectors are checked in are:
111
112
\list 1
113
\li Selectors set via setExtraSelectors(), in the order they are in the list
114
\li Selectors in the \c QT_FILE_SELECTORS environment variable, from left to right
115
\li Locale
116
\li Platform
117
\endlist
118
119
Here is an example involving multiple selectors matching at the same time. It uses platform
120
selectors, plus an extra selector named "admin" is set by the application based on user
121
credentials. The example is sorted so that the lowest matching file would be chosen if all
122
selectors were present:
123
124
\snippet code/src_corelib_io_qfileselector.cpp 5
125
126
Because extra selectors are checked before platform the \c{+admin/background.png} will be chosen
127
on Windows when the admin selector is set, and \c{+windows/background.png} will be chosen on
128
Windows when the admin selector is not set. On Linux, the \c{+admin/+linux/background.png} will be
129
chosen when admin is set, and the \c{+linux/background.png} when it is not.
130
131
*/
132
133
/*!
134
Create a QFileSelector instance. This instance will have the same static selectors as other
135
QFileSelector instances, but its own set of extra selectors.
136
137
If supplied, it will have the given QObject \a parent.
138
*/
139
QFileSelector::QFileSelector(QObject *parent)
140
: QObject(*(
new
QFileSelectorPrivate()), parent)
141
{
142
}
143
144
/*!
145
Destroys this selector instance.
146
*/
147
QFileSelector::~QFileSelector()
148
{
149
}
150
151
/*!
152
This function returns the selected version of the path, based on the conditions at runtime.
153
If no selectable files are present, returns the original \a filePath.
154
155
If the original file does not exist, the original \a filePath is returned. This means that you
156
must have a base file to fall back on, you cannot have only files in selectable sub-directories.
157
158
See the class overview for the selection algorithm.
159
*/
160
QString QFileSelector::select(
const
QString &filePath)
const
161
{
162
Q_D(
const
QFileSelector);
163
return
d->select(filePath);
164
}
165
166
static
bool
isLocalScheme
(
const
QString &file)
167
{
168
bool
local = file ==
"qrc"_L1
;
169
#
ifdef
Q_OS_ANDROID
170
local |= file ==
"assets"_L1
;
171
#
endif
172
return
local;
173
}
174
175
/*!
176
This is a convenience version of select operating on QUrl objects. If the scheme is not file or qrc,
177
\a filePath is returned immediately. Otherwise selection is applied to the path of \a filePath
178
and a QUrl is returned with the selected path and other QUrl parts the same as \a filePath.
179
180
See the class overview for the selection algorithm.
181
*/
182
QUrl QFileSelector::select(
const
QUrl &filePath)
const
183
{
184
Q_D(
const
QFileSelector);
185
if
(!isLocalScheme(filePath.scheme()) && !filePath.isLocalFile())
186
return
filePath;
187
QUrl ret(filePath);
188
if
(isLocalScheme(filePath.scheme())) {
189
auto
scheme =
":"_L1
;
190
#
ifdef
Q_OS_ANDROID
191
// use other scheme because ":" means "qrc" here
192
if
(filePath.scheme() ==
"assets"_L1
)
193
scheme =
"assets:"_L1
;
194
#
endif
195
196
QString equivalentPath = scheme + filePath.path();
197
QString selectedPath = d->select(equivalentPath);
198
ret.setPath(selectedPath.remove(0, scheme.size()));
199
}
else
{
200
// we need to store the original query and fragment, since toLocalFile() will strip it off
201
QString frag;
202
if
(ret.hasFragment())
203
frag = ret.fragment();
204
QString query;
205
if
(ret.hasQuery())
206
query= ret.query();
207
ret = QUrl::fromLocalFile(d->select(ret.toLocalFile()));
208
if
(!frag.isNull())
209
ret.setFragment(frag);
210
if
(!query.isNull())
211
ret.setQuery(query);
212
}
213
return
ret;
214
}
215
216
QString QFileSelectorPrivate::selectionHelper(
const
QString &path,
const
QString &fileName,
217
QSpan<QString> selectors, QChar indicator)
218
{
219
/* selectionHelper does a depth-first search of possible selected files. Because there is strict
220
selector ordering in the API, we can stop checking as soon as we find the file in a directory
221
which does not contain any other valid selector directories.
222
223
The caller is expected to pass a duplicate-free selector list; the current selector is then
224
dropped by index rather than value when recursing. To recurse over "everything but selector i"
225
without copying, we rotate selector i to the front of [0, i] (which keeps the relative order of
226
the rest intact), recurse over the tail, then rotate back.
227
*/
228
Q_ASSERT(path.isEmpty() || path.endsWith(u'/'));
229
230
for
(qsizetype i = 0; i < selectors.size(); ++i) {
231
QString prospectiveBase = path;
232
if
(!indicator.isNull())
233
prospectiveBase += indicator;
234
prospectiveBase += selectors[i];
235
prospectiveBase += u'/';
236
if
(!QFileInfo(prospectiveBase).isDir())
237
continue
;
238
239
std::rotate(selectors.begin(), selectors.begin() + i, selectors.begin() + i + 1);
240
QString prospectiveFile =
241
selectionHelper(prospectiveBase, fileName, selectors.subspan(1), indicator);
242
std::rotate(selectors.begin(), selectors.begin() + 1, selectors.begin() + i + 1);
243
if
(!prospectiveFile.isEmpty())
244
return
prospectiveFile;
245
}
246
247
// If we reach here there were no successful files found at a lower level in this branch, so we
248
// should check this level as a potential result.
249
if
(!QFile::exists(path + fileName))
250
return
QString();
251
return
path + fileName;
252
}
253
254
QString QFileSelectorPrivate::select(
const
QString &filePath)
const
255
{
256
Q_Q(
const
QFileSelector);
257
QFileInfo fi(filePath);
258
259
QString pathString;
260
if
(
auto
path = fi.path(); !path.isEmpty())
261
pathString = path.endsWith(u'/') ? path : path + u'/';
262
QStringList selectors = q->allSelectors();
263
selectors.removeDuplicates();
264
QString ret = selectionHelper(pathString, fi.fileName(), selectors);
265
266
if
(!ret.isEmpty())
267
return
ret;
268
return
filePath;
269
}
270
271
/*!
272
Returns the list of extra selectors which have been added programmatically to this instance.
273
*/
274
QStringList QFileSelector::extraSelectors()
const
275
{
276
Q_D(
const
QFileSelector);
277
return
d->extras;
278
}
279
280
/*!
281
Sets the \a list of extra selectors which have been added programmatically to this instance.
282
283
These selectors have priority over any which have been automatically picked up.
284
*/
285
void
QFileSelector::setExtraSelectors(
const
QStringList &list)
286
{
287
Q_D(QFileSelector);
288
d->extras = list;
289
}
290
291
/*!
292
Returns the complete, ordered list of selectors used by this instance
293
*/
294
QStringList QFileSelector::allSelectors()
const
295
{
296
Q_D(
const
QFileSelector);
297
const
auto
locker = qt_scoped_lock(sharedDataMutex);
298
QFileSelectorPrivate::updateSelectors();
299
return
d->extras + sharedData->staticSelectors;
300
}
301
302
void
QFileSelectorPrivate::updateSelectors()
303
{
304
if
(!sharedData->staticSelectors.isEmpty())
305
return
;
//Already loaded
306
307
QLatin1Char pathSep(
','
);
308
QStringList envSelectors = QString::fromLatin1(qgetenv(
"QT_FILE_SELECTORS"
))
309
.split(pathSep, Qt::SkipEmptyParts);
310
if
(envSelectors.size())
311
sharedData->staticSelectors << envSelectors;
312
313
if
(!qEnvironmentVariableIsEmpty(env_override))
314
return
;
315
316
sharedData->staticSelectors << sharedData->preloadedStatics;
//Potential for static selectors from other modules
317
318
// TODO: Update on locale changed?
319
sharedData->staticSelectors << QLocale().name();
320
321
sharedData->staticSelectors << platformSelectors();
322
}
323
324
QStringList QFileSelectorPrivate::platformSelectors()
325
{
326
// similar, but not identical to QSysInfo::osType
327
QStringList ret;
328
#
if
defined
(
Q_OS_WIN
)
329
ret << QStringLiteral(
"windows"
);
330
ret << QSysInfo::kernelType();
// "winnt"
331
#
elif
defined
(
Q_OS_UNIX
)
332
ret << QStringLiteral(
"unix"
);
333
#
if
!
defined
(
Q_OS_ANDROID
)
&&
!
defined
(
Q_OS_QNX
)
&&
!
defined
(
Q_OS_VXWORKS
)
334
&&
!
defined
(
Q_OS_HARMONY
)
335
// we don't want "linux" for Android, or a second instance of the product
336
// type for QNX, VxWorks and HarmonyOS
337
ret << QSysInfo::kernelType();
338
#
endif
339
QString productName = QSysInfo::productType();
340
if
(productName !=
"unknown"_L1
)
341
ret << productName;
// "opensuse", "fedora", "osx", "ios", "android"
342
#
endif
343
return
ret;
344
}
345
346
void
QFileSelectorPrivate::addStatics(
const
QStringList &statics)
347
{
348
const
auto
locker = qt_scoped_lock(sharedDataMutex);
349
sharedData->preloadedStatics << statics;
350
sharedData->staticSelectors.clear();
351
}
352
353
qsizetype QFileSelectorPrivate::removeStatics(
const
QStringList &statics)
354
{
355
const
auto
locker = qt_scoped_lock(sharedDataMutex);
356
// Clearing staticSelectors ensures that it's repopulated in QFileSelectorPrivate::updateSelectors()
357
sharedData->staticSelectors.clear();
358
return
sharedData->preloadedStatics.removeIf([statics](
auto
&s) {
return
statics.contains(s, Qt::CaseSensitive);});
359
}
360
361
QT_END_NAMESPACE
362
363
#
include
"moc_qfileselector.cpp"
isLocalScheme
static bool isLocalScheme(const QString &file)
Definition
qfileselector.cpp:166
env_override
static const char env_override[]
Definition
qfileselector.cpp:25
Q_GLOBAL_STATIC
Q_GLOBAL_STATIC(QFileSelectorSharedData, sharedData)
QFileSelectorSharedData
Definition
qfileselector_p.h:27
qtbase
src
corelib
io
qfileselector.cpp
Generated on
for Qt by
1.18.0