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 }