10#include "kconfigini_p.h"
12#include "kconfig_core_log_settings.h"
13#include "kconfigdata_p.h"
22#include <QStandardPaths>
23#include <qplatformdefs.h>
31using namespace Qt::StringLiterals;
33KCONFIGCORE_EXPORT
bool kde_kiosk_exception =
false;
45QString KConfigIniBackend::warningProlog(
const QFile &file,
int line)
49 return QStringLiteral(
"KConfigIni: In file %2, line %1:").
arg(line).
arg(file.
fileName());
52KConfigIniBackend::KConfigIniBackend()
56KConfigIniBackend::ParseInfo KConfigIniBackend::parseConfig(
const QByteArray ¤tLocale, KEntryMap &entryMap, ParseOptions options)
58 return parseConfig(currentLocale, entryMap, options,
false);
63KConfigIniBackend::ParseInfo KConfigIniBackend::parseConfig(
const QByteArray ¤tLocale, KEntryMap &entryMap, ParseOptions options,
bool merging)
65 if (filePath().isEmpty()) {
69 QFile file(filePath());
71 return file.
exists() ? ParseOpenError : ParseOk;
76 bool fileOptionImmutable =
false;
77 bool groupOptionImmutable =
false;
78 bool groupSkip =
false;
86 const int langIdx = currentLocale.
indexOf(
'_');
87 const QByteArray currentLanguage = langIdx >= 0 ? currentLocale.
left(langIdx) : currentLocale;
89 QString currentGroup = QStringLiteral(
"<default>");
90 bool bDefault = options & ParseDefaults;
91 bool allowExecutableValues = options & ParseExpansions;
101 while (!contents.isEmpty()) {
103 if (
const auto idx = contents.indexOf(
'\n'); idx < 0) {
107 line = contents.
left(idx);
108 contents = contents.
mid(idx + 1);
114 if (line.
isEmpty() || line.
at(0) ==
'#') {
118 if (line.
at(0) ==
'[') {
119 groupOptionImmutable = fileOptionImmutable;
127 if (end == line.
length()) {
128 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid group header.";
132 if (line.
at(end) ==
']') {
138 if (end + 1 == line.
length()
141 && line.
at(
start + 1) ==
'i') {
143 fileOptionImmutable = !kde_kiosk_exception;
145 groupOptionImmutable = !kde_kiosk_exception;
152 printableToString(namePart, file, lineNo);
155 }
while ((
start = end + 2) <= line.
length() && line.
at(end + 1) ==
'[');
158 groupSkip = entryMap.getEntryOption(currentGroup, {}, {}, KEntryMap::EntryImmutable);
160 if (groupSkip && !bDefault) {
164 if (groupOptionImmutable)
168 immutableGroups.
append(currentGroup);
171 if (groupSkip && !bDefault) {
183 line = line.
mid(eqpos + 1);
187 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (empty key)";
191 KEntryMap::EntryOptions entryOptions = {};
192 if (groupOptionImmutable) {
193 entryOptions |= KEntryMap::EntryImmutable;
201 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (missing ']')";
203 }
else if (end >
start + 1 && aKey.
at(
start + 1) ==
'$') {
206 switch (aKey.
at(i)) {
208 if (!kde_kiosk_exception) {
209 entryOptions |= KEntryMap::EntryImmutable;
213 if (allowExecutableValues) {
214 entryOptions |= KEntryMap::EntryExpansion;
218 entryOptions |= KEntryMap::EntryDeleted;
220 printableToString(aKey, file, lineNo);
230 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (second locale!?)";
239 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (missing '=')";
242 printableToString(aKey, file, lineNo);
244 if (locale != currentLocale && locale != currentLanguage) {
246 if (locale.
at(0) !=
'C' || currentLocale !=
"en_US") {
248 entryOptions |= KEntryMap::EntryRawKey;
256 if (!(entryOptions & KEntryMap::EntryRawKey)) {
257 printableToString(aKey, file, lineNo);
260 if (options & ParseGlobal) {
261 entryOptions |= KEntryMap::EntryGlobal;
264 entryOptions |= KEntryMap::EntryDefault;
267 entryOptions |= KEntryMap::EntryLocalized;
268 if (locale.
indexOf(
'_') != -1) {
269 entryOptions |= KEntryMap::EntryLocalizedCountry;
272 printableToString(line, file, lineNo);
273 if (entryOptions & KEntryMap::EntryRawKey) {
278 entryMap.setEntry(currentGroup, rawKey, lookup(line, &cache), entryOptions);
280 entryMap.setEntry(currentGroup, lookup(aKey, &cache), lookup(line, &cache), entryOptions);
288 for (
const QString &group : std::as_const(immutableGroups)) {
292 return fileOptionImmutable ? ParseImmutable : ParseOk;
295void KConfigIniBackend::writeEntries(
const QByteArray &locale,
QIODevice &file,
const KEntryMap &map,
bool defaultGroup,
bool &firstEntry)
298 bool groupIsImmutable =
false;
299 for (
const auto &[key, entry] : map) {
301 if ((key.mGroup != QStringLiteral(
"<default>")) == defaultGroup) {
306 if (key.mKey.isNull()) {
307 groupIsImmutable = entry.bImmutable;
311 const KEntry ¤tEntry = entry;
312 if (!defaultGroup && currentGroup != key.mGroup) {
316 currentGroup = key.mGroup;
321 int cgl = currentGroup.
length();
323 for (
int i =
start + 1; i < cgl; i++) {
324 const QChar c = currentGroup.
at(i);
336 if (groupIsImmutable) {
337 file.
write(
"[$i]", 4);
352 file.
write(key.mKey);
354 file.
write(stringToPrintable(key.mKey, KeyString));
355 if (key.bLocal && locale !=
"C") {
361 if (currentEntry.bDeleted) {
362 if (currentEntry.bImmutable) {
363 file.
write(
"[$di]", 5);
365 file.
write(
"[$d]", 4);
368 if (currentEntry.bImmutable || currentEntry.bExpand) {
370 if (currentEntry.bImmutable) {
373 if (currentEntry.bExpand) {
379 file.
write(stringToPrintable(currentEntry.mValue, ValueString));
385void KConfigIniBackend::writeEntries(
const QByteArray &locale,
QIODevice &file,
const KEntryMap &map)
387 bool firstEntry =
true;
390 writeEntries(locale, file, map,
true, firstEntry);
393 writeEntries(locale, file, map,
false, firstEntry);
396bool KConfigIniBackend::writeConfig(
const QByteArray &locale, KEntryMap &entryMap, WriteOptions options)
398 Q_ASSERT(!filePath().isEmpty());
401 const bool bGlobal = options & WriteGlobal;
406 ParseOptions opts = ParseExpansions;
410 ParseInfo info = parseConfig(locale, writeMap, opts,
true);
411 if (info != ParseOk) {
416 for (
auto &[key, entry] : entryMap) {
417 if (!key.mKey.isEmpty() && !entry.bDirty) {
422 if (entry.bGlobal == bGlobal) {
423 if (entry.bReverted && entry.bOverridesGlobal) {
424 entry.bDeleted =
true;
425 writeMap[key] = entry;
426 }
else if (entry.bReverted) {
428 }
else if (!entry.bDeleted) {
429 writeMap[key] = entry;
431 KEntryKey defaultKey = key;
432 defaultKey.bDefault =
true;
433 if (entryMap.find(defaultKey) == entryMap.end() && !entry.bOverridesGlobal) {
437 writeMap[key] = entry;
441 entry.bDirty =
false;
452 bool createNew =
true;
460 if (fi.ownerId() == ::getuid()) {
462 fileMode = fi.permissions();
477 file.setDirectWriteFallback(
true);
479 qWarning(KCONFIG_CORE_LOG) <<
"Couldn't create a new file:" << filePath() <<
". Error:" << file.
errorString();
483 qWarning(KCONFIG_CORE_LOG) <<
"Couldn't create a new file:" << filePath() <<
". Error:" << file.
errorString();
489 writeEntries(locale, file, writeMap);
493 file.cancelWriting();
509 qCWarning(KCONFIG_CORE_LOG) <<
"Couldn't write" << filePath() <<
". Disk full?";
514#if defined(Q_OS_UNIX) && !defined(Q_OS_ANDROID)
524 writeEntries(locale, f, writeMap);
534 writeEntries(locale, f, writeMap);
540bool KConfigIniBackend::isWritable()
const
542 const QString filePath = this->filePath();
554 while (!
dir.exists()) {
556 if (parent ==
dir.filePath()) {
562 return dir.isDir() &&
dir.isWritable();
565QString KConfigIniBackend::nonWritableErrorMessage()
const
567 return tr(
"Configuration file \"%1\" not writable.\n").
arg(filePath());
570void KConfigIniBackend::createEnclosing()
572 const QString file = filePath();
581void KConfigIniBackend::setFilePath(
const QString &path)
591 setLocalFilePath(info.canonicalFilePath());
595 if (
QString filePath = info.dir().canonicalPath(); !filePath.
isEmpty()) {
597 setLocalFilePath(filePath);
599 setLocalFilePath(path);
605 if (filePath().isEmpty()) {
606 return KConfigBase::NoAccess;
610 return KConfigBase::ReadWrite;
613 return KConfigBase::ReadOnly;
616bool KConfigIniBackend::lock()
618 Q_ASSERT(!filePath().isEmpty());
631 lockFile = std::make_unique<QLockFile>(filePath() +
QLatin1String(
".lock"));
636 lockFile = std::make_unique<QLockFile>(filePath() +
QLatin1String(
".lock"));
640 if (!lockFile->lock()) {
644 return lockFile->isLocked();
647void KConfigIniBackend::unlock()
654bool KConfigIniBackend::isLocked()
const
656 return lockFile && lockFile->isLocked();
663char *escapeByte(
char *data,
unsigned char s)
665 static const char nibbleLookup[] = {
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'a',
'b',
'c',
'd',
'e',
'f'};
668 *data++ = nibbleLookup[s >> 4];
669 *data++ = nibbleLookup[s & 0x0f];
678 unsigned char bytes[4];
680 unsigned char charLength;
693 bool addByte(
unsigned char b)
696 if (b > 0xc1 && (b & 0xe0) == 0xc0) {
698 }
else if ((b & 0xf0) == 0xe0) {
700 }
else if (b < 0xf5 && (b & 0xf8) == 0xf0) {
707 }
else if (count < 4 && (b & 0xc0) == 0x80) {
709 if (charLength == 3 && bytes[0] == 0xe0 && b < 0xa0) {
712 if (charLength == 4) {
713 if (bytes[0] == 0xf0 && b < 0x90) {
716 if (bytes[0] == 0xf4 && b > 0x8f) {
728 bool isComplete()
const
730 return count > 0 && count == charLength;
733 char *escapeBytes(
char *data)
735 for (
unsigned char i = 0; i < count; ++i) {
736 data = escapeByte(data, bytes[i]);
743 char *writeUtf8(
char *data)
745 for (
unsigned char i = 0; i < count; ++i) {
755 char *write(
char *data)
758 data = writeUtf8(data);
760 data = escapeBytes(data);
769 const int len = aString.
size();
778 char *data = result.
data();
782 if (s[0] ==
' ' && type != GroupString) {
789 for (; i < len; ++i) {
792 if (utf8.addByte(s[i])) {
795 data = utf8.escapeBytes(data);
798 if (((
unsigned char)s[i]) < 32) {
803 if (type == ValueString && ((
unsigned char)s[i]) >= 127) {
825 if (type != KeyString) {
833 if (type == ValueString) {
838 data = escapeByte(data, s[i]);
841 if (utf8.isComplete()) {
842 data = utf8.writeUtf8(data);
845 data = utf8.write(data);
850 if (result.
endsWith(
' ') && type != GroupString) {
857char KConfigIniBackend::charFromHex(
const char *str,
const QFile &file,
int line)
859 unsigned char ret = 0;
860 for (
int i = 0; i < 2; i++) {
862 quint8 c = quint8(str[i]);
864 if (c >=
'0' && c <=
'9') {
866 }
else if (c >=
'a' && c <=
'f') {
867 ret |= c -
'a' + 0x0a;
868 }
else if (c >=
'A' && c <=
'F') {
869 ret |= c -
'A' + 0x0a;
873 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, line) <<
"Invalid hex character " << c <<
" in \\x<nn>-type escape sequence \"" << e.constData()
881void KConfigIniBackend::printableToString(
QByteArrayView &aString,
const QFile &file,
int line)
887 int l = aString.
size();
888 char *r =
const_cast<char *
>(aString.
data());
891 for (
int i = 0; i < l; i++, r++) {
892 if (str[i] !=
'\\') {
932 *r = charFromHex(str + i + 1, file, line);
941 qCWarning(KCONFIG_CORE_LOG).noquote() << warningProlog(file, line) << QStringLiteral(
"Invalid escape sequence: «\\%1»").arg(str[i]);
948QString KConfigIniBackend::filePath()
const
950 return mLocalFilePath;
953void KConfigIniBackend::setLocalFilePath(
const QString &file)
955 mLocalFilePath = file;
958#include "moc_kconfigini_p.cpp"
AccessMode
Possible return values for accessMode().
Q_SCRIPTABLE QString start(QString train="")
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
QString path(const QString &relativePath)
KIOCORE_EXPORT QString dir(const QString &fileClass)
QByteArray & append(QByteArrayView data)
const char * constData() const const
bool endsWith(QByteArrayView bv) const const
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
bool isEmpty() const const
QByteArray left(qsizetype len) const const
qsizetype length() const const
QByteArray & replace(QByteArrayView before, QByteArrayView after)
void reserve(qsizetype size)
void resize(qsizetype newSize, char c)
qsizetype size() const const
QByteArrayView left(qsizetype length) const const
QByteArrayView mid(qsizetype start, qsizetype length) const const
char at(qsizetype n) const const
const_pointer constData() const const
const_pointer data() const const
qsizetype indexOf(QByteArrayView bv, qsizetype from) const const
bool isEmpty() const const
bool isNull() const const
qsizetype lastIndexOf(QByteArrayView bv) const const
qsizetype length() const const
qsizetype size() const const
QByteArray toByteArray() const const
QByteArrayView trimmed() const const
void truncate(qsizetype length)
bool isAbsolutePath(const QString &path)
bool mkpath(const QString &dirPath) const const
QByteArray encodeName(const QString &fileName)
bool exists(const QString &fileName)
virtual QString fileName() const const override
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
virtual bool setPermissions(Permissions permissions) override
virtual void close() override
const_iterator constEnd() const const
const_iterator constFind(const Key &key) const const
iterator insert(const Key &key, const T &value)
void reserve(qsizetype size)
QString errorString() const const
bool isWritable() const const
virtual bool open(QIODeviceBase::OpenMode mode)
void setTextModeEnabled(bool enabled)
virtual qint64 size() const const
qint64 write(const QByteArray &data)
void append(QList< T > &&value)
QString writableLocation(StandardLocation type)
QString arg(Args &&... args) const const
const QChar at(qsizetype position) const const
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const