File indexing completed on 2024-06-23 05:13:12

0001 /*
0002     SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org>
0003     SPDX-License-Identifier: LGPL-2.0-or-later
0004 */
0005 
0006 #include "plistreader_p.h"
0007 #include "plistdata_p.h"
0008 
0009 #include <QDebug>
0010 #include <QJsonArray>
0011 #include <QJsonObject>
0012 #include <QStringDecoder>
0013 
0014 #include <cassert>
0015 #include <cstring>
0016 
0017 using namespace KItinerary;
0018 
0019 PListArray::PListArray() = default;
0020 PListArray::PListArray(std::string_view data, const PListReader *reader) : m_data(data), m_reader(reader) {}
0021 PListArray::~PListArray() = default;
0022 
0023 uint64_t PListArray::size() const
0024 {
0025     return m_reader ? (m_data.size() / m_reader->trailer()->objectRefSize) : 0;
0026 }
0027 
0028 uint64_t PListArray::value(uint64_t index) const
0029 {
0030     return m_reader ? m_reader->readObjectRef(m_data, index) : 0;
0031 }
0032 
0033 PListObjectType PListArray::objectType(uint64_t index) const
0034 {
0035     return m_reader ? m_reader->objectType(value(index)) : PListObjectType::Unused;
0036 }
0037 PListObjectType PListArray::objectType(PListUid uid) const { return objectType(uid.value); }
0038 
0039 QVariant PListArray::object(uint64_t index) const
0040 {
0041     return m_reader ? m_reader->object(value(index)) : QVariant();
0042 }
0043 QVariant PListArray::object(PListUid uid) const { return object(uid.value); }
0044 
0045 
0046 PListDict::PListDict() = default;
0047 PListDict::PListDict(std::string_view data, const PListReader *reader) : m_data(data), m_reader(reader) {}
0048 PListDict::~PListDict() = default;
0049 
0050 uint64_t PListDict::size() const
0051 {
0052     return m_reader ? (m_data.size() / (m_reader->trailer()->objectRefSize * 2)) : 0;
0053 }
0054 
0055 uint64_t PListDict::key(uint64_t index) const
0056 {
0057     return m_reader ? m_reader->readObjectRef(m_data, index) : 0;
0058 }
0059 
0060 uint64_t PListDict::value(uint64_t index) const
0061 {
0062     return m_reader ? m_reader->readObjectRef(m_data, size() + index) : 0;
0063 }
0064 
0065 std::optional<uint64_t> PListDict::value(QLatin1StringView keyName) const {
0066   if (keyName.isEmpty()) {
0067     return {};
0068   }
0069 
0070   for (uint64_t i = 0; i < size(); ++i) {
0071     const auto n = m_reader->object(key(i)).toString();
0072     if (n == keyName) {
0073       return value(i);
0074     }
0075   }
0076 
0077   return {};
0078 }
0079 
0080 QVariant PListDict::object(QLatin1StringView keyName) const {
0081   const auto v = value(keyName);
0082   return v && m_reader ? m_reader->object(*v) : QVariant();
0083 }
0084 
0085 PListReader::PListReader(const QByteArray &data)
0086 {
0087     if (!maybePList(data)) {
0088         return;
0089     }
0090 
0091     m_data = data;
0092 }
0093 
0094 PListReader::~PListReader() = default;
0095 
0096 bool PListReader::isValid() const
0097 {
0098     return !m_data.isEmpty();
0099 }
0100 
0101 uint64_t PListReader::objectCount() const
0102 {
0103     return trailer()->numObjects;
0104 }
0105 
0106 uint64_t PListReader::rootObjectIndex() const
0107 {
0108     return trailer()->rootObjectIndex;
0109 }
0110 
0111 uint64_t PListReader::objectOffset(uint64_t index) const
0112 {
0113     const auto t = trailer();
0114     if (!t || index >= t->numObjects) {
0115         return 0;
0116     }
0117 
0118     uint64_t offset = 0;
0119     for (uint64_t i = 0; i < t->offsetIntSize; ++i) {
0120         offset <<= 8;
0121         offset |= (uint8_t)m_data.at(t->offsetTableOffset + index * t->offsetIntSize + i);
0122     }
0123     return offset;
0124 }
0125 
0126 struct {
0127     uint8_t marker;
0128     uint8_t mask;
0129     PListObjectType type;
0130 }
0131 static constexpr const marker_map[] = {
0132     { 0b0000'0000, 0b1111'1111, PListObjectType::Null },
0133     { 0b0000'1000, 0b1111'1110, PListObjectType::Bool },
0134     { 0b0000'1100, 0b1111'1110, PListObjectType::Url },
0135     { 0b0000'1110, 0b1111'1111, PListObjectType::Uuid },
0136     { 0b0000'1111, 0b1111'1111, PListObjectType::Fill },
0137     { 0b0001'0000, 0b1111'1000, PListObjectType::Int },
0138     { 0b0010'0000, 0b1111'1000, PListObjectType::Real },
0139     { 0b0011'0011, 0b1111'1111, PListObjectType::Date },
0140     { 0b0100'0000, PListContainerTypeMask, PListObjectType::Data },
0141     { 0b0101'0000, PListContainerTypeMask, PListObjectType::String },
0142     { 0b0110'0000, 0b1110'0000, PListObjectType::String },
0143     { 0b1000'0000, 0b1111'0000, PListObjectType::Uid },
0144     { 0b1010'0000, PListContainerTypeMask, PListObjectType::Array },
0145     { 0b1011'0000, PListContainerTypeMask, PListObjectType::Ordset },
0146     { 0b1100'0000, PListContainerTypeMask, PListObjectType::Set },
0147     { 0b1101'0000, PListContainerTypeMask, PListObjectType::Dict },
0148 };
0149 
0150 static PListObjectType objectTypeFromMarker(uint8_t b)
0151 {
0152     for (const auto &m : marker_map) {
0153         if ((b & m.mask) == m.marker) {
0154             return m.type;
0155         }
0156     }
0157     return PListObjectType::Unused;
0158 }
0159 
0160 PListObjectType PListReader::objectType(uint64_t index) const
0161 {
0162     const auto offset = objectOffset(index);
0163     if (offset >= (uint64_t)m_data.size()) {
0164         return PListObjectType::Unused;
0165     }
0166 
0167     return objectTypeFromMarker(m_data.at(offset));
0168 }
0169 
0170 QVariant PListReader::object(uint64_t index) const
0171 {
0172     auto offset = objectOffset(index);
0173     if (offset >= (uint64_t)m_data.size()) {
0174         return {};
0175     }
0176 
0177     const uint8_t b = m_data.at(offset++);
0178     const auto type = objectTypeFromMarker(b);
0179     switch (type) {
0180         case PListObjectType::Null:
0181         case PListObjectType::Fill:
0182         case PListObjectType::Unused:
0183             return {};
0184         case PListObjectType::Bool:
0185             return b == PListTrue;
0186         case PListObjectType::Int:
0187             return QVariant::fromValue<quint64>(readBigEndianNumber(offset, 1 << (b & 0b0000'0111)));
0188         case PListObjectType::Data:
0189         case PListObjectType::String:
0190         {
0191             if ((b & PListContainerTypeMask) == 0b0110'0000) {
0192                 const auto size = readContainerSize(offset, b) * 2;
0193                 const auto v = view(offset, size);
0194                 auto codec = QStringDecoder(QStringDecoder::Utf16BE);
0195                 return QString(codec.decode(QByteArrayView(v.data(), v.size())));
0196             }
0197 
0198             const auto size = readContainerSize(offset, b);
0199             const auto v = view(offset, size);
0200             if ((b & PListContainerTypeMask) == 0b0101'0000) {
0201                 return QString::fromLatin1(v.data(), v.size());
0202             }
0203             if ((b & PListContainerTypeMask) == 0b0111'0000) {
0204                 return QString::fromUtf8(v.data(), v.size());
0205             }
0206             return QByteArray(v.data(), v.size());
0207         }
0208         case PListObjectType::Uid:
0209             return QVariant::fromValue(PListUid{readBigEndianNumber(offset, (b & 0b0000'1111) + 1)});
0210         case PListObjectType::Array:
0211         {
0212             const auto size = readContainerSize(offset, b) * trailer()->objectRefSize;
0213             return QVariant::fromValue(PListArray(view(offset, size), this));
0214         }
0215         case PListObjectType::Dict:
0216         {
0217             const auto size = readContainerSize(offset, b) * trailer()->objectRefSize * 2;
0218             return QVariant::fromValue(PListDict(view(offset, size), this));
0219         }
0220 
0221         case PListObjectType::Real:
0222         case PListObjectType::Date:
0223             // TODO
0224 
0225         // v1+ types we don't support/need yet
0226         case PListObjectType::Url:
0227         case PListObjectType::Uuid:
0228         case PListObjectType::Ordset:
0229         case PListObjectType::Set:
0230             qDebug() << "unsupposed plist object type:" << (int)type << (int)b;
0231             break;
0232     }
0233     return {};
0234 }
0235 
0236 const PListTrailer* PListReader::trailer() const
0237 {
0238     return isValid() ? reinterpret_cast<const PListTrailer*>(m_data.constData() + m_data.size() - sizeof(PListTrailer)) : nullptr;
0239 }
0240 
0241 uint64_t PListReader::readBigEndianNumber(uint64_t offset, int size) const
0242 {
0243     if (size >= 8) {
0244         qDebug() << "oversized int not supported" << offset << size;
0245         return {};
0246     }
0247     if ((uint64_t)m_data.size() <= offset + size) {
0248         qDebug() << "attempting to read number beyond input data" << offset << size;
0249         return {};
0250     }
0251 
0252     uint64_t v = 0;
0253     for (auto i = 0; i < size; ++i) {
0254         v <<= 8;
0255         v |= (uint8_t)m_data.at(offset + i);
0256     }
0257     return v;
0258 }
0259 
0260 uint64_t PListReader::readBigEndianInteger(uint64_t& offset) const
0261 {
0262     assert(offset < (uint64_t)m_data.size());
0263     const auto b = m_data.at(offset++);
0264     const auto size = 1 << (b & 0b0000'0111);
0265     const auto v = readBigEndianNumber(offset, size);
0266     offset += size;
0267     return v;
0268 }
0269 
0270 uint64_t PListReader::readContainerSize(uint64_t &offset, uint8_t marker) const
0271 {
0272     uint64_t size = (marker & PListContainerSizeMask);
0273     if (offset + size >= (uint64_t)m_data.size()) {
0274         qDebug() << "attempt to read data beyond bounds";
0275         return {};
0276     }
0277 
0278     if (size == PListContainerSizeMask) {
0279         size = readBigEndianInteger(offset);
0280     }
0281     return size;
0282 }
0283 
0284 std::string_view PListReader::view(uint64_t offset, uint64_t size) const
0285 {
0286     return offset + size < (uint64_t)m_data.size() ? std::string_view(m_data.constData() + offset, size) : std::string_view();
0287 }
0288 
0289 uint64_t PListReader::readObjectRef(std::string_view data, uint64_t index) const
0290 {
0291     if ((index + 1) * trailer()->objectRefSize > data.size()) {
0292         qDebug() << "object reference read beyond data size";
0293         return {};
0294     }
0295 
0296     uint64_t ref = 0;
0297     for (auto i = 0; i < trailer()->objectRefSize; ++i) {
0298         ref <<= 8;
0299         ref |= (uint8_t)data[index * trailer()->objectRefSize + i];
0300     }
0301     return ref;
0302 }
0303 
0304 QJsonValue PListReader::unpackKeyedArchive() const
0305 {
0306     // TODO cycle detection
0307     const auto root = object(rootObjectIndex()).value<PListDict>();
0308     if (root.object(QLatin1StringView("$archiver")).toString() !=
0309         QLatin1String("NSKeyedArchiver")) {
0310       qDebug() << "not NSKeyedArchiver data"
0311                << root.object(QLatin1StringView("$archiver"));
0312       return {};
0313     }
0314 
0315     const auto top = root.object(QLatin1StringView("$top")).value<PListDict>();
0316     const auto objects =
0317         root.object(QLatin1StringView("$objects")).value<PListArray>();
0318     const auto uid = top.object(QLatin1StringView("root")).value<PListUid>();
0319     return unpackKeyedArchiveRecursive(uid, objects);
0320 }
0321 
0322 QJsonValue PListReader::unpackKeyedArchiveRecursive(PListUid uid, const PListArray &objects) const
0323 {
0324     const auto type = objects.objectType(uid);
0325     const auto v = objects.object(uid);
0326     switch (type) {
0327         case PListObjectType::Dict:
0328         {
0329             const auto obj = v.value<PListDict>();
0330             const auto classObj =
0331                 objects
0332                     .object(obj.object(QLatin1StringView("$class"))
0333                                 .value<PListUid>())
0334                     .value<PListDict>();
0335             const auto classNames =
0336                 classObj.object(QLatin1StringView("$classes"))
0337                     .value<PListArray>();
0338             for (uint64_t j = 0; j < classNames.size(); ++j) {
0339                 const auto className = classNames.object(j).toString();
0340                 if (className == QLatin1StringView("NSDictionary")) {
0341                   QJsonObject result;
0342                   const auto keys = obj.object(QLatin1StringView("NS.keys"))
0343                                         .value<PListArray>();
0344                   const auto vals = obj.object(QLatin1StringView("NS.objects"))
0345                                         .value<PListArray>();
0346                   for (uint64_t i = 0; i < std::min(keys.size(), vals.size());
0347                        ++i) {
0348                     const auto key =
0349                         objects.object(keys.object(i).value<PListUid>())
0350                             .toString();
0351                     const auto valUid = vals.object(i).value<PListUid>();
0352                     result.insert(key,
0353                                   unpackKeyedArchiveRecursive(valUid, objects));
0354                   }
0355                   return result;
0356                 }
0357                 if (className == QLatin1StringView("NSArray")) {
0358                   QJsonArray result;
0359                   const auto elems = obj.object(QLatin1StringView("NS.objects"))
0360                                          .value<PListArray>();
0361                   for (uint64_t i = 0; i < elems.size(); ++i) {
0362                     const auto elemUid = elems.object(i).value<PListUid>();
0363                     result.push_back(
0364                         unpackKeyedArchiveRecursive(elemUid, objects));
0365                   }
0366                   return result;
0367                 }
0368             }
0369             qDebug() << "unhandled dict object" << uid.value;
0370             break;
0371         }
0372         case PListObjectType::Int:
0373             return v.toInt();
0374         case PListObjectType::String:
0375             return v.toString();
0376         default:
0377             qDebug() << "unhandled class" << (int)type;
0378             return {};
0379     }
0380 
0381     return {};
0382 }
0383 
0384 bool PListReader::maybePList(const QByteArray &data)
0385 {
0386     if ((std::size_t)data.size() <= sizeof(PListHeader) + sizeof(PListReader)) {
0387         return false;
0388     }
0389 
0390     const auto header = reinterpret_cast<const PListHeader*>(data.constData());
0391     if (std::memcmp(header->magic, PListMagic, PListMagicSize) != 0) {
0392         return false;
0393     }
0394     qDebug() << "found plist version:" << header->version[0] << header->version[1];
0395 
0396     // verify trailer content
0397     const auto t = reinterpret_cast<const PListTrailer*>(data.constData() + data.size() - sizeof(PListTrailer));
0398     if (t->offsetIntSize == 0 || t->offsetIntSize > 8 || t->objectRefSize == 0 || t->objectRefSize > 8) {
0399         return false;
0400     }
0401 
0402     return t->offsetTableOffset + t->numObjects * t->offsetIntSize < (uint64_t)data.size();
0403 }