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