10#include "kconfigini_p.h"
12#include "bufferfragment_p.h"
13#include "kconfig_core_log_settings.h"
14#include "kconfigdata_p.h"
23#include <QStandardPaths>
24#include <qplatformdefs.h>
32using namespace Qt::StringLiterals;
34KCONFIGCORE_EXPORT
bool kde_kiosk_exception =
false;
43 return cache->
insert(fragment, fragment.toByteArray()).value();
46QString KConfigIniBackend::warningProlog(
const QFile &file,
int line)
50 return QStringLiteral(
"KConfigIni: In file %2, line %1:").
arg(line).
arg(file.
fileName());
53KConfigIniBackend::KConfigIniBackend()
58KConfigIniBackend::ParseInfo KConfigIniBackend::parseConfig(
const QByteArray ¤tLocale, KEntryMap &entryMap, ParseOptions options)
60 return parseConfig(currentLocale, entryMap, options,
false);
65KConfigIniBackend::ParseInfo KConfigIniBackend::parseConfig(
const QByteArray ¤tLocale, KEntryMap &entryMap, ParseOptions options,
bool merging)
67 if (filePath().isEmpty()) {
71 QFile file(filePath());
73 return file.
exists() ? ParseOpenError : ParseOk;
78 bool fileOptionImmutable =
false;
79 bool groupOptionImmutable =
false;
80 bool groupSkip =
false;
86 BufferFragment contents(buffer.
data(), buffer.
size());
87 unsigned int len = contents.length();
88 unsigned int startOfLine = 0;
90 const int langIdx = currentLocale.
indexOf(
'_');
91 const QByteArray currentLanguage = langIdx >= 0 ? currentLocale.
left(langIdx) : currentLocale;
93 QString currentGroup = QStringLiteral(
"<default>");
94 bool bDefault = options & ParseDefaults;
95 bool allowExecutableValues = options & ParseExpansions;
105 while (startOfLine < len) {
106 BufferFragment line = contents.split(
'\n', &startOfLine);
111 if (line.isEmpty() || line.at(0) ==
'#') {
115 if (line.at(0) ==
'[') {
116 groupOptionImmutable = fileOptionImmutable;
124 if (end == line.length()) {
125 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid group header.";
129 if (line.at(end) ==
']') {
135 if (end + 1 == line.length()
137 && line.at(
start) ==
'$'
138 && line.at(
start + 1) ==
'i') {
140 fileOptionImmutable = !kde_kiosk_exception;
142 groupOptionImmutable = !kde_kiosk_exception;
148 BufferFragment namePart = line.mid(
start, end -
start);
149 printableToString(&namePart, file, lineNo);
150 newGroup += namePart.toByteArray();
152 }
while ((
start = end + 2) <= line.length() && line.at(end + 1) ==
'[');
155 groupSkip = entryMap.getEntryOption(currentGroup, {}, {}, KEntryMap::EntryImmutable);
157 if (groupSkip && !bDefault) {
161 if (groupOptionImmutable)
165 immutableGroups.
append(currentGroup);
168 if (groupSkip && !bDefault) {
173 int eqpos = line.indexOf(
'=');
178 BufferFragment temp = line.left(eqpos);
181 line.truncateLeft(eqpos + 1);
184 if (aKey.isEmpty()) {
185 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (empty key)";
189 KEntryMap::EntryOptions entryOptions = {};
190 if (groupOptionImmutable) {
191 entryOptions |= KEntryMap::EntryImmutable;
194 BufferFragment locale;
196 while ((
start = aKey.lastIndexOf(
'[')) >= 0) {
199 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (missing ']')";
201 }
else if (end >
start + 1 && aKey.at(
start + 1) ==
'$') {
204 switch (aKey.at(i)) {
206 if (!kde_kiosk_exception) {
207 entryOptions |= KEntryMap::EntryImmutable;
211 if (allowExecutableValues) {
212 entryOptions |= KEntryMap::EntryExpansion;
216 entryOptions |= KEntryMap::EntryDeleted;
217 aKey.truncate(
start);
218 printableToString(&aKey, file, lineNo);
219 entryMap.setEntry(currentGroup, aKey.toByteArray(),
QByteArray(), entryOptions);
227 if (!locale.isNull()) {
228 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (second locale!?)";
232 locale = aKey.mid(
start + 1, end -
start - 1);
234 aKey.truncate(
start);
237 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (missing '=')";
240 printableToString(&aKey, file, lineNo);
241 if (!locale.isEmpty()) {
242 if (locale != currentLocale && locale != currentLanguage) {
244 if (locale.at(0) !=
'C' || currentLocale !=
"en_US") {
246 entryOptions |= KEntryMap::EntryRawKey;
254 if (!(entryOptions & KEntryMap::EntryRawKey)) {
255 printableToString(&aKey, file, lineNo);
258 if (options & ParseGlobal) {
259 entryOptions |= KEntryMap::EntryGlobal;
262 entryOptions |= KEntryMap::EntryDefault;
264 if (!locale.isNull()) {
265 entryOptions |= KEntryMap::EntryLocalized;
266 if (locale.indexOf(
'_') != -1) {
267 entryOptions |= KEntryMap::EntryLocalizedCountry;
270 printableToString(&line, file, lineNo);
271 if (entryOptions & KEntryMap::EntryRawKey) {
273 rawKey.
reserve(aKey.length() + locale.length() + 2);
274 rawKey.
append(aKey.toVolatileByteArray());
276 entryMap.setEntry(currentGroup, rawKey, lookup(line, &cache), entryOptions);
278 entryMap.setEntry(currentGroup, lookup(aKey, &cache), lookup(line, &cache), entryOptions);
286 for (
const QString &group : std::as_const(immutableGroups)) {
290 return fileOptionImmutable ? ParseImmutable : ParseOk;
293void KConfigIniBackend::writeEntries(
const QByteArray &locale,
QIODevice &file,
const KEntryMap &map,
bool defaultGroup,
bool &firstEntry)
296 bool groupIsImmutable =
false;
297 for (
const auto &[key, entry] : map) {
299 if ((key.mGroup != QStringLiteral(
"<default>")) == defaultGroup) {
304 if (key.mKey.isNull()) {
305 groupIsImmutable = entry.bImmutable;
309 const KEntry ¤tEntry = entry;
310 if (!defaultGroup && currentGroup != key.mGroup) {
314 currentGroup = key.mGroup;
319 int cgl = currentGroup.
length();
321 for (
int i =
start + 1; i < cgl; i++) {
322 const QChar c = currentGroup.
at(i);
334 if (groupIsImmutable) {
335 file.
write(
"[$i]", 4);
350 file.
write(key.mKey);
352 file.
write(stringToPrintable(key.mKey, KeyString));
353 if (key.bLocal && locale !=
"C") {
359 if (currentEntry.bDeleted) {
360 if (currentEntry.bImmutable) {
361 file.
write(
"[$di]", 5);
363 file.
write(
"[$d]", 4);
366 if (currentEntry.bImmutable || currentEntry.bExpand) {
368 if (currentEntry.bImmutable) {
371 if (currentEntry.bExpand) {
377 file.
write(stringToPrintable(currentEntry.mValue, ValueString));
383void KConfigIniBackend::writeEntries(
const QByteArray &locale,
QIODevice &file,
const KEntryMap &map)
385 bool firstEntry =
true;
388 writeEntries(locale, file, map,
true, firstEntry);
391 writeEntries(locale, file, map,
false, firstEntry);
394bool KConfigIniBackend::writeConfig(
const QByteArray &locale, KEntryMap &entryMap, WriteOptions options)
396 Q_ASSERT(!filePath().isEmpty());
399 const bool bGlobal = options & WriteGlobal;
404 ParseOptions opts = ParseExpansions;
408 ParseInfo info = parseConfig(locale, writeMap, opts,
true);
409 if (info != ParseOk) {
414 for (
auto &[key, entry] : entryMap) {
415 if (!key.mKey.isEmpty() && !entry.bDirty) {
420 if (entry.bGlobal == bGlobal) {
421 if (entry.bReverted && entry.bOverridesGlobal) {
422 entry.bDeleted =
true;
423 writeMap[key] = entry;
424 }
else if (entry.bReverted) {
426 }
else if (!entry.bDeleted) {
427 writeMap[key] = entry;
429 KEntryKey defaultKey = key;
430 defaultKey.bDefault =
true;
431 if (entryMap.find(defaultKey) == entryMap.end() && !entry.bOverridesGlobal) {
435 writeMap[key] = entry;
439 entry.bDirty =
false;
450 bool createNew =
true;
458 if (fi.ownerId() == ::getuid()) {
460 fileMode = fi.permissions();
475 file.setDirectWriteFallback(
true);
477 qWarning(KCONFIG_CORE_LOG) <<
"Couldn't create a new file:" << filePath() <<
". Error:" << file.
errorString();
481 qWarning(KCONFIG_CORE_LOG) <<
"Couldn't create a new file:" << filePath() <<
". Error:" << file.
errorString();
487 writeEntries(locale, file, writeMap);
491 file.cancelWriting();
507 qCWarning(KCONFIG_CORE_LOG) <<
"Couldn't write" << filePath() <<
". Disk full?";
512#if defined(Q_OS_UNIX) && !defined(Q_OS_ANDROID)
522 writeEntries(locale, f, writeMap);
532 writeEntries(locale, f, writeMap);
538bool KConfigIniBackend::isWritable()
const
540 const QString filePath = this->filePath();
552 while (!
dir.exists()) {
554 if (parent ==
dir.filePath()) {
560 return dir.isDir() &&
dir.isWritable();
563QString KConfigIniBackend::nonWritableErrorMessage()
const
565 return tr(
"Configuration file \"%1\" not writable.\n").
arg(filePath());
568void KConfigIniBackend::createEnclosing()
570 const QString file = filePath();
579void KConfigIniBackend::setFilePath(
const QString &path)
589 setLocalFilePath(info.canonicalFilePath());
593 if (
QString filePath = info.dir().canonicalPath(); !filePath.
isEmpty()) {
595 setLocalFilePath(filePath);
597 setLocalFilePath(path);
603 if (filePath().isEmpty()) {
604 return KConfigBase::NoAccess;
608 return KConfigBase::ReadWrite;
611 return KConfigBase::ReadOnly;
614bool KConfigIniBackend::lock()
616 Q_ASSERT(!filePath().isEmpty());
638 if (!lockFile->lock()) {
642 return lockFile->isLocked();
645void KConfigIniBackend::unlock()
653bool KConfigIniBackend::isLocked()
const
655 return lockFile && lockFile->isLocked();
662char *escapeByte(
char *data,
unsigned char s)
664 static const char nibbleLookup[] = {
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'a',
'b',
'c',
'd',
'e',
'f'};
667 *data++ = nibbleLookup[s >> 4];
668 *data++ = nibbleLookup[s & 0x0f];
677 unsigned char bytes[4];
679 unsigned char charLength;
692 bool addByte(
unsigned char b)
695 if (b > 0xc1 && (b & 0xe0) == 0xc0) {
697 }
else if ((b & 0xf0) == 0xe0) {
699 }
else if (b < 0xf5 && (b & 0xf8) == 0xf0) {
706 }
else if (count < 4 && (b & 0xc0) == 0x80) {
708 if (charLength == 3 && bytes[0] == 0xe0 && b < 0xa0) {
711 if (charLength == 4) {
712 if (bytes[0] == 0xf0 && b < 0x90) {
715 if (bytes[0] == 0xf4 && b > 0x8f) {
727 bool isComplete()
const
729 return count > 0 && count == charLength;
732 char *escapeBytes(
char *data)
734 for (
unsigned char i = 0; i < count; ++i) {
735 data = escapeByte(data, bytes[i]);
742 char *writeUtf8(
char *data)
744 for (
unsigned char i = 0; i < count; ++i) {
754 char *write(
char *data)
757 data = writeUtf8(data);
759 data = escapeBytes(data);
768 const int len = aString.
size();
777 char *data = result.
data();
781 if (s[0] ==
' ' && type != GroupString) {
788 for (; i < len; ++i) {
791 if (utf8.addByte(s[i])) {
794 data = utf8.escapeBytes(data);
797 if (((
unsigned char)s[i]) < 32) {
802 if (type == ValueString && ((
unsigned char)s[i]) >= 127) {
824 if (type != KeyString) {
832 if (type == ValueString) {
837 data = escapeByte(data, s[i]);
840 if (utf8.isComplete()) {
841 data = utf8.writeUtf8(data);
844 data = utf8.write(data);
849 if (result.
endsWith(
' ') && type != GroupString) {
856char KConfigIniBackend::charFromHex(
const char *str,
const QFile &file,
int line)
858 unsigned char ret = 0;
859 for (
int i = 0; i < 2; i++) {
861 quint8 c = quint8(str[i]);
863 if (c >=
'0' && c <=
'9') {
865 }
else if (c >=
'a' && c <=
'f') {
866 ret |= c -
'a' + 0x0a;
867 }
else if (c >=
'A' && c <=
'F') {
868 ret |= c -
'A' + 0x0a;
872 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, line) <<
"Invalid hex character " << c <<
" in \\x<nn>-type escape sequence \"" << e.constData()
880void KConfigIniBackend::printableToString(BufferFragment *aString,
const QFile &file,
int line)
882 if (aString->isEmpty() || aString->indexOf(
'\\') == -1) {
886 int l = aString->length();
887 char *r = aString->data();
890 for (
int i = 0; i < l; i++, r++) {
891 if (str[i] !=
'\\') {
931 *r = charFromHex(str + i + 1, file, line);
940 qCWarning(KCONFIG_CORE_LOG).noquote() << warningProlog(file, line) << QStringLiteral(
"Invalid escape sequence: «\\%1»").arg(str[i]);
944 aString->truncate(r - aString->constData());
947QString KConfigIniBackend::filePath()
const
949 return mLocalFilePath;
952void KConfigIniBackend::setLocalFilePath(
const QString &file)
954 mLocalFilePath = file;
957#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 & 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