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
qstandardpaths_unix.cpp
Go to the documentation of this file.
1// Copyright (C) 2020 The Qt Company Ltd.
2// Copyright (C) 2020 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:provides-trusted-directory-paths
5
7#include <qdir.h>
8#include <qfile.h>
9#include <qhash.h>
10#include <qtextstream.h>
11#if QT_CONFIG(regularexpression)
12#include <qregularexpression.h>
13#endif
14#include <private/qfilesystemengine_p.h>
15#include <errno.h>
16#include <stdlib.h>
17
18#ifndef QT_BOOTSTRAPPED
19#include <qcoreapplication.h>
20#endif
21
22#ifndef QT_NO_STANDARDPATHS
23
25
26using namespace Qt::StringLiterals;
27
28static void appendOrganizationAndApp(QString &path)
29{
30#ifndef QT_BOOTSTRAPPED
31 const QString org = QCoreApplication::organizationName();
32 if (!org.isEmpty())
33 path += u'/' + org;
34 const QString appName = QCoreApplication::applicationName();
35 if (!appName.isEmpty())
36 path += u'/' + appName;
37#else
38 Q_UNUSED(path);
39#endif
40}
41
42#if QT_CONFIG(regularexpression)
43static QLatin1StringView xdg_key_name(QStandardPaths::StandardLocation type)
44{
45 switch (type) {
46 case QStandardPaths::DesktopLocation:
47 return "DESKTOP"_L1;
48 case QStandardPaths::DocumentsLocation:
49 return "DOCUMENTS"_L1;
50 case QStandardPaths::PicturesLocation:
51 return "PICTURES"_L1;
52 case QStandardPaths::MusicLocation:
53 return "MUSIC"_L1;
54 case QStandardPaths::MoviesLocation:
55 return "VIDEOS"_L1;
56 case QStandardPaths::DownloadLocation:
57 return "DOWNLOAD"_L1;
58 case QStandardPaths::PublicShareLocation:
59 return "PUBLICSHARE"_L1;
60 case QStandardPaths::TemplatesLocation:
61 return "TEMPLATES"_L1;
62 default:
63 return {};
64 }
65}
66#endif
67
68static QByteArray unixPermissionsText(QFile::Permissions permissions)
69{
70 mode_t perms = 0;
71 if (permissions & QFile::ReadOwner)
72 perms |= S_IRUSR;
73 if (permissions & QFile::WriteOwner)
74 perms |= S_IWUSR;
75 if (permissions & QFile::ExeOwner)
76 perms |= S_IXUSR;
77 if (permissions & QFile::ReadGroup)
78 perms |= S_IRGRP;
79 if (permissions & QFile::WriteGroup)
80 perms |= S_IWGRP;
81 if (permissions & QFile::ExeGroup)
82 perms |= S_IXGRP;
83 if (permissions & QFile::ReadOther)
84 perms |= S_IROTH;
85 if (permissions & QFile::WriteOther)
86 perms |= S_IWOTH;
87 if (permissions & QFile::ExeOther)
88 perms |= S_IXOTH;
89 return '0' + QByteArray::number(perms, 8);
90}
91
92static bool checkXdgRuntimeDir(const QString &xdgRuntimeDir)
93{
94 auto describeMetaData = [](const QFileSystemMetaData &metaData) -> QByteArray {
95 if (!metaData.exists())
96 return "a broken symlink";
97
98 QByteArray description;
99 if (metaData.isLink())
100 description = "a symbolic link to ";
101
102 if (metaData.isFile())
103 description += "a regular file";
104 else if (metaData.isDirectory())
105 description += "a directory";
106 else if (metaData.isSequential())
107 description += "a character device, socket or FIFO";
108 else
109 description += "a block device";
110
111 description += " permissions " + unixPermissionsText(metaData.permissions());
112
113 return description
114 + " owned by UID " + QByteArray::number(metaData.userId())
115 + " GID " + QByteArray::number(metaData.groupId());
116 };
117
118 // http://standards.freedesktop.org/basedir-spec/latest/
119 const uint myUid = uint(geteuid());
120 const QFile::Permissions wantedPerms = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner;
121 const QFileSystemMetaData::MetaDataFlags statFlags = QFileSystemMetaData::PosixStatFlags
122 | QFileSystemMetaData::LinkType;
123 QFileSystemMetaData metaData;
124 QFileSystemEntry entry(xdgRuntimeDir);
125
126 // Check that the xdgRuntimeDir is a directory by attempting to create it.
127 // A stat() before mkdir() that concluded it doesn't exist is a meaningless
128 // result: we'd race against someone else attempting to create it.
129 // ### QFileSystemEngine::createDirectory cannot take the extra mode argument.
130 if (QT_MKDIR(entry.nativeFilePath(), 0700) == 0)
131 return true;
132 if (errno != EEXIST) {
133 qErrnoWarning("QStandardPaths: error creating runtime directory '%ls'",
134 qUtf16Printable(xdgRuntimeDir));
135 return false;
136 }
137
138 // We use LinkType to force an lstat(), but fillMetaData() still returns error
139 // on broken symlinks.
140 if (!QFileSystemEngine::fillMetaData(entry, metaData, statFlags) && !metaData.isLink()) {
141 qErrnoWarning("QStandardPaths: error obtaining permissions of runtime directory '%ls'",
142 qUtf16Printable(xdgRuntimeDir));
143 return false;
144 }
145
146 // Checks:
147 // - is a directory
148 // - is not a symlink (even is pointing to a directory)
149 if (metaData.isLink() || !metaData.isDirectory()) {
150 qWarning("QStandardPaths: runtime directory '%ls' is not a directory, but %s",
151 qUtf16Printable(xdgRuntimeDir), describeMetaData(metaData).constData());
152 return false;
153 }
154
155 // - "The directory MUST be owned by the user"
156 if (metaData.userId() != myUid) {
157 qWarning("QStandardPaths: runtime directory '%ls' is not owned by UID %d, but %s",
158 qUtf16Printable(xdgRuntimeDir), myUid, describeMetaData(metaData).constData());
159 return false;
160 }
161
162 // "and he MUST be the only one having read and write access to it. Its Unix access mode MUST be 0700."
163 if (metaData.permissions() != wantedPerms) {
164 qWarning("QStandardPaths: wrong permissions on runtime directory %ls, %s instead of %s",
165 qUtf16Printable(xdgRuntimeDir),
166 unixPermissionsText(metaData.permissions()).constData(),
167 unixPermissionsText(wantedPerms).constData());
168 return false;
169 }
170
171 return true;
172}
173
174QString QStandardPaths::writableLocation(StandardLocation type)
175{
176 switch (type) {
177 case HomeLocation:
178 return QDir::homePath();
179 case TempLocation:
180 return QDir::tempPath();
181 case CacheLocation:
182 case GenericCacheLocation:
183 {
184 QString xdgCacheHome;
185 if (isTestModeEnabled()) {
186 xdgCacheHome = QDir::homePath() + "/.qttest/cache"_L1;
187 } else {
188 // http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
189 xdgCacheHome = qEnvironmentVariable("XDG_CACHE_HOME");
190 if (!xdgCacheHome.startsWith(u'/'))
191 xdgCacheHome.clear(); // spec says relative paths should be ignored
192
193 if (xdgCacheHome.isEmpty())
194 xdgCacheHome = QDir::homePath() + "/.cache"_L1;
195 }
196 if (type == QStandardPaths::CacheLocation)
197 appendOrganizationAndApp(xdgCacheHome);
198 return xdgCacheHome;
199 }
200 case StateLocation:
201 case GenericStateLocation:
202 {
203 QString xdgStateHome;
204 if (isTestModeEnabled()) {
205 xdgStateHome = QDir::homePath() + "/.qttest/state"_L1;
206 } else {
207 // http://standards.freedesktop.org/basedir-spec/basedir-spec-0.8.html
208 xdgStateHome = qEnvironmentVariable("XDG_STATE_HOME");
209 if (!xdgStateHome.startsWith(u'/'))
210 xdgStateHome.clear(); // spec says relative paths should be ignored
211
212 if (xdgStateHome.isEmpty())
213 xdgStateHome = QDir::homePath() + "/.local/state"_L1;
214 }
215 if (type == QStandardPaths::StateLocation)
216 appendOrganizationAndApp(xdgStateHome);
217 return xdgStateHome;
218 }
219 case AppDataLocation:
220 case AppLocalDataLocation:
221 case GenericDataLocation:
222 {
223 QString xdgDataHome;
224 if (isTestModeEnabled()) {
225 xdgDataHome = QDir::homePath() + "/.qttest/share"_L1;
226 } else {
227 xdgDataHome = qEnvironmentVariable("XDG_DATA_HOME");
228 if (!xdgDataHome.startsWith(u'/'))
229 xdgDataHome.clear(); // spec says relative paths should be ignored
230
231 if (xdgDataHome.isEmpty())
232 xdgDataHome = QDir::homePath() + "/.local/share"_L1;
233 }
234 if (type == AppDataLocation || type == AppLocalDataLocation)
235 appendOrganizationAndApp(xdgDataHome);
236 return xdgDataHome;
237 }
238 case ConfigLocation:
239 case GenericConfigLocation:
240 case AppConfigLocation:
241 {
242 QString xdgConfigHome;
243 if (isTestModeEnabled()) {
244 xdgConfigHome = QDir::homePath() + "/.qttest/config"_L1;
245 } else {
246 // http://standards.freedesktop.org/basedir-spec/latest/
247 xdgConfigHome = qEnvironmentVariable("XDG_CONFIG_HOME");
248 if (!xdgConfigHome.startsWith(u'/'))
249 xdgConfigHome.clear(); // spec says relative paths should be ignored
250
251 if (xdgConfigHome.isEmpty())
252 xdgConfigHome = QDir::homePath() + "/.config"_L1;
253 }
254 if (type == AppConfigLocation)
255 appendOrganizationAndApp(xdgConfigHome);
256 return xdgConfigHome;
257 }
258 case RuntimeLocation:
259 {
260 QString xdgRuntimeDir = qEnvironmentVariable("XDG_RUNTIME_DIR");
261 if (!xdgRuntimeDir.startsWith(u'/'))
262 xdgRuntimeDir.clear(); // spec says relative paths should be ignored
263
264 bool fromEnv = !xdgRuntimeDir.isEmpty();
265 if (xdgRuntimeDir.isEmpty() || !checkXdgRuntimeDir(xdgRuntimeDir)) {
266 // environment variable not set or is set to something unsuitable
267 const uint myUid = uint(geteuid());
268 const QString userName = QFileSystemEngine::resolveUserName(myUid);
269 xdgRuntimeDir = QDir::tempPath() + "/runtime-"_L1 + userName;
270
271 if (!fromEnv) {
272#ifndef Q_OS_WASM
273 qWarning("QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '%ls'", qUtf16Printable(xdgRuntimeDir));
274#endif
275 }
276
277 if (!checkXdgRuntimeDir(xdgRuntimeDir))
278 xdgRuntimeDir.clear();
279 }
280
281 return xdgRuntimeDir;
282 }
283 default:
284 break;
285 }
286
287#if QT_CONFIG(regularexpression)
288 // http://www.freedesktop.org/wiki/Software/xdg-user-dirs
289 QString xdgConfigHome = qEnvironmentVariable("XDG_CONFIG_HOME");
290 if (!xdgConfigHome.startsWith(u'/'))
291 xdgConfigHome.clear(); // spec says relative paths should be ignored
292
293 if (xdgConfigHome.isEmpty())
294 xdgConfigHome = QDir::homePath() + "/.config"_L1;
295 QFile file(xdgConfigHome + "/user-dirs.dirs"_L1);
296 const QLatin1StringView key = xdg_key_name(type);
297 if (!key.isEmpty() && !isTestModeEnabled() && file.open(QIODevice::ReadOnly)) {
298 QTextStream stream(&file);
299 // Only look for lines like: XDG_DESKTOP_DIR="$HOME/Desktop"
300 static const QRegularExpression exp(u"^XDG_(.*)_DIR=(.*)$"_s);
301 QString result;
302 while (!stream.atEnd()) {
303 const QString &line = stream.readLine();
304 QRegularExpressionMatch match = exp.match(line);
305 if (match.hasMatch() && match.capturedView(1) == key) {
306 QStringView value = match.capturedView(2);
307 if (value.size() > 2
308 && value.startsWith(u'\"')
309 && value.endsWith(u'\"'))
310 value = value.mid(1, value.size() - 2);
311 // value can start with $HOME
312 if (value.startsWith("$HOME"_L1))
313 result = QDir::homePath() + value.mid(5);
314 else
315 result = value.toString();
316 if (result.size() > 1 && result.endsWith(u'/'))
317 result.chop(1);
318 }
319 }
320 if (!result.isNull())
321 return result;
322 }
323#endif // QT_CONFIG(regularexpression)
324
325 QString path;
326 switch (type) {
327 case DesktopLocation:
328 path = QDir::homePath() + "/Desktop"_L1;
329 break;
330 case DocumentsLocation:
331 path = QDir::homePath() + "/Documents"_L1;
332 break;
333 case PicturesLocation:
334 path = QDir::homePath() + "/Pictures"_L1;
335 break;
336
337 case FontsLocation:
338 path = writableLocation(GenericDataLocation) + "/fonts"_L1;
339 break;
340
341 case MusicLocation:
342 path = QDir::homePath() + "/Music"_L1;
343 break;
344
345 case MoviesLocation:
346 path = QDir::homePath() + "/Videos"_L1;
347 break;
348 case DownloadLocation:
349 path = QDir::homePath() + "/Downloads"_L1;
350 break;
351 case ApplicationsLocation:
352 path = writableLocation(GenericDataLocation) + "/applications"_L1;
353 break;
354
355 case PublicShareLocation:
356 path = QDir::homePath() + "/Public"_L1;
357 break;
358
359 case TemplatesLocation:
360 path = QDir::homePath() + "/Templates"_L1;
361 break;
362
363 default:
364 break;
365 }
366
367 return path;
368}
369
370static QStringList dirsList(const QString &xdgEnvVar)
371{
372 QStringList dirs;
373 // http://standards.freedesktop.org/basedir-spec/latest/
374 // Normalize paths, skip relative paths (the spec says relative paths
375 // should be ignored)
376 for (const auto dir : qTokenize(xdgEnvVar, u':'))
377 if (dir.startsWith(u'/'))
378 dirs.push_back(QDir::cleanPath(dir.toString()));
379
380 // Remove duplicates from the list, there's no use for duplicated paths
381 // in XDG_* env vars - if whatever is being looked for is not found in
382 // the given directory the first time, it won't be there the second time.
383 // Plus duplicate paths causes problems for example for mimetypes,
384 // where duplicate paths here lead to duplicated mime types returned
385 // for a file, eg "text/plain,text/plain" instead of "text/plain"
386 dirs.removeDuplicates();
387
388 return dirs;
389}
390
392{
393 // http://standards.freedesktop.org/basedir-spec/latest/
394 QString xdgDataDirsEnv = qEnvironmentVariable("XDG_DATA_DIRS");
395
396 QStringList dirs = dirsList(xdgDataDirsEnv);
397 if (dirs.isEmpty())
398 dirs = QStringList{u"/usr/local/share"_s, u"/usr/share"_s};
399
400 return dirs;
401}
402
404{
405 // http://standards.freedesktop.org/basedir-spec/latest/
406 const QString xdgConfigDirs = qEnvironmentVariable("XDG_CONFIG_DIRS");
407
408 QStringList dirs = dirsList(xdgConfigDirs);
409 if (dirs.isEmpty())
410 dirs.push_back(u"/etc/xdg"_s);
411
412 return dirs;
413}
414
415QStringList QStandardPaths::standardLocations(StandardLocation type)
416{
417 QStringList dirs;
418 switch (type) {
419 case ConfigLocation:
420 case GenericConfigLocation:
421 dirs = xdgConfigDirs();
422 break;
423 case AppConfigLocation:
424 dirs = xdgConfigDirs();
425 for (int i = 0; i < dirs.size(); ++i)
426 appendOrganizationAndApp(dirs[i]);
427 break;
428 case GenericDataLocation:
429 dirs = xdgDataDirs();
430 break;
431 case ApplicationsLocation:
432 dirs = xdgDataDirs();
433 for (int i = 0; i < dirs.size(); ++i)
434 dirs[i].append("/applications"_L1);
435 break;
436 case AppDataLocation:
437 case AppLocalDataLocation:
438 dirs = xdgDataDirs();
439 for (int i = 0; i < dirs.size(); ++i)
440 appendOrganizationAndApp(dirs[i]);
441 break;
442 case FontsLocation:
443 dirs += QDir::homePath() + "/.fonts"_L1;
444 dirs += xdgDataDirs();
445 for (int i = 1; i < dirs.size(); ++i)
446 dirs[i].append("/fonts"_L1);
447 break;
448 default:
449 break;
450 }
451 const QString localDir = writableLocation(type);
452 dirs.prepend(localDir);
453 return dirs;
454}
455
456QT_END_NAMESPACE
457
458#endif // QT_NO_STANDARDPATHS
static QByteArray unixPermissionsText(QFile::Permissions permissions)
static QStringList dirsList(const QString &xdgEnvVar)
static bool checkXdgRuntimeDir(const QString &xdgRuntimeDir)
static QStringList xdgConfigDirs()
static void appendOrganizationAndApp(QString &path)
static QStringList xdgDataDirs()