10#include "kconfigini_p.h"
12#include "bufferfragment_p.h"
14#include "kconfig_core_log_settings.h"
15#include "kconfigbackend_p.h"
16#include "kconfigdata_p.h"
25#include <qplatformdefs.h>
33KCONFIGCORE_EXPORT
bool kde_kiosk_exception =
false;
42 return cache->
insert(fragment, fragment.toByteArray()).value();
45QString KConfigIniBackend::warningProlog(
const QFile &file,
int line)
49 return QStringLiteral(
"KConfigIni: In file %2, line %1:").
arg(line).
arg(file.
fileName());
52KConfigIniBackend::KConfigIniBackend()
58KConfigIniBackend::~KConfigIniBackend()
62KConfigBackend::ParseInfo KConfigIniBackend::parseConfig(
const QByteArray ¤tLocale, KEntryMap &entryMap, ParseOptions options)
64 return parseConfig(currentLocale, entryMap, options,
false);
69KConfigBackend::ParseInfo KConfigIniBackend::parseConfig(
const QByteArray ¤tLocale, KEntryMap &entryMap, ParseOptions options,
bool merging)
71 if (filePath().isEmpty()) {
75 QFile file(filePath());
77 return file.
exists() ? ParseOpenError : ParseOk;
82 bool fileOptionImmutable =
false;
83 bool groupOptionImmutable =
false;
84 bool groupSkip =
false;
90 BufferFragment contents(buffer.
data(), buffer.
size());
91 unsigned int len = contents.length();
92 unsigned int startOfLine = 0;
94 const int langIdx = currentLocale.
indexOf(
'_');
95 const QByteArray currentLanguage = langIdx >= 0 ? currentLocale.
left(langIdx) : currentLocale;
97 QString currentGroup = QStringLiteral(
"<default>");
98 bool bDefault = options & ParseDefaults;
99 bool allowExecutableValues = options & ParseExpansions;
109 while (startOfLine < len) {
110 BufferFragment line = contents.split(
'\n', &startOfLine);
115 if (line.isEmpty() || line.at(0) ==
'#') {
119 if (line.at(0) ==
'[') {
120 groupOptionImmutable = fileOptionImmutable;
128 if (end == line.length()) {
129 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid group header.";
133 if (line.at(end) ==
']') {
139 if (end + 1 == line.length()
141 && line.at(
start) ==
'$'
142 && line.at(
start + 1) ==
'i') {
144 fileOptionImmutable = !kde_kiosk_exception;
146 groupOptionImmutable = !kde_kiosk_exception;
153 printableToString(&namePart, file, lineNo);
154 newGroup += namePart.toByteArray();
156 }
while ((
start = end + 2) <= line.length() && line.at(end + 1) ==
'[');
159 groupSkip = entryMap.getEntryOption(currentGroup, {}, {}, KEntryMap::EntryImmutable);
161 if (groupSkip && !bDefault) {
165 if (groupOptionImmutable)
169 immutableGroups.
append(currentGroup);
172 if (groupSkip && !bDefault) {
177 int eqpos = line.indexOf(
'=');
182 BufferFragment temp = line.left(eqpos);
185 line.truncateLeft(eqpos + 1);
188 if (aKey.isEmpty()) {
189 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (empty key)";
193 KEntryMap::EntryOptions entryOptions = {};
194 if (groupOptionImmutable) {
195 entryOptions |= KEntryMap::EntryImmutable;
198 BufferFragment locale;
200 while ((
start = aKey.lastIndexOf(
'[')) >= 0) {
203 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (missing ']')";
205 }
else if (end >
start + 1 && aKey.at(
start + 1) ==
'$') {
208 switch (aKey.at(i)) {
210 if (!kde_kiosk_exception) {
211 entryOptions |= KEntryMap::EntryImmutable;
215 if (allowExecutableValues) {
216 entryOptions |= KEntryMap::EntryExpansion;
220 entryOptions |= KEntryMap::EntryDeleted;
221 aKey.truncate(
start);
222 printableToString(&aKey, file, lineNo);
223 entryMap.setEntry(currentGroup, aKey.toByteArray(),
QByteArray(), entryOptions);
231 if (!locale.isNull()) {
232 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (second locale!?)";
236 locale = aKey.mid(
start + 1, end -
start - 1);
238 aKey.truncate(
start);
241 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (missing '=')";
244 printableToString(&aKey, file, lineNo);
245 if (!locale.isEmpty()) {
246 if (locale != currentLocale && locale != currentLanguage) {
248 if (locale.at(0) !=
'C' || currentLocale !=
"en_US") {
250 entryOptions |= KEntryMap::EntryRawKey;
258 if (!(entryOptions & KEntryMap::EntryRawKey)) {
259 printableToString(&aKey, file, lineNo);
262 if (options & ParseGlobal) {
263 entryOptions |= KEntryMap::EntryGlobal;
266 entryOptions |= KEntryMap::EntryDefault;
268 if (!locale.isNull()) {
269 entryOptions |= KEntryMap::EntryLocalized;
270 if (locale.indexOf(
'_') != -1) {
271 entryOptions |= KEntryMap::EntryLocalizedCountry;
274 printableToString(&line, file, lineNo);
275 if (entryOptions & KEntryMap::EntryRawKey) {
277 rawKey.
reserve(aKey.length() + locale.length() + 2);
278 rawKey.
append(aKey.toVolatileByteArray());
280 entryMap.setEntry(currentGroup, rawKey, lookup(line, &cache), entryOptions);
282 entryMap.setEntry(currentGroup, lookup(aKey, &cache), lookup(line, &cache), entryOptions);
290 for (
const QString &group :
std::as_const(immutableGroups)) {
294 return fileOptionImmutable ? ParseImmutable : ParseOk;
297void KConfigIniBackend::writeEntries(
const QByteArray &locale,
QIODevice &file,
const KEntryMap &map,
bool defaultGroup,
bool &firstEntry)
300 bool groupIsImmutable =
false;
301 for (
const auto &[key, entry] :
map) {
303 if ((key.mGroup != QStringLiteral(
"<default>")) == defaultGroup) {
308 if (key.mKey.isNull()) {
309 groupIsImmutable = entry.bImmutable;
313 const KEntry ¤tEntry = entry;
314 if (!defaultGroup && currentGroup != key.mGroup) {
318 currentGroup = key.mGroup;
323 int cgl = currentGroup.
length();
325 for (
int i =
start + 1; i < cgl; i++) {
326 const QChar c = currentGroup.
at(i);
338 if (groupIsImmutable) {
339 file.
write(
"[$i]", 4);
354 file.
write(key.mKey);
356 file.
write(stringToPrintable(key.mKey, KeyString));
357 if (key.bLocal && locale !=
"C") {
363 if (currentEntry.bDeleted) {
364 if (currentEntry.bImmutable) {
365 file.
write(
"[$di]", 5);
367 file.
write(
"[$d]", 4);
370 if (currentEntry.bImmutable || currentEntry.bExpand) {
372 if (currentEntry.bImmutable) {
375 if (currentEntry.bExpand) {
381 file.
write(stringToPrintable(currentEntry.mValue, ValueString));
387void KConfigIniBackend::writeEntries(
const QByteArray &locale,
QIODevice &file,
const KEntryMap &map)
389 bool firstEntry =
true;
392 writeEntries(locale, file, map,
true, firstEntry);
395 writeEntries(locale, file, map,
false, firstEntry);
398bool KConfigIniBackend::writeConfig(
const QByteArray &locale, KEntryMap &entryMap, WriteOptions options)
400 Q_ASSERT(!filePath().isEmpty());
403 const bool bGlobal = options & WriteGlobal;
408 ParseOptions opts = ParseExpansions;
412 ParseInfo info = parseConfig(locale, writeMap, opts,
true);
413 if (info != ParseOk) {
418 for (
auto &[key, entry] : entryMap) {
419 if (!key.mKey.isEmpty() && !entry.bDirty) {
424 if (entry.bGlobal == bGlobal) {
425 if (entry.bReverted && entry.bOverridesGlobal) {
426 entry.bDeleted =
true;
427 writeMap[key] = entry;
428 }
else if (entry.bReverted) {
430 }
else if (!entry.bDeleted) {
431 writeMap[key] = entry;
433 KEntryKey defaultKey = key;
434 defaultKey.bDefault =
true;
435 if (entryMap.find(defaultKey) == entryMap.end() && !entry.bOverridesGlobal) {
439 writeMap[key] = entry;
443 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());
640 if (!lockFile->lock()) {
644 return lockFile->isLocked();
647void KConfigIniBackend::unlock()
655bool KConfigIniBackend::isLocked()
const
657 return lockFile && lockFile->isLocked();
664char *escapeByte(
char *data,
unsigned char s)
666 static const char nibbleLookup[] = {
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'a',
'b',
'c',
'd',
'e',
'f'};
669 *data++ = nibbleLookup[s >> 4];
670 *data++ = nibbleLookup[s & 0x0f];
679 unsigned char bytes[4];
681 unsigned char charLength;
694 bool addByte(
unsigned char b)
697 if (b > 0xc1 && (b & 0xe0) == 0xc0) {
699 }
else if ((b & 0xf0) == 0xe0) {
701 }
else if (b < 0xf5 && (b & 0xf8) == 0xf0) {
708 }
else if (count < 4 && (b & 0xc0) == 0x80) {
710 if (charLength == 3 && bytes[0] == 0xe0 && b < 0xa0) {
713 if (charLength == 4) {
714 if (bytes[0] == 0xf0 && b < 0x90) {
717 if (bytes[0] == 0xf4 && b > 0x8f) {
729 bool isComplete()
const
731 return count > 0 && count == charLength;
734 char *escapeBytes(
char *data)
736 for (
unsigned char i = 0; i < count; ++i) {
737 data = escapeByte(data, bytes[i]);
744 char *writeUtf8(
char *data)
746 for (
unsigned char i = 0; i < count; ++i) {
756 char *write(
char *data)
759 data = writeUtf8(data);
761 data = escapeBytes(data);
770 const int len = aString.
size();
779 char *data = result.
data();
783 if (s[0] ==
' ' && type != GroupString) {
790 for (; i < len; ++i) {
793 if (utf8.addByte(s[i])) {
796 data = utf8.escapeBytes(data);
799 if (((
unsigned char)s[i]) < 32) {
804 if (type == ValueString && ((
unsigned char)s[i]) >= 127) {
826 if (type != KeyString) {
834 if (type == ValueString) {
839 data = escapeByte(data, s[i]);
842 if (utf8.isComplete()) {
843 data = utf8.writeUtf8(data);
846 data = utf8.write(data);
851 if (result.
endsWith(
' ') && type != GroupString) {
858char KConfigIniBackend::charFromHex(
const char *str,
const QFile &file,
int line)
860 unsigned char ret = 0;
861 for (
int i = 0; i < 2; i++) {
863 quint8 c = quint8(str[i]);
865 if (c >=
'0' && c <=
'9') {
867 }
else if (c >=
'a' && c <=
'f') {
868 ret |= c -
'a' + 0x0a;
869 }
else if (c >=
'A' && c <=
'F') {
870 ret |= c -
'A' + 0x0a;
874 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, line) <<
"Invalid hex character " << c <<
" in \\x<nn>-type escape sequence \"" << e.constData()
882void KConfigIniBackend::printableToString(BufferFragment *aString,
const QFile &file,
int line)
884 if (aString->isEmpty() || aString->indexOf(
'\\') == -1) {
888 int l = aString->length();
889 char *r = aString->data();
892 for (
int i = 0; i < l; i++, r++) {
893 if (str[i] !=
'\\') {
933 *r = charFromHex(str + i + 1, file, line);
942 qCWarning(KCONFIG_CORE_LOG).noquote() << warningProlog(file, line) << QStringLiteral(
"Invalid escape sequence: «\\%1»").arg(str[i]);
946 aString->truncate(r - aString->constData());
949#include "moc_kconfigini_p.cpp"
AccessMode
Possible return values for accessMode().
Q_SCRIPTABLE Q_NOREPLY void start()
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 mid(qsizetype pos, qsizetype len) const const
QByteArray & replace(QByteArrayView before, QByteArrayView after)
void reserve(qsizetype size)
void resize(qsizetype newSize, char c)
qsizetype size() const const
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
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)