1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtCore module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qstandardpaths.h"
41#include <qdir.h>
42#include <qfile.h>
43#include <qhash.h>
44#include <qtextstream.h>
45#if QT_CONFIG(regularexpression)
46#include <qregularexpression.h>
47#endif
48#include <private/qfilesystemengine_p.h>
49#include <errno.h>
50#include <stdlib.h>
51
52#ifndef QT_BOOTSTRAPPED
53#include <qcoreapplication.h>
54#endif
55
56#ifndef QT_NO_STANDARDPATHS
57
58QT_BEGIN_NAMESPACE
59
60static void appendOrganizationAndApp(QString &path)
61{
62#ifndef QT_BOOTSTRAPPED
63 const QString org = QCoreApplication::organizationName();
64 if (!org.isEmpty())
65 path += QLatin1Char('/') + org;
66 const QString appName = QCoreApplication::applicationName();
67 if (!appName.isEmpty())
68 path += QLatin1Char('/') + appName;
69#else
70 Q_UNUSED(path);
71#endif
72}
73
74#if QT_CONFIG(regularexpression)
75static QLatin1String xdg_key_name(QStandardPaths::StandardLocation type)
76{
77 switch (type) {
78 case QStandardPaths::DesktopLocation:
79 return QLatin1String("DESKTOP");
80 case QStandardPaths::DocumentsLocation:
81 return QLatin1String("DOCUMENTS");
82 case QStandardPaths::PicturesLocation:
83 return QLatin1String("PICTURES");
84 case QStandardPaths::MusicLocation:
85 return QLatin1String("MUSIC");
86 case QStandardPaths::MoviesLocation:
87 return QLatin1String("VIDEOS");
88 case QStandardPaths::DownloadLocation:
89 return QLatin1String("DOWNLOAD");
90 default:
91 return QLatin1String();
92 }
93}
94#endif
95
96QString QStandardPaths::writableLocation(StandardLocation type)
97{
98 switch (type) {
99 case HomeLocation:
100 return QDir::homePath();
101 case TempLocation:
102 return QDir::tempPath();
103 case CacheLocation:
104 case GenericCacheLocation:
105 {
106 // http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
107 QString xdgCacheHome = QFile::decodeName(qgetenv("XDG_CACHE_HOME"));
108 if (isTestModeEnabled())
109 xdgCacheHome = QDir::homePath() + QLatin1String("/.qttest/cache");
110 if (xdgCacheHome.isEmpty())
111 xdgCacheHome = QDir::homePath() + QLatin1String("/.cache");
112 if (type == QStandardPaths::CacheLocation)
113 appendOrganizationAndApp(xdgCacheHome);
114 return xdgCacheHome;
115 }
116 case AppDataLocation:
117 case AppLocalDataLocation:
118 case GenericDataLocation:
119 {
120 QString xdgDataHome = QFile::decodeName(qgetenv("XDG_DATA_HOME"));
121 if (isTestModeEnabled())
122 xdgDataHome = QDir::homePath() + QLatin1String("/.qttest/share");
123 if (xdgDataHome.isEmpty())
124 xdgDataHome = QDir::homePath() + QLatin1String("/.local/share");
125 if (type == AppDataLocation || type == AppLocalDataLocation)
126 appendOrganizationAndApp(xdgDataHome);
127 return xdgDataHome;
128 }
129 case ConfigLocation:
130 case GenericConfigLocation:
131 case AppConfigLocation:
132 {
133 // http://standards.freedesktop.org/basedir-spec/latest/
134 QString xdgConfigHome = QFile::decodeName(qgetenv("XDG_CONFIG_HOME"));
135 if (isTestModeEnabled())
136 xdgConfigHome = QDir::homePath() + QLatin1String("/.qttest/config");
137 if (xdgConfigHome.isEmpty())
138 xdgConfigHome = QDir::homePath() + QLatin1String("/.config");
139 if (type == AppConfigLocation)
140 appendOrganizationAndApp(xdgConfigHome);
141 return xdgConfigHome;
142 }
143 case RuntimeLocation:
144 {
145 const uint myUid = uint(geteuid());
146 // http://standards.freedesktop.org/basedir-spec/latest/
147 QFileInfo fileInfo;
148 QString xdgRuntimeDir = QFile::decodeName(qgetenv("XDG_RUNTIME_DIR"));
149 if (xdgRuntimeDir.isEmpty()) {
150 const QString userName = QFileSystemEngine::resolveUserName(myUid);
151 xdgRuntimeDir = QDir::tempPath() + QLatin1String("/runtime-") + userName;
152 fileInfo.setFile(xdgRuntimeDir);
153 if (!fileInfo.isDir()) {
154 if (!QDir().mkdir(xdgRuntimeDir)) {
155 qErrnoWarning("QStandardPaths: error creating runtime directory %ls",
156 qUtf16Printable(xdgRuntimeDir));
157 return QString();
158 }
159 }
160#ifndef Q_OS_WASM
161 qWarning("QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '%ls'", qUtf16Printable(xdgRuntimeDir));
162#endif
163 } else {
164 fileInfo.setFile(xdgRuntimeDir);
165 if (!fileInfo.exists()) {
166 qWarning("QStandardPaths: XDG_RUNTIME_DIR points to non-existing path '%ls', "
167 "please create it with 0700 permissions.", qUtf16Printable(xdgRuntimeDir));
168 return QString();
169 }
170 if (!fileInfo.isDir()) {
171 qWarning("QStandardPaths: XDG_RUNTIME_DIR points to '%ls' which is not a directory",
172 qUtf16Printable(xdgRuntimeDir));
173 return QString();
174 }
175 }
176 // "The directory MUST be owned by the user"
177 if (fileInfo.ownerId() != myUid) {
178 qWarning("QStandardPaths: wrong ownership on runtime directory %ls, %d instead of %d",
179 qUtf16Printable(xdgRuntimeDir),
180 fileInfo.ownerId(), myUid);
181 return QString();
182 }
183 // "and he MUST be the only one having read and write access to it. Its Unix access mode MUST be 0700."
184 // since the current user is the owner, set both xxxUser and xxxOwner
185 const QFile::Permissions wantedPerms = QFile::ReadUser | QFile::WriteUser | QFile::ExeUser
186 | QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner;
187 if (fileInfo.permissions() != wantedPerms) {
188 QFile file(xdgRuntimeDir);
189 if (!file.setPermissions(wantedPerms)) {
190 qWarning("QStandardPaths: could not set correct permissions on runtime directory %ls: %ls",
191 qUtf16Printable(xdgRuntimeDir), qUtf16Printable(file.errorString()));
192 return QString();
193 }
194 }
195 return xdgRuntimeDir;
196 }
197 default:
198 break;
199 }
200
201#if QT_CONFIG(regularexpression)
202 // http://www.freedesktop.org/wiki/Software/xdg-user-dirs
203 QString xdgConfigHome = QFile::decodeName(qgetenv("XDG_CONFIG_HOME"));
204 if (xdgConfigHome.isEmpty())
205 xdgConfigHome = QDir::homePath() + QLatin1String("/.config");
206 QFile file(xdgConfigHome + QLatin1String("/user-dirs.dirs"));
207 const QLatin1String key = xdg_key_name(type);
208 if (!key.isEmpty() && !isTestModeEnabled() && file.open(QIODevice::ReadOnly)) {
209 QTextStream stream(&file);
210 // Only look for lines like: XDG_DESKTOP_DIR="$HOME/Desktop"
211 QRegularExpression exp(QLatin1String("^XDG_(.*)_DIR=(.*)$"));
212 QString result;
213 while (!stream.atEnd()) {
214 const QString &line = stream.readLine();
215 QRegularExpressionMatch match = exp.match(line);
216 if (match.hasMatch() && match.capturedView(1) == key) {
217 QStringView value = match.capturedView(2);
218 if (value.length() > 2
219 && value.startsWith(QLatin1Char('\"'))
220 && value.endsWith(QLatin1Char('\"')))
221 value = value.mid(1, value.length() - 2);
222 // value can start with $HOME
223 if (value.startsWith(QLatin1String("$HOME")))
224 result = QDir::homePath() + value.mid(5);
225 else
226 result = value.toString();
227 if (result.length() > 1 && result.endsWith(QLatin1Char('/')))
228 result.chop(1);
229 }
230 }
231 if (!result.isNull())
232 return result;
233 }
234#endif // QT_CONFIG(regularexpression)
235
236 QString path;
237 switch (type) {
238 case DesktopLocation:
239 path = QDir::homePath() + QLatin1String("/Desktop");
240 break;
241 case DocumentsLocation:
242 path = QDir::homePath() + QLatin1String("/Documents");
243 break;
244 case PicturesLocation:
245 path = QDir::homePath() + QLatin1String("/Pictures");
246 break;
247
248 case FontsLocation:
249 path = writableLocation(GenericDataLocation) + QLatin1String("/fonts");
250 break;
251
252 case MusicLocation:
253 path = QDir::homePath() + QLatin1String("/Music");
254 break;
255
256 case MoviesLocation:
257 path = QDir::homePath() + QLatin1String("/Videos");
258 break;
259 case DownloadLocation:
260 path = QDir::homePath() + QLatin1String("/Downloads");
261 break;
262 case ApplicationsLocation:
263 path = writableLocation(GenericDataLocation) + QLatin1String("/applications");
264 break;
265
266 default:
267 break;
268 }
269
270 return path;
271}
272
273static QStringList xdgDataDirs()
274{
275 QStringList dirs;
276 // http://standards.freedesktop.org/basedir-spec/latest/
277 QString xdgDataDirsEnv = QFile::decodeName(qgetenv("XDG_DATA_DIRS"));
278 if (xdgDataDirsEnv.isEmpty()) {
279 dirs.append(QString::fromLatin1("/usr/local/share"));
280 dirs.append(QString::fromLatin1("/usr/share"));
281 } else {
282 const auto parts = xdgDataDirsEnv.splitRef(QLatin1Char(':'), QString::SkipEmptyParts);
283
284 // Normalize paths, skip relative paths
285 for (const QStringRef &dir : parts) {
286 if (dir.startsWith(QLatin1Char('/')))
287 dirs.push_back(QDir::cleanPath(dir.toString()));
288 }
289
290 // Remove duplicates from the list, there's no use for duplicated
291 // paths in XDG_DATA_DIRS - if it's not found in the given
292 // directory the first time, it won't be there the second time.
293 // Plus duplicate paths causes problems for example for mimetypes,
294 // where duplicate paths here lead to duplicated mime types returned
295 // for a file, eg "text/plain,text/plain" instead of "text/plain"
296 dirs.removeDuplicates();
297 }
298 return dirs;
299}
300
301static QStringList xdgConfigDirs()
302{
303 QStringList dirs;
304 // http://standards.freedesktop.org/basedir-spec/latest/
305 const QString xdgConfigDirs = QFile::decodeName(qgetenv("XDG_CONFIG_DIRS"));
306 if (xdgConfigDirs.isEmpty())
307 dirs.append(QString::fromLatin1("/etc/xdg"));
308 else
309 dirs = xdgConfigDirs.split(QLatin1Char(':'));
310 return dirs;
311}
312
313QStringList QStandardPaths::standardLocations(StandardLocation type)
314{
315 QStringList dirs;
316 switch (type) {
317 case ConfigLocation:
318 case GenericConfigLocation:
319 dirs = xdgConfigDirs();
320 break;
321 case AppConfigLocation:
322 dirs = xdgConfigDirs();
323 for (int i = 0; i < dirs.count(); ++i)
324 appendOrganizationAndApp(dirs[i]);
325 break;
326 case GenericDataLocation:
327 dirs = xdgDataDirs();
328 break;
329 case ApplicationsLocation:
330 dirs = xdgDataDirs();
331 for (int i = 0; i < dirs.count(); ++i)
332 dirs[i].append(QLatin1String("/applications"));
333 break;
334 case AppDataLocation:
335 case AppLocalDataLocation:
336 dirs = xdgDataDirs();
337 for (int i = 0; i < dirs.count(); ++i)
338 appendOrganizationAndApp(dirs[i]);
339 break;
340 case FontsLocation:
341 dirs += QDir::homePath() + QLatin1String("/.fonts");
342 break;
343 default:
344 break;
345 }
346 const QString localDir = writableLocation(type);
347 dirs.prepend(localDir);
348 return dirs;
349}
350
351QT_END_NAMESPACE
352
353#endif // QT_NO_STANDARDPATHS
354