/* Copyright © 2015 by The qTox Project This file is part of qTox, a Qt-based graphical interface for Tox. qTox is libre software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. qTox 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 General Public License for more details. You should have received a copy of the GNU General Public License along with qTox. If not, see . */ #include "settingsserializer.h" #include "serialize.h" #include "src/nexus.h" #include "src/persistence/profile.h" #include "src/core/core.h" #include #include #include #include using namespace std; constexpr char SettingsSerializer::magic[] = {0x51,0x54,0x4F,0x58}; QDataStream& operator<<(QDataStream& dataStream, const SettingsSerializer::RecordTag& tag) { return dataStream << static_cast(tag); } QDataStream& operator<<(QDataStream& dataStream, const QString& str) { return dataStream << str.toUtf8(); } QDataStream& operator<<(QDataStream& dataStream, const QByteArray& data) { QByteArray size = vuintToData(data.size()); dataStream.writeRawData(size.data(), size.size()); dataStream.writeRawData(data.data(), data.size()); return dataStream; } QDataStream& operator>>(QDataStream& dataStream, SettingsSerializer::RecordTag& tag) { return dataStream.operator >>((uint8_t&)tag); } QDataStream& operator>>(QDataStream& dataStream, QByteArray& data) { unsigned char num3; size_t num = 0; int num2 = 0; do { dataStream.readRawData((char*)&num3, 1); num |= (num3 & 0x7f) << num2; num2 += 7; } while ((num3 & 0x80) != 0); data.resize(num); dataStream.readRawData(data.data(), num); return dataStream; } SettingsSerializer::SettingsSerializer(QString filePath, QString password) : path{filePath}, password{password}, group{-1}, array{-1}, arrayIndex{-1} { } void SettingsSerializer::beginGroup(const QString &prefix) { if (prefix.isEmpty()) endGroup(); int index = groups.indexOf(prefix); if (index >= 0) { group = index; } else { group = groups.size(); groups.append(prefix); } } void SettingsSerializer::endGroup() { group = -1; } int SettingsSerializer::beginReadArray(const QString &prefix) { auto index = find_if(begin(arrays), end(arrays), [=](const Array& a){return a.name==prefix;}); if (index != end(arrays)) { array = index-begin(arrays); arrayIndex = -1; return index->size; } else { array = arrays.size(); arrays.push_back({group, 0, prefix, {}}); arrayIndex = -1; return 0; } } void SettingsSerializer::beginWriteArray(const QString &prefix, int size) { auto index = find_if(begin(arrays), end(arrays), [=](const Array& a){return a.name==prefix;}); if (index != end(arrays)) { array = index-begin(arrays); arrayIndex = -1; if (size > 0) index->size = max(index->size, (quint64)size); } else { if (size < 0) size = 0; array = arrays.size(); arrays.push_back({group, (uint64_t)size, prefix, {}}); arrayIndex = -1; } } void SettingsSerializer::endArray() { array = -1; } void SettingsSerializer::setArrayIndex(unsigned i) { arrayIndex = i; } void SettingsSerializer::setValue(const QString &key, const QVariant &value) { Value* v = findValue(key); if (v) { v->value = value; } else { Value nv{group, array, arrayIndex, key, value}; if (array >= 0) arrays[array].values.append(values.size()); values.append(nv); } } QVariant SettingsSerializer::value(const QString &key, const QVariant &defaultValue) const { const Value* v = findValue(key); if (v) return v->value; else return defaultValue; } const SettingsSerializer::Value* SettingsSerializer::findValue(const QString& key) const { if (array != -1) { for (const Array& a : arrays) { if (a.group != group) continue; for (int vi : a.values) if (values[vi].arrayIndex == arrayIndex && values[vi].key == key) return &values[vi]; } } else { for (const Value& v : values) if (v.group == group && v.array == -1 && v.key == key) return &v; } return nullptr; } SettingsSerializer::Value* SettingsSerializer::findValue(const QString& key) { return const_cast(const_cast(this)->findValue(key)); } bool SettingsSerializer::isSerializedFormat(QString filePath) { QFile f(filePath); if (!f.open(QIODevice::ReadOnly)) return false; char fmagic[8]; if (f.read(fmagic, sizeof(fmagic)) != sizeof(fmagic)) return false; return !memcmp(fmagic, magic, 4) || tox_is_data_encrypted((uint8_t*)fmagic); } void SettingsSerializer::load() { if (isSerializedFormat(path)) readSerialized(); else readIni(); /* Dump state for debugging qDebug() << "SettingsSerializer data:"; for (int i=0; isetPassword(password); data = core->encryptData(data); } f.write(data); f.close(); } void SettingsSerializer::readSerialized() { QFile f(path); if (!f.open(QIODevice::ReadOnly)) { qWarning() << "Couldn't open file"; return; } QByteArray data = f.readAll(); f.close(); // Decrypt if (tox_is_data_encrypted((uint8_t*)data.data())) { if (password.isEmpty()) { qCritical() << "The settings file is encrypted, but we don't have a password!"; return; } Core* core = Nexus::getCore(); uint8_t salt[TOX_PASS_SALT_LENGTH]; tox_get_salt(reinterpret_cast(data.data()), salt); core->setPassword(password, salt); data = core->decryptData(data); if (data.isEmpty()) qCritical() << "Failed to decrypt the settings file"; } else { if (!password.isEmpty()) qWarning() << "We have a password, but the settings file is not encrypted"; } if (memcmp(data.data(), magic, 4)) { qWarning() << "Bad magic!"; return; } data = data.mid(4); QDataStream stream(&data, QIODevice::ReadOnly); stream.setVersion(QDataStream::Qt_5_0); while (!stream.atEnd()) { RecordTag tag; stream >> tag; if (tag == RecordTag::Value) { QByteArray key; QByteArray value; stream >> key; stream >> value; setValue(QString::fromUtf8(key), QVariant(QString::fromUtf8(value))); //qDebug() << "!Got key"<> prefix; beginGroup(QString::fromUtf8(prefix)); //qDebug()<<"!Group start"<> prefix; beginReadArray(QString::fromUtf8(prefix)); QByteArray sizeData; stream >> sizeData; if (sizeData.isEmpty()) { qWarning("The personnal save file is corrupted!"); return; } quint64 size = dataToVUint(sizeData); arrays[array].size = max(size, arrays[array].size); //qDebug()<<"!Array start"<> indexData; if (indexData.isEmpty()) { qWarning("The personnal save file is corrupted!"); return; } quint64 index = dataToVUint(indexData); setArrayIndex(index); QByteArray key; QByteArray value; stream >> key; stream >> value; setValue(QString::fromUtf8(key), QVariant(QString::fromUtf8(value))); //qDebug() << "!Got array key"< gstack; do { // Add all keys if (!s.group().isEmpty()) beginGroup(s.group()); for (QString k : s.childKeys()) { setValue(k, s.value(k)); //qDebug() << "Read key "</]/" // Find groups that only have 1 key std::unique_ptr groupSizes{new int[groups.size()]}; memset(groupSizes.get(), 0, groups.size()*sizeof(int)); for (const Value& v : values) { if (v.group < 0 || v.group > groups.size()) continue; groupSizes[v.group]++; } // Find arrays, remove their size key from the values, and add them to `arrays` QVector groupsToKill; for (int i=values.size()-1; i>=0; i--) { const Value& v = values[i]; if (v.group < 0 || v.group > groups.size()) continue; if (groupSizes[v.group] != 1) continue; if (v.key != "size") continue; if (!v.value.canConvert(QVariant::Int)) continue; Array a; a.size = v.value.toInt(); int slashIndex = groups[v.group].lastIndexOf('/'); if (slashIndex == -1) { a.group = -1; a.name = groups[v.group]; a.size = v.value.toInt(); } else { a.group = -1; for (int i=0; i a.size) a.size = groupArrayIndex; // Associate the values for this array index for (int vi=values.size()-1; vi>=0; vi--) { Value& v = values[vi]; if (v.group != g) continue; groupSizes[g]--; v.group = a.group; v.array = ai; v.arrayIndex = groupArrayIndex; a.values.append(vi); //qDebug() << "Found key"<()); for (int g : groupsToKill) { if (groupSizes[g]) continue; //qDebug() << "Removing spurious array group"< group) a.group--; } for (Value& v : values) { assert(v.group != group); if (v.group > group) v.group--; } groups.removeAt(group); } void SettingsSerializer::writePackedVariant(QDataStream& stream, const QVariant& v) { assert(v.canConvert(QVariant::String)); QString str = v.toString(); if (str == "true") stream << QString("1"); else if (str == "false") stream << QString("0"); else stream << str.toUtf8(); }