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

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