00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 #include "kconfigini_p.h"
00024
00025 #include <config.h>
00026
00027 #include <fcntl.h>
00028
00029 #include "kconfig.h"
00030 #include "kconfigbackend.h"
00031 #include "bufferfragment_p.h"
00032 #include "kconfigdata.h"
00033 #include <ksavefile.h>
00034 #include <kde_file.h>
00035 #include "kstandarddirs.h"
00036
00037 #include <qdatetime.h>
00038 #include <qdir.h>
00039 #include <qfile.h>
00040 #include <qfileinfo.h>
00041 #include <qdebug.h>
00042 #include <qmetaobject.h>
00043 #include <qregexp.h>
00044
00045 extern bool kde_kiosk_exception;
00046
00047 QString KConfigIniBackend::warningProlog(const QFile &file, int line)
00048 {
00049 return QString("KConfigIni: In file %2, line %1: ")
00050 .arg(line).arg(file.fileName());
00051 }
00052
00053 KConfigIniBackend::KConfigIniBackend()
00054 : KConfigBackend()
00055 {
00056 }
00057
00058 KConfigIniBackend::~KConfigIniBackend()
00059 {
00060 }
00061
00062 KConfigBackend::ParseInfo
00063 KConfigIniBackend::parseConfig(const QByteArray& currentLocale, KEntryMap& entryMap,
00064 ParseOptions options)
00065 {
00066 return parseConfig(currentLocale, entryMap, options, false);
00067 }
00068
00069 KConfigBackend::ParseInfo
00070 KConfigIniBackend::parseConfig(const QByteArray& currentLocale, KEntryMap& entryMap,
00071 ParseOptions options, bool merging)
00072 {
00073 if (filePath().isEmpty() || !QFile::exists(filePath()))
00074 return ParseOk;
00075
00076 bool bDefault = options&ParseDefaults;
00077 bool allowExecutableValues = options&ParseExpansions;
00078
00079 QByteArray currentGroup("<default>");
00080
00081 QFile file(filePath());
00082 if (!file.open(QIODevice::ReadOnly|QIODevice::Text))
00083 return ParseOpenError;
00084
00085 QList<QByteArray> immutableGroups;
00086
00087 bool fileOptionImmutable = false;
00088 bool groupOptionImmutable = false;
00089 bool groupSkip = false;
00090
00091 int lineNo = 0;
00092
00093
00094 QByteArray buffer = file.readAll();
00095 BufferFragment contents(buffer.data(), buffer.size());
00096 unsigned int len = contents.length();
00097 unsigned int startOfLine = 0;
00098
00099 while (startOfLine < len) {
00100 BufferFragment line = contents.split('\n', &startOfLine);
00101 line.trim();
00102 lineNo++;
00103
00104
00105 if (line.isEmpty() || line.at(0) == '#')
00106 continue;
00107
00108 if (line.at(0) == '[') {
00109 groupOptionImmutable = fileOptionImmutable;
00110
00111 QByteArray newGroup;
00112 int start = 1, end;
00113 do {
00114 end = start;
00115 for (;;) {
00116 if (end == line.length()) {
00117 qWarning() << warningProlog(file, lineNo) << "Invalid group header.";
00118
00119 goto next_line;
00120 }
00121 if (line.at(end) == ']')
00122 break;
00123 end++;
00124 }
00125 if (end + 1 == line.length() && start + 2 == end &&
00126 line.at(start) == '$' && line.at(start + 1) == 'i')
00127 {
00128 if (newGroup.isEmpty())
00129 fileOptionImmutable = !kde_kiosk_exception;
00130 else
00131 groupOptionImmutable = !kde_kiosk_exception;
00132 }
00133 else {
00134 if (!newGroup.isEmpty())
00135 newGroup += '\x1d';
00136 BufferFragment namePart=line.mid(start, end - start);
00137 printableToString(&namePart, file, lineNo);
00138 newGroup += namePart.toByteArray();
00139 }
00140 } while ((start = end + 2) <= line.length() && line.at(end + 1) == '[');
00141 currentGroup = newGroup;
00142
00143 groupSkip = entryMap.getEntryOption(currentGroup, 0, 0, KEntryMap::EntryImmutable);
00144
00145 if (groupSkip && !bDefault)
00146 continue;
00147
00148 if (groupOptionImmutable)
00149
00150
00151 immutableGroups.append(currentGroup);
00152 } else {
00153 if (groupSkip && !bDefault)
00154 continue;
00155
00156 BufferFragment aKey;
00157 int eqpos = line.indexOf('=');
00158 if (eqpos < 0) {
00159 aKey = line;
00160 line.clear();
00161 } else {
00162 BufferFragment temp = line.left(eqpos);
00163 temp.trim();
00164 aKey = temp;
00165 line.truncateLeft(eqpos + 1);
00166 }
00167 if (aKey.isEmpty()) {
00168 qWarning() << warningProlog(file, lineNo) << "Invalid entry (empty key)";
00169 continue;
00170 }
00171
00172 KEntryMap::EntryOptions entryOptions=0;
00173 if (groupOptionImmutable)
00174 entryOptions |= KEntryMap::EntryImmutable;
00175
00176 BufferFragment locale;
00177 int start;
00178 while ((start = aKey.lastIndexOf('[')) >= 0) {
00179 int end = aKey.indexOf(']', start);
00180 if (end < 0) {
00181 qWarning() << warningProlog(file, lineNo)
00182 << "Invalid entry (missing ']')";
00183 goto next_line;
00184 } else if (end > start + 1 && aKey.at(start + 1) == '$') {
00185 int i = start + 2;
00186 while (i < end) {
00187 switch (aKey.at(i)) {
00188 case 'i':
00189 if (!kde_kiosk_exception)
00190 entryOptions |= KEntryMap::EntryImmutable;
00191 break;
00192 case 'e':
00193 if (allowExecutableValues)
00194 entryOptions |= KEntryMap::EntryExpansion;
00195 break;
00196 case 'd':
00197 entryOptions |= KEntryMap::EntryDeleted;
00198 aKey = aKey.left(start);
00199 printableToString(&aKey, file, lineNo);
00200 entryMap.setEntry(currentGroup, aKey.toByteArray(), QByteArray(), entryOptions);
00201 goto next_line;
00202 default:
00203 break;
00204 }
00205 i++;
00206 }
00207 } else {
00208 if (!locale.isNull()) {
00209 qWarning() << warningProlog(file, lineNo)
00210 << "Invalid entry (second locale!?)";
00211 goto next_line;
00212 }
00213
00214 locale = aKey.mid(start + 1,end - start - 1);
00215 }
00216 aKey.truncate(start);
00217 }
00218 if (eqpos < 0) {
00219 qWarning() << warningProlog(file, lineNo) << "Invalid entry (missing '=')";
00220 continue;
00221 }
00222 printableToString(&aKey, file, lineNo);
00223 if (!locale.isEmpty()) {
00224 if (locale != currentLocale) {
00225
00226 if (locale.at(0) != 'C' || currentLocale != "en_US") {
00227 if (merging)
00228 entryOptions |= KEntryMap::EntryRawKey;
00229 else
00230 goto next_line;
00231 }
00232 }
00233 }
00234
00235 if (!(entryOptions & KEntryMap::EntryRawKey))
00236 printableToString(&aKey, file, lineNo);
00237
00238 if (options&ParseGlobal)
00239 entryOptions |= KEntryMap::EntryGlobal;
00240 if (bDefault)
00241 entryOptions |= KEntryMap::EntryDefault;
00242 if (!locale.isNull())
00243 entryOptions |= KEntryMap::EntryLocalized;
00244 printableToString(&line, file, lineNo);
00245 if (entryOptions & KEntryMap::EntryRawKey) {
00246 QByteArray rawKey;
00247 rawKey.reserve(aKey.length() + locale.length() + 2);
00248 rawKey.append(aKey.toVolatileByteArray());
00249 rawKey.append('[').append(locale.toVolatileByteArray()).append(']');
00250 entryMap.setEntry(currentGroup, rawKey, line.toByteArray(), entryOptions);
00251 } else {
00252 entryMap.setEntry(currentGroup, aKey.toByteArray(), line.toByteArray(), entryOptions);
00253 }
00254 }
00255 next_line:
00256 continue;
00257 }
00258
00259
00260 foreach(const QByteArray& group, immutableGroups) {
00261 entryMap.setEntry(group, QByteArray(), QByteArray(), KEntryMap::EntryImmutable);
00262 }
00263
00264 return fileOptionImmutable ? ParseImmutable : ParseOk;
00265 }
00266
00267 void KConfigIniBackend::writeEntries(const QByteArray& locale, QFile& file,
00268 const KEntryMap& map, bool defaultGroup, bool &firstEntry)
00269 {
00270 QByteArray currentGroup;
00271 bool groupIsImmutable = false;
00272 const KEntryMapConstIterator end = map.constEnd();
00273 for (KEntryMapConstIterator it = map.constBegin(); it != end; ++it) {
00274 const KEntryKey& key = it.key();
00275
00276
00277 if ((key.mGroup != "<default>") == defaultGroup)
00278 continue;
00279
00280
00281 if (key.mKey.isNull()) {
00282 groupIsImmutable = it->bImmutable;
00283 continue;
00284 }
00285
00286 const KEntry& currentEntry = *it;
00287 if (!defaultGroup && currentGroup != key.mGroup) {
00288 if (!firstEntry)
00289 file.putChar('\n');
00290 currentGroup = key.mGroup;
00291 for (int start = 0, end;; start = end + 1) {
00292 file.putChar('[');
00293 end = currentGroup.indexOf('\x1d', start);
00294 if (end < 0) {
00295 int cgl = currentGroup.length();
00296 if (currentGroup.at(start) == '$' && cgl - start <= 10) {
00297 for (int i = start + 1; i < cgl; i++) {
00298 char c = currentGroup.at(i);
00299 if (c < 'a' || c > 'z')
00300 goto nope;
00301 }
00302 file.write("\\x24");
00303 start++;
00304 }
00305 nope:
00306 file.write(stringToPrintable(currentGroup.mid(start), GroupString));
00307 file.putChar(']');
00308 if (groupIsImmutable) {
00309 file.write("[$i]", 4);
00310 }
00311 file.putChar('\n');
00312 break;
00313 } else {
00314 file.write(stringToPrintable(currentGroup.mid(start, end - start), GroupString));
00315 file.putChar(']');
00316 }
00317 }
00318 }
00319
00320 firstEntry = false;
00321
00322
00323 if (key.bRaw)
00324 file.write(key.mKey);
00325 else {
00326 file.write(stringToPrintable(key.mKey, KeyString));
00327 if (key.bLocal && locale != "C") {
00328 file.putChar('[');
00329 file.write(locale);
00330 file.putChar(']');
00331 }
00332 }
00333 if (currentEntry.bDeleted) {
00334 if (currentEntry.bImmutable)
00335 file.write("[$di]", 5);
00336 else
00337 file.write("[$d]", 4);
00338 } else {
00339 if (currentEntry.bImmutable || currentEntry.bExpand) {
00340 file.write("[$", 2);
00341 if (currentEntry.bImmutable)
00342 file.putChar('i');
00343 if (currentEntry.bExpand)
00344 file.putChar('e');
00345 file.putChar(']');
00346 }
00347 file.putChar('=');
00348 file.write(stringToPrintable(currentEntry.mValue, ValueString));
00349 }
00350 file.putChar('\n');
00351 }
00352 }
00353
00354 void KConfigIniBackend::writeEntries(const QByteArray& locale, QFile& file, const KEntryMap& map)
00355 {
00356 bool firstEntry = true;
00357
00358
00359 writeEntries(locale, file, map, true, firstEntry);
00360
00361
00362 writeEntries(locale, file, map, false, firstEntry);
00363 }
00364
00365 bool KConfigIniBackend::writeConfig(const QByteArray& locale, KEntryMap& entryMap,
00366 WriteOptions options, const KComponentData &data)
00367 {
00368 Q_ASSERT(!filePath().isEmpty());
00369
00370 KEntryMap writeMap;
00371 bool bGlobal = options & WriteGlobal;
00372
00373 {
00374 ParseOptions opts = ParseExpansions;
00375 if (bGlobal)
00376 opts |= ParseGlobal;
00377 ParseInfo info = parseConfig(locale, writeMap, opts, true);
00378 if (info != ParseOk)
00379 return false;
00380 }
00381 const KEntryMapIterator end = entryMap.end();
00382 for (KEntryMapIterator it=entryMap.begin(); it != end; ++it) {
00383 if (!it.key().mKey.isEmpty() && !it->bDirty)
00384 continue;
00385
00386 const KEntryKey& key = it.key();
00387
00388
00389 if (it->bGlobal == bGlobal) {
00390 if (!it->bDeleted) {
00391 writeMap[key] = *it;
00392 } else {
00393 KEntryKey defaultKey = key;
00394 defaultKey.bDefault = true;
00395 if (!entryMap.contains(defaultKey))
00396 writeMap.remove(key);
00397 else
00398 writeMap[key] = *it;
00399 }
00400 it->bDirty = false;
00401 }
00402 }
00403
00404
00405
00406
00407
00408 QFile::Permissions fileMode = QFile::ReadUser | QFile::WriteUser;
00409 bool createNew = true;
00410
00411 QFileInfo fi(filePath());
00412 if (fi.exists())
00413 {
00414 if (fi.ownerId() == ::getuid())
00415 {
00416
00417 fileMode = fi.permissions();
00418 }
00419 else
00420 {
00421
00422
00423 createNew = false;
00424 }
00425 }
00426
00427 if (createNew) {
00428 KSaveFile file( filePath(), data );
00429 if (!file.open()) {
00430 return false;
00431 }
00432
00433 file.setPermissions(fileMode);
00434
00435 file.setTextModeEnabled(true);
00436 writeEntries(locale, file, writeMap);
00437
00438 if (!file.size() && (fileMode == (QFile::ReadUser | QFile::WriteUser))) {
00439
00440 file.abort();
00441
00442 if (fi.exists()) {
00443
00444
00445
00446
00447 QFile::remove(filePath());
00448 }
00449 } else {
00450
00451 return file.finalize();
00452 }
00453 } else {
00454
00455 #ifdef Q_OS_UNIX
00456 int fd = KDE_open(QFile::encodeName(filePath()), O_WRONLY | O_TRUNC);
00457 if (fd < 0) {
00458 return false;
00459 }
00460 FILE *fp = KDE_fdopen(fd, "w");
00461 if (!fp) {
00462 close(fd);
00463 return false;
00464 }
00465 QFile f;
00466 if (!f.open(fp, QIODevice::WriteOnly)) {
00467 fclose(fp);
00468 return false;
00469 }
00470 writeEntries(locale, f, writeMap);
00471 f.close();
00472 fclose(fp);
00473 #else
00474 QFile f( filePath() );
00475
00476 if (!f.open( QIODevice::WriteOnly | QIODevice::Truncate )) {
00477 return false;
00478 }
00479 f.setTextModeEnabled(true);
00480 writeEntries(locale, f, writeMap);
00481 #endif
00482 }
00483 return true;
00484 }
00485
00486 bool KConfigIniBackend::isWritable() const
00487 {
00488 if (!filePath().isEmpty()) {
00489 if (KStandardDirs::checkAccess(filePath(), W_OK)) {
00490 return true;
00491 }
00492
00493
00494
00495 if (!QFileInfo(filePath()).exists()) {
00496 QDir dir = QFileInfo(filePath()).absolutePath();
00497 while (!dir.exists()) {
00498 if (!dir.cdUp()) {
00499 return false;
00500 }
00501 }
00502 return QFileInfo(dir.absolutePath()).isWritable();
00503 }
00504 }
00505
00506 return false;
00507 }
00508
00509 QString KConfigIniBackend::nonWritableErrorMessage() const
00510 {
00511 return i18n("Configuration file \"%1\" not writable.\n", filePath());
00512 }
00513
00514 void KConfigIniBackend::createEnclosing()
00515 {
00516 const QString file = filePath();
00517 if (file.isEmpty())
00518 return;
00519
00520
00521 QDir dir;
00522 dir.mkpath(QFileInfo(file).absolutePath());
00523 }
00524
00525 void KConfigIniBackend::setFilePath(const QString& file)
00526 {
00527 if (file.isEmpty())
00528 return;
00529
00530 Q_ASSERT(QDir::isAbsolutePath(file));
00531
00532 if (QFile::exists(file)) {
00533 const QFileInfo info(file);
00534 setLocalFilePath(info.canonicalFilePath());
00535 setLastModified(info.lastModified());
00536 setSize(info.size());
00537 } else {
00538 setLocalFilePath(file);
00539 setSize(0);
00540 QDateTime dummy;
00541 dummy.setTime_t(0);
00542 setLastModified(dummy);
00543 }
00544 }
00545
00546 KConfigBase::AccessMode KConfigIniBackend::accessMode() const
00547 {
00548 if (filePath().isEmpty())
00549 return KConfigBase::NoAccess;
00550
00551 if (isWritable())
00552 return KConfigBase::ReadWrite;
00553
00554 return KConfigBase::ReadOnly;
00555 }
00556
00557 bool KConfigIniBackend::lock(const KComponentData& componentData)
00558 {
00559 Q_ASSERT(!filePath().isEmpty());
00560
00561 if (!lockFile) {
00562 lockFile = new KLockFile(filePath() + QLatin1String(".lock"), componentData);
00563 }
00564
00565 if (lockFile->lock() == KLockFile::LockStale)
00566 lockFile->lock(KLockFile::ForceFlag);
00567 return lockFile->isLocked();
00568 }
00569
00570 void KConfigIniBackend::unlock()
00571 {
00572 lockFile->unlock();
00573 lockFile.clear();
00574 }
00575
00576 bool KConfigIniBackend::isLocked() const
00577 {
00578 return lockFile && lockFile->isLocked();
00579 }
00580
00581 QByteArray KConfigIniBackend::stringToPrintable(const QByteArray& aString, StringType type)
00582 {
00583 static const char nibbleLookup[] = {
00584 '0', '1', '2', '3', '4', '5', '6', '7',
00585 '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
00586 };
00587
00588 if (aString.isEmpty())
00589 return aString;
00590 const int l = aString.length();
00591
00592 QByteArray result;
00593 result.reserve(l * 4);
00594 register const char *s = aString.constData();
00595 int i = 0;
00596
00597
00598 if (s[0] == ' ' && type != GroupString) {
00599 result.append("\\s");
00600 i++;
00601 }
00602
00603 for (; i < l; ++i) {
00604 switch (s[i]) {
00605 default:
00606
00607 if (((unsigned char)s[i]) < 32)
00608 goto doEscape;
00609 result.append(s[i]);
00610 break;
00611 case '\n':
00612 result.append("\\n");
00613 break;
00614 case '\t':
00615 result.append("\\t");
00616 break;
00617 case '\r':
00618 result.append("\\r");
00619 break;
00620 case '\\':
00621 result.append("\\\\");
00622 break;
00623 case '=':
00624 if (type != KeyString) {
00625 result.append(s[i]);
00626 break;
00627 }
00628 goto doEscape;
00629 case '[':
00630 case ']':
00631
00632 if (type == ValueString) {
00633 result.append(s[i]);
00634 break;
00635 }
00636 doEscape:
00637 result.append("\\x")
00638 .append(nibbleLookup[((unsigned char)s[i]) >> 4])
00639 .append(nibbleLookup[((unsigned char)s[i]) & 0x0f]);
00640 break;
00641 }
00642 }
00643
00644
00645 if (result.endsWith(' ') && type != GroupString) {
00646 result.replace(result.length() - 1, 1, "\\s");
00647 }
00648 result.squeeze();
00649
00650 return result;
00651 }
00652
00653 char KConfigIniBackend::charFromHex(const char *str, const QFile& file, int line)
00654 {
00655 unsigned char ret = 0;
00656 for (int i = 0; i < 2; i++) {
00657 ret <<= 4;
00658 quint8 c = quint8(str[i]);
00659
00660 if (c >= '0' && c <= '9') {
00661 ret |= c - '0';
00662 } else if (c >= 'a' && c <= 'f') {
00663 ret |= c - 'a' + 0x0a;
00664 } else if (c >= 'A' && c <= 'F') {
00665 ret |= c - 'A' + 0x0a;
00666 } else {
00667 QByteArray e(str, 2);
00668 e.prepend("\\x");
00669 qWarning() << warningProlog(file, line) << "Invalid hex character " << c
00670 << " in \\x<nn>-type escape sequence \"" << e.constData() << "\".";
00671 return 'x';
00672 }
00673 }
00674 return char(ret);
00675 }
00676
00677 void KConfigIniBackend::printableToString(BufferFragment* aString, const QFile& file, int line)
00678 {
00679 if (aString->isEmpty() || aString->indexOf('\\')==-1)
00680 return;
00681 aString->trim();
00682 int l = aString->length();
00683 char *r = aString->data();
00684 char *str=r;
00685
00686 for(int i = 0; i < l; i++, r++) {
00687 if (str[i]!= '\\') {
00688 *r=str[i];
00689 } else {
00690
00691 i++;
00692 if (i >= l) {
00693 *r = '\\';
00694 break;
00695 }
00696
00697 switch(str[i]) {
00698 case 's':
00699 *r = ' ';
00700 break;
00701 case 't':
00702 *r = '\t';
00703 break;
00704 case 'n':
00705 *r = '\n';
00706 break;
00707 case 'r':
00708 *r = '\r';
00709 break;
00710 case '\\':
00711 *r = '\\';
00712 break;
00713 case 'x':
00714 if (i + 2 < l) {
00715 *r = charFromHex(str + i + 1, file, line);
00716 i += 2;
00717 } else {
00718 *r = 'x';
00719 i = l - 1;
00720 }
00721 break;
00722 default:
00723 *r = '\\';
00724 qWarning() << warningProlog(file, line)
00725 << QString("Invalid escape sequence \"\\%1\".").arg(str[i]);
00726 }
00727 }
00728 }
00729 aString->truncate(r - aString->constData());
00730 }