diff options
Diffstat (limited to 'src/corelib/io/qstandardpaths_unix.cpp')
-rw-r--r-- | src/corelib/io/qstandardpaths_unix.cpp | 421 |
1 files changed, 259 insertions, 162 deletions
diff --git a/src/corelib/io/qstandardpaths_unix.cpp b/src/corelib/io/qstandardpaths_unix.cpp index ca2a2689f1..e38f670895 100644 --- a/src/corelib/io/qstandardpaths_unix.cpp +++ b/src/corelib/io/qstandardpaths_unix.cpp @@ -1,41 +1,6 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2020 The Qt Company Ltd. +// Copyright (C) 2020 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qstandardpaths.h" #include <qdir.h> @@ -57,42 +22,154 @@ QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + static void appendOrganizationAndApp(QString &path) { #ifndef QT_BOOTSTRAPPED const QString org = QCoreApplication::organizationName(); if (!org.isEmpty()) - path += QLatin1Char('/') + org; + path += u'/' + org; const QString appName = QCoreApplication::applicationName(); if (!appName.isEmpty()) - path += QLatin1Char('/') + appName; + path += u'/' + appName; #else Q_UNUSED(path); #endif } #if QT_CONFIG(regularexpression) -static QLatin1String xdg_key_name(QStandardPaths::StandardLocation type) +static QLatin1StringView xdg_key_name(QStandardPaths::StandardLocation type) { switch (type) { case QStandardPaths::DesktopLocation: - return QLatin1String("DESKTOP"); + return "DESKTOP"_L1; case QStandardPaths::DocumentsLocation: - return QLatin1String("DOCUMENTS"); + return "DOCUMENTS"_L1; case QStandardPaths::PicturesLocation: - return QLatin1String("PICTURES"); + return "PICTURES"_L1; case QStandardPaths::MusicLocation: - return QLatin1String("MUSIC"); + return "MUSIC"_L1; case QStandardPaths::MoviesLocation: - return QLatin1String("VIDEOS"); + return "VIDEOS"_L1; case QStandardPaths::DownloadLocation: - return QLatin1String("DOWNLOAD"); + return "DOWNLOAD"_L1; + case QStandardPaths::PublicShareLocation: + return "PUBLICSHARE"_L1; + case QStandardPaths::TemplatesLocation: + return "TEMPLATES"_L1; default: - return QLatin1String(); + return {}; } } #endif +static QByteArray unixPermissionsText(QFile::Permissions permissions) +{ + mode_t perms = 0; + if (permissions & QFile::ReadOwner) + perms |= S_IRUSR; + if (permissions & QFile::WriteOwner) + perms |= S_IWUSR; + if (permissions & QFile::ExeOwner) + perms |= S_IXUSR; + if (permissions & QFile::ReadGroup) + perms |= S_IRGRP; + if (permissions & QFile::WriteGroup) + perms |= S_IWGRP; + if (permissions & QFile::ExeGroup) + perms |= S_IXGRP; + if (permissions & QFile::ReadOther) + perms |= S_IROTH; + if (permissions & QFile::WriteOther) + perms |= S_IWOTH; + if (permissions & QFile::ExeOther) + perms |= S_IXOTH; + return '0' + QByteArray::number(perms, 8); +} + +static bool checkXdgRuntimeDir(const QString &xdgRuntimeDir) +{ + auto describeMetaData = [](const QFileSystemMetaData &metaData) -> QByteArray { + if (!metaData.exists()) + return "a broken symlink"; + + QByteArray description; + if (metaData.isLink()) + description = "a symbolic link to "; + + if (metaData.isFile()) + description += "a regular file"; + else if (metaData.isDirectory()) + description += "a directory"; + else if (metaData.isSequential()) + description += "a character device, socket or FIFO"; + else + description += "a block device"; + + description += " permissions " + unixPermissionsText(metaData.permissions()); + + return description + + " owned by UID " + QByteArray::number(metaData.userId()) + + " GID " + QByteArray::number(metaData.groupId()); + }; + + // http://standards.freedesktop.org/basedir-spec/latest/ + const uint myUid = uint(geteuid()); + const QFile::Permissions wantedPerms = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner; + const QFileSystemMetaData::MetaDataFlags statFlags = QFileSystemMetaData::PosixStatFlags + | QFileSystemMetaData::LinkType; + QFileSystemMetaData metaData; + QFileSystemEntry entry(xdgRuntimeDir); + + // Check that the xdgRuntimeDir is a directory by attempting to create it. + // A stat() before mkdir() that concluded it doesn't exist is a meaningless + // result: we'd race against someone else attempting to create it. + // ### QFileSystemEngine::createDirectory cannot take the extra mode argument. + if (QT_MKDIR(entry.nativeFilePath(), 0700) == 0) + return true; + if (errno != EEXIST) { + qErrnoWarning("QStandardPaths: error creating runtime directory '%ls'", + qUtf16Printable(xdgRuntimeDir)); + return false; + } + + // We use LinkType to force an lstat(), but fillMetaData() still returns error + // on broken symlinks. + if (!QFileSystemEngine::fillMetaData(entry, metaData, statFlags) && !metaData.isLink()) { + qErrnoWarning("QStandardPaths: error obtaining permissions of runtime directory '%ls'", + qUtf16Printable(xdgRuntimeDir)); + return false; + } + + // Checks: + // - is a directory + // - is not a symlink (even is pointing to a directory) + if (metaData.isLink() || !metaData.isDirectory()) { + qWarning("QStandardPaths: runtime directory '%ls' is not a directory, but %s", + qUtf16Printable(xdgRuntimeDir), describeMetaData(metaData).constData()); + return false; + } + + // - "The directory MUST be owned by the user" + if (metaData.userId() != myUid) { + qWarning("QStandardPaths: runtime directory '%ls' is not owned by UID %d, but %s", + qUtf16Printable(xdgRuntimeDir), myUid, describeMetaData(metaData).constData()); + return false; + } + + // "and he MUST be the only one having read and write access to it. Its Unix access mode MUST be 0700." + if (metaData.permissions() != wantedPerms) { + qWarning("QStandardPaths: wrong permissions on runtime directory %ls, %s instead of %s", + qUtf16Printable(xdgRuntimeDir), + unixPermissionsText(metaData.permissions()).constData(), + unixPermissionsText(wantedPerms).constData()); + return false; + } + + return true; +} + QString QStandardPaths::writableLocation(StandardLocation type) { switch (type) { @@ -103,25 +180,56 @@ QString QStandardPaths::writableLocation(StandardLocation type) case CacheLocation: case GenericCacheLocation: { - // http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html - QString xdgCacheHome = QFile::decodeName(qgetenv("XDG_CACHE_HOME")); - if (isTestModeEnabled()) - xdgCacheHome = QDir::homePath() + QLatin1String("/.qttest/cache"); - if (xdgCacheHome.isEmpty()) - xdgCacheHome = QDir::homePath() + QLatin1String("/.cache"); + QString xdgCacheHome; + if (isTestModeEnabled()) { + xdgCacheHome = QDir::homePath() + "/.qttest/cache"_L1; + } else { + // http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html + xdgCacheHome = QFile::decodeName(qgetenv("XDG_CACHE_HOME")); + if (!xdgCacheHome.startsWith(u'/')) + xdgCacheHome.clear(); // spec says relative paths should be ignored + + if (xdgCacheHome.isEmpty()) + xdgCacheHome = QDir::homePath() + "/.cache"_L1; + } if (type == QStandardPaths::CacheLocation) appendOrganizationAndApp(xdgCacheHome); return xdgCacheHome; } + case StateLocation: + case GenericStateLocation: + { + QString xdgStateHome; + if (isTestModeEnabled()) { + xdgStateHome = QDir::homePath() + "/.qttest/state"_L1; + } else { + // http://standards.freedesktop.org/basedir-spec/basedir-spec-0.8.html + xdgStateHome = QFile::decodeName(qgetenv("XDG_STATE_HOME")); + if (!xdgStateHome.startsWith(u'/')) + xdgStateHome.clear(); // spec says relative paths should be ignored + + if (xdgStateHome.isEmpty()) + xdgStateHome = QDir::homePath() + "/.local/state"_L1; + } + if (type == QStandardPaths::StateLocation) + appendOrganizationAndApp(xdgStateHome); + return xdgStateHome; + } case AppDataLocation: case AppLocalDataLocation: case GenericDataLocation: { - QString xdgDataHome = QFile::decodeName(qgetenv("XDG_DATA_HOME")); - if (isTestModeEnabled()) - xdgDataHome = QDir::homePath() + QLatin1String("/.qttest/share"); - if (xdgDataHome.isEmpty()) - xdgDataHome = QDir::homePath() + QLatin1String("/.local/share"); + QString xdgDataHome; + if (isTestModeEnabled()) { + xdgDataHome = QDir::homePath() + "/.qttest/share"_L1; + } else { + xdgDataHome = QFile::decodeName(qgetenv("XDG_DATA_HOME")); + if (!xdgDataHome.startsWith(u'/')) + xdgDataHome.clear(); // spec says relative paths should be ignored + + if (xdgDataHome.isEmpty()) + xdgDataHome = QDir::homePath() + "/.local/share"_L1; + } if (type == AppDataLocation || type == AppLocalDataLocation) appendOrganizationAndApp(xdgDataHome); return xdgDataHome; @@ -130,70 +238,43 @@ QString QStandardPaths::writableLocation(StandardLocation type) case GenericConfigLocation: case AppConfigLocation: { - // http://standards.freedesktop.org/basedir-spec/latest/ - QString xdgConfigHome = QFile::decodeName(qgetenv("XDG_CONFIG_HOME")); - if (isTestModeEnabled()) - xdgConfigHome = QDir::homePath() + QLatin1String("/.qttest/config"); - if (xdgConfigHome.isEmpty()) - xdgConfigHome = QDir::homePath() + QLatin1String("/.config"); + QString xdgConfigHome; + if (isTestModeEnabled()) { + xdgConfigHome = QDir::homePath() + "/.qttest/config"_L1; + } else { + // http://standards.freedesktop.org/basedir-spec/latest/ + xdgConfigHome = QFile::decodeName(qgetenv("XDG_CONFIG_HOME")); + if (!xdgConfigHome.startsWith(u'/')) + xdgConfigHome.clear(); // spec says relative paths should be ignored + + if (xdgConfigHome.isEmpty()) + xdgConfigHome = QDir::homePath() + "/.config"_L1; + } if (type == AppConfigLocation) appendOrganizationAndApp(xdgConfigHome); return xdgConfigHome; } case RuntimeLocation: { - // http://standards.freedesktop.org/basedir-spec/latest/ - const uint myUid = uint(geteuid()); - // since the current user is the owner, set both xxxUser and xxxOwner - const QFile::Permissions wantedPerms = QFile::ReadUser | QFile::WriteUser | QFile::ExeUser - | QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner; - QFileInfo fileInfo; QString xdgRuntimeDir = QFile::decodeName(qgetenv("XDG_RUNTIME_DIR")); - if (xdgRuntimeDir.isEmpty()) { + if (!xdgRuntimeDir.startsWith(u'/')) + xdgRuntimeDir.clear(); // spec says relative paths should be ignored + + bool fromEnv = !xdgRuntimeDir.isEmpty(); + if (xdgRuntimeDir.isEmpty() || !checkXdgRuntimeDir(xdgRuntimeDir)) { + // environment variable not set or is set to something unsuitable + const uint myUid = uint(geteuid()); const QString userName = QFileSystemEngine::resolveUserName(myUid); - xdgRuntimeDir = QDir::tempPath() + QLatin1String("/runtime-") + userName; - fileInfo.setFile(xdgRuntimeDir); + xdgRuntimeDir = QDir::tempPath() + "/runtime-"_L1 + userName; + + if (!fromEnv) { #ifndef Q_OS_WASM - qWarning("QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '%ls'", qUtf16Printable(xdgRuntimeDir)); + qWarning("QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '%ls'", qUtf16Printable(xdgRuntimeDir)); #endif - } else { - fileInfo.setFile(xdgRuntimeDir); - } - if (fileInfo.exists()) { - if (!fileInfo.isDir()) { - qWarning("QStandardPaths: XDG_RUNTIME_DIR points to '%ls' which is not a directory", - qUtf16Printable(xdgRuntimeDir)); - return QString(); } - } else { - QFileSystemEntry entry(xdgRuntimeDir); - if (!QFileSystemEngine::createDirectory(entry, false)) { - if (errno != EEXIST) { - qErrnoWarning("QStandardPaths: error creating runtime directory %ls", - qUtf16Printable(xdgRuntimeDir)); - return QString(); - } - } else { - QSystemError error; - if (!QFileSystemEngine::setPermissions(entry, wantedPerms, error)) { - qWarning("QStandardPaths: could not set correct permissions on runtime directory %ls: %ls", - qUtf16Printable(xdgRuntimeDir), qUtf16Printable(error.toString())); - return QString(); - } - } - } - // "The directory MUST be owned by the user" - if (fileInfo.ownerId() != myUid) { - qWarning("QStandardPaths: wrong ownership on runtime directory %ls, %d instead of %d", - qUtf16Printable(xdgRuntimeDir), - fileInfo.ownerId(), myUid); - return QString(); - } - // "and he MUST be the only one having read and write access to it. Its Unix access mode MUST be 0700." - if (fileInfo.permissions() != wantedPerms) { - qWarning("QStandardPaths: wrong permissions on runtime directory %ls, %x instead of %x", - qUtf16Printable(xdgRuntimeDir), uint(fileInfo.permissions()), uint(wantedPerms)); - return QString(); + + if (!checkXdgRuntimeDir(xdgRuntimeDir)) + xdgRuntimeDir.clear(); } return xdgRuntimeDir; @@ -205,30 +286,33 @@ QString QStandardPaths::writableLocation(StandardLocation type) #if QT_CONFIG(regularexpression) // http://www.freedesktop.org/wiki/Software/xdg-user-dirs QString xdgConfigHome = QFile::decodeName(qgetenv("XDG_CONFIG_HOME")); + if (!xdgConfigHome.startsWith(u'/')) + xdgConfigHome.clear(); // spec says relative paths should be ignored + if (xdgConfigHome.isEmpty()) - xdgConfigHome = QDir::homePath() + QLatin1String("/.config"); - QFile file(xdgConfigHome + QLatin1String("/user-dirs.dirs")); - const QLatin1String key = xdg_key_name(type); + xdgConfigHome = QDir::homePath() + "/.config"_L1; + QFile file(xdgConfigHome + "/user-dirs.dirs"_L1); + const QLatin1StringView key = xdg_key_name(type); if (!key.isEmpty() && !isTestModeEnabled() && file.open(QIODevice::ReadOnly)) { QTextStream stream(&file); // Only look for lines like: XDG_DESKTOP_DIR="$HOME/Desktop" - QRegularExpression exp(QLatin1String("^XDG_(.*)_DIR=(.*)$")); + static const QRegularExpression exp(u"^XDG_(.*)_DIR=(.*)$"_s); QString result; while (!stream.atEnd()) { const QString &line = stream.readLine(); QRegularExpressionMatch match = exp.match(line); if (match.hasMatch() && match.capturedView(1) == key) { QStringView value = match.capturedView(2); - if (value.length() > 2 - && value.startsWith(QLatin1Char('\"')) - && value.endsWith(QLatin1Char('\"'))) - value = value.mid(1, value.length() - 2); + if (value.size() > 2 + && value.startsWith(u'\"') + && value.endsWith(u'\"')) + value = value.mid(1, value.size() - 2); // value can start with $HOME - if (value.startsWith(QLatin1String("$HOME"))) + if (value.startsWith("$HOME"_L1)) result = QDir::homePath() + value.mid(5); else result = value.toString(); - if (result.length() > 1 && result.endsWith(QLatin1Char('/'))) + if (result.size() > 1 && result.endsWith(u'/')) result.chop(1); } } @@ -240,31 +324,39 @@ QString QStandardPaths::writableLocation(StandardLocation type) QString path; switch (type) { case DesktopLocation: - path = QDir::homePath() + QLatin1String("/Desktop"); + path = QDir::homePath() + "/Desktop"_L1; break; case DocumentsLocation: - path = QDir::homePath() + QLatin1String("/Documents"); + path = QDir::homePath() + "/Documents"_L1; break; case PicturesLocation: - path = QDir::homePath() + QLatin1String("/Pictures"); + path = QDir::homePath() + "/Pictures"_L1; break; case FontsLocation: - path = writableLocation(GenericDataLocation) + QLatin1String("/fonts"); + path = writableLocation(GenericDataLocation) + "/fonts"_L1; break; case MusicLocation: - path = QDir::homePath() + QLatin1String("/Music"); + path = QDir::homePath() + "/Music"_L1; break; case MoviesLocation: - path = QDir::homePath() + QLatin1String("/Videos"); + path = QDir::homePath() + "/Videos"_L1; break; case DownloadLocation: - path = QDir::homePath() + QLatin1String("/Downloads"); + path = QDir::homePath() + "/Downloads"_L1; break; case ApplicationsLocation: - path = writableLocation(GenericDataLocation) + QLatin1String("/applications"); + path = writableLocation(GenericDataLocation) + "/applications"_L1; + break; + + case PublicShareLocation: + path = QDir::homePath() + "/Public"_L1; + break; + + case TemplatesLocation: + path = QDir::homePath() + "/Templates"_L1; break; default: @@ -274,43 +366,48 @@ QString QStandardPaths::writableLocation(StandardLocation type) return path; } -static QStringList xdgDataDirs() +static QStringList dirsList(const QString &xdgEnvVar) { QStringList dirs; // http://standards.freedesktop.org/basedir-spec/latest/ + // Normalize paths, skip relative paths (the spec says relative paths + // should be ignored) + for (const auto dir : qTokenize(xdgEnvVar, u':')) + if (dir.startsWith(u'/')) + dirs.push_back(QDir::cleanPath(dir.toString())); + + // Remove duplicates from the list, there's no use for duplicated paths + // in XDG_* env vars - if whatever is being looked for is not found in + // the given directory the first time, it won't be there the second time. + // Plus duplicate paths causes problems for example for mimetypes, + // where duplicate paths here lead to duplicated mime types returned + // for a file, eg "text/plain,text/plain" instead of "text/plain" + dirs.removeDuplicates(); + + return dirs; +} + +static QStringList xdgDataDirs() +{ + // http://standards.freedesktop.org/basedir-spec/latest/ QString xdgDataDirsEnv = QFile::decodeName(qgetenv("XDG_DATA_DIRS")); - if (xdgDataDirsEnv.isEmpty()) { - dirs.append(QString::fromLatin1("/usr/local/share")); - dirs.append(QString::fromLatin1("/usr/share")); - } else { - const auto parts = QStringView{xdgDataDirsEnv}.split(QLatin1Char(':'), Qt::SkipEmptyParts); - - // Normalize paths, skip relative paths - for (const auto &dir : parts) { - if (dir.startsWith(QLatin1Char('/'))) - dirs.push_back(QDir::cleanPath(dir.toString())); - } - // Remove duplicates from the list, there's no use for duplicated - // paths in XDG_DATA_DIRS - if it's not found in the given - // directory the first time, it won't be there the second time. - // Plus duplicate paths causes problems for example for mimetypes, - // where duplicate paths here lead to duplicated mime types returned - // for a file, eg "text/plain,text/plain" instead of "text/plain" - dirs.removeDuplicates(); - } + QStringList dirs = dirsList(xdgDataDirsEnv); + if (dirs.isEmpty()) + dirs = QStringList{u"/usr/local/share"_s, u"/usr/share"_s}; + return dirs; } static QStringList xdgConfigDirs() { - QStringList dirs; // http://standards.freedesktop.org/basedir-spec/latest/ const QString xdgConfigDirs = QFile::decodeName(qgetenv("XDG_CONFIG_DIRS")); - if (xdgConfigDirs.isEmpty()) - dirs.append(QString::fromLatin1("/etc/xdg")); - else - dirs = xdgConfigDirs.split(QLatin1Char(':')); + + QStringList dirs = dirsList(xdgConfigDirs); + if (dirs.isEmpty()) + dirs.push_back(u"/etc/xdg"_s); + return dirs; } @@ -324,7 +421,7 @@ QStringList QStandardPaths::standardLocations(StandardLocation type) break; case AppConfigLocation: dirs = xdgConfigDirs(); - for (int i = 0; i < dirs.count(); ++i) + for (int i = 0; i < dirs.size(); ++i) appendOrganizationAndApp(dirs[i]); break; case GenericDataLocation: @@ -332,20 +429,20 @@ QStringList QStandardPaths::standardLocations(StandardLocation type) break; case ApplicationsLocation: dirs = xdgDataDirs(); - for (int i = 0; i < dirs.count(); ++i) - dirs[i].append(QLatin1String("/applications")); + for (int i = 0; i < dirs.size(); ++i) + dirs[i].append("/applications"_L1); break; case AppDataLocation: case AppLocalDataLocation: dirs = xdgDataDirs(); - for (int i = 0; i < dirs.count(); ++i) + for (int i = 0; i < dirs.size(); ++i) appendOrganizationAndApp(dirs[i]); break; case FontsLocation: - dirs += QDir::homePath() + QLatin1String("/.fonts"); + dirs += QDir::homePath() + "/.fonts"_L1; dirs += xdgDataDirs(); - for (int i = 1; i < dirs.count(); ++i) - dirs[i].append(QLatin1String("/fonts")); + for (int i = 1; i < dirs.size(); ++i) + dirs[i].append("/fonts"_L1); break; default: break; |