diff --git a/autotests/kconfig_compiler/test4main.cpp b/autotests/kconfig_compiler/test4main.cpp index 7249923..2c63e13 100644 --- a/autotests/kconfig_compiler/test4main.cpp +++ b/autotests/kconfig_compiler/test4main.cpp @@ -1,46 +1,46 @@ /* Copyright (c) 2003,2004 Waldo Bastian Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "test4.h" #include #include #include int main(int argc, char **argv) { QGuiApplication app(argc, argv); Q_UNUSED(app); { KConfig initialConfig(QStringLiteral("test4rc")); KConfigGroup group = initialConfig.group(QStringLiteral("Foo")); group.writeEntry(QStringLiteral("foo bar"), QStringLiteral("Value")); } Test4 *t = Test4::self(); bool ok = QFile::exists(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + "/test4rc"); if (!ok) { qWarning() << "config file was not created!"; } - if (t->fooBar() != QStringLiteral("Value")) { + if (t->fooBar() != QLatin1String("Value")) { qWarning() << "wrong value for foo bar:" << t->fooBar(); } delete t; return ok ? 0 : 1; } diff --git a/src/core/kconfigini.cpp b/src/core/kconfigini.cpp index d07b59c..798ce57 100644 --- a/src/core/kconfigini.cpp +++ b/src/core/kconfigini.cpp @@ -1,933 +1,933 @@ /* This file is part of the KDE libraries Copyright (c) 2006, 2007 Thomas Braxton Copyright (c) 1999 Preston Brown Copyright (C) 1997-1999 Matthias Kalle Dalheimer (kalle@kde.org) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kconfigini_p.h" #include "kconfig.h" #include "kconfigbackend_p.h" #include "bufferfragment_p.h" #include "kconfigdata.h" #include "kconfig_core_log_settings.h" #include #include #include #include #include #include #include #include #ifndef Q_OS_WIN #include // getuid, close #endif #include // uid_t #include // open KCONFIGCORE_EXPORT bool kde_kiosk_exception = false; // flag to disable kiosk restrictions static QByteArray lookup(const KConfigIniBackend::BufferFragment fragment, QHash *cache) { auto it = cache->constFind(fragment); if (it != cache->constEnd()) { return it.value(); } return cache->insert(fragment, fragment.toByteArray()).value(); } QString KConfigIniBackend::warningProlog(const QFile &file, int line) { return QStringLiteral("KConfigIni: In file %2, line %1: ") .arg(line).arg(file.fileName()); } KConfigIniBackend::KConfigIniBackend() : KConfigBackend(), lockFile(nullptr) { } KConfigIniBackend::~KConfigIniBackend() { } KConfigBackend::ParseInfo KConfigIniBackend::parseConfig(const QByteArray ¤tLocale, KEntryMap &entryMap, ParseOptions options) { return parseConfig(currentLocale, entryMap, options, false); } // merging==true is the merging that happens at the beginning of writeConfig: // merge changes in the on-disk file with the changes in the KConfig object. KConfigBackend::ParseInfo KConfigIniBackend::parseConfig(const QByteArray ¤tLocale, KEntryMap &entryMap, ParseOptions options, bool merging) { if (filePath().isEmpty() || !QFile::exists(filePath())) { return ParseOk; } const QByteArray currentLanguage = currentLocale.split('_').first(); bool bDefault = options & ParseDefaults; bool allowExecutableValues = options & ParseExpansions; QByteArray currentGroup(""); QFile file(filePath()); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { return ParseOpenError; } QList immutableGroups; bool fileOptionImmutable = false; bool groupOptionImmutable = false; bool groupSkip = false; int lineNo = 0; // on systems using \r\n as end of line, \r will be taken care of by // trim() below QByteArray buffer = file.readAll(); BufferFragment contents(buffer.data(), buffer.size()); unsigned int len = contents.length(); unsigned int startOfLine = 0; // Reduce memory overhead by making use of implicit sharing // This assumes that config files contain only a small amount of // different fragments which are repeated often. // This is often the case, especially sub groups will all have // the same list of keys and similar values as well. QHash cache; cache.reserve(4096); while (startOfLine < len) { BufferFragment line = contents.split('\n', &startOfLine); line.trim(); lineNo++; // skip empty lines and lines beginning with '#' if (line.isEmpty() || line.at(0) == '#') { continue; } if (line.at(0) == '[') { // found a group groupOptionImmutable = fileOptionImmutable; QByteArray newGroup; int start = 1, end; do { end = start; for (;;) { if (end == line.length()) { qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) << "Invalid group header."; // XXX maybe reset the current group here? goto next_line; } if (line.at(end) == ']') { break; } end++; } if (end + 1 == line.length() && start + 2 == end && line.at(start) == '$' && line.at(start + 1) == 'i') { if (newGroup.isEmpty()) { fileOptionImmutable = !kde_kiosk_exception; } else { groupOptionImmutable = !kde_kiosk_exception; } } else { if (!newGroup.isEmpty()) { newGroup += '\x1d'; } BufferFragment namePart = line.mid(start, end - start); printableToString(&namePart, file, lineNo); newGroup += namePart.toByteArray(); } } while ((start = end + 2) <= line.length() && line.at(end + 1) == '['); currentGroup = newGroup; groupSkip = entryMap.getEntryOption(currentGroup, nullptr, nullptr, KEntryMap::EntryImmutable); if (groupSkip && !bDefault) { continue; } if (groupOptionImmutable) // Do not make the groups immutable until the entries from // this file have been added. { immutableGroups.append(currentGroup); } } else { if (groupSkip && !bDefault) { continue; // skip entry } BufferFragment aKey; int eqpos = line.indexOf('='); if (eqpos < 0) { aKey = line; line.clear(); } else { BufferFragment temp = line.left(eqpos); temp.trim(); aKey = temp; line.truncateLeft(eqpos + 1); line.trim(); } if (aKey.isEmpty()) { qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) << "Invalid entry (empty key)"; continue; } KEntryMap::EntryOptions entryOptions = nullptr; if (groupOptionImmutable) { entryOptions |= KEntryMap::EntryImmutable; } BufferFragment locale; int start; while ((start = aKey.lastIndexOf('[')) >= 0) { int end = aKey.indexOf(']', start); if (end < 0) { qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) << "Invalid entry (missing ']')"; goto next_line; } else if (end > start + 1 && aKey.at(start + 1) == '$') { // found option(s) int i = start + 2; while (i < end) { switch (aKey.at(i)) { case 'i': if (!kde_kiosk_exception) { entryOptions |= KEntryMap::EntryImmutable; } break; case 'e': if (allowExecutableValues) { entryOptions |= KEntryMap::EntryExpansion; } break; case 'd': entryOptions |= KEntryMap::EntryDeleted; - aKey = aKey.left(start); + aKey.truncate(start); printableToString(&aKey, file, lineNo); entryMap.setEntry(currentGroup, aKey.toByteArray(), QByteArray(), entryOptions); goto next_line; default: break; } i++; } } else { // found a locale if (!locale.isNull()) { qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) << "Invalid entry (second locale!?)"; goto next_line; } locale = aKey.mid(start + 1, end - start - 1); } aKey.truncate(start); } if (eqpos < 0) { // Do this here after [$d] was checked qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) << "Invalid entry (missing '=')"; continue; } printableToString(&aKey, file, lineNo); if (!locale.isEmpty()) { if (locale != currentLocale && locale != currentLanguage) { // backward compatibility. C == en_US if (locale.at(0) != 'C' || currentLocale != "en_US") { if (merging) { entryOptions |= KEntryMap::EntryRawKey; } else { goto next_line; // skip this entry if we're not merging } } } } if (!(entryOptions & KEntryMap::EntryRawKey)) { printableToString(&aKey, file, lineNo); } if (options & ParseGlobal) { entryOptions |= KEntryMap::EntryGlobal; } if (bDefault) { entryOptions |= KEntryMap::EntryDefault; } if (!locale.isNull()) { entryOptions |= KEntryMap::EntryLocalized; if (locale.indexOf('_') != -1) { entryOptions |= KEntryMap::EntryLocalizedCountry; } } printableToString(&line, file, lineNo); if (entryOptions & KEntryMap::EntryRawKey) { QByteArray rawKey; rawKey.reserve(aKey.length() + locale.length() + 2); rawKey.append(aKey.toVolatileByteArray()); rawKey.append('[').append(locale.toVolatileByteArray()).append(']'); entryMap.setEntry(currentGroup, rawKey, lookup(line, &cache), entryOptions); } else { entryMap.setEntry(currentGroup, lookup(aKey, &cache), lookup(line, &cache), entryOptions); } } next_line: continue; } // now make sure immutable groups are marked immutable for (const QByteArray &group : qAsConst(immutableGroups)) { entryMap.setEntry(group, QByteArray(), QByteArray(), KEntryMap::EntryImmutable); } return fileOptionImmutable ? ParseImmutable : ParseOk; } void KConfigIniBackend::writeEntries(const QByteArray &locale, QIODevice &file, const KEntryMap &map, bool defaultGroup, bool &firstEntry) { QByteArray currentGroup; bool groupIsImmutable = false; const KEntryMapConstIterator end = map.constEnd(); for (KEntryMapConstIterator it = map.constBegin(); it != end; ++it) { const KEntryKey &key = it.key(); // Either process the default group or all others if ((key.mGroup != "") == defaultGroup) { continue; // skip } // the only thing we care about groups is, is it immutable? if (key.mKey.isNull()) { groupIsImmutable = it->bImmutable; continue; // skip } const KEntry ¤tEntry = *it; if (!defaultGroup && currentGroup != key.mGroup) { if (!firstEntry) { file.putChar('\n'); } currentGroup = key.mGroup; for (int start = 0, end;; start = end + 1) { file.putChar('['); end = currentGroup.indexOf('\x1d', start); if (end < 0) { int cgl = currentGroup.length(); if (currentGroup.at(start) == '$' && cgl - start <= 10) { for (int i = start + 1; i < cgl; i++) { char c = currentGroup.at(i); if (c < 'a' || c > 'z') { goto nope; } } file.write("\\x24"); start++; } nope: file.write(stringToPrintable(currentGroup.mid(start), GroupString)); file.putChar(']'); if (groupIsImmutable) { file.write("[$i]", 4); } file.putChar('\n'); break; } else { file.write(stringToPrintable(currentGroup.mid(start, end - start), GroupString)); file.putChar(']'); } } } firstEntry = false; // it is data for a group if (key.bRaw) { // unprocessed key with attached locale from merge file.write(key.mKey); } else { file.write(stringToPrintable(key.mKey, KeyString)); // Key if (key.bLocal && locale != "C") { // 'C' locale == untranslated file.putChar('['); file.write(locale); // locale tag file.putChar(']'); } } if (currentEntry.bDeleted) { if (currentEntry.bImmutable) { file.write("[$di]", 5); // Deleted + immutable } else { file.write("[$d]", 4); // Deleted } } else { if (currentEntry.bImmutable || currentEntry.bExpand) { file.write("[$", 2); if (currentEntry.bImmutable) { file.putChar('i'); } if (currentEntry.bExpand) { file.putChar('e'); } file.putChar(']'); } file.putChar('='); file.write(stringToPrintable(currentEntry.mValue, ValueString)); } file.putChar('\n'); } } void KConfigIniBackend::writeEntries(const QByteArray &locale, QIODevice &file, const KEntryMap &map) { bool firstEntry = true; // write default group writeEntries(locale, file, map, true, firstEntry); // write all other groups writeEntries(locale, file, map, false, firstEntry); } bool KConfigIniBackend::writeConfig(const QByteArray &locale, KEntryMap &entryMap, WriteOptions options) { Q_ASSERT(!filePath().isEmpty()); KEntryMap writeMap; const bool bGlobal = options & WriteGlobal; // First, reparse the file on disk, to merge our changes with the ones done by other apps // Store the result into writeMap. { ParseOptions opts = ParseExpansions; if (bGlobal) { opts |= ParseGlobal; } ParseInfo info = parseConfig(locale, writeMap, opts, true); if (info != ParseOk) { // either there was an error or the file became immutable return false; } } const KEntryMapIterator end = entryMap.end(); for (KEntryMapIterator it = entryMap.begin(); it != end; ++it) { if (!it.key().mKey.isEmpty() && !it->bDirty) { // not dirty, doesn't overwrite entry in writeMap. skips default entries, too. continue; } const KEntryKey &key = it.key(); // only write entries that have the same "globality" as the file if (it->bGlobal == bGlobal) { if (it->bReverted) { writeMap.remove(key); } else if (!it->bDeleted) { writeMap[key] = *it; } else { KEntryKey defaultKey = key; defaultKey.bDefault = true; if (!entryMap.contains(defaultKey)) { writeMap.remove(key); // remove the deleted entry if there is no default //qDebug() << "Detected as deleted=>removed:" << key.mGroup << key.mKey << "global=" << bGlobal; } else { writeMap[key] = *it; // otherwise write an explicitly deleted entry //qDebug() << "Detected as deleted=>[$d]:" << key.mGroup << key.mKey << "global=" << bGlobal; } } it->bDirty = false; } } // now writeMap should contain only entries to be written // so write it out to disk // check if file exists QFile::Permissions fileMode = QFile::ReadUser | QFile::WriteUser; bool createNew = true; QFileInfo fi(filePath()); if (fi.exists()) { #ifdef Q_OS_WIN //TODO: getuid does not exist on windows, use GetSecurityInfo and GetTokenInformation instead createNew = false; #else if (fi.ownerId() == ::getuid()) { // Preserve file mode if file exists and is owned by user. fileMode = fi.permissions(); } else { // File is not owned by user: // Don't create new file but write to existing file instead. createNew = false; } #endif } if (createNew) { QSaveFile file(filePath()); if (!file.open(QIODevice::WriteOnly)) { return false; } file.setTextModeEnabled(true); // to get eol translation writeEntries(locale, file, writeMap); if (!file.size() && (fileMode == (QFile::ReadUser | QFile::WriteUser))) { // File is empty and doesn't have special permissions: delete it. file.cancelWriting(); if (fi.exists()) { // also remove the old file in case it existed. this can happen // when we delete all the entries in an existing config file. // if we don't do this, then deletions and revertToDefault's // will mysteriously fail QFile::remove(filePath()); } } else { // Normal case: Close the file if (file.commit()) { QFile::setPermissions(filePath(), fileMode); return true; } // Couldn't write. Disk full? qCWarning(KCONFIG_CORE_LOG) << "Couldn't write" << filePath() << ". Disk full?"; return false; } } else { // Open existing file. *DON'T* create it if it suddenly does not exist! #ifdef Q_OS_UNIX int fd = QT_OPEN(QFile::encodeName(filePath()).constData(), O_WRONLY | O_TRUNC); if (fd < 0) { return false; } FILE *fp = ::fdopen(fd, "w"); if (!fp) { QT_CLOSE(fd); return false; } QFile f; if (!f.open(fp, QIODevice::WriteOnly)) { fclose(fp); return false; } writeEntries(locale, f, writeMap); f.close(); fclose(fp); #else QFile f(filePath()); // XXX This is broken - it DOES create the file if it is suddenly gone. if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) { return false; } f.setTextModeEnabled(true); writeEntries(locale, f, writeMap); #endif } return true; } bool KConfigIniBackend::isWritable() const { const QString filePath = this->filePath(); if (!filePath.isEmpty()) { QFileInfo file(filePath); if (!file.exists()) { // If the file does not exist, check if the deepest // existing dir is writable. QFileInfo dir(file.absolutePath()); while (!dir.exists()) { QString parent = dir.absolutePath(); // Go up. Can't use cdUp() on non-existing dirs. if (parent == dir.filePath()) { // no parent return false; } dir.setFile(parent); } return dir.isDir() && dir.isWritable(); } else { return file.isWritable(); } } return false; } QString KConfigIniBackend::nonWritableErrorMessage() const { return tr("Configuration file \"%1\" not writable.\n").arg(filePath()); } void KConfigIniBackend::createEnclosing() { const QString file = filePath(); if (file.isEmpty()) { return; // nothing to do } // Create the containing dir, maybe it wasn't there QDir dir; dir.mkpath(QFileInfo(file).absolutePath()); } void KConfigIniBackend::setFilePath(const QString &file) { if (file.isEmpty()) { return; } Q_ASSERT(QDir::isAbsolutePath(file)); const QFileInfo info(file); if (info.exists()) { setLocalFilePath(info.canonicalFilePath()); } else { const QString dir = info.dir().canonicalPath(); if (!dir.isEmpty()) setLocalFilePath(dir + QLatin1Char('/') + info.fileName()); else setLocalFilePath(file); } } KConfigBase::AccessMode KConfigIniBackend::accessMode() const { if (filePath().isEmpty()) { return KConfigBase::NoAccess; } if (isWritable()) { return KConfigBase::ReadWrite; } return KConfigBase::ReadOnly; } bool KConfigIniBackend::lock() { Q_ASSERT(!filePath().isEmpty()); if (!lockFile) { lockFile = new QLockFile(filePath() + QLatin1String(".lock")); } lockFile->lock(); return lockFile->isLocked(); } void KConfigIniBackend::unlock() { lockFile->unlock(); delete lockFile; lockFile = nullptr; } bool KConfigIniBackend::isLocked() const { return lockFile && lockFile->isLocked(); } namespace { // serialize an escaped byte at the end of @param data // @param data should have room for 4 bytes char* escapeByte(char* data, unsigned char s) { static const char nibbleLookup[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; *data++ = '\\'; *data++ = 'x'; *data++ = nibbleLookup[s >> 4]; *data++ = nibbleLookup[s & 0x0f]; return data; } // Struct that represents a multi-byte UTF-8 character. // This struct is used to keep track of bytes that seem to be valid // UTF-8. struct Utf8Char { public: unsigned char bytes[4]; unsigned char count; unsigned char charLength; Utf8Char() { clear(); charLength = 0; } void clear() { count = 0; } // Add a byte to the UTF8 character. // When an additional byte leads to an invalid character, return false. bool addByte(unsigned char b) { if (count == 0) { if (b > 0xc1 && (b & 0xe0) == 0xc0) { charLength = 2; } else if ((b & 0xf0) == 0xe0) { charLength = 3; } else if (b < 0xf5 && (b & 0xf8) == 0xf0) { charLength = 4; } else { return false; } bytes[0] = b; count = 1; } else if (count < 4 && (b & 0xc0) == 0x80) { if (count == 1) { if (charLength == 3 && bytes[0] == 0xe0 && b < 0xa0) { return false; // overlong 3 byte sequence } if (charLength == 4) { if (bytes[0] == 0xf0 && b < 0x90) { return false; // overlong 4 byte sequence } if (bytes[0] == 0xf4 && b > 0x8f) { return false; // Unicode value larger than U+10FFFF } } } bytes[count++] = b; } else { return false; } return true; } // Return true if Utf8Char contains one valid character. bool isComplete() { return count > 0 && count == charLength; } // Add the bytes in this UTF8 character in escaped form to data. char* escapeBytes(char* data) { for (unsigned char i = 0; i < count; ++i) { data = escapeByte(data, bytes[i]); } clear(); return data; } // Add the bytes of the UTF8 character to a buffer. // Only call this if isComplete() returns true. char* writeUtf8(char* data) { for (unsigned char i = 0; i < count; ++i) { *data++ = bytes[i]; } clear(); return data; } // Write the bytes in the UTF8 character literally, or, if the // character is not complete, write the escaped bytes. // This is useful to handle the state that remains after handling // all bytes in a buffer. char* write(char* data) { if (isComplete()) { data = writeUtf8(data); } else { data = escapeBytes(data); } return data; } }; } QByteArray KConfigIniBackend::stringToPrintable(const QByteArray &aString, StringType type) { if (aString.isEmpty()) { return aString; } const int l = aString.length(); QByteArray result; // Guesstimated that it's good to avoid data() initialization for a length of l*4 result.resize(l * 4); // Maximum 4x as long as source string due to \x escape sequences const char *s = aString.constData(); int i = 0; char *data = result.data(); char *start = data; // Protect leading space if (s[0] == ' ' && type != GroupString) { *data++ = '\\'; *data++ = 's'; i++; } Utf8Char utf8; for (; i < l; ++i/*, r++*/) { switch (s[i]) { default: if (utf8.addByte(s[i])) { break; } else { data = utf8.escapeBytes(data); } // The \n, \t, \r cases (all < 32) are handled below; we can ignore them here if (((unsigned char)s[i]) < 32) { goto doEscape; } // GroupString and KeyString should be valid UTF-8, but ValueString // can be a bytearray with non-UTF-8 bytes that should be escaped. if (type == ValueString && ((unsigned char)s[i]) >= 127) { goto doEscape; } *data++ = s[i]; break; case '\n': *data++ = '\\'; *data++ = 'n'; break; case '\t': *data++ = '\\'; *data++ = 't'; break; case '\r': *data++ = '\\'; *data++ = 'r'; break; case '\\': *data++ = '\\'; *data++ = '\\'; break; case '=': if (type != KeyString) { *data++ = s[i]; break; } goto doEscape; case '[': case ']': // Above chars are OK to put in *value* strings as plaintext if (type == ValueString) { *data++ = s[i]; break; } doEscape: data = escapeByte(data, s[i]); break; } if (utf8.isComplete()) { data = utf8.writeUtf8(data); } } data = utf8.write(data); *data = 0; result.resize(data - start); // Protect trailing space if (result.endsWith(' ') && type != GroupString) { result.replace(result.length() - 1, 1, "\\s"); } return result; } char KConfigIniBackend::charFromHex(const char *str, const QFile &file, int line) { unsigned char ret = 0; for (int i = 0; i < 2; i++) { ret <<= 4; quint8 c = quint8(str[i]); if (c >= '0' && c <= '9') { ret |= c - '0'; } else if (c >= 'a' && c <= 'f') { ret |= c - 'a' + 0x0a; } else if (c >= 'A' && c <= 'F') { ret |= c - 'A' + 0x0a; } else { QByteArray e(str, 2); e.prepend("\\x"); qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, line) << "Invalid hex character " << c << " in \\x-type escape sequence \"" << e.constData() << "\"."; return 'x'; } } return char(ret); } void KConfigIniBackend::printableToString(BufferFragment *aString, const QFile &file, int line) { if (aString->isEmpty() || aString->indexOf('\\') == -1) { return; } aString->trim(); int l = aString->length(); char *r = aString->data(); char *str = r; for (int i = 0; i < l; i++, r++) { if (str[i] != '\\') { *r = str[i]; } else { // Probable escape sequence i++; if (i >= l) { // Line ends after backslash - stop. *r = '\\'; break; } switch (str[i]) { case 's': *r = ' '; break; case 't': *r = '\t'; break; case 'n': *r = '\n'; break; case 'r': *r = '\r'; break; case '\\': *r = '\\'; break; case ';': // not really an escape sequence, but allowed in .desktop files, don't strip '\;' from the string *r = '\\'; r++; *r = ';'; break; case ',': // not really an escape sequence, but allowed in .desktop files, don't strip '\,' from the string *r = '\\'; r++; *r = ','; break; case 'x': if (i + 2 < l) { *r = charFromHex(str + i + 1, file, line); i += 2; } else { *r = 'x'; i = l - 1; } break; default: *r = '\\'; qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, line) << QStringLiteral("Invalid escape sequence \"\\%1\".").arg(str[i]); } } } aString->truncate(r - aString->constData()); } diff --git a/src/core/kdesktopfile.cpp b/src/core/kdesktopfile.cpp index 367a7d8..d34400e 100644 --- a/src/core/kdesktopfile.cpp +++ b/src/core/kdesktopfile.cpp @@ -1,388 +1,388 @@ /* This file is part of the KDE libraries Copyright (c) 1999 Pietro Iglio Copyright (c) 1999 Preston Brown This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kdesktopfile.h" #ifndef Q_OS_WIN #include #endif #include #include #include #include #include "kauthorized.h" #include "kconfig_p.h" #include "kconfiggroup.h" #include "kconfigini_p.h" #include "kconfig_core_log_settings.h" class KDesktopFilePrivate : public KConfigPrivate { public: KDesktopFilePrivate(QStandardPaths::StandardLocation resourceType, const QString &fileName); KConfigGroup desktopGroup; }; KDesktopFilePrivate::KDesktopFilePrivate(QStandardPaths::StandardLocation resourceType, const QString &fileName) : KConfigPrivate(KConfig::NoGlobals, resourceType) { mBackend = new KConfigIniBackend(); bDynamicBackend = false; changeFileName(fileName); } KDesktopFile::KDesktopFile(QStandardPaths::StandardLocation resourceType, const QString &fileName) : KConfig(*new KDesktopFilePrivate(resourceType, fileName)) { Q_D(KDesktopFile); reparseConfiguration(); d->desktopGroup = KConfigGroup(this, "Desktop Entry"); } KDesktopFile::KDesktopFile(const QString &fileName) : KConfig(*new KDesktopFilePrivate(QStandardPaths::ApplicationsLocation, fileName)) { Q_D(KDesktopFile); reparseConfiguration(); d->desktopGroup = KConfigGroup(this, "Desktop Entry"); } KDesktopFile::~KDesktopFile() { } KConfigGroup KDesktopFile::desktopGroup() const { Q_D(const KDesktopFile); return d->desktopGroup; } QString KDesktopFile::locateLocal(const QString &path) { QString relativePath; QChar plus(QLatin1Char('/')); // Relative to config? (e.g. for autostart) const QStringList lstGenericConfigLocation = QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation); for (const QString &dir : lstGenericConfigLocation) { if (path.startsWith(dir + plus)) { relativePath = path.mid(dir.length() + 1); return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + relativePath; } } // Relative to xdg data dir? (much more common) const QStringList lstGenericDataLocation = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); for (const QString &dir : lstGenericDataLocation) { if (path.startsWith(dir + plus)) { relativePath = path.mid(dir.length() + 1); } } if (relativePath.isEmpty()) { // What now? The desktop file doesn't come from XDG_DATA_DIRS. Use filename only and hope for the best. relativePath = path.mid(path.lastIndexOf(QLatin1Char('/')) + 1); } return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + relativePath; } bool KDesktopFile::isDesktopFile(const QString &path) { return path.endsWith(QLatin1String(".desktop")); } bool KDesktopFile::isAuthorizedDesktopFile(const QString &path) { if (path.isEmpty()) { return false; // Empty paths are not ok. } if (QDir::isRelativePath(path)) { return true; // Relative paths are ok. } const QString realPath = QFileInfo(path).canonicalFilePath(); if (realPath.isEmpty()) { return false; // File doesn't exist. } #ifndef Q_OS_WIN const Qt::CaseSensitivity sensitivity = Qt::CaseSensitive; #else const Qt::CaseSensitivity sensitivity = Qt::CaseInsensitive; #endif // Check if the .desktop file is installed as part of KDE or XDG. const QStringList appsDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation); for (const QString &prefix : appsDirs) { if (QDir(prefix).exists() && realPath.startsWith(QFileInfo(prefix).canonicalFilePath(), sensitivity)) { return true; } } const QString servicesDir = QStringLiteral("kservices5/"); // KGlobal::dirs()->xdgDataRelativePath("services") const QStringList lstGenericDataLocation = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); for (const QString &xdgDataPrefix : lstGenericDataLocation) { if (QDir(xdgDataPrefix).exists()) { const QString prefix = QFileInfo(xdgDataPrefix).canonicalFilePath(); if (realPath.startsWith(prefix + QLatin1Char('/') + servicesDir, sensitivity)) { return true; } } } const QString autostartDir = QStringLiteral("autostart/"); const QStringList lstConfigPath = QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation); for (const QString &xdgDataPrefix : lstConfigPath) { if (QDir(xdgDataPrefix).exists()) { const QString prefix = QFileInfo(xdgDataPrefix).canonicalFilePath(); if (realPath.startsWith(prefix + QLatin1Char('/') + autostartDir, sensitivity)) { return true; } } } // Forbid desktop files outside of standard locations if kiosk is set so if (!KAuthorized::authorize(QStringLiteral("run_desktop_files"))) { qCWarning(KCONFIG_CORE_LOG) << "Access to '" << path << "' denied because of 'run_desktop_files' restriction." << endl; return false; } // Not otherwise permitted, so only allow if the file is executable, or if // owned by root (uid == 0) QFileInfo entryInfo(path); if (entryInfo.isExecutable() || entryInfo.ownerId() == 0) { return true; } qCWarning(KCONFIG_CORE_LOG) << "Access to '" << path << "' denied, not owned by root, executable flag not set." << endl; return false; } QString KDesktopFile::readType() const { Q_D(const KDesktopFile); return d->desktopGroup.readEntry("Type", QString()); } QString KDesktopFile::readIcon() const { Q_D(const KDesktopFile); return d->desktopGroup.readEntry("Icon", QString()); } QString KDesktopFile::readName() const { Q_D(const KDesktopFile); return d->desktopGroup.readEntry("Name", QString()); } QString KDesktopFile::readComment() const { Q_D(const KDesktopFile); return d->desktopGroup.readEntry("Comment", QString()); } QString KDesktopFile::readGenericName() const { Q_D(const KDesktopFile); return d->desktopGroup.readEntry("GenericName", QString()); } QString KDesktopFile::readPath() const { Q_D(const KDesktopFile); // NOT readPathEntry, it is not XDG-compliant: it performs // various expansions, like $HOME. Note that the expansion // behaviour still happens if the "e" flag is set, maintaining // backwards compatibility. return d->desktopGroup.readEntry("Path", QString()); } QString KDesktopFile::readDevice() const { Q_D(const KDesktopFile); return d->desktopGroup.readEntry("Dev", QString()); } QString KDesktopFile::readUrl() const { Q_D(const KDesktopFile); if (hasDeviceType()) { return d->desktopGroup.readEntry("MountPoint", QString()); } else { // NOT readPathEntry (see readPath()) QString url = d->desktopGroup.readEntry("URL", QString()); if (!url.isEmpty() && !QDir::isRelativePath(url)) { // Handle absolute paths as such (i.e. we need to escape them) return QUrl::fromLocalFile(url).toString(); } return url; } } QStringList KDesktopFile::readActions() const { Q_D(const KDesktopFile); return d->desktopGroup.readXdgListEntry("Actions"); } QStringList KDesktopFile::readMimeTypes() const { Q_D(const KDesktopFile); return d->desktopGroup.readXdgListEntry("MimeType"); } KConfigGroup KDesktopFile::actionGroup(const QString &group) { return KConfigGroup(this, QLatin1String("Desktop Action ") + group); } const KConfigGroup KDesktopFile::actionGroup(const QString &group) const { return const_cast(this)->actionGroup(group); } bool KDesktopFile::hasActionGroup(const QString &group) const { return hasGroup(QString(QLatin1String("Desktop Action ") + group).toUtf8().constData()); } bool KDesktopFile::hasLinkType() const { return readType() == QLatin1String("Link"); } bool KDesktopFile::hasApplicationType() const { return readType() == QLatin1String("Application"); } bool KDesktopFile::hasDeviceType() const { return readType() == QLatin1String("FSDevice"); } bool KDesktopFile::tryExec() const { Q_D(const KDesktopFile); // Test for TryExec and "X-KDE-AuthorizeAction" // NOT readPathEntry (see readPath()) QString te = d->desktopGroup.readEntry("TryExec", QString()); if (!te.isEmpty()) { if (QStandardPaths::findExecutable(te).isEmpty()) { return false; } } const QStringList list = d->desktopGroup.readEntry("X-KDE-AuthorizeAction", QStringList()); if (!list.isEmpty()) { for (QStringList::ConstIterator it = list.begin(); it != list.end(); ++it) { if (!KAuthorized::authorize((*it).trimmed())) { return false; } } } // See also KService::username() bool su = d->desktopGroup.readEntry("X-KDE-SubstituteUID", false); if (su) { QString user = d->desktopGroup.readEntry("X-KDE-Username", QString()); if (user.isEmpty()) { user = QString::fromLocal8Bit(qgetenv("ADMIN_ACCOUNT")); } if (user.isEmpty()) { user = QStringLiteral("root"); } if (!KAuthorized::authorize(QLatin1String("user/") + user)) { return false; } } return true; } /** * @return the filename as passed to the constructor. */ //QString KDesktopFile::fileName() const { return backEnd->fileName(); } /** * @return the resource type as passed to the constructor. */ //QString //KDesktopFile::resource() const { return backEnd->resource(); } #ifndef KDE_NO_DEPRECATED QStringList KDesktopFile::sortOrder() const { Q_D(const KDesktopFile); return d->desktopGroup.readXdgListEntry("SortOrder"); } #endif //void KDesktopFile::virtual_hook( int id, void* data ) //{ KConfig::virtual_hook( id, data ); } QString KDesktopFile::readDocPath() const { Q_D(const KDesktopFile); return d->desktopGroup.readPathEntry("X-DocPath", QString()); } KDesktopFile *KDesktopFile::copyTo(const QString &file) const { KDesktopFile *config = new KDesktopFile(QString()); this->KConfig::copyTo(file, config); // config->setDesktopGroup(); return config; } QStandardPaths::StandardLocation KDesktopFile::resource() const { Q_D(const KDesktopFile); return d->resourceType; } QString KDesktopFile::fileName() const { return name(); } bool KDesktopFile::noDisplay() const { Q_D(const KDesktopFile); if (d->desktopGroup.readEntry("NoDisplay", false)) { return true; } if (d->desktopGroup.hasKey("OnlyShowIn")) { - if (!d->desktopGroup.readXdgListEntry("OnlyShowIn").contains(QStringLiteral("KDE"))) { + if (!d->desktopGroup.readXdgListEntry("OnlyShowIn").contains(QLatin1String("KDE"))) { return true; } } if (d->desktopGroup.hasKey("NotShowIn")) { - if (d->desktopGroup.readXdgListEntry("NotShowIn").contains(QStringLiteral("KDE"))) { + if (d->desktopGroup.readXdgListEntry("NotShowIn").contains(QLatin1String("KDE"))) { return true; } } return false; }