KConfig

kconfigini.cpp
1 /*
2  This file is part of the KDE libraries
3  SPDX-FileCopyrightText: 2006, 2007 Thomas Braxton <[email protected]>
4  SPDX-FileCopyrightText: 1999 Preston Brown <[email protected]>
5  SPDX-FileCopyrightText: 1997-1999 Matthias Kalle Dalheimer <[email protected]>
6 
7  SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9 
10 #include "kconfigini_p.h"
11 
12 #include "kconfig.h"
13 #include "kconfigbackend_p.h"
14 #include "bufferfragment_p.h"
15 #include "kconfigdata.h"
16 #include "kconfig_core_log_settings.h"
17 
18 #include <QSaveFile>
19 #include <QLockFile>
20 #include <QDateTime>
21 #include <QDir>
22 #include <QFile>
23 #include <QFileInfo>
24 #include <QDebug>
25 #include <qplatformdefs.h>
26 
27 #ifndef Q_OS_WIN
28 #include <unistd.h> // getuid, close
29 #endif
30 #include <sys/types.h> // uid_t
31 #include <fcntl.h> // open
32 
33 KCONFIGCORE_EXPORT bool kde_kiosk_exception = false; // flag to disable kiosk restrictions
34 
35 static QByteArray lookup(const KConfigIniBackend::BufferFragment fragment, QHash<KConfigIniBackend::BufferFragment, QByteArray> *cache)
36 {
37  auto it = cache->constFind(fragment);
38  if (it != cache->constEnd()) {
39  return it.value();
40  }
41 
42  return cache->insert(fragment, fragment.toByteArray()).value();
43 }
44 
45 QString KConfigIniBackend::warningProlog(const QFile &file, int line)
46 {
47  return QStringLiteral("KConfigIni: In file %2, line %1: ")
48  .arg(line).arg(file.fileName());
49 }
50 
51 KConfigIniBackend::KConfigIniBackend()
52  : KConfigBackend(), lockFile(nullptr)
53 {
54 }
55 
56 KConfigIniBackend::~KConfigIniBackend()
57 {
58 }
59 
60 KConfigBackend::ParseInfo
61 KConfigIniBackend::parseConfig(const QByteArray &currentLocale, KEntryMap &entryMap,
62  ParseOptions options)
63 {
64  return parseConfig(currentLocale, entryMap, options, false);
65 }
66 
67 // merging==true is the merging that happens at the beginning of writeConfig:
68 // merge changes in the on-disk file with the changes in the KConfig object.
69 KConfigBackend::ParseInfo
70 KConfigIniBackend::parseConfig(const QByteArray &currentLocale, KEntryMap &entryMap,
71  ParseOptions options, bool merging)
72 {
73  if (filePath().isEmpty() || !QFile::exists(filePath())) {
74  return ParseOk;
75  }
76 
77  const QByteArray currentLanguage = currentLocale.split('_').first();
78 
79  bool bDefault = options & ParseDefaults;
80  bool allowExecutableValues = options & ParseExpansions;
81 
82  QByteArray currentGroup("<default>");
83 
84  QFile file(filePath());
86  return ParseOpenError;
87  }
88 
89  QList<QByteArray> immutableGroups;
90 
91  bool fileOptionImmutable = false;
92  bool groupOptionImmutable = false;
93  bool groupSkip = false;
94 
95  int lineNo = 0;
96  // on systems using \r\n as end of line, \r will be taken care of by
97  // trim() below
98  QByteArray buffer = file.readAll();
99  BufferFragment contents(buffer.data(), buffer.size());
100  unsigned int len = contents.length();
101  unsigned int startOfLine = 0;
102 
103  // Reduce memory overhead by making use of implicit sharing
104  // This assumes that config files contain only a small amount of
105  // different fragments which are repeated often.
106  // This is often the case, especially sub groups will all have
107  // the same list of keys and similar values as well.
109  cache.reserve(4096);
110 
111  while (startOfLine < len) {
112  BufferFragment line = contents.split('\n', &startOfLine);
113  line.trim();
114  lineNo++;
115 
116  // skip empty lines and lines beginning with '#'
117  if (line.isEmpty() || line.at(0) == '#') {
118  continue;
119  }
120 
121  if (line.at(0) == '[') { // found a group
122  groupOptionImmutable = fileOptionImmutable;
123 
124  QByteArray newGroup;
125  int start = 1, end;
126  do {
127  end = start;
128  for (;;) {
129  if (end == line.length()) {
130  qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) << "Invalid group header.";
131  // XXX maybe reset the current group here?
132  goto next_line;
133  }
134  if (line.at(end) == ']') {
135  break;
136  }
137  end++;
138  }
139  if (end + 1 == line.length() && start + 2 == end &&
140  line.at(start) == '$' && line.at(start + 1) == 'i') {
141  if (newGroup.isEmpty()) {
142  fileOptionImmutable = !kde_kiosk_exception;
143  } else {
144  groupOptionImmutable = !kde_kiosk_exception;
145  }
146  } else {
147  if (!newGroup.isEmpty()) {
148  newGroup += '\x1d';
149  }
150  BufferFragment namePart = line.mid(start, end - start);
151  printableToString(&namePart, file, lineNo);
152  newGroup += namePart.toByteArray();
153  }
154  } while ((start = end + 2) <= line.length() && line.at(end + 1) == '[');
155  currentGroup = newGroup;
156 
157  groupSkip = entryMap.getEntryOption(currentGroup, {}, {}, KEntryMap::EntryImmutable);
158 
159  if (groupSkip && !bDefault) {
160  continue;
161  }
162 
163  if (groupOptionImmutable)
164  // Do not make the groups immutable until the entries from
165  // this file have been added.
166  {
167  immutableGroups.append(currentGroup);
168  }
169  } else {
170  if (groupSkip && !bDefault) {
171  continue; // skip entry
172  }
173 
174  BufferFragment aKey;
175  int eqpos = line.indexOf('=');
176  if (eqpos < 0) {
177  aKey = line;
178  line.clear();
179  } else {
180  BufferFragment temp = line.left(eqpos);
181  temp.trim();
182  aKey = temp;
183  line.truncateLeft(eqpos + 1);
184  line.trim();
185  }
186  if (aKey.isEmpty()) {
187  qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) << "Invalid entry (empty key)";
188  continue;
189  }
190 
191  KEntryMap::EntryOptions entryOptions = {};
192  if (groupOptionImmutable) {
193  entryOptions |= KEntryMap::EntryImmutable;
194  }
195 
196  BufferFragment locale;
197  int start;
198  while ((start = aKey.lastIndexOf('[')) >= 0) {
199  int end = aKey.indexOf(']', start);
200  if (end < 0) {
201  qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo)
202  << "Invalid entry (missing ']')";
203  goto next_line;
204  } else if (end > start + 1 && aKey.at(start + 1) == '$') { // found option(s)
205  int i = start + 2;
206  while (i < end) {
207  switch (aKey.at(i)) {
208  case 'i':
209  if (!kde_kiosk_exception) {
210  entryOptions |= KEntryMap::EntryImmutable;
211  }
212  break;
213  case 'e':
214  if (allowExecutableValues) {
215  entryOptions |= KEntryMap::EntryExpansion;
216  }
217  break;
218  case 'd':
219  entryOptions |= KEntryMap::EntryDeleted;
220  aKey.truncate(start);
221  printableToString(&aKey, file, lineNo);
222  entryMap.setEntry(currentGroup, aKey.toByteArray(), QByteArray(), entryOptions);
223  goto next_line;
224  default:
225  break;
226  }
227  i++;
228  }
229  } else { // found a locale
230  if (!locale.isNull()) {
231  qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo)
232  << "Invalid entry (second locale!?)";
233  goto next_line;
234  }
235 
236  locale = aKey.mid(start + 1, end - start - 1);
237  }
238  aKey.truncate(start);
239  }
240  if (eqpos < 0) { // Do this here after [$d] was checked
241  qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, lineNo) << "Invalid entry (missing '=')";
242  continue;
243  }
244  printableToString(&aKey, file, lineNo);
245  if (!locale.isEmpty()) {
246  if (locale != currentLocale && locale != currentLanguage) {
247  // backward compatibility. C == en_US
248  if (locale.at(0) != 'C' || currentLocale != "en_US") {
249  if (merging) {
250  entryOptions |= KEntryMap::EntryRawKey;
251  } else {
252  goto next_line; // skip this entry if we're not merging
253  }
254  }
255  }
256  }
257 
258  if (!(entryOptions & KEntryMap::EntryRawKey)) {
259  printableToString(&aKey, file, lineNo);
260  }
261 
262  if (options & ParseGlobal) {
263  entryOptions |= KEntryMap::EntryGlobal;
264  }
265  if (bDefault) {
266  entryOptions |= KEntryMap::EntryDefault;
267  }
268  if (!locale.isNull()) {
269  entryOptions |= KEntryMap::EntryLocalized;
270  if (locale.indexOf('_') != -1) {
271  entryOptions |= KEntryMap::EntryLocalizedCountry;
272  }
273  }
274  printableToString(&line, file, lineNo);
275  if (entryOptions & KEntryMap::EntryRawKey) {
276  QByteArray rawKey;
277  rawKey.reserve(aKey.length() + locale.length() + 2);
278  rawKey.append(aKey.toVolatileByteArray());
279  rawKey.append('[').append(locale.toVolatileByteArray()).append(']');
280  entryMap.setEntry(currentGroup, rawKey, lookup(line, &cache), entryOptions);
281  } else {
282  entryMap.setEntry(currentGroup, lookup(aKey, &cache), lookup(line, &cache), entryOptions);
283  }
284  }
285  next_line:
286  continue;
287  }
288 
289  // now make sure immutable groups are marked immutable
290  for (const QByteArray &group : qAsConst(immutableGroups)) {
291  entryMap.setEntry(group, QByteArray(), QByteArray(), KEntryMap::EntryImmutable);
292  }
293 
294  return fileOptionImmutable ? ParseImmutable : ParseOk;
295 }
296 
297 void KConfigIniBackend::writeEntries(const QByteArray &locale, QIODevice &file,
298  const KEntryMap &map, bool defaultGroup, bool &firstEntry)
299 {
300  QByteArray currentGroup;
301  bool groupIsImmutable = false;
302  const KEntryMapConstIterator end = map.constEnd();
303  for (KEntryMapConstIterator it = map.constBegin(); it != end; ++it) {
304  const KEntryKey &key = it.key();
305 
306  // Either process the default group or all others
307  if ((key.mGroup != "<default>") == defaultGroup) {
308  continue; // skip
309  }
310 
311  // the only thing we care about groups is, is it immutable?
312  if (key.mKey.isNull()) {
313  groupIsImmutable = it->bImmutable;
314  continue; // skip
315  }
316 
317  const KEntry &currentEntry = *it;
318  if (!defaultGroup && currentGroup != key.mGroup) {
319  if (!firstEntry) {
320  file.putChar('\n');
321  }
322  currentGroup = key.mGroup;
323  for (int start = 0, end;; start = end + 1) {
324  file.putChar('[');
325  end = currentGroup.indexOf('\x1d', start);
326  if (end < 0) {
327  int cgl = currentGroup.length();
328  if (currentGroup.at(start) == '$' && cgl - start <= 10) {
329  for (int i = start + 1; i < cgl; i++) {
330  char c = currentGroup.at(i);
331  if (c < 'a' || c > 'z') {
332  goto nope;
333  }
334  }
335  file.write("\\x24");
336  start++;
337  }
338  nope:
339  file.write(stringToPrintable(currentGroup.mid(start), GroupString));
340  file.putChar(']');
341  if (groupIsImmutable) {
342  file.write("[$i]", 4);
343  }
344  file.putChar('\n');
345  break;
346  } else {
347  file.write(stringToPrintable(currentGroup.mid(start, end - start), GroupString));
348  file.putChar(']');
349  }
350  }
351  }
352 
353  firstEntry = false;
354  // it is data for a group
355 
356  if (key.bRaw) { // unprocessed key with attached locale from merge
357  file.write(key.mKey);
358  } else {
359  file.write(stringToPrintable(key.mKey, KeyString)); // Key
360  if (key.bLocal && locale != "C") { // 'C' locale == untranslated
361  file.putChar('[');
362  file.write(locale); // locale tag
363  file.putChar(']');
364  }
365  }
366  if (currentEntry.bDeleted) {
367  if (currentEntry.bImmutable) {
368  file.write("[$di]", 5); // Deleted + immutable
369  } else {
370  file.write("[$d]", 4); // Deleted
371  }
372  } else {
373  if (currentEntry.bImmutable || currentEntry.bExpand) {
374  file.write("[$", 2);
375  if (currentEntry.bImmutable) {
376  file.putChar('i');
377  }
378  if (currentEntry.bExpand) {
379  file.putChar('e');
380  }
381  file.putChar(']');
382  }
383  file.putChar('=');
384  file.write(stringToPrintable(currentEntry.mValue, ValueString));
385  }
386  file.putChar('\n');
387  }
388 }
389 
390 void KConfigIniBackend::writeEntries(const QByteArray &locale, QIODevice &file, const KEntryMap &map)
391 {
392  bool firstEntry = true;
393 
394  // write default group
395  writeEntries(locale, file, map, true, firstEntry);
396 
397  // write all other groups
398  writeEntries(locale, file, map, false, firstEntry);
399 }
400 
401 bool KConfigIniBackend::writeConfig(const QByteArray &locale, KEntryMap &entryMap,
402  WriteOptions options)
403 {
404  Q_ASSERT(!filePath().isEmpty());
405 
406  KEntryMap writeMap;
407  const bool bGlobal = options & WriteGlobal;
408 
409  // First, reparse the file on disk, to merge our changes with the ones done by other apps
410  // Store the result into writeMap.
411  {
412  ParseOptions opts = ParseExpansions;
413  if (bGlobal) {
414  opts |= ParseGlobal;
415  }
416  ParseInfo info = parseConfig(locale, writeMap, opts, true);
417  if (info != ParseOk) { // either there was an error or the file became immutable
418  return false;
419  }
420  }
421 
422  const KEntryMapIterator end = entryMap.end();
423  for (KEntryMapIterator it = entryMap.begin(); it != end; ++it) {
424  if (!it.key().mKey.isEmpty() && !it->bDirty) { // not dirty, doesn't overwrite entry in writeMap. skips default entries, too.
425  continue;
426  }
427 
428  const KEntryKey &key = it.key();
429 
430  // only write entries that have the same "globality" as the file
431  if (it->bGlobal == bGlobal) {
432  if (it->bReverted && it->bOverridesGlobal) {
433  it->bDeleted = true;
434  writeMap[key] = *it;
435  } else if (it->bReverted) {
436  writeMap.remove(key);
437  } else if (!it->bDeleted) {
438  writeMap[key] = *it;
439  } else {
440  KEntryKey defaultKey = key;
441  defaultKey.bDefault = true;
442  if (!entryMap.contains(defaultKey)) {
443  writeMap.remove(key); // remove the deleted entry if there is no default
444  //qDebug() << "Detected as deleted=>removed:" << key.mGroup << key.mKey << "global=" << bGlobal;
445  } else {
446  writeMap[key] = *it; // otherwise write an explicitly deleted entry
447  //qDebug() << "Detected as deleted=>[$d]:" << key.mGroup << key.mKey << "global=" << bGlobal;
448  }
449  }
450  it->bDirty = false;
451  }
452  }
453 
454  // now writeMap should contain only entries to be written
455  // so write it out to disk
456 
457  // check if file exists
459  bool createNew = true;
460 
461  QFileInfo fi(filePath());
462  if (fi.exists()) {
463 #ifdef Q_OS_WIN
464  //TODO: getuid does not exist on windows, use GetSecurityInfo and GetTokenInformation instead
465  createNew = false;
466 #else
467  if (fi.ownerId() == ::getuid()) {
468  // Preserve file mode if file exists and is owned by user.
469  fileMode = fi.permissions();
470  } else {
471  // File is not owned by user:
472  // Don't create new file but write to existing file instead.
473  createNew = false;
474  }
475 #endif
476  }
477 
478  if (createNew) {
479  QSaveFile file(filePath());
480  if (!file.open(QIODevice::WriteOnly)) {
481  return false;
482  }
483 
484  file.setTextModeEnabled(true); // to get eol translation
485  writeEntries(locale, file, writeMap);
486 
487  if (!file.size() && (fileMode == (QFile::ReadUser | QFile::WriteUser))) {
488  // File is empty and doesn't have special permissions: delete it.
489  file.cancelWriting();
490 
491  if (fi.exists()) {
492  // also remove the old file in case it existed. this can happen
493  // when we delete all the entries in an existing config file.
494  // if we don't do this, then deletions and revertToDefault's
495  // will mysteriously fail
496  QFile::remove(filePath());
497  }
498  } else {
499  // Normal case: Close the file
500  if (file.commit()) {
501  QFile::setPermissions(filePath(), fileMode);
502  return true;
503  }
504  // Couldn't write. Disk full?
505  qCWarning(KCONFIG_CORE_LOG) << "Couldn't write" << filePath() << ". Disk full?";
506  return false;
507  }
508  } else {
509  // Open existing file. *DON'T* create it if it suddenly does not exist!
510 #ifdef Q_OS_UNIX
511  int fd = QT_OPEN(QFile::encodeName(filePath()).constData(), O_WRONLY | O_TRUNC);
512  if (fd < 0) {
513  return false;
514  }
515  FILE *fp = ::fdopen(fd, "w");
516  if (!fp) {
517  QT_CLOSE(fd);
518  return false;
519  }
520  QFile f;
521  if (!f.open(fp, QIODevice::WriteOnly)) {
522  fclose(fp);
523  return false;
524  }
525  writeEntries(locale, f, writeMap);
526  f.close();
527  fclose(fp);
528 #else
529  QFile f(filePath());
530  // XXX This is broken - it DOES create the file if it is suddenly gone.
532  return false;
533  }
534  f.setTextModeEnabled(true);
535  writeEntries(locale, f, writeMap);
536 #endif
537  }
538  return true;
539 }
540 
541 bool KConfigIniBackend::isWritable() const
542 {
543  const QString filePath = this->filePath();
544  if (!filePath.isEmpty()) {
545  QFileInfo file(filePath);
546  if (!file.exists()) {
547  // If the file does not exist, check if the deepest
548  // existing dir is writable.
549  QFileInfo dir(file.absolutePath());
550  while (!dir.exists()) {
551  QString parent = dir.absolutePath(); // Go up. Can't use cdUp() on non-existing dirs.
552  if (parent == dir.filePath()) {
553  // no parent
554  return false;
555  }
556  dir.setFile(parent);
557  }
558  return dir.isDir() && dir.isWritable();
559  } else {
560  return file.isWritable();
561  }
562  }
563 
564  return false;
565 }
566 
567 QString KConfigIniBackend::nonWritableErrorMessage() const
568 {
569  return tr("Configuration file \"%1\" not writable.\n").arg(filePath());
570 }
571 
572 void KConfigIniBackend::createEnclosing()
573 {
574  const QString file = filePath();
575  if (file.isEmpty()) {
576  return; // nothing to do
577  }
578 
579  // Create the containing dir, maybe it wasn't there
580  QDir dir;
581  dir.mkpath(QFileInfo(file).absolutePath());
582 }
583 
584 void KConfigIniBackend::setFilePath(const QString &file)
585 {
586  if (file.isEmpty()) {
587  return;
588  }
589 
590  Q_ASSERT(QDir::isAbsolutePath(file));
591 
592  const QFileInfo info(file);
593  if (info.exists()) {
594  setLocalFilePath(info.canonicalFilePath());
595  } else {
596  const QString dir = info.dir().canonicalPath();
597  if (!dir.isEmpty())
598  setLocalFilePath(dir + QLatin1Char('/') + info.fileName());
599  else
600  setLocalFilePath(file);
601  }
602 }
603 
604 KConfigBase::AccessMode KConfigIniBackend::accessMode() const
605 {
606  if (filePath().isEmpty()) {
607  return KConfigBase::NoAccess;
608  }
609 
610  if (isWritable()) {
611  return KConfigBase::ReadWrite;
612  }
613 
614  return KConfigBase::ReadOnly;
615 }
616 
617 bool KConfigIniBackend::lock()
618 {
619  Q_ASSERT(!filePath().isEmpty());
620 
621  if (!lockFile) {
622  lockFile = new QLockFile(filePath() + QLatin1String(".lock"));
623  }
624 
625  lockFile->lock();
626  return lockFile->isLocked();
627 }
628 
629 void KConfigIniBackend::unlock()
630 {
631  lockFile->unlock();
632  delete lockFile;
633  lockFile = nullptr;
634 }
635 
636 bool KConfigIniBackend::isLocked() const
637 {
638  return lockFile && lockFile->isLocked();
639 }
640 
641 namespace {
642  // serialize an escaped byte at the end of @param data
643  // @param data should have room for 4 bytes
644  char* escapeByte(char* data, unsigned char s) {
645  static const char nibbleLookup[] = {
646  '0', '1', '2', '3', '4', '5', '6', '7',
647  '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
648  };
649  *data++ = '\\';
650  *data++ = 'x';
651  *data++ = nibbleLookup[s >> 4];
652  *data++ = nibbleLookup[s & 0x0f];
653  return data;
654  }
655 
656  // Struct that represents a multi-byte UTF-8 character.
657  // This struct is used to keep track of bytes that seem to be valid
658  // UTF-8.
659  struct Utf8Char {
660  public:
661  unsigned char bytes[4];
662  unsigned char count;
663  unsigned char charLength;
664 
665  Utf8Char() {
666  clear();
667  charLength = 0;
668  }
669  void clear() {
670  count = 0;
671  }
672  // Add a byte to the UTF8 character.
673  // When an additional byte leads to an invalid character, return false.
674  bool addByte(unsigned char b) {
675  if (count == 0) {
676  if (b > 0xc1 && (b & 0xe0) == 0xc0) {
677  charLength = 2;
678  } else if ((b & 0xf0) == 0xe0) {
679  charLength = 3;
680  } else if (b < 0xf5 && (b & 0xf8) == 0xf0) {
681  charLength = 4;
682  } else {
683  return false;
684  }
685  bytes[0] = b;
686  count = 1;
687  } else if (count < 4 && (b & 0xc0) == 0x80) {
688  if (count == 1) {
689  if (charLength == 3 && bytes[0] == 0xe0 && b < 0xa0) {
690  return false; // overlong 3 byte sequence
691  }
692  if (charLength == 4) {
693  if (bytes[0] == 0xf0 && b < 0x90) {
694  return false; // overlong 4 byte sequence
695  }
696  if (bytes[0] == 0xf4 && b > 0x8f) {
697  return false; // Unicode value larger than U+10FFFF
698  }
699  }
700  }
701  bytes[count++] = b;
702  } else {
703  return false;
704  }
705  return true;
706  }
707  // Return true if Utf8Char contains one valid character.
708  bool isComplete() {
709  return count > 0 && count == charLength;
710  }
711  // Add the bytes in this UTF8 character in escaped form to data.
712  char* escapeBytes(char* data) {
713  for (unsigned char i = 0; i < count; ++i) {
714  data = escapeByte(data, bytes[i]);
715  }
716  clear();
717  return data;
718  }
719  // Add the bytes of the UTF8 character to a buffer.
720  // Only call this if isComplete() returns true.
721  char* writeUtf8(char* data) {
722  for (unsigned char i = 0; i < count; ++i) {
723  *data++ = bytes[i];
724  }
725  clear();
726  return data;
727  }
728  // Write the bytes in the UTF8 character literally, or, if the
729  // character is not complete, write the escaped bytes.
730  // This is useful to handle the state that remains after handling
731  // all bytes in a buffer.
732  char* write(char* data) {
733  if (isComplete()) {
734  data = writeUtf8(data);
735  } else {
736  data = escapeBytes(data);
737  }
738  return data;
739  }
740  };
741 }
742 
743 QByteArray KConfigIniBackend::stringToPrintable(const QByteArray &aString, StringType type)
744 {
745  if (aString.isEmpty()) {
746  return aString;
747  }
748  const int l = aString.length();
749 
750  QByteArray result; // Guesstimated that it's good to avoid data() initialization for a length of l*4
751  result.resize(l * 4); // Maximum 4x as long as source string due to \x<ab> escape sequences
752  const char *s = aString.constData();
753  int i = 0;
754  char *data = result.data();
755  char *start = data;
756 
757  // Protect leading space
758  if (s[0] == ' ' && type != GroupString) {
759  *data++ = '\\';
760  *data++ = 's';
761  i++;
762  }
763  Utf8Char utf8;
764 
765  for (; i < l; ++i/*, r++*/) {
766  switch (s[i]) {
767  default:
768  if (utf8.addByte(s[i])) {
769  break;
770  } else {
771  data = utf8.escapeBytes(data);
772  }
773  // The \n, \t, \r cases (all < 32) are handled below; we can ignore them here
774  if (((unsigned char)s[i]) < 32) {
775  goto doEscape;
776  }
777  // GroupString and KeyString should be valid UTF-8, but ValueString
778  // can be a bytearray with non-UTF-8 bytes that should be escaped.
779  if (type == ValueString && ((unsigned char)s[i]) >= 127) {
780  goto doEscape;
781  }
782  *data++ = s[i];
783  break;
784  case '\n':
785  *data++ = '\\';
786  *data++ = 'n';
787  break;
788  case '\t':
789  *data++ = '\\';
790  *data++ = 't';
791  break;
792  case '\r':
793  *data++ = '\\';
794  *data++ = 'r';
795  break;
796  case '\\':
797  *data++ = '\\';
798  *data++ = '\\';
799  break;
800  case '=':
801  if (type != KeyString) {
802  *data++ = s[i];
803  break;
804  }
805  goto doEscape;
806  case '[':
807  case ']':
808  // Above chars are OK to put in *value* strings as plaintext
809  if (type == ValueString) {
810  *data++ = s[i];
811  break;
812  }
813  doEscape:
814  data = escapeByte(data, s[i]);
815  break;
816  }
817  if (utf8.isComplete()) {
818  data = utf8.writeUtf8(data);
819  }
820  }
821  data = utf8.write(data);
822  *data = 0;
823  result.resize(data - start);
824 
825  // Protect trailing space
826  if (result.endsWith(' ') && type != GroupString) {
827  result.replace(result.length() - 1, 1, "\\s");
828  }
829 
830  return result;
831 }
832 
833 char KConfigIniBackend::charFromHex(const char *str, const QFile &file, int line)
834 {
835  unsigned char ret = 0;
836  for (int i = 0; i < 2; i++) {
837  ret <<= 4;
838  quint8 c = quint8(str[i]);
839 
840  if (c >= '0' && c <= '9') {
841  ret |= c - '0';
842  } else if (c >= 'a' && c <= 'f') {
843  ret |= c - 'a' + 0x0a;
844  } else if (c >= 'A' && c <= 'F') {
845  ret |= c - 'A' + 0x0a;
846  } else {
847  QByteArray e(str, 2);
848  e.prepend("\\x");
849  qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, line) << "Invalid hex character " << c
850  << " in \\x<nn>-type escape sequence \"" << e.constData() << "\".";
851  return 'x';
852  }
853  }
854  return char(ret);
855 }
856 
857 void KConfigIniBackend::printableToString(BufferFragment *aString, const QFile &file, int line)
858 {
859  if (aString->isEmpty() || aString->indexOf('\\') == -1) {
860  return;
861  }
862  aString->trim();
863  int l = aString->length();
864  char *r = aString->data();
865  char *str = r;
866 
867  for (int i = 0; i < l; i++, r++) {
868  if (str[i] != '\\') {
869  *r = str[i];
870  } else {
871  // Probable escape sequence
872  i++;
873  if (i >= l) { // Line ends after backslash - stop.
874  *r = '\\';
875  break;
876  }
877 
878  switch (str[i]) {
879  case 's':
880  *r = ' ';
881  break;
882  case 't':
883  *r = '\t';
884  break;
885  case 'n':
886  *r = '\n';
887  break;
888  case 'r':
889  *r = '\r';
890  break;
891  case '\\':
892  *r = '\\';
893  break;
894  case ';':
895  // not really an escape sequence, but allowed in .desktop files, don't strip '\;' from the string
896  *r = '\\';
897  r++;
898  *r = ';';
899  break;
900  case ',':
901  // not really an escape sequence, but allowed in .desktop files, don't strip '\,' from the string
902  *r = '\\';
903  r++;
904  *r = ',';
905  break;
906  case 'x':
907  if (i + 2 < l) {
908  *r = charFromHex(str + i + 1, file, line);
909  i += 2;
910  } else {
911  *r = 'x';
912  i = l - 1;
913  }
914  break;
915  default:
916  *r = '\\';
917  qCWarning(KCONFIG_CORE_LOG) << warningProlog(file, line)
918  << QStringLiteral("Invalid escape sequence \"\\%1\".").arg(str[i]);
919  }
920  }
921  }
922  aString->truncate(r - aString->constData());
923 }
QHash::iterator insert(const Key &key, const T &value)
bool isWritable() const const
bool contains(const Key &key) const const
QList< QByteArray > split(char sep) const const
bool bDeleted
Entry has been deleted.
Definition: kconfigdata.h:45
void reserve(int size)
char at(int i) const const
map/dict/list config node entry.
Definition: kconfigdata.h:22
bool remove()
QMap::const_iterator constBegin() const const
bool bDefault
Entry indicates if this is a default value.
Definition: kconfigdata.h:111
bool isNull() const const
virtual QString fileName() const const override
bool isEmpty() const const
void reserve(int size)
QHash::const_iterator constFind(const Key &key) const const
virtual bool setPermissions(QFileDevice::Permissions permissions) override
int length() const const
bool exists() const const
virtual bool open(QIODevice::OpenMode mode)
void resize(int size)
int indexOf(char ch, int from) const const
bool bImmutable
Entry can not be modified.
Definition: kconfigdata.h:41
void append(const T &value)
key structure holding both the actual key and the group to which it belongs.
Definition: kconfigdata.h:88
QByteArray mGroup
The "group" to which this EntryKey belongs.
Definition: kconfigdata.h:99
QHash::const_iterator constEnd() const const
virtual qint64 size() const const
bool isEmpty() const const
QMap::const_iterator constEnd() const const
const char * constData() const const
QByteArray readAll()
QByteArray & replace(int pos, int len, const char *after)
bool bLocal
Entry is localised or not.
Definition: kconfigdata.h:107
T & first()
QMap::iterator end()
QByteArray mid(int pos, int len) const const
virtual bool open(QIODevice::OpenMode mode) override
KDEGAMES_EXPORT QAction * end(const QObject *recvr, const char *slot, QObject *parent)
QMap::iterator begin()
QByteArray & append(char ch)
bool putChar(char c)
bool setEntry(const QByteArray &group, const QByteArray &key, const QByteArray &value, EntryOptions options)
Returns true if the entry gets dirtied or false in other case.
Definition: kconfigdata.cpp:71
bool isAbsolutePath(const QString &path)
KIOFILEWIDGETS_EXPORT QString dir(const QString &fileClass)
virtual void close() override
QByteArray mValue
Definition: kconfigdata.h:29
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QByteArray mKey
The actual key of the entry in question.
Definition: kconfigdata.h:103
AccessMode
Possible return values for accessMode().
Definition: kconfigbase.h:134
char * data()
void setTextModeEnabled(bool enabled)
qint64 write(const char *data, qint64 maxSize)
typedef Permissions
QString canonicalPath() const const
int size() const const
LocaleWrapper locale()
bool bExpand
Whether to apply dollar expansion or not.
Definition: kconfigdata.h:49
bool endsWith(const QByteArray &ba) const const
QByteArray encodeName(const QString &fileName)
bool mkpath(const QString &dirPath) const const
int remove(const Key &key)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Sun Jul 12 2020 22:48:12 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.