mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-23 18:32:49 +00:00
804 lines
No EOL
26 KiB
C++
804 lines
No EOL
26 KiB
C++
/*
|
|
This file is part of the KDE libraries
|
|
Copyright (c) 2006, 2007 Thomas Braxton <kde.braxton@gmail.com>
|
|
Copyright (c) 1999 Preston Brown <pbrown@kde.org>
|
|
Copyright (C) 1997-1999 Matthias Kalle Dalheimer (kalle@kde.org)
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Library General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
This library is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Library General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to
|
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "kconfigini_p.h"
|
|
|
|
#include "kconfig.h"
|
|
#include "bufferfragment_p.h"
|
|
#include "kconfigdata.h"
|
|
#include <ksavefile.h>
|
|
#include <kde_file.h>
|
|
#include "kstandarddirs.h"
|
|
#include <kdebug.h>
|
|
#include <klocale.h>
|
|
|
|
#include <qdatetime.h>
|
|
#include <qdir.h>
|
|
#include <qfile.h>
|
|
#include <qfileinfo.h>
|
|
#include <qdebug.h>
|
|
#include <qmetaobject.h>
|
|
#include <qregexp.h>
|
|
|
|
static QByteArray lookup(const KConfigIniBackend::BufferFragment& fragment, QHash<KConfigIniBackend::BufferFragment, QByteArray>* cache)
|
|
{
|
|
QHash<KConfigIniBackend::BufferFragment, QByteArray>::iterator it = cache->find(fragment);
|
|
if (it == cache->end()) {
|
|
it = cache->insert(fragment, fragment.toByteArray());
|
|
}
|
|
return it.value();
|
|
}
|
|
|
|
QString KConfigIniBackend::warningProlog(const QFile &file, int line)
|
|
{
|
|
return QString::fromLatin1("KConfigIni: In file %2, line %1: ")
|
|
.arg(line).arg(file.fileName());
|
|
}
|
|
|
|
KConfigIniBackend::KConfigIniBackend()
|
|
: QObject(), QSharedData(),
|
|
m_lockfile(nullptr)
|
|
{
|
|
}
|
|
|
|
KConfigIniBackend::~KConfigIniBackend()
|
|
{
|
|
}
|
|
|
|
KConfigIniBackend::ParseInfo KConfigIniBackend::parseConfig(const QByteArray& currentLocale,
|
|
KEntryMap& entryMap, ParseOptions options)
|
|
{
|
|
return parseConfig(currentLocale, entryMap, options, false);
|
|
}
|
|
|
|
// merging==true is the merging that happens at the beginning of writeConfig:
|
|
// merge changes in the on-disk file with the changes in the KConfig object.
|
|
KConfigIniBackend::ParseInfo
|
|
KConfigIniBackend::parseConfig(const QByteArray& currentLocale, KEntryMap& entryMap,
|
|
ParseOptions options, bool merging)
|
|
{
|
|
if (filePath().isEmpty() || !QFile::exists(filePath()))
|
|
return ParseOk;
|
|
|
|
bool bDefault = options&ParseDefaults;
|
|
bool allowExecutableValues = options&ParseExpansions;
|
|
|
|
QByteArray currentGroup("<default>");
|
|
|
|
QFile file(filePath());
|
|
if (!file.open(QIODevice::ReadOnly|QIODevice::Text))
|
|
return ParseOpenError;
|
|
|
|
QList<QByteArray> immutableGroups;
|
|
|
|
bool fileOptionImmutable = false;
|
|
bool groupOptionImmutable = false;
|
|
bool groupSkip = false;
|
|
|
|
int lineNo = 0;
|
|
// on systems using \r\n as end of line, \r will be taken care of by
|
|
// trim() below
|
|
QByteArray buffer = file.readAll();
|
|
BufferFragment contents(buffer.data(), buffer.size());
|
|
unsigned int len = contents.length();
|
|
unsigned int startOfLine = 0;
|
|
|
|
// Reduce memory overhead by making use of implicit sharing
|
|
// This assumes that config files contain only a small amount of
|
|
// different fragments which are repeated often.
|
|
// This is often the case, especially sub groups will all have
|
|
// the same list of keys and similar values as well.
|
|
QHash<BufferFragment, QByteArray> cache;
|
|
cache.reserve(4096);
|
|
|
|
while (startOfLine < len) {
|
|
BufferFragment line = contents.split('\n', &startOfLine);
|
|
line.trim();
|
|
lineNo++;
|
|
|
|
// skip empty lines and lines beginning with '#'
|
|
if (line.isEmpty() || line.at(0) == '#')
|
|
continue;
|
|
|
|
if (line.at(0) == '[') { // found a group
|
|
groupOptionImmutable = fileOptionImmutable;
|
|
|
|
QByteArray newGroup;
|
|
int start = 1, end;
|
|
do {
|
|
end = start;
|
|
for (;;) {
|
|
if (end == line.length()) {
|
|
qWarning() << warningProlog(file, lineNo) << "Invalid group header.";
|
|
// XXX maybe reset the current group here?
|
|
goto next_line;
|
|
}
|
|
if (line.at(end) == ']')
|
|
break;
|
|
end++;
|
|
}
|
|
if (end + 1 == line.length() && start + 2 == end &&
|
|
line.at(start) == '$' && line.at(start + 1) == 'i')
|
|
{
|
|
if (newGroup.isEmpty())
|
|
fileOptionImmutable = true;
|
|
else
|
|
groupOptionImmutable = true;
|
|
}
|
|
else {
|
|
if (!newGroup.isEmpty())
|
|
newGroup += '\x1d';
|
|
BufferFragment namePart=line.mid(start, end - start);
|
|
printableToString(&namePart, file, lineNo);
|
|
newGroup += namePart.toByteArray();
|
|
}
|
|
} while ((start = end + 2) <= line.length() && line.at(end + 1) == '[');
|
|
currentGroup = newGroup;
|
|
|
|
groupSkip = entryMap.getEntryOption(currentGroup, 0, 0, KEntryMap::EntryImmutable);
|
|
|
|
if (groupSkip && !bDefault)
|
|
continue;
|
|
|
|
if (groupOptionImmutable)
|
|
// Do not make the groups immutable until the entries from
|
|
// this file have been added.
|
|
immutableGroups.append(currentGroup);
|
|
} else {
|
|
if (groupSkip && !bDefault)
|
|
continue; // skip entry
|
|
|
|
BufferFragment aKey;
|
|
int eqpos = line.indexOf('=');
|
|
if (eqpos < 0) {
|
|
aKey = line;
|
|
line.clear();
|
|
} else {
|
|
BufferFragment temp = line.left(eqpos);
|
|
temp.trim();
|
|
aKey = temp;
|
|
line.truncateLeft(eqpos + 1);
|
|
}
|
|
if (aKey.isEmpty()) {
|
|
qWarning() << warningProlog(file, lineNo) << "Invalid entry (empty key)";
|
|
continue;
|
|
}
|
|
|
|
KEntryMap::EntryOptions entryOptions=0;
|
|
if (groupOptionImmutable)
|
|
entryOptions |= KEntryMap::EntryImmutable;
|
|
|
|
BufferFragment locale;
|
|
int start;
|
|
while ((start = aKey.lastIndexOf('[')) >= 0) {
|
|
int end = aKey.indexOf(']', start);
|
|
if (end < 0) {
|
|
qWarning() << warningProlog(file, lineNo)
|
|
<< "Invalid entry (missing ']')";
|
|
goto next_line;
|
|
} else if (end > start + 1 && aKey.at(start + 1) == '$') { // found option(s)
|
|
int i = start + 2;
|
|
while (i < end) {
|
|
switch (aKey.at(i)) {
|
|
case 'i':
|
|
entryOptions |= KEntryMap::EntryImmutable;
|
|
break;
|
|
case 'e':
|
|
if (allowExecutableValues)
|
|
entryOptions |= KEntryMap::EntryExpansion;
|
|
break;
|
|
case 'd':
|
|
entryOptions |= KEntryMap::EntryDeleted;
|
|
aKey = aKey.left(start);
|
|
printableToString(&aKey, file, lineNo);
|
|
entryMap.setEntry(currentGroup, aKey.toByteArray(), QByteArray(), entryOptions);
|
|
goto next_line;
|
|
default:
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
} else { // found a locale
|
|
if (!locale.isNull()) {
|
|
qWarning() << warningProlog(file, lineNo)
|
|
<< "Invalid entry (second locale!?)";
|
|
goto next_line;
|
|
}
|
|
|
|
locale = aKey.mid(start + 1,end - start - 1);
|
|
}
|
|
aKey.truncate(start);
|
|
}
|
|
if (eqpos < 0) { // Do this here after [$d] was checked
|
|
qWarning() << warningProlog(file, lineNo) << "Invalid entry (missing '=')";
|
|
continue;
|
|
}
|
|
printableToString(&aKey, file, lineNo);
|
|
if (!locale.isEmpty()) {
|
|
if (locale != currentLocale) {
|
|
// backward compatibility. C == en_US
|
|
if (locale.at(0) != 'C' || currentLocale != "en_US") {
|
|
if (merging)
|
|
entryOptions |= KEntryMap::EntryRawKey;
|
|
else
|
|
goto next_line; // skip this entry if we're not merging
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!(entryOptions & KEntryMap::EntryRawKey))
|
|
printableToString(&aKey, file, lineNo);
|
|
|
|
if (options&ParseGlobal)
|
|
entryOptions |= KEntryMap::EntryGlobal;
|
|
if (bDefault)
|
|
entryOptions |= KEntryMap::EntryDefault;
|
|
if (!locale.isNull())
|
|
entryOptions |= KEntryMap::EntryLocalized;
|
|
printableToString(&line, file, lineNo);
|
|
if (entryOptions & KEntryMap::EntryRawKey) {
|
|
QByteArray rawKey;
|
|
rawKey.reserve(aKey.length() + locale.length() + 2);
|
|
rawKey.append(aKey.toVolatileByteArray());
|
|
rawKey.append('[').append(locale.toVolatileByteArray()).append(']');
|
|
entryMap.setEntry(currentGroup, rawKey, lookup(line, &cache), entryOptions);
|
|
} else {
|
|
entryMap.setEntry(currentGroup, lookup(aKey, &cache), lookup(line, &cache), entryOptions);
|
|
}
|
|
}
|
|
next_line:
|
|
continue;
|
|
}
|
|
|
|
// now make sure immutable groups are marked immutable
|
|
foreach(const QByteArray& group, immutableGroups) {
|
|
entryMap.setEntry(group, QByteArray(), QByteArray(), KEntryMap::EntryImmutable);
|
|
}
|
|
|
|
return fileOptionImmutable ? ParseImmutable : ParseOk;
|
|
}
|
|
|
|
void KConfigIniBackend::writeEntries(const QByteArray& locale, QFile& file,
|
|
const KEntryMap& map, bool defaultGroup, bool &firstEntry)
|
|
{
|
|
QByteArray currentGroup;
|
|
bool groupIsImmutable = false;
|
|
const KEntryMapConstIterator end = map.constEnd();
|
|
for (KEntryMapConstIterator it = map.constBegin(); it != end; ++it) {
|
|
const KEntryKey& key = it.key();
|
|
|
|
// Either process the default group or all others
|
|
if ((key.mGroup != "<default>") == defaultGroup)
|
|
continue; // skip
|
|
|
|
// the only thing we care about groups is, is it immutable?
|
|
if (key.mKey.isNull()) {
|
|
groupIsImmutable = it->bImmutable;
|
|
continue; // skip
|
|
}
|
|
|
|
const KEntry& currentEntry = *it;
|
|
if (!defaultGroup && currentGroup != key.mGroup) {
|
|
if (!firstEntry)
|
|
file.putChar('\n');
|
|
currentGroup = key.mGroup;
|
|
for (int start = 0, end;; start = end + 1) {
|
|
file.putChar('[');
|
|
end = currentGroup.indexOf('\x1d', start);
|
|
if (end < 0) {
|
|
int cgl = currentGroup.length();
|
|
if (currentGroup.at(start) == '$' && cgl - start <= 10) {
|
|
for (int i = start + 1; i < cgl; i++) {
|
|
char c = currentGroup.at(i);
|
|
if (c < 'a' || c > 'z')
|
|
goto nope;
|
|
}
|
|
file.write("\\x24");
|
|
start++;
|
|
}
|
|
nope:
|
|
file.write(stringToPrintable(currentGroup.mid(start), GroupString));
|
|
file.putChar(']');
|
|
if (groupIsImmutable) {
|
|
file.write("[$i]", 4);
|
|
}
|
|
file.putChar('\n');
|
|
break;
|
|
} else {
|
|
file.write(stringToPrintable(currentGroup.mid(start, end - start), GroupString));
|
|
file.putChar(']');
|
|
}
|
|
}
|
|
}
|
|
|
|
firstEntry = false;
|
|
// it is data for a group
|
|
|
|
if (key.bRaw) // unprocessed key with attached locale from merge
|
|
file.write(key.mKey);
|
|
else {
|
|
file.write(stringToPrintable(key.mKey, KeyString)); // Key
|
|
if (key.bLocal && locale != "C") { // 'C' locale == untranslated
|
|
file.putChar('[');
|
|
file.write(locale); // locale tag
|
|
file.putChar(']');
|
|
}
|
|
}
|
|
if (currentEntry.bDeleted) {
|
|
if (currentEntry.bImmutable)
|
|
file.write("[$di]", 5); // Deleted + immutable
|
|
else
|
|
file.write("[$d]", 4); // Deleted
|
|
} else {
|
|
if (currentEntry.bImmutable || currentEntry.bExpand) {
|
|
file.write("[$", 2);
|
|
if (currentEntry.bImmutable)
|
|
file.putChar('i');
|
|
if (currentEntry.bExpand)
|
|
file.putChar('e');
|
|
file.putChar(']');
|
|
}
|
|
file.putChar('=');
|
|
file.write(stringToPrintable(currentEntry.mValue, ValueString));
|
|
}
|
|
file.putChar('\n');
|
|
}
|
|
}
|
|
|
|
void KConfigIniBackend::writeEntries(const QByteArray& locale, QFile& file, const KEntryMap& map)
|
|
{
|
|
bool firstEntry = true;
|
|
|
|
// write default group
|
|
writeEntries(locale, file, map, true, firstEntry);
|
|
|
|
// write all other groups
|
|
writeEntries(locale, file, map, false, firstEntry);
|
|
}
|
|
|
|
bool KConfigIniBackend::writeConfig(const QByteArray& locale, KEntryMap& entryMap,
|
|
WriteOptions options)
|
|
{
|
|
Q_ASSERT(!filePath().isEmpty());
|
|
|
|
KEntryMap writeMap;
|
|
const bool bGlobal = options & WriteGlobal;
|
|
|
|
// First, reparse the file on disk, to merge our changes with the ones done by other apps
|
|
// Store the result into writeMap.
|
|
{
|
|
ParseOptions opts = ParseExpansions;
|
|
if (bGlobal)
|
|
opts |= ParseGlobal;
|
|
ParseInfo info = parseConfig(locale, writeMap, opts, true);
|
|
if (info != ParseOk) // either there was an error or the file became immutable
|
|
return false;
|
|
}
|
|
|
|
const KEntryMapIterator end = entryMap.end();
|
|
for (KEntryMapIterator it=entryMap.begin(); it != end; ++it) {
|
|
if (!it.key().mKey.isEmpty() && !it->bDirty) // not dirty, doesn't overwrite entry in writeMap. skips default entries, too.
|
|
continue;
|
|
|
|
const KEntryKey& key = it.key();
|
|
|
|
// only write entries that have the same "globality" as the file
|
|
if (it->bGlobal == bGlobal) {
|
|
if (it->bReverted) {
|
|
writeMap.remove(key);
|
|
} else if (!it->bDeleted) {
|
|
writeMap[key] = *it;
|
|
} else {
|
|
KEntryKey defaultKey = key;
|
|
defaultKey.bDefault = true;
|
|
if (!entryMap.contains(defaultKey)) {
|
|
writeMap.remove(key); // remove the deleted entry if there is no default
|
|
//qDebug() << "Detected as deleted=>removed:" << key.mGroup << key.mKey << "global=" << bGlobal;
|
|
} else {
|
|
writeMap[key] = *it; // otherwise write an explicitly deleted entry
|
|
//qDebug() << "Detected as deleted=>[$d]:" << key.mGroup << key.mKey << "global=" << bGlobal;
|
|
}
|
|
}
|
|
it->bDirty = false;
|
|
}
|
|
}
|
|
|
|
// now writeMap should contain only entries to be written
|
|
// so write it out to disk
|
|
|
|
// check if file exists
|
|
QFile::Permissions fileMode = QFile::ReadUser | QFile::WriteUser;
|
|
bool createNew = true;
|
|
|
|
QFileInfo fi(filePath());
|
|
if (fi.exists())
|
|
{
|
|
if (fi.ownerId() == ::getuid())
|
|
{
|
|
// Preserve file mode if file exists and is owned by user.
|
|
fileMode = fi.permissions();
|
|
}
|
|
else
|
|
{
|
|
// File is not owned by user:
|
|
// Don't create new file but write to existing file instead.
|
|
createNew = false;
|
|
}
|
|
}
|
|
|
|
if (createNew) {
|
|
KSaveFile file( filePath() );
|
|
if (!file.open()) {
|
|
return false;
|
|
}
|
|
|
|
file.setPermissions(fileMode);
|
|
|
|
file.setTextModeEnabled(true); // to get eol translation
|
|
writeEntries(locale, file, writeMap);
|
|
|
|
if (!file.flush()) {
|
|
// Couldn't write. Disk full?
|
|
kWarning() << "Couldn't write" << filePath() << ". Disk full?";
|
|
file.abort();
|
|
return false;
|
|
}
|
|
|
|
if (!file.size() && (fileMode == (QFile::ReadUser | QFile::WriteUser))) {
|
|
// File is empty and doesn't have special permissions: delete it.
|
|
file.abort();
|
|
|
|
if (fi.exists()) {
|
|
// also remove the old file in case it existed. this can happen
|
|
// when we delete all the entries in an existing config file.
|
|
// if we don't do this, then deletions and revertToDefault's
|
|
// will mysteriously fail
|
|
QFile::remove(filePath());
|
|
}
|
|
} else {
|
|
// Normal case: Close the file
|
|
return file.finalize();
|
|
}
|
|
} else {
|
|
// Open existing file. *DON'T* create it if it suddenly does not exist!
|
|
#ifdef Q_OS_UNIX
|
|
int fd = KDE_open(QFile::encodeName(filePath()), O_WRONLY | O_TRUNC);
|
|
if (fd < 0) {
|
|
return false;
|
|
}
|
|
FILE *fp = KDE_fdopen(fd, "w");
|
|
if (!fp) {
|
|
close(fd);
|
|
return false;
|
|
}
|
|
QFile f;
|
|
if (!f.open(fp, QIODevice::WriteOnly)) {
|
|
fclose(fp);
|
|
return false;
|
|
}
|
|
writeEntries(locale, f, writeMap);
|
|
f.close();
|
|
fclose(fp);
|
|
#else
|
|
QFile f( filePath() );
|
|
// XXX This is broken - it DOES create the file if it is suddenly gone.
|
|
if (!f.open( QIODevice::WriteOnly | QIODevice::Truncate )) {
|
|
return false;
|
|
}
|
|
f.setTextModeEnabled(true);
|
|
writeEntries(locale, f, writeMap);
|
|
#endif
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool KConfigIniBackend::isWritable() const
|
|
{
|
|
if (!filePath().isEmpty()) {
|
|
if (KStandardDirs::checkAccess(filePath(), W_OK)) {
|
|
return true;
|
|
}
|
|
// The check might have failed because any of the containing dirs
|
|
// did not exist. If the file does not exist, check if the deepest
|
|
// existing dir is writable.
|
|
QFileInfo file(filePath());
|
|
if (!file.exists()) {
|
|
QFileInfo dir(file.absolutePath());
|
|
while (!dir.exists()) {
|
|
QString parent = dir.absolutePath(); // Go up. Can't use cdUp() on non-existing dirs.
|
|
if (parent == dir.filePath()) {
|
|
// no parent
|
|
return false;
|
|
}
|
|
dir.setFile(parent);
|
|
}
|
|
return dir.isDir() && dir.isWritable();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QString KConfigIniBackend::nonWritableErrorMessage() const
|
|
{
|
|
return i18n("Configuration file \"%1\" not writable.\n", filePath());
|
|
}
|
|
|
|
void KConfigIniBackend::createEnclosing()
|
|
{
|
|
const QString file = filePath();
|
|
if (file.isEmpty())
|
|
return; // nothing to do
|
|
|
|
// Create the containing dir, maybe it wasn't there
|
|
QDir dir;
|
|
dir.mkpath(QFileInfo(file).absolutePath());
|
|
}
|
|
|
|
void KConfigIniBackend::setFilePath(const QString& file)
|
|
{
|
|
if (file.isEmpty())
|
|
return;
|
|
|
|
Q_ASSERT(QDir::isAbsolutePath(file));
|
|
|
|
const QFileInfo info(file);
|
|
if (info.exists()) {
|
|
setLocalFilePath(info.canonicalFilePath());
|
|
setLastModified(info.lastModified());
|
|
setSize(info.size());
|
|
} else {
|
|
setLocalFilePath(file);
|
|
setSize(0);
|
|
QDateTime dummy;
|
|
dummy.setTime_t(0);
|
|
setLastModified(dummy);
|
|
}
|
|
}
|
|
|
|
KConfigBase::AccessMode KConfigIniBackend::accessMode() const
|
|
{
|
|
if (filePath().isEmpty())
|
|
return KConfigBase::NoAccess;
|
|
|
|
if (isWritable())
|
|
return KConfigBase::ReadWrite;
|
|
|
|
return KConfigBase::ReadOnly;
|
|
}
|
|
|
|
bool KConfigIniBackend::lock(const KComponentData& componentData)
|
|
{
|
|
Q_ASSERT(!filePath().isEmpty());
|
|
|
|
if (!m_lockfile) {
|
|
m_lockfile = new KLockFile(filePath());
|
|
}
|
|
|
|
if (m_lockfile->lock() == KLockFile::LockStale) {
|
|
// attempt to break the lock
|
|
m_lockfile->lock(KLockFile::ForceFlag);
|
|
}
|
|
return m_lockfile->isLocked();
|
|
}
|
|
|
|
void KConfigIniBackend::unlock()
|
|
{
|
|
m_lockfile->unlock();
|
|
delete m_lockfile;
|
|
m_lockfile = nullptr;
|
|
}
|
|
|
|
bool KConfigIniBackend::isLocked() const
|
|
{
|
|
return m_lockfile && m_lockfile->isLocked();
|
|
}
|
|
|
|
QByteArray KConfigIniBackend::stringToPrintable(const QByteArray& aString, StringType type)
|
|
{
|
|
static const char nibbleLookup[] = {
|
|
'0', '1', '2', '3', '4', '5', '6', '7',
|
|
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
|
|
};
|
|
|
|
if (aString.isEmpty())
|
|
return aString;
|
|
const int l = aString.length();
|
|
|
|
QByteArray result(l * 4, Qt::Uninitialized); // Maximum 4x as long as source string due to \x<ab> escape sequences
|
|
const char *s = aString.constData();
|
|
int i = 0;
|
|
char *data = result.data();
|
|
char *start = data;
|
|
|
|
// Protect leading space
|
|
if (s[0] == ' ' && type != GroupString) {
|
|
*data++ = '\\';
|
|
*data++ = 's';
|
|
i++;
|
|
}
|
|
|
|
for (; i < l; ++i/*, r++*/) {
|
|
switch (s[i]) {
|
|
default:
|
|
// The \n, \t, \r cases (all < 32) are handled below; we can ignore them here
|
|
if (((unsigned char)s[i]) < 32)
|
|
goto doEscape;
|
|
*data++ = s[i];
|
|
break;
|
|
case '\n':
|
|
*data++ = '\\';
|
|
*data++ = 'n';
|
|
break;
|
|
case '\t':
|
|
*data++ = '\\';
|
|
*data++ = 't';
|
|
break;
|
|
case '\r':
|
|
*data++ = '\\';
|
|
*data++ = 'r';
|
|
break;
|
|
case '\\':
|
|
*data++ = '\\';
|
|
*data++ = '\\';
|
|
break;
|
|
case '=':
|
|
if (type != KeyString) {
|
|
*data++ = s[i];
|
|
break;
|
|
}
|
|
goto doEscape;
|
|
case '[':
|
|
case ']':
|
|
// Above chars are OK to put in *value* strings as plaintext
|
|
if (type == ValueString) {
|
|
*data++ = s[i];
|
|
break;
|
|
}
|
|
doEscape:
|
|
*data++ = '\\';
|
|
*data++ = 'x';
|
|
*data++ = nibbleLookup[((unsigned char)s[i]) >> 4];
|
|
*data++ = nibbleLookup[((unsigned char)s[i]) & 0x0f];
|
|
break;
|
|
}
|
|
}
|
|
*data = 0;
|
|
result.resize(data - start);
|
|
|
|
// Protect trailing space
|
|
if (result.endsWith(' ') && type != GroupString) {
|
|
result.replace(result.length() - 1, 1, "\\s");
|
|
}
|
|
result.squeeze();
|
|
|
|
return result;
|
|
}
|
|
|
|
char KConfigIniBackend::charFromHex(const char *str, const QFile& file, int line)
|
|
{
|
|
unsigned char ret = 0;
|
|
for (int i = 0; i < 2; i++) {
|
|
ret <<= 4;
|
|
quint8 c = quint8(str[i]);
|
|
|
|
if (c >= '0' && c <= '9') {
|
|
ret |= c - '0';
|
|
} else if (c >= 'a' && c <= 'f') {
|
|
ret |= c - 'a' + 0x0a;
|
|
} else if (c >= 'A' && c <= 'F') {
|
|
ret |= c - 'A' + 0x0a;
|
|
} else {
|
|
QByteArray e(str, 2);
|
|
e.prepend("\\x");
|
|
qWarning() << warningProlog(file, line) << "Invalid hex character " << c
|
|
<< " in \\x<nn>-type escape sequence \"" << e.constData() << "\".";
|
|
return 'x';
|
|
}
|
|
}
|
|
return char(ret);
|
|
}
|
|
|
|
void KConfigIniBackend::printableToString(BufferFragment* aString, const QFile& file, int line)
|
|
{
|
|
if (aString->isEmpty() || aString->indexOf('\\')==-1)
|
|
return;
|
|
aString->trim();
|
|
int l = aString->length();
|
|
char *r = aString->data();
|
|
char *str=r;
|
|
|
|
for(int i = 0; i < l; i++, r++) {
|
|
if (str[i]!= '\\') {
|
|
*r=str[i];
|
|
} else {
|
|
// Probable escape sequence
|
|
i++;
|
|
if (i >= l) { // Line ends after backslash - stop.
|
|
*r = '\\';
|
|
break;
|
|
}
|
|
|
|
switch(str[i]) {
|
|
case 's':
|
|
*r = ' ';
|
|
break;
|
|
case 't':
|
|
*r = '\t';
|
|
break;
|
|
case 'n':
|
|
*r = '\n';
|
|
break;
|
|
case 'r':
|
|
*r = '\r';
|
|
break;
|
|
case '\\':
|
|
*r = '\\';
|
|
break;
|
|
case 'x':
|
|
if (i + 2 < l) {
|
|
*r = charFromHex(str + i + 1, file, line);
|
|
i += 2;
|
|
} else {
|
|
*r = 'x';
|
|
i = l - 1;
|
|
}
|
|
break;
|
|
default:
|
|
*r = '\\';
|
|
qWarning() << warningProlog(file, line)
|
|
<< QString::fromLatin1("Invalid escape sequence \"\\%1\".").arg(str[i]);
|
|
}
|
|
}
|
|
}
|
|
aString->truncate(r - aString->constData());
|
|
}
|
|
|
|
QString KConfigIniBackend::filePath() const
|
|
{
|
|
return m_localFileName;
|
|
}
|
|
|
|
void KConfigIniBackend::setLocalFilePath(const QString& file)
|
|
{
|
|
m_localFileName = file;
|
|
}
|
|
|
|
QDateTime KConfigIniBackend::lastModified() const
|
|
{
|
|
return m_lastModified;
|
|
}
|
|
|
|
void KConfigIniBackend::setLastModified(const QDateTime& dt)
|
|
{
|
|
m_lastModified = dt;
|
|
}
|
|
|
|
qint64 KConfigIniBackend::size() const
|
|
{
|
|
return m_size;
|
|
}
|
|
|
|
void KConfigIniBackend::setSize(qint64 sz)
|
|
{
|
|
m_size = sz;
|
|
} |