Page MenuHomePhorge

No OneTemporary

diff --git a/kdecore/config/kconfigini.cpp b/kdecore/config/kconfigini.cpp
index 5f7ba7c3ca..6e2021c1d7 100644
--- a/kdecore/config/kconfigini.cpp
+++ b/kdecore/config/kconfigini.cpp
@@ -1,748 +1,752 @@
/*
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 "kconfigbackend.h"
#include "bufferfragment_p.h"
#include "kconfigdata.h"
#include <ksavefile.h>
#include <kde_file.h>
#include "kstandarddirs.h"
#include <qdatetime.h>
#include <qdir.h>
#include <qfile.h>
#include <qfileinfo.h>
#include <qdebug.h>
#include <qmetaobject.h>
#include <qregexp.h>
extern bool kde_kiosk_exception;
QString KConfigIniBackend::warningProlog(const QFile &file, int line)
{
return QString::fromLatin1("KConfigIni: In file %2, line %1: ")
.arg(line).arg(file.fileName());
}
KConfigIniBackend::KConfigIniBackend()
: KConfigBackend()
{
}
KConfigIniBackend::~KConfigIniBackend()
{
}
KConfigBackend::ParseInfo
KConfigIniBackend::parseConfig(const QByteArray& currentLocale, KEntryMap& entryMap,
ParseOptions options)
{
return parseConfig(currentLocale, entryMap, options, false);
}
KConfigBackend::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;
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 = !kde_kiosk_exception;
else
groupOptionImmutable = !kde_kiosk_exception;
}
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':
if (!kde_kiosk_exception)
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, line.toByteArray(), entryOptions);
} else {
entryMap.setEntry(currentGroup, aKey.toByteArray(), line.toByteArray(), 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, const KComponentData &data)
{
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
{
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->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(), data );
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.
- if (!QFileInfo(filePath()).exists()) {
- QDir dir = QFileInfo(filePath()).absolutePath();
+ QFileInfo file(filePath());
+ if (!file.exists()) {
+ QFileInfo dir(file.absolutePath());
while (!dir.exists()) {
- if (!dir.cdUp()) {
+ 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 QFileInfo(dir.absolutePath()).isWritable();
+ 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 (!lockFile) {
lockFile = new KLockFile(filePath() + QLatin1String(".lock"), componentData);
}
if (lockFile->lock() == KLockFile::LockStale) // attempt to break the lock
lockFile->lock(KLockFile::ForceFlag);
return lockFile->isLocked();
}
void KConfigIniBackend::unlock()
{
lockFile->unlock();
lockFile.clear();
}
bool KConfigIniBackend::isLocked() const
{
return lockFile && 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; // Guesstimated that it's good to avoid data() initialization for a length of l*4
result.resize(l * 4); // Maximum 4x as long as source string due to \x<ab> escape sequences
register 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());
}

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 1, 8:38 AM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
10075158
Default Alt Text
(25 KB)

Event Timeline