qTox  Version: nightly | Commit: bc751c8e1cac455f9690654fcfe0f560d2d7dfdd
settingsserializer.cpp
Go to the documentation of this file.
1 /*
2  Copyright © 2015-2019 by The qTox Project Contributors
3 
4  This file is part of qTox, a Qt-based graphical interface for Tox.
5 
6  qTox is libre software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  qTox is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with qTox. If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include "settingsserializer.h"
21 #include "serialize.h"
22 
23 #include "src/core/toxencrypt.h"
25 
26 #include <QDebug>
27 #include <QFile>
28 #include <QSaveFile>
29 #include <cassert>
30 #include <memory>
31 
53 enum class RecordTag : uint8_t
54 {
55 
56 };
61 const char SettingsSerializer::magic[] = {0x51, 0x54, 0x4F, 0x58};
62 
63 QDataStream& writeStream(QDataStream& dataStream, const SettingsSerializer::RecordTag& tag)
64 {
65  return dataStream << static_cast<uint8_t>(tag);
66 }
67 
68 QDataStream& writeStream(QDataStream& dataStream, const QByteArray& data)
69 {
70  QByteArray size = vintToData(data.size());
71  dataStream.writeRawData(size.data(), size.size());
72  dataStream.writeRawData(data.data(), data.size());
73  return dataStream;
74 }
75 
76 QDataStream& writeStream(QDataStream& dataStream, const QString& str)
77 {
78  return writeStream(dataStream, str.toUtf8());
79 }
80 
81 QDataStream& readStream(QDataStream& dataStream, SettingsSerializer::RecordTag& tag)
82 {
83  return dataStream >> reinterpret_cast<quint8&>(tag);
84 }
85 
86 
87 QDataStream& readStream(QDataStream& dataStream, QByteArray& data)
88 {
89  char num3;
90  int num = 0;
91  int num2 = 0;
92  do {
93  dataStream.readRawData(&num3, 1);
94  num |= (num3 & 0x7f) << num2;
95  num2 += 7;
96  } while ((num3 & 0x80) != 0);
97  data.resize(num);
98  dataStream.readRawData(data.data(), num);
99  return dataStream;
100 }
101 
102 SettingsSerializer::SettingsSerializer(QString filePath, const ToxEncrypt* passKey)
103  : path{filePath}
104  , passKey{passKey}
105  , group{-1}
106  , array{-1}
107  , arrayIndex{-1}
108 {
109 }
110 
111 void SettingsSerializer::beginGroup(const QString& prefix)
112 {
113  if (prefix.isEmpty())
114  endGroup();
115  int index = groups.indexOf(prefix);
116  if (index >= 0) {
117  group = index;
118  } else {
119  group = groups.size();
120  groups.append(prefix);
121  }
122 }
123 
125 {
126  group = -1;
127 }
128 
129 int SettingsSerializer::beginReadArray(const QString& prefix)
130 {
131  auto index = std::find_if(std::begin(arrays), std::end(arrays),
132  [=](const Array& a) { return a.name == prefix; });
133 
134  if (index != std::end(arrays)) {
135  array = static_cast<int>(index - std::begin(arrays));
136  arrayIndex = -1;
137  return index->size;
138  } else {
139  array = arrays.size();
140  arrays.push_back({group, 0, prefix, {}});
141  arrayIndex = -1;
142  return 0;
143  }
144 }
145 
146 void SettingsSerializer::beginWriteArray(const QString& prefix, int size)
147 {
148  auto index = std::find_if(std::begin(arrays), std::end(arrays),
149  [=](const Array& a) { return a.name == prefix; });
150 
151  if (index != std::end(arrays)) {
152  array = static_cast<int>(index - std::begin(arrays));
153  arrayIndex = -1;
154  if (size > 0)
155  index->size = std::max(index->size, size);
156  } else {
157  if (size < 0)
158  size = 0;
159  array = arrays.size();
160  arrays.push_back({group, size, prefix, {}});
161  arrayIndex = -1;
162  }
163 }
164 
166 {
167  array = -1;
168 }
169 
171 {
172  arrayIndex = i;
173 }
174 
175 void SettingsSerializer::setValue(const QString& key, const QVariant& value)
176 {
177  Value* v = findValue(key);
178  if (v) {
179  v->value = value;
180  } else {
181  Value nv{group, array, arrayIndex, key, value};
182  if (array >= 0)
183  arrays[array].values.append(values.size());
184  values.append(nv);
185  }
186 }
187 
188 QVariant SettingsSerializer::value(const QString& key, const QVariant& defaultValue) const
189 {
190  const Value* v = findValue(key);
191  if (v)
192  return v->value;
193  else
194  return defaultValue;
195 }
196 
198 {
199  if (array != -1) {
200  for (const Array& a : arrays) {
201  if (a.group != group)
202  continue;
203 
204  for (int vi : a.values) {
205  const Value& v = values[vi];
206  if (v.arrayIndex == arrayIndex && v.key == key)
207  return &v;
208  }
209  }
210  } else {
211  for (const Value& v : values)
212  if (v.group == group && v.array == -1 && v.key == key)
213  return &v;
214  }
215  return nullptr;
216 }
217 
219 {
220  return const_cast<Value*>(const_cast<const SettingsSerializer*>(this)->findValue(key));
221 }
222 
229 {
230  QFile f(filePath);
231  if (!f.open(QIODevice::ReadOnly))
232  return false;
233  char fmagic[8];
234  if (f.read(fmagic, sizeof(fmagic)) != sizeof(fmagic))
235  return false;
236  return !memcmp(fmagic, magic, 4) || tox_is_data_encrypted(reinterpret_cast<uint8_t*>(fmagic));
237 }
238 
243 {
245  readSerialized();
246  else
247  readIni();
248 }
249 
254 {
255  QSaveFile f(path);
256  if (!f.open(QIODevice::Truncate | QIODevice::WriteOnly)) {
257  qWarning() << "Couldn't open file";
258  return;
259  }
260 
261  QByteArray data(magic, 4);
262  QDataStream stream(&data, QIODevice::ReadWrite | QIODevice::Append);
263  stream.setVersion(QDataStream::Qt_5_0);
264 
265  // prevent signed overflow and the associated warning
266  int numGroups = std::max(0, groups.size());
267  for (int g = -1; g < numGroups; ++g) {
268  // Save the group name, if any
269  if (g != -1) {
271  writeStream(stream, groups[g].toUtf8());
272  }
273 
274  // Save all the arrays of this group
275  for (const Array& a : arrays) {
276  if (a.group != g)
277  continue;
278  if (a.size <= 0)
279  continue;
281  writeStream(stream, a.name.toUtf8());
282  writeStream(stream, vintToData(a.size));
283 
284  for (int vi : a.values) {
285  const Value& v = values[vi];
287  writeStream(stream, vintToData(values[vi].arrayIndex));
288  writeStream(stream, v.key.toUtf8());
289  writePackedVariant(stream, v.value);
290  }
292  }
293 
294  // Save all the values of this group that aren't in an array
295  for (const Value& v : values) {
296  if (v.group != g || v.array != -1)
297  continue;
298  writeStream(stream, RecordTag::Value);
299  writeStream(stream, v.key.toUtf8());
300  writePackedVariant(stream, v.value);
301  }
302  }
303 
304  // Encrypt
305  if (passKey) {
306  data = passKey->encrypt(data);
307  }
308 
309  f.write(data);
310 
311  // check if everything got written
312  if (f.flush()) {
313  f.commit();
314  } else {
315  f.cancelWriting();
316  qCritical() << "Failed to write, can't save!";
317  }
318 }
319 
321 {
322  QFile f(path);
323  if (!f.open(QIODevice::ReadOnly)) {
324  qWarning() << "Couldn't open file";
325  return;
326  }
327  QByteArray data = f.readAll();
328  f.close();
329 
330  // Decrypt
331  if (ToxEncrypt::isEncrypted(data)) {
332  if (!passKey) {
333  qCritical() << "The settings file is encrypted, but we don't have a passkey!";
334  return;
335  }
336 
337  data = passKey->decrypt(data);
338  if (data.isEmpty()) {
339  qCritical() << "Failed to decrypt the settings file";
340  return;
341  }
342  } else {
343  if (passKey)
344  qWarning() << "We have a password, but the settings file is not encrypted";
345  }
346 
347  if (memcmp(data.data(), magic, 4)) {
348  qWarning() << "Bad magic!";
349  return;
350  }
351  data = data.mid(4);
352 
353  QDataStream stream(&data, QIODevice::ReadOnly);
354  stream.setVersion(QDataStream::Qt_5_0);
355 
356  while (!stream.atEnd()) {
357  RecordTag tag;
358  readStream(stream, tag);
359  if (tag == RecordTag::Value) {
360  QByteArray key;
361  QByteArray value;
362  readStream(stream, key);
363  readStream(stream, value);
364  setValue(QString::fromUtf8(key), QVariant(QString::fromUtf8(value)));
365  } else if (tag == RecordTag::GroupStart) {
366  QByteArray prefix;
367  readStream(stream, prefix);
368  beginGroup(QString::fromUtf8(prefix));
369  } else if (tag == RecordTag::ArrayStart) {
370  QByteArray prefix;
371  readStream(stream, prefix);
372  beginReadArray(QString::fromUtf8(prefix));
373  QByteArray sizeData;
374  readStream(stream, sizeData);
375  if (sizeData.isEmpty()) {
376  qWarning("The personal save file is corrupted!");
377  return;
378  }
379  int size = dataToVInt(sizeData);
380  arrays[array].size = qMax(size, arrays[array].size);
381  } else if (tag == RecordTag::ArrayValue) {
382  QByteArray indexData;
383  readStream(stream, indexData);
384  if (indexData.isEmpty()) {
385  qWarning("The personal save file is corrupted!");
386  return;
387  }
388  setArrayIndex(dataToVInt(indexData));
389  QByteArray key;
390  QByteArray value;
391  readStream(stream, key);
392  readStream(stream, value);
393  setValue(QString::fromUtf8(key), QVariant(QString::fromUtf8(value)));
394  } else if (tag == RecordTag::ArrayEnd) {
395  endArray();
396  }
397  }
398 
399  group = array = -1;
400 }
401 
403 {
404  QSettings s(path, QSettings::IniFormat);
405 
406  // Read all keys of all groups, reading arrays as raw keys
407  QList<QString> gstack;
408  do {
409  // Add all keys
410  if (!s.group().isEmpty())
411  beginGroup(s.group());
412 
413  for (QString k : s.childKeys()) {
414  setValue(k, s.value(k));
415  }
416 
417  // Add all groups
418  gstack.push_back(QString());
419  for (QString g : s.childGroups())
420  gstack.push_back(g);
421 
422  // Visit the next group, if any
423  while (!gstack.isEmpty()) {
424  QString g = gstack.takeLast();
425  if (g.isEmpty()) {
426  if (gstack.isEmpty())
427  break;
428  else
429  s.endGroup();
430  } else {
431  s.beginGroup(g);
432  break;
433  }
434  }
435  } while (!gstack.isEmpty());
436 
437  // We can convert keys that look like arrays into real arrays
438  // If a group's only key is called size, we'll consider it to be an array,
439  // and its elements are all groups matching the pattern "[<group>/]<arrayName>/<arrayIndex>"
440 
441  // Find groups that only have 1 key
442  std::unique_ptr<int[]> groupSizes{new int[groups.size()]};
443  memset(groupSizes.get(), 0, static_cast<size_t>(groups.size()) * sizeof(int));
444  for (const Value& v : values) {
445  if (v.group < 0 || v.group > groups.size())
446  continue;
447  groupSizes[static_cast<size_t>(v.group)]++;
448  }
449 
450  // Find arrays, remove their size key from the values, and add them to `arrays`
451  QVector<int> groupsToKill;
452  for (int i = values.size() - 1; i >= 0; i--) {
453  const Value& v = values[i];
454  if (v.group < 0 || v.group > groups.size())
455  continue;
456  if (groupSizes[static_cast<size_t>(v.group)] != 1)
457  continue;
458  if (v.key != "size")
459  continue;
460  if (!v.value.canConvert(QVariant::Int))
461  continue;
462 
463  Array a;
464  a.size = v.value.toInt();
465  int slashIndex = groups[static_cast<int>(v.group)].lastIndexOf('/');
466  if (slashIndex == -1) {
467  a.group = -1;
468  a.name = groups[static_cast<int>(v.group)];
469  a.size = v.value.toInt();
470  } else {
471  a.group = -1;
472  for (int i = 0; i < groups.size(); ++i)
473  if (groups[i] == groups[static_cast<int>(v.group)].left(slashIndex))
474  a.group = i;
475  a.name = groups[static_cast<int>(v.group)].mid(slashIndex + 1);
476  }
477  groupSizes[static_cast<size_t>(v.group)]--;
478  groupsToKill.append(static_cast<int>(v.group));
479  arrays.append(a);
480  values.removeAt(i);
481  }
482 
483  // Associate each array's values with the array
484  for (int ai = 0; ai < arrays.size(); ++ai) {
485  Array& a = arrays[ai];
486  QString arrayPrefix;
487  if (a.group != -1)
488  arrayPrefix += groups[static_cast<int>(a.group)] + '/';
489  arrayPrefix += a.name + '/';
490 
491  // Find groups which represent each array index
492  for (int g = 0; g < groups.size(); ++g) {
493  if (!groups[g].startsWith(arrayPrefix))
494  continue;
495  bool ok;
496  int groupArrayIndex = groups[g].mid(arrayPrefix.size()).toInt(&ok);
497  if (!ok)
498  continue;
499  groupsToKill.append(g);
500 
501  if (groupArrayIndex > a.size)
502  a.size = groupArrayIndex;
503 
504  // Associate the values for this array index
505  for (int vi = values.size() - 1; vi >= 0; vi--) {
506  Value& v = values[vi];
507  if (v.group != g)
508  continue;
509  groupSizes[static_cast<size_t>(g)]--;
510  v.group = a.group;
511  v.array = ai;
512  v.arrayIndex = groupArrayIndex;
513  a.values.append(vi);
514  }
515  }
516  }
517 
518  // Clean up spurious array element groups
519  std::sort(std::begin(groupsToKill), std::end(groupsToKill), std::greater_equal<int>());
520 
521  for (int g : groupsToKill) {
522  if (groupSizes[static_cast<size_t>(g)])
523  continue;
524 
525  removeGroup(g);
526  }
527 
528  group = array = -1;
529 }
530 
537 {
538  assert(group < groups.size());
539  for (Array& a : arrays) {
540  assert(a.group != group);
541  if (a.group > group)
542  a.group--;
543  }
544  for (Value& v : values) {
545  assert(v.group != group);
546  if (v.group > group)
547  v.group--;
548  }
549  groups.removeAt(group);
550 }
551 
552 void SettingsSerializer::writePackedVariant(QDataStream& stream, const QVariant& v)
553 {
554  assert(v.canConvert(QVariant::String));
555  QString str = v.toString();
556  if (str == "true")
557  writeStream(stream, QString("1"));
558  else if (str == "false")
559  writeStream(stream, QString("0"));
560  else
561  writeStream(stream, str.toUtf8());
562 }
SettingsSerializer::beginWriteArray
void beginWriteArray(const QString &prefix, int size=-1)
Definition: settingsserializer.cpp:146
ToxEncrypt::encrypt
QByteArray encrypt(const QByteArray &plaintext) const
Encrypts the plaintext with the stored key.
Definition: toxencrypt.cpp:218
profile.h
SettingsSerializer::Array::size
int size
Definition: settingsserializer.h:90
SettingsSerializer::Array::values
QVector< int > values
Definition: settingsserializer.h:92
writeStream
QDataStream & writeStream(QDataStream &dataStream, const SettingsSerializer::RecordTag &tag)
Definition: settingsserializer.cpp:63
RecordTag
RecordTag
Definition: settingsserializer.cpp:53
SettingsSerializer::setArrayIndex
void setArrayIndex(int i)
Definition: settingsserializer.cpp:170
SettingsSerializer::values
QVector< Value > values
Definition: settingsserializer.h:110
SettingsSerializer::load
void load()
Loads the settings from file.
Definition: settingsserializer.cpp:242
SettingsSerializer::isSerializedFormat
static bool isSerializedFormat(QString filePath)
Checks if the file is serialized settings.
Definition: settingsserializer.cpp:228
SettingsSerializer::setValue
void setValue(const QString &key, const QVariant &value)
Definition: settingsserializer.cpp:175
SettingsSerializer::Value::group
qint64 group
Definition: settingsserializer.h:80
vintToData
QByteArray vintToData(int num)
Definition: serialize.cpp:99
SettingsSerializer::writePackedVariant
void writePackedVariant(QDataStream &dataStream, const QVariant &v)
Definition: settingsserializer.cpp:552
settingsserializer.h
SettingsSerializer::RecordTag::ArrayEnd
@ ArrayEnd
QList< QString >
SettingsSerializer::passKey
const ToxEncrypt * passKey
Definition: settingsserializer.h:106
ToxEncrypt::decrypt
QByteArray decrypt(const QByteArray &ciphertext) const
Decrypts data encrypted with this module, using the stored key.
Definition: toxencrypt.cpp:245
SettingsSerializer::group
int group
Definition: settingsserializer.h:107
SettingsSerializer::magic
static const char magic[]
Definition: settingsserializer.h:111
SettingsSerializer::endGroup
void endGroup()
Definition: settingsserializer.cpp:124
SettingsSerializer::SettingsSerializer
SettingsSerializer(QString filePath, const ToxEncrypt *passKey=nullptr)
Definition: settingsserializer.cpp:102
SettingsSerializer::readSerialized
void readSerialized()
Definition: settingsserializer.cpp:320
SettingsSerializer::findValue
const Value * findValue(const QString &key) const
Definition: settingsserializer.cpp:197
SettingsSerializer::beginGroup
void beginGroup(const QString &prefix)
Definition: settingsserializer.cpp:111
SettingsSerializer::writeStream
friend QDataStream & writeStream(QDataStream &dataStream, const SettingsSerializer::RecordTag &tag)
Definition: settingsserializer.cpp:63
ToxEncrypt::isEncrypted
static bool isEncrypted(const QByteArray &ciphertext)
Checks if the data was encrypted by this module.
Definition: toxencrypt.cpp:74
SettingsSerializer::arrays
QVector< Array > arrays
Definition: settingsserializer.h:109
SettingsSerializer::RecordTag::ArrayValue
@ ArrayValue
SettingsSerializer::RecordTag
RecordTag
Definition: settingsserializer.h:51
SettingsSerializer::endArray
void endArray()
Definition: settingsserializer.cpp:165
ToxEncrypt
Encapsulates the toxencrypsave API. Since key derivation is work intensive and to avoid storing plain...
Definition: toxencrypt.h:29
readStream
QDataStream & readStream(QDataStream &dataStream, SettingsSerializer::RecordTag &tag)
Definition: settingsserializer.cpp:81
SettingsSerializer::RecordTag::GroupStart
@ GroupStart
SettingsSerializer::readStream
friend QDataStream & readStream(QDataStream &dataStream, SettingsSerializer::RecordTag &tag)
Definition: settingsserializer.cpp:81
SettingsSerializer::array
int array
Definition: settingsserializer.h:107
SettingsSerializer::RecordTag::ArrayStart
@ ArrayStart
SettingsSerializer::removeGroup
void removeGroup(int group)
Remove group.
Definition: settingsserializer.cpp:536
SettingsSerializer::groups
QStringList groups
Definition: settingsserializer.h:108
SettingsSerializer::Array
Definition: settingsserializer.h:87
SettingsSerializer::Value::array
qint64 array
Definition: settingsserializer.h:81
SettingsSerializer
Serializes a QSettings's data in an (optionally) encrypted binary format. SettingsSerializer can dete...
Definition: settingsserializer.h:29
SettingsSerializer::save
void save()
Saves the current settings back to file.
Definition: settingsserializer.cpp:253
SettingsSerializer::value
QVariant value(const QString &key, const QVariant &defaultValue=QVariant()) const
Definition: settingsserializer.cpp:188
SettingsSerializer::Array::group
qint64 group
Definition: settingsserializer.h:89
toxencrypt.h
SettingsSerializer::Value::arrayIndex
int arrayIndex
Definition: settingsserializer.h:82
SettingsSerializer::beginReadArray
int beginReadArray(const QString &prefix)
Definition: settingsserializer.cpp:129
SettingsSerializer::arrayIndex
int arrayIndex
Definition: settingsserializer.h:107
serialize.h
SettingsSerializer::RecordTag::Value
@ Value
SettingsSerializer::Value::key
QString key
Definition: settingsserializer.h:83
SettingsSerializer::readIni
void readIni()
Definition: settingsserializer.cpp:402
SettingsSerializer::Array::name
QString name
Definition: settingsserializer.h:91
SettingsSerializer::path
QString path
Definition: settingsserializer.h:105
SettingsSerializer::Value::value
QVariant value
Definition: settingsserializer.h:84
dataToVInt
int dataToVInt(const QByteArray &data)
Definition: serialize.cpp:58
SettingsSerializer::Value
Definition: settingsserializer.h:62