KItinerary

plistreader.cpp
1/*
2 SPDX-FileCopyrightText: 2023 Volker Krause <vkrause@kde.org>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#include "plistreader_p.h"
7#include "plistdata_p.h"
8
9#include <QDebug>
10#include <QJsonArray>
11#include <QJsonObject>
12#include <QStringDecoder>
13
14#include <cassert>
15#include <cstring>
16
17using namespace KItinerary;
18
19PListArray::PListArray() = default;
20PListArray::PListArray(std::string_view data, const PListReader *reader) : m_data(data), m_reader(reader) {}
21PListArray::~PListArray() = default;
22
23uint64_t PListArray::size() const
24{
25 return m_reader ? (m_data.size() / m_reader->trailer()->objectRefSize) : 0;
26}
27
28uint64_t PListArray::value(uint64_t index) const
29{
30 return m_reader ? m_reader->readObjectRef(m_data, index) : 0;
31}
32
33PListObjectType PListArray::objectType(uint64_t index) const
34{
35 return m_reader ? m_reader->objectType(value(index)) : PListObjectType::Unused;
36}
37PListObjectType PListArray::objectType(PListUid uid) const { return objectType(uid.value); }
38
39QVariant PListArray::object(uint64_t index) const
40{
41 return m_reader ? m_reader->object(value(index)) : QVariant();
42}
43QVariant PListArray::object(PListUid uid) const { return object(uid.value); }
44
45
46PListDict::PListDict() = default;
47PListDict::PListDict(std::string_view data, const PListReader *reader) : m_data(data), m_reader(reader) {}
48PListDict::~PListDict() = default;
49
50uint64_t PListDict::size() const
51{
52 return m_reader ? (m_data.size() / (m_reader->trailer()->objectRefSize * 2)) : 0;
53}
54
55uint64_t PListDict::key(uint64_t index) const
56{
57 return m_reader ? m_reader->readObjectRef(m_data, index) : 0;
58}
59
60uint64_t PListDict::value(uint64_t index) const
61{
62 return m_reader ? m_reader->readObjectRef(m_data, size() + index) : 0;
63}
64
65std::optional<uint64_t> PListDict::value(QLatin1StringView keyName) const {
66 if (keyName.isEmpty()) {
67 return {};
68 }
69
70 for (uint64_t i = 0; i < size(); ++i) {
71 const auto n = m_reader->object(key(i)).toString();
72 if (n == keyName) {
73 return value(i);
74 }
75 }
76
77 return {};
78}
79
80QVariant PListDict::object(QLatin1StringView keyName) const {
81 const auto v = value(keyName);
82 return v && m_reader ? m_reader->object(*v) : QVariant();
83}
84
85PListReader::PListReader(const QByteArray &data)
86{
87 if (!maybePList(data)) {
88 return;
89 }
90
91 m_data = data;
92}
93
94PListReader::~PListReader() = default;
95
96bool PListReader::isValid() const
97{
98 return !m_data.isEmpty();
99}
100
101uint64_t PListReader::objectCount() const
102{
103 return trailer()->numObjects;
104}
105
106uint64_t PListReader::rootObjectIndex() const
107{
108 return trailer()->rootObjectIndex;
109}
110
111uint64_t PListReader::objectOffset(uint64_t index) const
112{
113 const auto t = trailer();
114 if (!t || index >= t->numObjects) {
115 return 0;
116 }
117
118 uint64_t offset = 0;
119 for (uint64_t i = 0; i < t->offsetIntSize; ++i) {
120 offset <<= 8;
121 offset |= (uint8_t)m_data.at(t->offsetTableOffset + index * t->offsetIntSize + i);
122 }
123 return offset;
124}
125
126struct {
127 uint8_t marker;
128 uint8_t mask;
129 PListObjectType type;
130}
131static constexpr const marker_map[] = {
132 { 0b0000'0000, 0b1111'1111, PListObjectType::Null },
133 { 0b0000'1000, 0b1111'1110, PListObjectType::Bool },
134 { 0b0000'1100, 0b1111'1110, PListObjectType::Url },
135 { 0b0000'1110, 0b1111'1111, PListObjectType::Uuid },
136 { 0b0000'1111, 0b1111'1111, PListObjectType::Fill },
137 { 0b0001'0000, 0b1111'1000, PListObjectType::Int },
138 { 0b0010'0000, 0b1111'1000, PListObjectType::Real },
139 { 0b0011'0011, 0b1111'1111, PListObjectType::Date },
140 { 0b0100'0000, PListContainerTypeMask, PListObjectType::Data },
141 { 0b0101'0000, PListContainerTypeMask, PListObjectType::String },
142 { 0b0110'0000, 0b1110'0000, PListObjectType::String },
143 { 0b1000'0000, 0b1111'0000, PListObjectType::Uid },
144 { 0b1010'0000, PListContainerTypeMask, PListObjectType::Array },
145 { 0b1011'0000, PListContainerTypeMask, PListObjectType::Ordset },
146 { 0b1100'0000, PListContainerTypeMask, PListObjectType::Set },
147 { 0b1101'0000, PListContainerTypeMask, PListObjectType::Dict },
148};
149
150static PListObjectType objectTypeFromMarker(uint8_t b)
151{
152 for (const auto &m : marker_map) {
153 if ((b & m.mask) == m.marker) {
154 return m.type;
155 }
156 }
157 return PListObjectType::Unused;
158}
159
160PListObjectType PListReader::objectType(uint64_t index) const
161{
162 const auto offset = objectOffset(index);
163 if (offset >= (uint64_t)m_data.size()) {
164 return PListObjectType::Unused;
165 }
166
167 return objectTypeFromMarker(m_data.at(offset));
168}
169
170QVariant PListReader::object(uint64_t index) const
171{
172 auto offset = objectOffset(index);
173 if (offset >= (uint64_t)m_data.size()) {
174 return {};
175 }
176
177 const uint8_t b = m_data.at(offset++);
178 const auto type = objectTypeFromMarker(b);
179 switch (type) {
180 case PListObjectType::Null:
181 case PListObjectType::Fill:
182 case PListObjectType::Unused:
183 return {};
184 case PListObjectType::Bool:
185 return b == PListTrue;
186 case PListObjectType::Int:
187 return QVariant::fromValue<quint64>(readBigEndianNumber(offset, 1 << (b & 0b0000'0111)));
188 case PListObjectType::Data:
189 case PListObjectType::String:
190 {
191 if ((b & PListContainerTypeMask) == 0b0110'0000) {
192 const auto size = readContainerSize(offset, b) * 2;
193 const auto v = view(offset, size);
195 return QString(codec.decode(QByteArrayView(v.data(), v.size())));
196 }
197
198 const auto size = readContainerSize(offset, b);
199 const auto v = view(offset, size);
200 if ((b & PListContainerTypeMask) == 0b0101'0000) {
201 return QString::fromLatin1(v.data(), v.size());
202 }
203 if ((b & PListContainerTypeMask) == 0b0111'0000) {
204 return QString::fromUtf8(v.data(), v.size());
205 }
206 return QByteArray(v.data(), v.size());
207 }
208 case PListObjectType::Uid:
209 return QVariant::fromValue(PListUid{readBigEndianNumber(offset, (b & 0b0000'1111) + 1)});
210 case PListObjectType::Array:
211 {
212 const auto size = readContainerSize(offset, b) * trailer()->objectRefSize;
213 return QVariant::fromValue(PListArray(view(offset, size), this));
214 }
215 case PListObjectType::Dict:
216 {
217 const auto size = readContainerSize(offset, b) * trailer()->objectRefSize * 2;
218 return QVariant::fromValue(PListDict(view(offset, size), this));
219 }
220
221 case PListObjectType::Real:
222 case PListObjectType::Date:
223 // TODO
224
225 // v1+ types we don't support/need yet
226 case PListObjectType::Url:
227 case PListObjectType::Uuid:
228 case PListObjectType::Ordset:
229 case PListObjectType::Set:
230 qDebug() << "unsupposed plist object type:" << (int)type << (int)b;
231 break;
232 }
233 return {};
234}
235
236const PListTrailer* PListReader::trailer() const
237{
238 return isValid() ? reinterpret_cast<const PListTrailer*>(m_data.constData() + m_data.size() - sizeof(PListTrailer)) : nullptr;
239}
240
241uint64_t PListReader::readBigEndianNumber(uint64_t offset, int size) const
242{
243 if (size >= 8) {
244 qDebug() << "oversized int not supported" << offset << size;
245 return {};
246 }
247 if ((uint64_t)m_data.size() <= offset + size) {
248 qDebug() << "attempting to read number beyond input data" << offset << size;
249 return {};
250 }
251
252 uint64_t v = 0;
253 for (auto i = 0; i < size; ++i) {
254 v <<= 8;
255 v |= (uint8_t)m_data.at(offset + i);
256 }
257 return v;
258}
259
260uint64_t PListReader::readBigEndianInteger(uint64_t& offset) const
261{
262 assert(offset < (uint64_t)m_data.size());
263 const auto b = m_data.at(offset++);
264 const auto size = 1 << (b & 0b0000'0111);
265 const auto v = readBigEndianNumber(offset, size);
266 offset += size;
267 return v;
268}
269
270uint64_t PListReader::readContainerSize(uint64_t &offset, uint8_t marker) const
271{
272 uint64_t size = (marker & PListContainerSizeMask);
273 if (offset + size >= (uint64_t)m_data.size()) {
274 qDebug() << "attempt to read data beyond bounds";
275 return {};
276 }
277
278 if (size == PListContainerSizeMask) {
279 size = readBigEndianInteger(offset);
280 }
281 return size;
282}
283
284std::string_view PListReader::view(uint64_t offset, uint64_t size) const
285{
286 return offset + size < (uint64_t)m_data.size() ? std::string_view(m_data.constData() + offset, size) : std::string_view();
287}
288
289uint64_t PListReader::readObjectRef(std::string_view data, uint64_t index) const
290{
291 if ((index + 1) * trailer()->objectRefSize > data.size()) {
292 qDebug() << "object reference read beyond data size";
293 return {};
294 }
295
296 uint64_t ref = 0;
297 for (auto i = 0; i < trailer()->objectRefSize; ++i) {
298 ref <<= 8;
299 ref |= (uint8_t)data[index * trailer()->objectRefSize + i];
300 }
301 return ref;
302}
303
304QJsonValue PListReader::unpackKeyedArchive() const
305{
306 // TODO cycle detection
307 const auto root = object(rootObjectIndex()).value<PListDict>();
308 if (root.object(QLatin1StringView("$archiver")).toString() !=
309 QLatin1StringView("NSKeyedArchiver")) {
310 qDebug() << "not NSKeyedArchiver data"
311 << root.object(QLatin1StringView("$archiver"));
312 return {};
313 }
314
315 const auto top = root.object(QLatin1StringView("$top")).value<PListDict>();
316 const auto objects =
317 root.object(QLatin1StringView("$objects")).value<PListArray>();
318 const auto uid = top.object(QLatin1StringView("root")).value<PListUid>();
319 return unpackKeyedArchiveRecursive(uid, objects);
320}
321
322QJsonValue PListReader::unpackKeyedArchiveRecursive(PListUid uid, const PListArray &objects) const
323{
324 const auto type = objects.objectType(uid);
325 const auto v = objects.object(uid);
326 switch (type) {
327 case PListObjectType::Dict:
328 {
329 const auto obj = v.value<PListDict>();
330 const auto classObj =
331 objects
332 .object(obj.object(QLatin1StringView("$class"))
333 .value<PListUid>())
334 .value<PListDict>();
335 const auto classNames =
336 classObj.object(QLatin1StringView("$classes"))
337 .value<PListArray>();
338 for (uint64_t j = 0; j < classNames.size(); ++j) {
339 const auto className = classNames.object(j).toString();
340 if (className == QLatin1StringView("NSDictionary")) {
341 QJsonObject result;
342 const auto keys = obj.object(QLatin1StringView("NS.keys"))
343 .value<PListArray>();
344 const auto vals = obj.object(QLatin1StringView("NS.objects"))
345 .value<PListArray>();
346 for (uint64_t i = 0; i < std::min(keys.size(), vals.size());
347 ++i) {
348 const auto key =
349 objects.object(keys.object(i).value<PListUid>())
350 .toString();
351 const auto valUid = vals.object(i).value<PListUid>();
352 result.insert(key,
353 unpackKeyedArchiveRecursive(valUid, objects));
354 }
355 return result;
356 }
357 if (className == QLatin1StringView("NSArray")) {
358 QJsonArray result;
359 const auto elems = obj.object(QLatin1StringView("NS.objects"))
360 .value<PListArray>();
361 for (uint64_t i = 0; i < elems.size(); ++i) {
362 const auto elemUid = elems.object(i).value<PListUid>();
363 result.push_back(
364 unpackKeyedArchiveRecursive(elemUid, objects));
365 }
366 return result;
367 }
368 }
369 qDebug() << "unhandled dict object" << uid.value;
370 break;
371 }
372 case PListObjectType::Int:
373 return v.toInt();
374 case PListObjectType::String:
375 return v.toString();
376 default:
377 qDebug() << "unhandled class" << (int)type;
378 return {};
379 }
380
381 return {};
382}
383
384bool PListReader::maybePList(const QByteArray &data)
385{
386 if ((std::size_t)data.size() <= sizeof(PListHeader) + sizeof(PListReader)) {
387 return false;
388 }
389
390 const auto header = reinterpret_cast<const PListHeader*>(data.constData());
391 if (std::memcmp(header->magic, PListMagic, PListMagicSize) != 0) {
392 return false;
393 }
394 qDebug() << "found plist version:" << header->version[0] << header->version[1];
395
396 // verify trailer content
397 const auto t = reinterpret_cast<const PListTrailer*>(data.constData() + data.size() - sizeof(PListTrailer));
398 if (t->offsetIntSize == 0 || t->offsetIntSize > 8 || t->objectRefSize == 0 || t->objectRefSize > 8) {
399 return false;
400 }
401
402 return t->offsetTableOffset + t->numObjects * t->offsetIntSize < (uint64_t)data.size();
403}
char * toString(const EngineQuery &query)
Classes for reservation/travel data models, data extraction and data augmentation.
Definition berelement.h:17
bool isValid(QStringView ifopt)
const char * constData() const const
bool isEmpty() const const
qsizetype size() const const
void push_back(const QJsonValue &value)
iterator insert(QLatin1StringView key, const QJsonValue &value)
QJsonValue value(QLatin1StringView key) const const
bool isEmpty() const const
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
QVariant fromValue(T &&value)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:09:59 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.