Torc  0.1
torcplist.cpp
Go to the documentation of this file.
1 /* Class TorcPList
2 *
3 * Copyright (C) Mark Kendall 2012-18
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19 
20 // Qt
21 #include <QtEndian>
22 #include <QDateTime>
23 #include <QTextStream>
24 #include <QTextCodec>
25 #include <QBuffer>
26 #include <QUuid>
27 
28 // Torc
29 #include "torclogging.h"
30 #include "torcplist.h"
31 
32 #define MAGIC QByteArray("bplist")
33 #define VERSION QByteArray("00")
34 #define MAGIC_SIZE 6
35 #define VERSION_SIZE 2
36 #define TRAILER_SIZE 26
37 #define MIN_SIZE (MAGIC_SIZE + VERSION_SIZE + TRAILER_SIZE)
38 #define TRAILER_OFFSIZE_INDEX 0
39 #define TRAILER_PARMSIZE_INDEX 1
40 #define TRAILER_NUMOBJ_INDEX 2
41 #define TRAILER_ROOTOBJ_INDEX 10
42 #define TRAILER_OFFTAB_INDEX 18
43 
44 // NB Do not call this twice on the same data
45 static void convert_float(quint8 *p, quint8 s)
46 {
47 #if Q_BYTE_ORDER == Q_BIG_ENDIAN && !defined (__VFP_FP__)
48  return;
49 #else
50  for (quint8 i = 0; i < (s / 2); i++)
51  {
52  quint8 t = p[i];
53  quint8 j = ((s - 1) + 0) - i;
54  p[i] = p[j];
55  p[j] = t;
56  }
57 #endif
58 }
59 
66 TorcPList::TorcPList(const QByteArray &Data)
67  : m_result(),
68  m_data(nullptr),
69  m_offsetTable(nullptr),
70  m_rootObj(0),
71  m_numObjs(0),
72  m_offsetSize(0),
73  m_parmSize(0),
74  m_codec(QTextCodec::codecForName("UTF-16BE"))
75 {
76  ParseBinaryPList(Data);
77 }
78 
80 QVariant TorcPList::GetValue(const QString &Key)
81 {
82  if (m_result.type() != QVariant::Map)
83  return QVariant();
84 
85  QVariantMap map = m_result.toMap();
86  QMapIterator<QString,QVariant> it(map);
87  while (it.hasNext())
88  {
89  it.next();
90  if (Key == it.key())
91  return it.value();
92  }
93  return QVariant();
94 }
95 
97 QString TorcPList::ToString(void)
98 {
99  QByteArray res;
100  QBuffer buf(&res);
101  buf.open(QBuffer::WriteOnly);
102  if (!this->ToXML(&buf))
103  return QStringLiteral("");
104  return QString::fromUtf8(res.data());
105 }
106 
108 bool TorcPList::ToXML(QIODevice *Device)
109 {
110  QXmlStreamWriter XML(Device);
111  XML.setAutoFormatting(true);
112  XML.setAutoFormattingIndent(4);
113  XML.writeStartDocument();
114  XML.writeDTD(QStringLiteral("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"));
115  XML.writeStartElement(QStringLiteral("plist"));
116  XML.writeAttribute(QStringLiteral("version"), QStringLiteral("1.0"));
117  bool success = ToXML(m_result, XML);
118  XML.writeEndElement();
119  XML.writeEndDocument();
120  if (!success)
121  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Invalid result."));
122  return success;
123 }
124 
126 bool TorcPList::ToXML(const QVariant &Data, QXmlStreamWriter &XML)
127 {
128  switch (static_cast<QMetaType::Type>(Data.type()))
129  {
130  case QMetaType::QVariantMap:
131  DictToXML(Data, XML);
132  break;
133  case QMetaType::QVariantList:
134  ArrayToXML(Data, XML);
135  break;
136  case QMetaType::Int:
137  case QMetaType::Short:
138  case QMetaType::Long:
139  case QMetaType::LongLong:
140  case QMetaType::Float:
141  case QMetaType::Double:
142  XML.writeTextElement(QStringLiteral("real"), QStringLiteral("%1").arg(Data.toDouble(), 0, 'f', 6));
143  break;
144  case QMetaType::QUuid:
145  XML.writeTextElement(QStringLiteral("uid"), Data.toByteArray().toHex().data());
146  break;
147  case QMetaType::QByteArray:
148  XML.writeTextElement(QStringLiteral("data"), Data.toByteArray().toBase64().data());
149  break;
150  case QMetaType::UInt:
151  case QMetaType::UShort:
152  case QMetaType::ULong:
153  case QMetaType::ULongLong:
154  XML.writeTextElement(QStringLiteral("integer"), QStringLiteral("%1").arg(Data.toULongLong()));
155  break;
156  case QMetaType::QString: XML.writeTextElement(QStringLiteral("string"), Data.toString());
157  break;
158  case QMetaType::QDateTime:
159  XML.writeTextElement(QStringLiteral("date"), Data.toDateTime().toString(Qt::ISODate));
160  break;
161  case QMetaType::Bool:
162  {
163  bool val = Data.toBool();
164  XML.writeEmptyElement(val ? QStringLiteral("true") : QStringLiteral("false"));
165  }
166  break;
167  case QMetaType::Char:
168  XML.writeEmptyElement(QStringLiteral("fill"));
169  break;
170  default:
171  LOG(VB_GENERAL, LOG_WARNING, QStringLiteral("Unknown type."));
172  return false;
173  }
174  return true;
175 }
176 
178 void TorcPList::DictToXML(const QVariant &Data, QXmlStreamWriter &XML)
179 {
180  XML.writeStartElement(QStringLiteral("dict"));
181 
182  QVariantMap map = Data.toMap();
183  QMapIterator<QString,QVariant> it(map);
184  while (it.hasNext())
185  {
186  it.next();
187  XML.writeTextElement(QStringLiteral("key"), it.key());
188  ToXML(it.value(), XML);
189  }
190 
191  XML.writeEndElement();
192 }
193 
195 void TorcPList::ArrayToXML(const QVariant &Data, QXmlStreamWriter &XML)
196 {
197  XML.writeStartElement(QStringLiteral("array"));
198 
199  QList<QVariant> list = Data.toList();
200  foreach (const QVariant &item, list)
201  ToXML(item, XML);
202 
203  XML.writeEndElement();
204 }
205 
207 void TorcPList::ParseBinaryPList(const QByteArray &Data)
208 {
209  // reset
210  m_result = QVariant();
211 
212  // check minimum size
213  quint32 size = Data.size();
214  if (size < MIN_SIZE)
215  return;
216 
217  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Binary: size %1, startswith '%2'")
218  .arg(size).arg(Data.left(8).data()));
219 
220  // check plist type & version
221  if ((Data.left(6) != MAGIC) ||
222  (Data.mid(MAGIC_SIZE, VERSION_SIZE) != VERSION))
223  {
224  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Unrecognised start sequence. Corrupt?"));
225  return;
226  }
227 
228  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Parsing binary plist (%1 bytes)")
229  .arg(size));
230 
231  m_data = (quint8*)Data.data();
232  quint8* trailer = m_data + size - TRAILER_SIZE;
233  m_offsetSize = *(trailer + TRAILER_OFFSIZE_INDEX);
234  m_parmSize = *(trailer + TRAILER_PARMSIZE_INDEX);
235  m_numObjs = qToBigEndian(*((quint64*)(trailer + TRAILER_NUMOBJ_INDEX)));
236  m_rootObj = qToBigEndian(*((quint64*)(trailer + TRAILER_ROOTOBJ_INDEX)));
237  quint64 offset_tindex = qToBigEndian(*((quint64*)(trailer + TRAILER_OFFTAB_INDEX)));
238  m_offsetTable = m_data + offset_tindex;
239 
240  LOG(VB_GENERAL, LOG_DEBUG,
241  QStringLiteral("numObjs: %1 parmSize: %2 offsetSize: %3 rootObj: %4 offsetindex: %5")
242  .arg(m_numObjs).arg(m_parmSize).arg(m_offsetSize).arg(m_rootObj).arg(offset_tindex));
243 
244  // something wrong?
245  if (!m_numObjs || !m_parmSize || !m_offsetSize)
246  {
247  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Error parsing binary plist. Corrupt?"));
248  return;
249  }
250 
251  // parse
252  m_result = ParseBinaryNode(m_rootObj);
253 
254  LOG(VB_GENERAL, LOG_INFO, QStringLiteral("Parse complete."));
255 }
256 
257 QVariant TorcPList::ParseBinaryNode(quint64 Num)
258 {
259  quint8* data = GetBinaryObject(Num);
260  if (!data)
261  return QVariant();
262 
263  quint16 type = (*data) & BPLIST_HIGH;
264  quint64 size = (*data) & BPLIST_LOW;
265 
266  switch (type)
267  {
268  case BPLIST_SET:
269  case BPLIST_ARRAY: return ParseBinaryArray(data);
270  case BPLIST_DICT: return ParseBinaryDict(data);
271  case BPLIST_STRING: return ParseBinaryString(data);
272  case BPLIST_UINT: return ParseBinaryUInt(&data);
273  case BPLIST_REAL: return ParseBinaryReal(data);
274  case BPLIST_DATE: return ParseBinaryDate(data);
275  case BPLIST_DATA: return ParseBinaryData(data);
276  case BPLIST_UNICODE: return ParseBinaryUnicode(data);
277  case BPLIST_UID: return ParseBinaryUID(data);
278  case BPLIST_FILL: return QVariant(QChar());
279  case BPLIST_NULL:
280  {
281  switch (size)
282  {
283  case BPLIST_TRUE: return QVariant(true);
284  case BPLIST_FALSE: return QVariant(false);
285  case BPLIST_NULL: return QVariant();
286  }
287  }
288  }
289 
290  return QVariant();
291 }
292 
293 quint64 TorcPList::GetBinaryUInt(quint8 *Data, quint64 Size)
294 {
295  if (Size == 1) return (quint64)(*(Data));
296  if (Size == 2) return (quint64)(qToBigEndian(*((quint16*)Data)));
297  if (Size == 4) return (quint64)(qToBigEndian(*((quint32*)Data)));
298  if (Size == 8) return (quint64)(qToBigEndian(*((quint64*)Data)));
299 
300  if (Size == 3)
301  {
302 #if Q_BYTE_ORDER == Q_BIG_ENDIAN
303  return (quint64)(((*Data) << 16) + (*(Data + 1) << 8) + (*(Data + 2)));
304 #else
305  return (quint64)((*Data) + (*(Data + 1) << 8) + ((*(Data + 2)) << 16));
306 #endif
307  }
308 
309  return 0;
310 }
311 
312 quint8* TorcPList::GetBinaryObject(quint64 Num)
313 {
314  if (Num > m_numObjs)
315  return nullptr;
316 
317  quint8* p = m_offsetTable + (Num * m_offsetSize);
318  quint64 offset = GetBinaryUInt(p, m_offsetSize);
319  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("GetBinaryObject Num %1, offsize %2 offset %3")
320  .arg(Num).arg(m_offsetSize).arg(offset));
321 
322  return m_data + offset;
323 }
324 
325 QVariantMap TorcPList::ParseBinaryDict(quint8 *Data)
326 {
327  QVariantMap result;
328  if (((*Data) & BPLIST_HIGH) != BPLIST_DICT)
329  return result;
330 
331  quint64 count = GetBinaryCount(&Data);
332 
333  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Dict: Size %1").arg(count));
334 
335  if (!count)
336  return result;
337 
338  quint64 off = m_parmSize * count;
339  for (quint64 i = 0; i < count; i++, Data += m_parmSize)
340  {
341  quint64 keyobj = GetBinaryUInt(Data, m_parmSize);
342  quint64 valobj = GetBinaryUInt(Data + off, m_parmSize);
343  QVariant key = ParseBinaryNode(keyobj);
344  QVariant val = ParseBinaryNode(valobj);
345  if (!key.canConvert<QString>())
346  {
347  LOG(VB_GENERAL, LOG_ERR, QStringLiteral("Invalid dictionary key type '%1' (key %2)")
348  .arg(key.type()).arg(keyobj));
349  return result;
350  }
351 
352  result.insertMulti(key.toString(), val);
353  }
354 
355  return result;
356 }
357 
358 QList<QVariant> TorcPList::ParseBinaryArray(quint8 *Data)
359 {
360  QList<QVariant> result;
361  if (((*Data) & BPLIST_HIGH) != BPLIST_ARRAY)
362  return result;
363 
364  quint64 count = GetBinaryCount(&Data);
365 
366  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Array: Size %1").arg(count));
367 
368  if (!count)
369  return result;
370 
371  result.reserve(count);
372  for (quint64 i = 0; i < count; i++, Data += m_parmSize)
373  {
374  quint64 obj = GetBinaryUInt(Data, m_parmSize);
375  QVariant val = ParseBinaryNode(obj);
376  result.push_back(val);
377  }
378  return result;
379 }
380 
381 QVariant TorcPList::ParseBinaryUInt(quint8 **Data)
382 {
383  quint64 result = 0;
384  if (((**Data) & BPLIST_HIGH) != BPLIST_UINT)
385  return QVariant(result);
386 
387  quint64 size = 1 << ((**Data) & BPLIST_LOW);
388  (*Data)++;
389  result = GetBinaryUInt(*Data, size);
390  (*Data) += size;
391 
392  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("UInt: %1").arg(result));
393  return QVariant(result);
394 }
395 
396 QVariant TorcPList::ParseBinaryUID(quint8 *Data)
397 {
398  if (((*Data) & BPLIST_HIGH) != BPLIST_UID)
399  return QVariant();
400 
401  quint64 count = ((*Data) & BPLIST_LOW) + 1; // nnnn+1 bytes
402  (*Data)++;
403 
404  // these are in reality limited to a bigendian 64bit uint - with 1,2,4 or 8 bytes.
405  quint64 uid = GetBinaryUInt(Data, count);
406  // which we pack into a QUuid to identify it
407  ushort b1 = (uid & 0xff00000000000000) >> 56;
408  ushort b2 = (uid & 0x00ff000000000000) >> 48;
409  ushort b3 = (uid & 0x0000ff0000000000) >> 40;
410  ushort b4 = (uid & 0x000000ff00000000) >> 32;
411  ushort b5 = (uid & 0x00000000ff000000) >> 24;
412  ushort b6 = (uid & 0x0000000000ff0000) >> 16;
413  ushort b7 = (uid & 0x000000000000ff00) >> 8;
414  ushort b8 = (uid & 0x00000000000000ff) >> 0;
415  return QVariant(QUuid(0, 0, 0, b1, b2, b3, b4, b5, b6, b7, b8));
416 }
417 
418 QVariant TorcPList::ParseBinaryString(quint8 *Data)
419 {
420  QString result;
421  if (((*Data) & BPLIST_HIGH) != BPLIST_STRING)
422  return result;
423 
424  quint64 count = GetBinaryCount(&Data);
425  if (!count)
426  return result;
427 
428  result = QString::fromLatin1((const char*)Data, count);
429  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("ASCII String: %1").arg(result));
430  return QVariant(result);
431 }
432 
433 QVariant TorcPList::ParseBinaryReal(quint8 *Data)
434 {
435  double result = 0.0;
436  if (((*Data) & BPLIST_HIGH) != BPLIST_REAL)
437  return result;
438 
439  quint64 count = GetBinaryCount(&Data);
440  if (!count)
441  return result;
442 
443  count = (quint64)((quint64)1 << count);
444  if (count == sizeof(float))
445  {
446  convert_float(Data, count);
447  result = (double)(*((float*)Data));
448  }
449  else if (count == sizeof(double))
450  {
451  convert_float(Data, count);
452  result = *((double*)Data);
453  }
454 
455  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Real: %1").arg(result, 0, 'f', 6));
456  return QVariant(result);
457 }
458 
459 QVariant TorcPList::ParseBinaryDate(quint8 *Data)
460 {
461  QDateTime result;
462  if (((*Data) & BPLIST_HIGH) != BPLIST_DATE)
463  return result;
464 
465  quint64 count = GetBinaryCount(&Data);
466  if (count != 3)
467  return result;
468 
469  convert_float(Data, 8);
470  result = QDateTime::fromTime_t((quint64)(*((double*)Data)));
471  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Date: %1").arg(result.toString(Qt::ISODate)));
472  return QVariant(result);
473 }
474 
475 QVariant TorcPList::ParseBinaryData(quint8 *Data)
476 {
477  QByteArray result;
478  if (((*Data) & BPLIST_HIGH) != BPLIST_DATA)
479  return result;
480 
481  quint64 count = GetBinaryCount(&Data);
482  if (!count)
483  return result;
484 
485  result = QByteArray((const char*)Data, count);
486  LOG(VB_GENERAL, LOG_DEBUG, QStringLiteral("Data: Size %1 (count %2)")
487  .arg(result.size()).arg(count));
488  return QVariant(result);
489 }
490 
491 QVariant TorcPList::ParseBinaryUnicode(quint8 *Data)
492 {
493  QString result;
494  if (((*Data) & BPLIST_HIGH) != BPLIST_UNICODE)
495  return result;
496 
497  quint64 count = GetBinaryCount(&Data);
498  if (!count)
499  return result;
500 
501  return QVariant(m_codec->toUnicode((char*)Data, count << 1));
502 }
503 
504 quint64 TorcPList::GetBinaryCount(quint8 **Data)
505 {
506  quint64 count = (**Data) & BPLIST_LOW;
507  (*Data)++;
508  if (count == BPLIST_LOW_MAX)
509  {
510  QVariant newcount = ParseBinaryUInt(Data);
511  if (!newcount.canConvert<quint64>())
512  return 0;
513  count = newcount.toULongLong();
514  }
515  return count;
516 }
#define MAGIC_SIZE
Definition: torcplist.cpp:34
#define VERSION
Definition: torcplist.cpp:33
bool ToXML(QIODevice *Device)
brief Convert the parsed plist to XML.
Definition: torcplist.cpp:108
#define BPLIST_HIGH
Definition: torcplist.h:8
#define TRAILER_PARMSIZE_INDEX
Definition: torcplist.cpp:39
#define MAGIC
Definition: torcplist.cpp:32
QString ToString(void)
brief Return the complete plist in formatted XML.
Definition: torcplist.cpp:97
#define TRAILER_NUMOBJ_INDEX
Definition: torcplist.cpp:40
#define TRAILER_OFFTAB_INDEX
Definition: torcplist.cpp:42
#define VERSION_SIZE
Definition: torcplist.cpp:35
TorcPList(const QByteArray &Data)
Definition: torcplist.cpp:66
#define TRAILER_SIZE
Definition: torcplist.cpp:36
#define TRAILER_OFFSIZE_INDEX
Definition: torcplist.cpp:38
#define BPLIST_LOW_MAX
Definition: torcplist.h:10
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: torclogging.h:20
#define TRAILER_ROOTOBJ_INDEX
Definition: torcplist.cpp:41
#define MIN_SIZE
Definition: torcplist.cpp:37
QVariant GetValue(const QString &Key)
brief Return the value for the given Key.
Definition: torcplist.cpp:80
static void convert_float(quint8 *p, quint8 s)
Definition: torcplist.cpp:45
#define BPLIST_LOW
Definition: torcplist.h:9