MythTV master
mythbinaryplist.cpp
Go to the documentation of this file.
1/* Class PList
2*
3* Copyright (C) Mark Kendall 2012
4*
5* This program is free software; you can redistribute it and/or modify
6* it under the terms of the GNU General Public License as published by
7* the Free Software Foundation; either version 2 of the License, or
8* (at your option) any later version.
9*
10* This program is distributed in the hope that it will be useful,
11* but WITHOUT ANY WARRANTY; without even the implied warranty of
12* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13* GNU General Public License for more details.
14*
15* You should have received a copy of the GNU General Public License
16* along with this program; if not, write to the Free Software
17* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
18*/
19
29// TODO
30// parse uid (and use QPair to differentiate?)
31
32// Std
33#include <array>
34#include <cmath>
35#include <limits> // workaround QTBUG-90395
36
37// Qt
38#include <QtGlobal>
39#include <QChar> // Fix Qt6 GCC SFINAE warning
40#include <QBitArray> // Fix Qt6 GCC SFINAE warning
41#include <QtEndian>
42#include <QDateTime>
43#if QT_VERSION < QT_VERSION_CHECK(6,11,0)
44#include <QSequentialIterable>
45#else
46#include <QMetaSequence>
47#endif
48#include <QTextStream>
49#include <QTimeZone>
50#include <QBuffer>
51
52// MythTV
53#include "mythlogging.h"
54#include "mythbinaryplist.h"
55
56#define LOC QString("PList: ")
57
58static const QByteArray MAGIC { "bplist" };
59static const QByteArray VERSION { "00" };
60#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
61static constexpr int8_t MAGIC_SIZE { 6 };
62static constexpr int8_t VERSION_SIZE { 2 };
63static constexpr int8_t TRAILER_SIZE { 26 };
64static constexpr int8_t MIN_SIZE { MAGIC_SIZE + VERSION_SIZE + TRAILER_SIZE};
65#else
66static constexpr ssize_t MAGIC_SIZE { 6 };
67static constexpr ssize_t VERSION_SIZE { 2 };
68static constexpr ssize_t TRAILER_SIZE { 26 };
69static constexpr ssize_t MIN_SIZE { MAGIC_SIZE + VERSION_SIZE + TRAILER_SIZE};
70#endif
71static constexpr uint8_t TRAILER_OFFSIZE_INDEX { 0 };
72static constexpr uint8_t TRAILER_PARMSIZE_INDEX { 1 };
73static constexpr uint8_t TRAILER_NUMOBJ_INDEX { 2 };
74static constexpr uint8_t TRAILER_ROOTOBJ_INDEX { 10 };
75static constexpr uint8_t TRAILER_OFFTAB_INDEX { 18 };
76
77// Apple's Core Data epoch starts 1/1/2001
78static constexpr uint64_t CORE_DATA_EPOCH { 978307200 };
79
80enum : std::uint8_t
81{
92 BPLIST_UID = 0x70,
94 BPLIST_SET = 0xC0,
96};
97
98template <typename T>
99static T convert_float(const uint8_t *p)
100{
101// note: floating point endianness is not necessarily the same as integer endianness
102#if (Q_BYTE_ORDER == Q_BIG_ENDIAN) && !defined (__VFP_FP__)
103 return *(reinterpret_cast<const T *>(p));
104#else
105 static std::array<uint8_t,sizeof(T)> temp;
106 for (size_t i = 0; i < (sizeof(T) / 2); i++)
107 {
108 size_t j = sizeof(T) - 1 - i;
109 temp[i] = p[j];
110 temp[j] = p[i];
111 }
112 return *(reinterpret_cast<const T *>(temp.data()));
113#endif
114}
115
116MythBinaryPList::MythBinaryPList(const QByteArray& Data)
117{
118 ParseBinaryPList(Data);
119}
120
121QVariant MythBinaryPList::GetValue(const QString& Key)
122{
123#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
124 auto type = static_cast<QMetaType::Type>(m_result.type());
125#else
126 auto type = m_result.typeId();
127#endif
128
129 if (type != QMetaType::QVariantMap)
130 return {};
131
132 QVariantMap map = m_result.toMap();
133 for (auto it = map.cbegin(); it != map.cend(); ++it)
134 if (Key == it.key())
135 return it.value();
136 return {};
137}
138
140{
141 QByteArray res;
142 QBuffer buf(&res);
143 buf.open(QBuffer::WriteOnly);
144 if (!ToXML(&buf))
145 return {""};
146 return {res.data()};
147}
148
150{
151 QXmlStreamWriter xml(Device);
152 xml.setAutoFormatting(true);
153 xml.setAutoFormattingIndent(4);
154 xml.writeStartDocument();
155 xml.writeDTD(R"(<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">)");
156 xml.writeStartElement("plist");
157 xml.writeAttribute("version", "1.0");
158 bool success = ToXML(m_result, xml);
159 xml.writeEndElement();
160 xml.writeEndDocument();
161 if (!success)
162 LOG(VB_GENERAL, LOG_WARNING, LOC + "Invalid result.");
163 return success;
164}
165
166bool MythBinaryPList::ToXML(const QVariant& Data, QXmlStreamWriter& Xml)
167{
168#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
169 auto type = static_cast<QMetaType::Type>(Data.type());
170#else
171 auto type = Data.typeId();
172#endif
173 switch (type)
174 {
175 case QMetaType::QVariantMap:
176 DictToXML(Data, Xml);
177 break;
178 case QMetaType::QVariantList:
179 ArrayToXML(Data, Xml);
180 break;
181 case QMetaType::Double:
182 Xml.writeTextElement("real", QString("%1").arg(Data.toDouble(), 0, 'f', 6));
183 break;
184 case QMetaType::QByteArray:
185 Xml.writeTextElement("data", Data.toByteArray().toBase64().data());
186 break;
187 case QMetaType::ULongLong:
188 Xml.writeTextElement("integer", QString("%1").arg(Data.toULongLong()));
189 break;
190 case QMetaType::QString:
191 Xml.writeTextElement("string", Data.toString());
192 break;
193 case QMetaType::QDateTime:
194 Xml.writeTextElement("date", Data.toDateTime().toString(Qt::ISODate));
195 break;
196 case QMetaType::Bool:
197 {
198 bool val = Data.toBool();
199 Xml.writeEmptyElement(val ? "true" : "false");
200 }
201 break;
202 default:
203 LOG(VB_GENERAL, LOG_WARNING, LOC + "Unknown type.");
204 return false;
205 }
206 return true;
207}
208
209void MythBinaryPList::DictToXML(const QVariant& Data, QXmlStreamWriter& Xml)
210{
211 Xml.writeStartElement("dict");
212 QVariantMap map = Data.toMap();
213 for (auto it = map.cbegin(); it != map.cend(); ++it)
214 {
215 Xml.writeStartElement("key");
216 Xml.writeCharacters(it.key());
217 Xml.writeEndElement();
218 ToXML(it.value(), Xml);
219 }
220 Xml.writeEndElement();
221}
222
223void MythBinaryPList::ArrayToXML(const QVariant& Data, QXmlStreamWriter& Xml)
224{
225 Xml.writeStartElement("array");
226#if QT_VERSION < QT_VERSION_CHECK(6,11,0)
227 auto list = Data.value<QSequentialIterable>();
228#else
229 auto list = Data.value<QMetaSequence::Iterable>();
230#endif
231 for (const auto & item : std::as_const(list))
232 ToXML(item, Xml);
233 Xml.writeEndElement();
234}
235
236void MythBinaryPList::ParseBinaryPList(const QByteArray& Data)
237{
238 // reset
239 m_result = QVariant();
240
241 // check minimum size
242 auto size = Data.size();
243 if (size < MIN_SIZE)
244 return;
245
246 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Binary: size %1, startswith '%2'")
247 .arg(size).arg(Data.left(8).data()));
248
249 // check plist type & version
250 if ((!Data.startsWith(MAGIC)) || (Data.mid(MAGIC_SIZE, VERSION_SIZE) != VERSION))
251 {
252 LOG(VB_GENERAL, LOG_ERR, LOC + "Unrecognised start sequence. Corrupt?");
253 return;
254 }
255
256 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Parsing binary plist (%1 bytes)").arg(size));
257
258 m_data = reinterpret_cast<uint8_t*>(const_cast<char*>(Data.data()));
259 uint8_t* trailer = m_data + size - TRAILER_SIZE;
260 m_offsetSize = *(trailer + TRAILER_OFFSIZE_INDEX);
261 m_parmSize = *(trailer + TRAILER_PARMSIZE_INDEX);
262 m_numObjs = qFromBigEndian<quint64>(trailer + TRAILER_NUMOBJ_INDEX);
263 m_rootObj = qFromBigEndian<quint64>(trailer + TRAILER_ROOTOBJ_INDEX);
264 auto offset_tindex = qFromBigEndian<quint64>(trailer + TRAILER_OFFTAB_INDEX);
265 m_offsetTable = m_data + offset_tindex;
266
267 LOG(VB_GENERAL, LOG_DEBUG, LOC +
268 QString("numObjs: %1 parmSize: %2 offsetSize: %3 rootObj: %4"
269 "offset_tindex: %5").arg(m_numObjs).arg(m_parmSize)
270 .arg(m_offsetSize).arg(m_rootObj).arg(offset_tindex));
271
272 // something wrong?
273 if (!m_numObjs || !m_parmSize || !m_offsetSize)
274 {
275 LOG(VB_GENERAL, LOG_ERR, LOC + "Error parsing binary plist. Corrupt?");
276 return;
277 }
278
279 // parse
281 LOG(VB_GENERAL, LOG_INFO, LOC + "Parse complete.");
282}
283
285{
286 uint8_t* data = GetBinaryObject(Num);
287 if (!data)
288 return {};
289
290 quint16 type = (*data) & 0xf0;
291 uint64_t size = (*data) & 0x0f;
292
293 switch (type)
294 {
295 case BPLIST_SET:
296 case BPLIST_ARRAY: return ParseBinaryArray(data);
297 case BPLIST_DICT: return ParseBinaryDict(data);
298 case BPLIST_STRING: return ParseBinaryString(data);
299 case BPLIST_UINT: return ParseBinaryUInt(&data);
300 case BPLIST_REAL: return ParseBinaryReal(data);
301 case BPLIST_DATE: return ParseBinaryDate(data);
302 case BPLIST_DATA: return ParseBinaryData(data);
303 case BPLIST_UNICODE: return ParseBinaryUnicode(data);
304 case BPLIST_NULL:
305 {
306 switch (size)
307 {
308 case BPLIST_TRUE: return {true};
309 case BPLIST_FALSE: return {false};
310 case BPLIST_NULL:
311 default: return {};
312 }
313 }
314 case BPLIST_UID: // FIXME
315 default: break;
316 }
317
318 return {};
319}
320
321uint64_t MythBinaryPList::GetBinaryUInt(uint8_t *Data, uint64_t Size)
322{
323 if (Size == 1) return static_cast<uint64_t>(*Data);
324 if (Size == 2) return qFromBigEndian<quint16>(Data);
325 if (Size == 4) return qFromBigEndian<quint32>(Data);
326 if (Size == 8) return qFromBigEndian<quint64>(Data);
327 if (Size == 3)
328 {
329#if (Q_BYTE_ORDER == Q_BIG_ENDIAN)
330 return static_cast<uint64_t>(((*Data) << 16) + (*(Data + 1) << 8) + (*(Data + 2)));
331#else
332 return static_cast<uint64_t>((*Data) + (*(Data + 1) << 8) + ((*(Data + 2)) << 16));
333#endif
334 }
335
336 return 0;
337}
338
340{
341 if (Num > m_numObjs)
342 return nullptr;
343
344 uint8_t* p = m_offsetTable + (Num * m_offsetSize);
345 uint64_t offset = GetBinaryUInt(p, m_offsetSize);
346 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("GetBinaryObject num %1, offsize %2 offset %3")
347 .arg(Num).arg(m_offsetSize).arg(offset));
348 return m_data + offset;
349}
350
351QVariantMap MythBinaryPList::ParseBinaryDict(uint8_t *Data)
352{
353 QVariantMap result;
354 if (((*Data) & 0xf0) != BPLIST_DICT)
355 return result;
356
357 uint64_t count = GetBinaryCount(&Data);
358 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Dict: Size %1").arg(count));
359 if (!count)
360 return result;
361
362 uint64_t off = m_parmSize * count;
363 for (uint64_t i = 0; i < count; i++, Data += m_parmSize)
364 {
365 uint64_t keyobj = GetBinaryUInt(Data, m_parmSize);
366 uint64_t valobj = GetBinaryUInt(Data + off, m_parmSize);
367 QVariant key = ParseBinaryNode(keyobj);
368 QVariant val = ParseBinaryNode(valobj);
369 if (!key.canConvert<QString>())
370 {
371 LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid dictionary key type.");
372 return result;
373 }
374 result.insert(key.toString(), val);
375 }
376 return result;
377}
378
379QList<QVariant> MythBinaryPList::ParseBinaryArray(uint8_t* Data)
380{
381 QList<QVariant> result;
382 if (((*Data) & 0xf0) != BPLIST_ARRAY)
383 return result;
384
385 uint64_t count = GetBinaryCount(&Data);
386 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Array: Size %1").arg(count));
387 if (!count)
388 return result;
389
390 result.reserve(count);
391 for (uint64_t i = 0; i < count; i++, Data += m_parmSize)
392 {
393 uint64_t obj = GetBinaryUInt(Data, m_parmSize);
394 QVariant val = ParseBinaryNode(obj);
395 result.push_back(val);
396 }
397 return result;
398}
399
400QVariant MythBinaryPList::ParseBinaryUInt(uint8_t** Data)
401{
402 uint64_t result = 0;
403 if (((**Data) & 0xf0) != BPLIST_UINT)
404 return {static_cast<quint64>(result)};
405
406 uint64_t size = 1 << ((**Data) & 0x0f);
407 (*Data)++;
408 result = GetBinaryUInt(*Data, size);
409 (*Data) += size;
410
411 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("UInt: %1").arg(result));
412 return {static_cast<quint64>(result)};
413}
414
416{
417 QString result;
418 if (((*Data) & 0xf0) != BPLIST_STRING)
419 return result;
420
421 uint64_t count = GetBinaryCount(&Data);
422 if (!count)
423 return result;
424
425 result = QString::fromLatin1(reinterpret_cast<const char*>(Data), static_cast<int>(count));
426 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("ASCII String: %1").arg(result));
427 return {result};
428}
429
430QVariant MythBinaryPList::ParseBinaryReal(uint8_t* Data)
431{
432 double result = 0.0;
433 if (((*Data) & 0xf0) != BPLIST_REAL)
434 return result;
435
436 uint64_t count = GetBinaryCount(&Data);
437 if (!count)
438 return result;
439
440 count = 1ULL << count;
441 if (count == sizeof(float))
442 {
443 result = convert_float<float>(Data);
444 }
445 else if (count == sizeof(double))
446 {
447 result = convert_float<double>(Data);
448 }
449
450 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Real: %1").arg(result, 0, 'f', 6));
451 return {result};
452}
453
454QVariant MythBinaryPList::ParseBinaryDate(uint8_t* Data)
455{
456 QDateTime result;
457 if (((*Data) & 0xf0) != BPLIST_DATE)
458 return result;
459
460 uint64_t count = GetBinaryCount(&Data);
461 if (count != 3)
462 return result;
463
464 auto sec = static_cast<uint64_t>(convert_float<double>(Data));
465#if QT_VERSION < QT_VERSION_CHECK(6,5,0)
466 result = QDateTime::fromSecsSinceEpoch(CORE_DATA_EPOCH + sec, Qt::UTC);
467#else
469 QTimeZone(QTimeZone::UTC));
470#endif
471
472 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Date: %1").arg(result.toString(Qt::ISODate)));
473 return {result};
474}
475
476QVariant MythBinaryPList::ParseBinaryData(uint8_t* Data)
477{
478 QByteArray result;
479 if (((*Data) & 0xf0) != BPLIST_DATA)
480 return result;
481
482 uint64_t count = GetBinaryCount(&Data);
483 if (!count)
484 return result;
485
486 result = QByteArray(reinterpret_cast<const char*>(Data), static_cast<int>(count));
487 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Data: Size %1 (count %2)")
488 .arg(result.size()).arg(count));
489 return {result};
490}
491
493{
494 QString result;
495 if (((*Data) & 0xf0) != BPLIST_UNICODE)
496 return result;
497
498 uint64_t count = GetBinaryCount(&Data);
499 if (!count)
500 return result;
501
502 // source is big endian (and no BOM?)
503 QByteArray tmp;
504 for (uint64_t i = 0; i < count; i++, Data += 2)
505 {
506 auto twobyte = qFromBigEndian<quint16>(Data);
507 tmp.append(static_cast<char>(twobyte & 0xff));
508 tmp.append(static_cast<char>((twobyte >> 8) & 0xff));
509 }
510 result = QString::fromUtf16(reinterpret_cast<const char16_t*>(tmp.data()), static_cast<int>(count));
511 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Unicode: %1").arg(result));
512 return {result};
513}
514
515uint64_t MythBinaryPList::GetBinaryCount(uint8_t** Data)
516{
517 uint64_t count = (**Data) & 0x0f;
518 (*Data)++;
519 if (count == 0x0f)
520 {
521 QVariant newcount = ParseBinaryUInt(Data);
522 if (!newcount.canConvert<uint64_t>())
523 return 0;
524 count = newcount.toULongLong();
525 }
526 return count;
527}
A device containing images (ie. USB stick, CD, storage group etc)
QVariant GetValue(const QString &Key)
QVariantMap ParseBinaryDict(uint8_t *Data)
uint8_t * m_offsetTable
QList< QVariant > ParseBinaryArray(uint8_t *Data)
static QVariant ParseBinaryUnicode(uint8_t *Data)
QVariant ParseBinaryNode(uint64_t Num)
void ParseBinaryPList(const QByteArray &Data)
static QVariant ParseBinaryDate(uint8_t *Data)
static uint64_t GetBinaryUInt(uint8_t *Data, uint64_t Size)
static QVariant ParseBinaryData(uint8_t *Data)
static QVariant ParseBinaryString(uint8_t *Data)
uint8_t * GetBinaryObject(uint64_t Num)
static uint64_t GetBinaryCount(uint8_t **Data)
void ArrayToXML(const QVariant &Data, QXmlStreamWriter &Xml)
void DictToXML(const QVariant &Data, QXmlStreamWriter &Xml)
bool ToXML(QIODevice *Device)
MythBinaryPList(const QByteArray &Data)
static QVariant ParseBinaryReal(uint8_t *Data)
static QVariant ParseBinaryUInt(uint8_t **Data)
#define LOC
static constexpr ssize_t MIN_SIZE
static constexpr uint8_t TRAILER_ROOTOBJ_INDEX
static T convert_float(const uint8_t *p)
static constexpr ssize_t MAGIC_SIZE
static constexpr uint8_t TRAILER_PARMSIZE_INDEX
static const QByteArray MAGIC
static constexpr ssize_t VERSION_SIZE
static constexpr uint8_t TRAILER_OFFSIZE_INDEX
static constexpr uint8_t TRAILER_OFFTAB_INDEX
static constexpr ssize_t TRAILER_SIZE
static const QByteArray VERSION
static constexpr uint64_t CORE_DATA_EPOCH
static constexpr uint8_t TRAILER_NUMOBJ_INDEX
@ BPLIST_STRING
@ BPLIST_FILL
@ BPLIST_ARRAY
@ BPLIST_DATE
@ BPLIST_UID
@ BPLIST_UINT
@ BPLIST_REAL
@ BPLIST_UNICODE
@ BPLIST_SET
@ BPLIST_TRUE
@ BPLIST_NULL
@ BPLIST_FALSE
@ BPLIST_DICT
@ BPLIST_DATA
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MBASE_PUBLIC QDateTime fromSecsSinceEpoch(int64_t seconds)
This function takes the number of seconds since the start of the epoch and returns a QDateTime with t...
Definition: mythdate.cpp:81
@ ISODate
Default UTC.
Definition: mythdate.h:18