Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F16569636
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
25 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
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)
Attached To
Mode
rKL kdelibs
Attached
Detach File
Event Timeline
Log In to Comment