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

source code of qtbase/src/corelib/io/qstandardpaths_unix.cpp