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  for (const auto& item : data.toList())
211  ToXML(item, xml);
212 
213  xml.writeEndElement();
214 }
215 
216 void PList::ParseBinaryPList(const QByteArray &data)
217 {
218  // reset
219  m_result = QVariant();
220 
221  // check minimum size
222  auto size = static_cast<quint32>(data.size());
223  if (size < MIN_SIZE)
224  return;
225 
226  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Binary: size %1, startswith '%2'")
227  .arg(size).arg(data.left(8).data()));
228 
229  // check plist type & version
230  if ((!data.startsWith(MAGIC)) ||
231  (data.mid(MAGIC_SIZE, VERSION_SIZE) != VERSION))
232  {
233  LOG(VB_GENERAL, LOG_ERR, LOC + "Unrecognised start sequence. Corrupt?");
234  return;
235  }
236 
237  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Parsing binary plist (%1 bytes)")
238  .arg(size));
239 
240  m_data = reinterpret_cast<quint8*>(const_cast<char*>(data.data()));
241  quint8* trailer = m_data + size - TRAILER_SIZE;
242  m_offsetSize = *(trailer + TRAILER_OFFSIZE_INDEX);
243  m_parmSize = *(trailer + TRAILER_PARMSIZE_INDEX);
244  m_numObjs = *(reinterpret_cast<quint64*>(convert_int(trailer + TRAILER_NUMOBJ_INDEX, 8)));
245  m_rootObj = *(reinterpret_cast<quint64*>(convert_int(trailer + TRAILER_ROOTOBJ_INDEX, 8)));
246  quint64 offset_tindex = *(reinterpret_cast<quint64*>(convert_int(trailer + TRAILER_OFFTAB_INDEX, 8)));
247  m_offsetTable = m_data + offset_tindex;
248 
249  LOG(VB_GENERAL, LOG_DEBUG, LOC +
250  QString("numObjs: %1 parmSize: %2 offsetSize: %3 rootObj: %4"
251  "offset_tindex: %5").arg(m_numObjs).arg(m_parmSize)
252  .arg(m_offsetSize).arg(m_rootObj).arg(offset_tindex));
253 
254  // something wrong?
255  if (!m_numObjs || !m_parmSize || !m_offsetSize)
256  {
257  LOG(VB_GENERAL, LOG_ERR, LOC + "Error parsing binary plist. Corrupt?");
258  return;
259  }
260 
261  // parse
263 
264  LOG(VB_GENERAL, LOG_INFO, LOC + "Parse complete.");
265 }
266 
267 QVariant PList::ParseBinaryNode(quint64 num)
268 {
269  quint8* data = GetBinaryObject(num);
270  if (!data)
271  return QVariant();
272 
273  quint16 type = (*data) & 0xf0;
274  quint64 size = (*data) & 0x0f;
275 
276  switch (type)
277  {
278  case BPLIST_SET:
279  case BPLIST_ARRAY: return ParseBinaryArray(data);
280  case BPLIST_DICT: return ParseBinaryDict(data);
281  case BPLIST_STRING: return ParseBinaryString(data);
282  case BPLIST_UINT: return ParseBinaryUInt(&data);
283  case BPLIST_REAL: return ParseBinaryReal(data);
284  case BPLIST_DATE: return ParseBinaryDate(data);
285  case BPLIST_DATA: return ParseBinaryData(data);
286  case BPLIST_UNICODE: return ParseBinaryUnicode(data);
287  case BPLIST_NULL:
288  {
289  switch (size)
290  {
291  case BPLIST_TRUE: return QVariant(true);
292  case BPLIST_FALSE: return QVariant(false);
293  case BPLIST_NULL:
294  default: return QVariant();
295  }
296  }
297  case BPLIST_UID: // FIXME
298  default: break;
299  }
300 
301  return QVariant();
302 }
303 
304 quint64 PList::GetBinaryUInt(quint8 *p, quint64 size)
305 {
306  if (size == 1) return static_cast<quint64>(*p);
307  if (size == 2) return static_cast<quint64>(*(reinterpret_cast<quint16*>(convert_int(p, 2))));
308  if (size == 4) return static_cast<quint64>(*(reinterpret_cast<quint32*>(convert_int(p, 4))));
309  if (size == 8) return (*(reinterpret_cast<quint64*>(convert_int(p, 8))));
310 
311  if (size == 3)
312  {
313 #if HAVE_BIGENDIAN
314  return static_cast<quint64>(((*p) << 16) + (*(p + 1) << 8) + (*(p + 2)));
315 #else
316  return static_cast<quint64>((*p) + (*(p + 1) << 8) + ((*(p + 2)) << 16));
317 #endif
318  }
319 
320  return 0;
321 }
322 
323 quint8* PList::GetBinaryObject(quint64 num)
324 {
325  if (num > m_numObjs)
326  return nullptr;
327 
328  quint8* p = m_offsetTable + (num * m_offsetSize);
329  quint64 offset = GetBinaryUInt(p, m_offsetSize);
330  LOG(VB_GENERAL, LOG_DEBUG, LOC +
331  QString("GetBinaryObject num %1, offsize %2 offset %3")
332  .arg(num).arg(m_offsetSize).arg(offset));
333 
334  return m_data + offset;
335 }
336 
337 QVariantMap PList::ParseBinaryDict(quint8 *data)
338 {
339  QVariantMap result;
340  if (((*data) & 0xf0) != BPLIST_DICT)
341  return result;
342 
343  quint64 count = GetBinaryCount(&data);
344 
345  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Dict: Size %1").arg(count));
346 
347  if (!count)
348  return result;
349 
350  quint64 off = m_parmSize * count;
351  for (quint64 i = 0; i < count; i++, data += m_parmSize)
352  {
353  quint64 keyobj = GetBinaryUInt(data, m_parmSize);
354  quint64 valobj = GetBinaryUInt(data + off, m_parmSize);
355  QVariant key = ParseBinaryNode(keyobj);
356  QVariant val = ParseBinaryNode(valobj);
357  if (!key.canConvert<QString>())
358  {
359  LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid dictionary key type.");
360  return result;
361  }
362 
363  result.insert(key.toString(), val);
364  }
365 
366  return result;
367 }
368 
369 QList<QVariant> PList::ParseBinaryArray(quint8 *data)
370 {
371  QList<QVariant> result;
372  if (((*data) & 0xf0) != BPLIST_ARRAY)
373  return result;
374 
375  quint64 count = GetBinaryCount(&data);
376 
377  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Array: Size %1").arg(count));
378 
379  if (!count)
380  return result;
381 
382  for (quint64 i = 0; i < count; i++, data += m_parmSize)
383  {
384  quint64 obj = GetBinaryUInt(data, m_parmSize);
385  QVariant val = ParseBinaryNode(obj);
386  result.push_back(val);
387  }
388  return result;
389 }
390 
391 QVariant PList::ParseBinaryUInt(quint8 **data)
392 {
393  quint64 result = 0;
394  if (((**data) & 0xf0) != BPLIST_UINT)
395  return QVariant(result);
396 
397  quint64 size = 1 << ((**data) & 0x0f);
398  (*data)++;
399  result = GetBinaryUInt(*data, size);
400  (*data) += size;
401 
402  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("UInt: %1").arg(result));
403  return QVariant(result);
404 }
405 
406 QVariant PList::ParseBinaryString(quint8 *data)
407 {
408  QString result;
409  if (((*data) & 0xf0) != BPLIST_STRING)
410  return result;
411 
412  quint64 count = GetBinaryCount(&data);
413  if (!count)
414  return result;
415 
416  result = QString::fromLatin1(reinterpret_cast<const char*>(data), static_cast<int>(count));
417  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("ASCII String: %1").arg(result));
418  return QVariant(result);
419 }
420 
421 QVariant PList::ParseBinaryReal(quint8 *data)
422 {
423  double result = 0.0;
424  if (((*data) & 0xf0) != BPLIST_REAL)
425  return result;
426 
427  quint64 count = GetBinaryCount(&data);
428  if (!count)
429  return result;
430 
431  count = 1ULL << count;
432  if (count == sizeof(float))
433  {
434  convert_float(data, static_cast<quint8>(count));
435  float temp = NAN;
436  std::copy(data, data + sizeof(float), reinterpret_cast<quint8*>(&temp));
437  result = static_cast<double>(temp);
438  }
439  else if (count == sizeof(double))
440  {
441  convert_float(data, static_cast<quint8>(count));
442  std::copy(data, data + sizeof(double), reinterpret_cast<quint8*>(&result));
443  }
444 
445  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Real: %1").arg(result, 0, 'f', 6));
446  return QVariant(result);
447 }
448 
449 QVariant PList::ParseBinaryDate(quint8 *data)
450 {
451  QDateTime result;
452  if (((*data) & 0xf0) != BPLIST_DATE)
453  return result;
454 
455  quint64 count = GetBinaryCount(&data);
456  if (count != 3)
457  return result;
458 
459  convert_float(data, 8);
460  double temp = NAN;
461  std::copy(data, data + sizeof(double), reinterpret_cast<quint8*>(&temp));
462  auto msec = static_cast<quint64>(temp * 1000.0);
463  result = QDateTime::fromMSecsSinceEpoch(static_cast<qint64>(msec), Qt::UTC);
464  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Date: %1").arg(result.toString(Qt::ISODate)));
465  return QVariant(result);
466 }
467 
468 QVariant PList::ParseBinaryData(quint8 *data)
469 {
470  QByteArray result;
471  if (((*data) & 0xf0) != BPLIST_DATA)
472  return result;
473 
474  quint64 count = GetBinaryCount(&data);
475  if (!count)
476  return result;
477 
478  result = QByteArray(reinterpret_cast<const char*>(data), static_cast<int>(count));
479  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Data: Size %1 (count %2)")
480  .arg(result.size()).arg(count));
481  return QVariant(result);
482 }
483 
484 QVariant PList::ParseBinaryUnicode(quint8 *data)
485 {
486  QString result;
487  if (((*data) & 0xf0) != BPLIST_UNICODE)
488  return result;
489 
490  quint64 count = GetBinaryCount(&data);
491  if (!count)
492  return result;
493 
494  // source is big endian (and no BOM?)
495  QByteArray tmp;
496  for (quint64 i = 0; i < count; i++, data += 2)
497  {
498  quint16 twobyte = *(reinterpret_cast<quint16*>(convert_int(data, 2)));
499  tmp.append(static_cast<char>(twobyte & 0xff));
500  tmp.append(static_cast<char>((twobyte >> 8) & 0xff));
501  }
502  result = QString::fromUtf16(reinterpret_cast<const quint16*>(tmp.data()), static_cast<int>(count));
503  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Unicode: %1").arg(result));
504  return QVariant(result);
505 }
506 
507 quint64 PList::GetBinaryCount(quint8 **data)
508 {
509  quint64 count = (**data) & 0x0f;
510  (*data)++;
511  if (count == 0x0f)
512  {
513  QVariant newcount = ParseBinaryUInt(data);
514  if (!newcount.canConvert<quint64>())
515  return 0;
516  count = newcount.toULongLong();
517  }
518  return count;
519 }
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:449
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:308
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:304
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:30
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:323
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:468
PList::ParseBinaryPList
void ParseBinaryPList(const QByteArray &data)
Definition: plist.cpp:216
PList::ParseBinaryUnicode
static QVariant ParseBinaryUnicode(quint8 *data)
Definition: plist.cpp:484
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:507
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:406
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:267
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:369
PList::ParseBinaryDict
QVariantMap ParseBinaryDict(quint8 *data)
Definition: plist.cpp:337
PList::ParseBinaryUInt
static QVariant ParseBinaryUInt(quint8 **data)
Definition: plist.cpp:391
PList::ParseBinaryReal
static QVariant ParseBinaryReal(quint8 *data)
Definition: plist.cpp:421
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