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()
57KConfigIniBackend::ParseInfo KConfigIniBackend::parseConfig(
const QByteArray ¤tLocale, KEntryMap &entryMap, ParseOptions options)
59 return parseConfig(currentLocale, entryMap, options,
false);
64KConfigIniBackend::ParseInfo KConfigIniBackend::parseConfig(
const QByteArray ¤tLocale, KEntryMap &entryMap, ParseOptions options,
bool merging)
66 if (filePath().isEmpty()) {
70 QFile file(filePath());
72 return file.
exists() ? ParseOpenError : ParseOk;
77 bool fileOptionImmutable =
false;
78 bool groupOptionImmutable =
false;
79 bool groupSkip =
false;
87 const int langIdx = currentLocale.
indexOf(
'_');
88 const QByteArray currentLanguage = langIdx >= 0 ? currentLocale.
left(langIdx) : currentLocale;
90 QString currentGroup = QStringLiteral(
"<default>");
91 bool bDefault = options & ParseDefaults;
92 bool allowExecutableValues = options & ParseExpansions;
102 while (!contents.isEmpty()) {
104 if (
const auto idx = contents.indexOf(
'\n'); idx < 0) {
108 line = contents.
left(idx);
109 contents = contents.
mid(idx + 1);
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()
142 && line.
at(
start + 1) ==
'i') {
144 fileOptionImmutable = !kde_kiosk_exception;
146 groupOptionImmutable = !kde_kiosk_exception;
153 printableToString(namePart, file, lineNo);
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) {
184 line = line.
mid(eqpos + 1);
188 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (empty key)";
192 KEntryMap::EntryOptions entryOptions = {};
193 if (groupOptionImmutable) {
194 entryOptions |= KEntryMap::EntryImmutable;
202 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (missing ']')";
204 }
else if (end >
start + 1 && aKey.
at(
start + 1) ==
'$') {
207 switch (aKey.
at(i)) {
209 if (!kde_kiosk_exception) {
210 entryOptions |= KEntryMap::EntryImmutable;
214 if (allowExecutableValues) {
215 entryOptions |= KEntryMap::EntryExpansion;
219 entryOptions |= KEntryMap::EntryDeleted;
221 printableToString(aKey, file, lineNo);
231 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (second locale!?)";
240 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) <<
"Invalid entry (missing '=')";
243 printableToString(aKey, file, lineNo);
245 if (locale != currentLocale && locale != currentLanguage) {
247 if (locale.
at(0) !=
'C' || currentLocale !=
"en_US") {
249 entryOptions |= KEntryMap::EntryRawKey;
257 if (!(entryOptions & KEntryMap::EntryRawKey)) {
258 printableToString(aKey, file, lineNo);
261 if (options & ParseGlobal) {
262 entryOptions |= KEntryMap::EntryGlobal;
265 entryOptions |= KEntryMap::EntryDefault;
268 entryOptions |= KEntryMap::EntryLocalized;
269 if (locale.
indexOf(
'_') != -1) {
270 entryOptions |= KEntryMap::EntryLocalizedCountry;
273 printableToString(line, file, lineNo);
274 if (entryOptions & KEntryMap::EntryRawKey) {
279 entryMap.setEntry(currentGroup, rawKey, lookup(line, &cache), entryOptions);
281 entryMap.setEntry(currentGroup, lookup(aKey, &cache), lookup(line, &cache), entryOptions);
289 for (
const QString &group : std::as_const(immutableGroups)) {
293 return fileOptionImmutable ? ParseImmutable : ParseOk;
296void KConfigIniBackend::writeEntries(
const QByteArray &locale,
QIODevice &file,
const KEntryMap &map,
bool defaultGroup,
bool &firstEntry)
299 bool groupIsImmutable =
false;
300 for (
const auto &[key, entry] : map) {
302 if ((key.mGroup != QStringLiteral(
"<default>")) == defaultGroup) {
307 if (key.mKey.isNull()) {
308 groupIsImmutable = entry.bImmutable;
312 const KEntry ¤tEntry = entry;
313 if (!defaultGroup && currentGroup != key.mGroup) {
317 currentGroup = key.mGroup;
322 int cgl = currentGroup.
length();
324 for (
int i =
start + 1; i < cgl; i++) {
325 const QChar c = currentGroup.
at(i);
337 if (groupIsImmutable) {
338 file.
write(
"[$i]", 4);
353 file.
write(key.mKey);
355 file.
write(stringToPrintable(key.mKey, KeyString));
356 if (key.bLocal && locale !=
"C") {
362 if (currentEntry.bDeleted) {
363 if (currentEntry.bImmutable) {
364 file.
write(
"[$di]", 5);
366 file.
write(
"[$d]", 4);
369 if (currentEntry.bImmutable || currentEntry.bExpand) {
371 if (currentEntry.bImmutable) {
374 if (currentEntry.bExpand) {
380 file.
write(stringToPrintable(currentEntry.mValue, ValueString));
386void KConfigIniBackend::writeEntries(
const QByteArray &locale,
QIODevice &file,
const KEntryMap &map)
388 bool firstEntry =
true;
391 writeEntries(locale, file, map,
true, firstEntry);
394 writeEntries(locale, file, map,
false, firstEntry);
397bool KConfigIniBackend::writeConfig(
const QByteArray &locale, KEntryMap &entryMap, WriteOptions options)
399 Q_ASSERT(!filePath().isEmpty());
402 const bool bGlobal = options & WriteGlobal;
407 ParseOptions opts = ParseExpansions;
411 ParseInfo info = parseConfig(locale, writeMap, opts,
true);
412 if (info != ParseOk) {
417 for (
auto &[key, entry] : entryMap) {
418 if (!key.mKey.isEmpty() && !entry.bDirty) {
423 if (entry.bGlobal == bGlobal) {
424 if (entry.bReverted && entry.bOverridesGlobal) {
425 entry.bDeleted =
true;
426 writeMap[key] = entry;
427 }
else if (entry.bReverted) {
429 }
else if (!entry.bDeleted) {
430 writeMap[key] = entry;
432 KEntryKey defaultKey = key;
433 defaultKey.bDefault =
true;
434 if (entryMap.find(defaultKey) == entryMap.end() && !entry.bOverridesGlobal) {
438 writeMap[key] = entry;
442 entry.bDirty =
false;
453 bool createNew =
true;
461 if (fi.ownerId() == ::getuid()) {
463 fileMode = fi.permissions();
478 file.setDirectWriteFallback(
true);
480 qWarning(KCONFIG_CORE_LOG) <<
"Couldn't create a new file:" << filePath() <<
". Error:" << file.
errorString();
484 qWarning(KCONFIG_CORE_LOG) <<
"Couldn't create a new file:" << filePath() <<
". Error:" << file.
errorString();
490 writeEntries(locale, file, writeMap);
494 file.cancelWriting();
510 qCWarning(KCONFIG_CORE_LOG) <<
"Couldn't write" << filePath() <<
". Disk full?";
515#if defined(Q_OS_UNIX) && !defined(Q_OS_ANDROID)
525 writeEntries(locale, f, writeMap);
535 writeEntries(locale, f, writeMap);
541bool KConfigIniBackend::isWritable()
const
543 const QString filePath = this->filePath();
555 while (!
dir.exists()) {
557 if (parent ==
dir.filePath()) {
563 return dir.isDir() &&
dir.isWritable();
566QString KConfigIniBackend::nonWritableErrorMessage()
const
568 return tr(
"Configuration file \"%1\" not writable.\n").
arg(filePath());
571void KConfigIniBackend::createEnclosing()
573 const QString file = filePath();
582void KConfigIniBackend::setFilePath(
const QString &path)
592 setLocalFilePath(info.canonicalFilePath());
596 if (
QString filePath = info.dir().canonicalPath(); !filePath.
isEmpty()) {
598 setLocalFilePath(filePath);
600 setLocalFilePath(path);
606 if (filePath().isEmpty()) {
607 return KConfigBase::NoAccess;
611 return KConfigBase::ReadWrite;
614 return KConfigBase::ReadOnly;
617bool KConfigIniBackend::lock()
619 Q_ASSERT(!filePath().isEmpty());
641 if (!lockFile->lock()) {
645 return lockFile->isLocked();
648void KConfigIniBackend::unlock()
656bool KConfigIniBackend::isLocked()
const
658 return lockFile && lockFile->isLocked();
665char *escapeByte(
char *data,
unsigned char s)
667 static const char nibbleLookup[] = {
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'a',
'b',
'c',
'd',
'e',
'f'};
670 *data++ = nibbleLookup[s >> 4];
671 *data++ = nibbleLookup[s & 0x0f];
680 unsigned char bytes[4];
682 unsigned char charLength;
695 bool addByte(
unsigned char b)
698 if (b > 0xc1 && (b & 0xe0) == 0xc0) {
700 }
else if ((b & 0xf0) == 0xe0) {
702 }
else if (b < 0xf5 && (b & 0xf8) == 0xf0) {
709 }
else if (count < 4 && (b & 0xc0) == 0x80) {
711 if (charLength == 3 && bytes[0] == 0xe0 && b < 0xa0) {
714 if (charLength == 4) {
715 if (bytes[0] == 0xf0 && b < 0x90) {
718 if (bytes[0] == 0xf4 && b > 0x8f) {
730 bool isComplete()
const
732 return count > 0 && count == charLength;
735 char *escapeBytes(
char *data)
737 for (
unsigned char i = 0; i < count; ++i) {
738 data = escapeByte(data, bytes[i]);
745 char *writeUtf8(
char *data)
747 for (
unsigned char i = 0; i < count; ++i) {
757 char *write(
char *data)
760 data = writeUtf8(data);
762 data = escapeBytes(data);
771 const int len = aString.
size();
780 char *data = result.
data();
784 if (s[0] ==
' ' && type != GroupString) {
791 for (; i < len; ++i) {
794 if (utf8.addByte(s[i])) {
797 data = utf8.escapeBytes(data);
800 if (((
unsigned char)s[i]) < 32) {
805 if (type == ValueString && ((
unsigned char)s[i]) >= 127) {
827 if (type != KeyString) {
835 if (type == ValueString) {
840 data = escapeByte(data, s[i]);
843 if (utf8.isComplete()) {
844 data = utf8.writeUtf8(data);
847 data = utf8.write(data);
852 if (result.
endsWith(
' ') && type != GroupString) {
859char KConfigIniBackend::charFromHex(
const char *str,
const QFile &file,
int line)
861 unsigned char ret = 0;
862 for (
int i = 0; i < 2; i++) {
864 quint8 c = quint8(str[i]);
866 if (c >=
'0' && c <=
'9') {
868 }
else if (c >=
'a' && c <=
'f') {
869 ret |= c -
'a' + 0x0a;
870 }
else if (c >=
'A' && c <=
'F') {
871 ret |= c -
'A' + 0x0a;
875 qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, line) <<
"Invalid hex character " << c <<
" in \\x<nn>-type escape sequence \"" << e.constData()
883void KConfigIniBackend::printableToString(
QByteArrayView &aString,
const QFile &file,
int line)
889 int l = aString.
size();
890 char *r =
const_cast<char *
>(aString.
data());
893 for (
int i = 0; i < l; i++, r++) {
894 if (str[i] !=
'\\') {
934 *r = charFromHex(str + i + 1, file, line);
943 qCWarning(KCONFIG_CORE_LOG).noquote() << warningProlog(file, line) << QStringLiteral(
"Invalid escape sequence: «\\%1»").arg(str[i]);
950QString KConfigIniBackend::filePath()
const
952 return mLocalFilePath;
955void KConfigIniBackend::setLocalFilePath(
const QString &file)
957 mLocalFilePath = file;
960#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