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