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