524 changes: 524 additions & 0 deletions mythtv/libs/libmythbase/plist.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,524 @@
/* Class PList
*
* Copyright (C) Mark Kendall 2012
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/

/**
* \class PList
* A parser for binary property lists, using QVariant for internal
* storage. Values can be queried using GetValue and the structure
* can be exported to Xml with ToXml().
*
* Support for importing Xml formatted property lists will be added.
*/

// TODO
// parse uid (and use QPair to differentiate?)

#include <QDateTime>
#include <QTextStream>
#include <QBuffer>

#include "mythlogging.h"
#include "plist.h"

#define LOC QString("PList: ")

#define MAGIC QByteArray("bplist")
#define VERSION QByteArray("00")
#define MAGIC_SIZE 6
#define VERSION_SIZE 2
#define TRAILER_SIZE 26
#define MIN_SIZE (MAGIC_SIZE + VERSION_SIZE + TRAILER_SIZE)
#define TRAILER_OFFSIZE_INDEX 0
#define TRAILER_PARMSIZE_INDEX 1
#define TRAILER_NUMOBJ_INDEX 2
#define TRAILER_ROOTOBJ_INDEX 10
#define TRAILER_OFFTAB_INDEX 18

enum
{
BPLIST_NULL = 0x00,
BPLIST_FALSE = 0x08,
BPLIST_TRUE = 0x09,
BPLIST_FILL = 0x0F,
BPLIST_UINT = 0x10,
BPLIST_REAL = 0x20,
BPLIST_DATE = 0x30,
BPLIST_DATA = 0x40,
BPLIST_STRING = 0x50,
BPLIST_UNICODE = 0x60,
BPLIST_UID = 0x70,
BPLIST_ARRAY = 0xA0,
BPLIST_SET = 0xC0,
BPLIST_DICT = 0xD0,
};

static void convert_float(quint8 *p, quint8 s)
{
#if HAVE_BIGENDIAN && !defined (__VFP_FP__)
return;
#else
for (quint8 i = 0; i < (s / 2); i++)
{
quint8 t = p[i];
quint8 j = ((s - 1) + 0) - i;
p[i] = p[j];
p[j] = t;
}
#endif
}

static quint8* convert_int(quint8 *p, quint8 s)
{
#if HAVE_BIGENDIAN
return p;
#else
for (quint8 i = 0; i < (s / 2); i++)
{
quint8 t = p[i];
quint8 j = ((s - 1) + 0) - i;
p[i] = p[j];
p[j] = t;
}
#endif
return p;
}

PList::PList(const QByteArray &data)
: m_data(NULL), m_offsetTable(NULL), m_rootObj(0),
m_numObjs(0), m_offsetSize(0), m_parmSize(0)
{
ParseBinaryPList(data);
}

QVariant PList::GetValue(const QString &key)
{
if (m_result.type() != QVariant::Map)
return QVariant();

QVariantMap map = m_result.toMap();
QMapIterator<QString,QVariant> it(map);
while (it.hasNext())
{
it.next();
if (key == it.key())
return it.value();
}
return QVariant();
}

QString PList::ToString(void)
{
QByteArray res;
QBuffer buf(&res);
buf.open(QBuffer::WriteOnly);
if (!ToXML(&buf))
return QString("");
return QString(res.data());
}

bool PList::ToXML(QIODevice *device)
{
QXmlStreamWriter xml(device);
xml.setAutoFormatting(true);
xml.setAutoFormattingIndent(4);
xml.writeStartDocument();
xml.writeDTD("<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
xml.writeStartElement("plist");
xml.writeAttribute("version", "1.0");
bool success = ToXML(m_result, xml);
xml.writeEndElement();
xml.writeEndDocument();
if (!success)
LOG(VB_GENERAL, LOG_WARNING, LOC + "Invalid result.");
return success;
}

bool PList::ToXML(const QVariant &data, QXmlStreamWriter &xml)
{
switch (data.type())
{
case QVariant::Map:
DictToXML(data, xml);
break;
case QVariant::List:
ArrayToXML(data, xml);
break;
case QVariant::Double:
xml.writeTextElement("real",
QString("%1").arg(data.toDouble(), 0, 'f', 6));
break;
case QVariant::ByteArray:
xml.writeTextElement("data",
data.toByteArray().toBase64().data());
break;
case QVariant::ULongLong:
xml.writeTextElement("integer",
QString("%1").arg(data.toULongLong()));
break;
case QVariant::String:
xml.writeTextElement("string", data.toString());
break;
case QVariant::DateTime:
xml.writeTextElement("date", data.toDateTime().toString(Qt::ISODate));
break;
case QVariant::Bool:
{
bool val = data.toBool();
xml.writeEmptyElement(val ? "true" : "false");
}
break;
default:
LOG(VB_GENERAL, LOG_WARNING, LOC + "Unknown type.");
return false;
}
return true;
}

void PList::DictToXML(const QVariant &data, QXmlStreamWriter &xml)
{
xml.writeStartElement("dict");

QVariantMap map = data.toMap();
QMapIterator<QString,QVariant> it(map);
while (it.hasNext())
{
it.next();
xml.writeStartElement("key");
xml.writeCharacters(it.key());
xml.writeEndElement();
ToXML(it.value(), xml);
}

xml.writeEndElement();
}

void PList::ArrayToXML(const QVariant &data, QXmlStreamWriter &xml)
{
xml.writeStartElement("array");

QList<QVariant> list = data.toList();
foreach (QVariant item, list)
ToXML(item, xml);

xml.writeEndElement();
}

void PList::ParseBinaryPList(const QByteArray &data)
{
// reset
m_result = QVariant();

// check minimum size
quint32 size = data.size();
if (size < MIN_SIZE)
return;

LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Binary: size %1, startswith '%2'")
.arg(size).arg(data.left(8).data()));

// check plist type & version
if ((data.left(6) != MAGIC) ||
(data.mid(MAGIC_SIZE, VERSION_SIZE) != VERSION))
{
LOG(VB_GENERAL, LOG_ERR, LOC + "Unrecognised start sequence. Corrupt?");
return;
}

LOG(VB_GENERAL, LOG_INFO, LOC + QString("Parsing binary plist (%1 bytes)")
.arg(size));

m_data = (quint8*)data.data();
quint8* trailer = m_data + size - TRAILER_SIZE;
m_offsetSize = *(trailer + TRAILER_OFFSIZE_INDEX);
m_parmSize = *(trailer + TRAILER_PARMSIZE_INDEX);
m_numObjs = *((quint64*)convert_int(trailer + TRAILER_NUMOBJ_INDEX, 8));
m_rootObj = *((quint64*)convert_int(trailer + TRAILER_ROOTOBJ_INDEX, 8));
quint64 offset_tindex = *((quint64*)convert_int(trailer + TRAILER_OFFTAB_INDEX, 8));
m_offsetTable = m_data + offset_tindex;

LOG(VB_GENERAL, LOG_DEBUG, LOC +
QString("numObjs: %1 parmSize: %2 offsetSize: %3 rootObj: %4"
"offset_tindex: %5").arg(m_numObjs).arg(m_parmSize)
.arg(m_offsetSize).arg(m_rootObj).arg(offset_tindex));

// something wrong?
if (!m_numObjs || !m_parmSize || !m_offsetSize)
{
LOG(VB_GENERAL, LOG_ERR, LOC + "Error parsing binary plist. Corrupt?");
return;
}

// parse
m_result = ParseBinaryNode(m_rootObj);

LOG(VB_GENERAL, LOG_INFO, LOC + "Parse complete.");
}

QVariant PList::ParseBinaryNode(quint64 num)
{
quint8* data = GetBinaryObject(num);
if (!data)
return QVariant();

quint16 type = (*data) & 0xf0;
quint64 size = (*data) & 0x0f;

switch (type)
{
case BPLIST_SET:
case BPLIST_ARRAY: return ParseBinaryArray(data);
case BPLIST_DICT: return ParseBinaryDict(data);
case BPLIST_STRING: return ParseBinaryString(data);
case BPLIST_UINT: return ParseBinaryUInt(&data);
case BPLIST_REAL: return ParseBinaryReal(data);
case BPLIST_DATE: return ParseBinaryDate(data);
case BPLIST_DATA: return ParseBinaryData(data);
case BPLIST_UNICODE: return ParseBinaryUnicode(data);
case BPLIST_NULL:
{
switch (size)
{
case BPLIST_TRUE: return QVariant(true);
case BPLIST_FALSE: return QVariant(false);
case BPLIST_NULL:
default: return QVariant();
}
}
case BPLIST_UID: // FIXME
default: return QVariant();
}

return QVariant();
}

quint64 PList::GetBinaryUInt(quint8 *p, quint64 size)
{
if (size == 1) return (quint64)(*(p));
if (size == 2) return (quint64)(*((quint16*)convert_int(p, 2)));
if (size == 4) return (quint64)(*((quint32*)convert_int(p, 4)));
if (size == 8) return (quint64)(*((quint64*)convert_int(p, 8)));

if (size == 3)
{
#if HAVE_BIGENDIAN
return (quint64)(((*p) << 16) + (*(p + 1) << 8) + (*(p + 2)));
#else
return (quint64)((*p) + (*(p + 1) << 8) + ((*(p + 2)) << 16));
#endif
}

return 0;
}

quint8* PList::GetBinaryObject(quint64 num)
{
if (num > m_numObjs)
return NULL;

quint8* p = m_offsetTable + (num * m_offsetSize);
quint64 offset = GetBinaryUInt(p, m_offsetSize);
LOG(VB_GENERAL, LOG_DEBUG, LOC +
QString("GetBinaryObject num %1, offsize %2 offset %3")
.arg(num).arg(m_offsetSize).arg(offset));

return m_data + offset;
}

QVariantMap PList::ParseBinaryDict(quint8 *data)
{
QVariantMap result;
if (((*data) & 0xf0) != BPLIST_DICT)
return result;

quint64 count = GetBinaryCount(&data);

LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Dict: Size %1").arg(count));

if (!count)
return result;

quint64 off = m_parmSize * count;
for (quint64 i = 0; i < count; i++, data += m_parmSize)
{
quint64 keyobj = GetBinaryUInt(data, m_parmSize);
quint64 valobj = GetBinaryUInt(data + off, m_parmSize);
QVariant key = ParseBinaryNode(keyobj);
QVariant val = ParseBinaryNode(valobj);
if (!key.canConvert<QString>())
{
LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid dictionary key type.");
return result;
}

result.insert(key.toString(), val);
}

return result;
}

QList<QVariant> PList::ParseBinaryArray(quint8 *data)
{
QList<QVariant> result;
if (((*data) & 0xf0) != BPLIST_ARRAY)
return result;

quint64 count = GetBinaryCount(&data);

LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Array: Size %1").arg(count));

if (!count)
return result;

for (quint64 i = 0; i < count; i++, data += m_parmSize)
{
quint64 obj = GetBinaryUInt(data, m_parmSize);
QVariant val = ParseBinaryNode(obj);
result.push_back(val);
}
return result;
}

QVariant PList::ParseBinaryUInt(quint8 **data)
{
quint64 result = 0;
if (((**data) & 0xf0) != BPLIST_UINT)
return QVariant(result);

quint64 size = 1 << ((**data) & 0x0f);
(*data)++;
result = GetBinaryUInt(*data, size);
(*data) += size;

LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("UInt: %1").arg(result));
return QVariant(result);
}

QVariant PList::ParseBinaryString(quint8 *data)
{
QString result;
if (((*data) & 0xf0) != BPLIST_STRING)
return result;

quint64 count = GetBinaryCount(&data);
if (!count)
return result;

result = QString::fromAscii((const char*)data, count);
LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("ASCII String: %1").arg(result));
return QVariant(result);
}

QVariant PList::ParseBinaryReal(quint8 *data)
{
double result = 0.0;
if (((*data) & 0xf0) != BPLIST_REAL)
return result;

quint64 count = GetBinaryCount(&data);
if (!count)
return result;

count = 1 << count;
if (count == sizeof(float))
{
convert_float(data, count);
result = (double)(*((float*)data));
}
else if (count == sizeof(double))
{
convert_float(data, count);
result = *((double*)data);
}

LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Real: %1").arg(result, 0, 'f', 6));
return QVariant(result);
}

QVariant PList::ParseBinaryDate(quint8 *data)
{
QDateTime result;
if (((*data) & 0xf0) != BPLIST_DATE)
return result;

quint64 count = GetBinaryCount(&data);
if (count != 3)
return result;

convert_float(data, 8);
quint64 msec = *((double*)data) * 1000.0f;
result = QDateTime::fromTime_t(msec / 1000);
QTime time = result.time();
time.addMSecs(msec % 1000);
result.setTime(time);
LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Date: %1").arg(result.toString(Qt::ISODate)));
return QVariant(result);
}

QVariant PList::ParseBinaryData(quint8 *data)
{
QByteArray result;
if (((*data) & 0xf0) != BPLIST_DATA)
return result;

quint64 count = GetBinaryCount(&data);
if (!count)
return result;

result = QByteArray((const char*)data, count);
LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Data: Size %1 (count %2)")
.arg(result.size()).arg(count));
return QVariant(result);
}

QVariant PList::ParseBinaryUnicode(quint8 *data)
{
QString result;
if (((*data) & 0xf0) != BPLIST_UNICODE)
return result;

quint64 count = GetBinaryCount(&data);
if (!count)
return result;

// source is big endian (and no BOM?)
QByteArray tmp;
for (quint64 i = 0; i < count; i++, data += 2)
{
quint16 twobyte = (quint16)(*((quint16*)convert_int(data, 2)));
tmp.append((quint8)(twobyte & 0xff));
tmp.append((quint8)((twobyte >> 8) & 0xff));
}
result = QString::fromUtf16((const quint16*)tmp.data(), count);
LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Unicode: %1").arg(result));
return QVariant(result);
}

quint64 PList::GetBinaryCount(quint8 **data)
{
quint64 count = (**data) & 0x0f;
(*data)++;
if (count == 0x0f)
{
QVariant newcount = ParseBinaryUInt(data);
if (!newcount.canConvert<quint64>())
return 0;
count = newcount.toULongLong();
}
return count;
}
46 changes: 46 additions & 0 deletions mythtv/libs/libmythbase/plist.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#ifndef PLIST_H
#define PLIST_H

#include <QVariant>
#include <QXmlStreamWriter>
#include "mythbaseexp.h"

class MBASE_PUBLIC PList
{
public:
PList(const QByteArray &data);

QVariant GetValue(const QString &key);
QString ToString(void);
bool ToXML(QIODevice *device);

private:
void ParseBinaryPList(const QByteArray &data);
QVariant ParseBinaryNode(quint64 num);
QVariantMap ParseBinaryDict(quint8 *data);
QList<QVariant> ParseBinaryArray(quint8 *data);
QVariant ParseBinaryString(quint8 *data);
QVariant ParseBinaryReal(quint8 *data);
QVariant ParseBinaryDate(quint8 *data);
QVariant ParseBinaryData(quint8 *data);
QVariant ParseBinaryUnicode(quint8 *data);
QVariant ParseBinaryUInt(quint8 **data);
quint64 GetBinaryCount(quint8 **data);
quint64 GetBinaryUInt(quint8 *p, quint64 size);
quint8* GetBinaryObject(quint64 num);

private:
bool ToXML(const QVariant &data, QXmlStreamWriter &xml);
void DictToXML(const QVariant &data, QXmlStreamWriter &xml);
void ArrayToXML(const QVariant &data, QXmlStreamWriter &xml);

QVariant m_result;
quint8 *m_data;
quint8 *m_offsetTable;
quint64 m_rootObj;
quint64 m_numObjs;
quint8 m_offsetSize;
quint8 m_parmSize;
};

#endif // PLIST_H
64 changes: 10 additions & 54 deletions mythtv/libs/libmythtv/mythairplayserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,9 @@
#include "mythuiactions.h"
#include "mythmainwindow.h"
#include "mythuistatetracker.h"
#include "plist.h"
#include "tv_play.h"

#if defined(Q_WS_MAC)
#include "util-osx-cocoa.h"
#include <CoreFoundation/CoreFoundation.h>
#endif

#include "bonjourregister.h"
#include "mythairplayserver.h"

Expand Down Expand Up @@ -625,55 +621,15 @@ void MythAirplayServer::HandleResponse(APHTTPRequest *req,
if (req->GetHeaders().contains("Content-Type") &&
req->GetHeaders()["Content-Type"] == "application/x-apple-binary-plist")
{
#if defined(Q_WS_MAC)
CocoaAutoReleasePool pool;
CFDataRef data = CFDataCreate(
NULL, (const UInt8*)req->GetBody().data(),
req->GetBody().size());
CFPropertyListRef plist =
CFPropertyListCreateFromXMLData(NULL, data, kCFPropertyListImmutable, NULL);
if (plist && CFPropertyListIsValid(plist, kCFPropertyListBinaryFormat_v1_0))
{
if (CFGetTypeID(plist) == CFDictionaryGetTypeID())
{
CFDictionaryRef dict = (CFDictionaryRef)plist;
const void* contentref;
if (CFDictionaryGetValueIfPresent(dict,
CFSTR("Content-Location"),
&contentref))
{
CFStringRef str = (CFStringRef)contentref;
if (str)
{
CFIndex l = 2 * (CFStringGetLength(str) + 1);
QByteArray buf(l, 0);
if (CFStringGetCString(str, buf.data(), l, kCFStringEncodingUTF8))
file = buf.trimmed();
}
}

const void* posref;
if (CFDictionaryGetValueIfPresent(dict,
CFSTR("Start-Position"),
&posref))
{
CFNumberRef num = (CFNumberRef)posref;
if (num) CFNumberGetValue(num, kCFNumberDoubleType, &start_pos);
}

}
else if (CFGetTypeID(plist) == CFArrayGetTypeID())
{
LOG(VB_GENERAL, LOG_WARNING, LOC + "Array plist not implemented.");
}
else
{
LOG(VB_GENERAL, LOG_WARNING, LOC + "Unknown plist type.");
}
}
#else
LOG(VB_GENERAL, LOG_ERR, LOC + "plist type not yet supported.");
#endif
PList plist(req->GetBody());
LOG(VB_GENERAL, LOG_DEBUG, LOC + plist.ToString());

QVariant start = plist.GetValue("Start-Position");
QVariant content = plist.GetValue("Content-Location");
if (start.isValid() && start.canConvert<double>())
start_pos = start.toDouble();
if (content.isValid() && content.canConvert<QByteArray>())
file = content.toByteArray();
}
else
{
Expand Down