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;
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 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)