// // PlistCpp Property List (plist) serialization and parsing library. // // https://github.com/animetrics/PlistCpp // // Copyright (c) 2011 Animetrics Inc. (marc@animetrics.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #include "plisthpp/Plist.hpp" #include #include #include #include "plisthpp/base64.hpp" #include "plisthpp/pugixml.hpp" namespace Plist { struct PlistHelperData { public: // binary helper data std::vector _offsetTable; std::vector _objectTable; int32_t _offsetByteSize; int64_t _offsetTableOffset; int32_t _objRefSize; int32_t _refCount; }; void writePlistBinary( PlistHelperData& d, const boost::any& message); void writePlistXML( pugi::xml_document& doc, const boost::any& message); // helper functions // msvc <= 2005 doesn't have std::vector::data() method template T* vecData(std::vector& vec) { return (vec.size() > 0) ? &vec[0] : 0; // if(vec.size() > 0) // return &vec[0]; // else // throw Error("vecData trying to get pointer to empty std::vector"); } template const T* vecData(const std::vector& vec) { return (vec.size() > 0) ? &vec[0] : 0; // if(vec.size() > 0) // return &vec[0]; // else // throw Error("vecData trying to get pointer to empty std::vector"); } // xml helper functions template std::string stringFromValue(const T& value); template void writeXMLSimpleNode(pugi::xml_node& node, const char* name, const boost::any& obj); // xml parsing dictionary_type parseDictionary(pugi::xml_node& node); array_type parseArray(pugi::xml_node& node); std::vector base64Decode(const char* data); void base64Encode(std::string& dataEncoded, const std::vector& data); Date parseDate(pugi::xml_node& node); boost::any parse(pugi::xml_node& doc); // xml writing void writeXMLArray(pugi::xml_node& node, const array_type& array); void writeXMLDictionary(pugi::xml_node& node, const dictionary_type& message); void writeXMLNode(pugi::xml_node& node, const boost::any& obj); // binary helper functions template IntegerType bytesToInt(const unsigned char* bytes, bool littleEndian); double bytesToDouble(const unsigned char* bytes, bool littleEndian); std::vector doubleToBytes(double val, bool littleEndian); template std::vector intToBytes(IntegerType val, bool littleEndian); std::vector getRange(const unsigned char* origBytes, int64_t index, int64_t size); std::vector getRange(const std::vector& origBytes, int64_t index, int64_t size); std::vector getRange(const char* origBytes, int64_t index, int64_t size); // binary parsing boost::any parseBinary(const PlistHelperData& d, int objRef); dictionary_type parseBinaryDictionary(const PlistHelperData& d, int objRef); array_type parseBinaryArray(const PlistHelperData& d, int objRef); std::vector getRefsForContainers(const PlistHelperData& d, int objRef); int64_t parseBinaryInt(const PlistHelperData& d, int headerPosition, int& intByteCount); double parseBinaryReal(const PlistHelperData& d, int headerPosition); Date parseBinaryDate(const PlistHelperData& d, int headerPosition); bool parseBinaryBool(const PlistHelperData& d, int headerPosition); std::string parseBinaryString(const PlistHelperData& d, int objRef); std::string parseBinaryUnicode(const PlistHelperData& d, int headerPosition); data_type parseBinaryByteArray(const PlistHelperData& d, int headerPosition); std::vector regulateNullBytes(const std::vector& origBytes, unsigned int minBytes); void parseTrailer(PlistHelperData& d, const std::vector& trailer); void parseOffsetTable(PlistHelperData& d, const std::vector& offsetTableBytes); int32_t getCount(const PlistHelperData& d, int bytePosition, unsigned char headerByte, int& startOffset); // binary writing int countAny(const boost::any& object); int countDictionary(const dictionary_type& dictionary); int countArray(const array_type& array); std::vector writeBinaryDictionary(PlistHelperData& d, const dictionary_type& dictionary); std::vector writeBinaryArray(PlistHelperData& d, const array_type& array); std::vector writeBinaryByteArray(PlistHelperData& d, const data_type& byteArray); std::vector writeBinaryInteger(PlistHelperData& d, int64_t value, bool write); std::vector writeBinaryBool(PlistHelperData& d, bool value); std::vector writeBinaryDate(PlistHelperData& d, const Date& date); std::vector writeBinaryDouble(PlistHelperData& d, double value); std::vector writeBinary(PlistHelperData& d, const boost::any& obj); std::vector writeBinaryString(PlistHelperData& d, const std::string& value, bool head); inline bool hostLittleEndian() { union { uint32_t x; uint8_t c[4]; } u; u.x = 0xab0000cd; return u.c[0] == 0xcd; } } // namespace Plist namespace Plist { template void writeXMLSimpleNode(pugi::xml_node& node, const char* name, const boost::any& obj) { pugi::xml_node newNode; newNode = node.append_child(name); newNode.append_child(pugi::node_pcdata).set_value(stringFromValue(boost::any_cast(obj)).c_str()); } void writeXMLNode(pugi::xml_node& node, const boost::any& obj) { using namespace std; const std::type_info &objType = obj.type(); if(objType == typeid(int32_t)) writeXMLSimpleNode(node, "integer", obj); else if(objType == typeid(int64_t)) writeXMLSimpleNode(node, "integer", obj); else if(objType == typeid(long)) writeXMLSimpleNode(node, "integer", obj); else if(objType == typeid(short)) writeXMLSimpleNode(node, "integer", obj); else if(objType == typeid(dictionary_type)) writeXMLDictionary(node, boost::any_cast(obj)); else if(objType == typeid(string_type)) writeXMLSimpleNode(node, "string", obj); else if(objType == typeid(array_type)) writeXMLArray(node, boost::any_cast(obj)); else if(objType == typeid(data_type)) { string dataEncoded; base64Encode(dataEncoded, boost::any_cast(obj)); writeXMLSimpleNode(node, "data", dataEncoded); } else if(objType == typeid(double)) writeXMLSimpleNode(node, "real", obj); else if(objType == typeid(float)) writeXMLSimpleNode(node, "real", obj); else if(objType == typeid(Date)) writeXMLSimpleNode(node, "date", boost::any_cast(obj).timeAsXMLConvention()); else if(objType == typeid(bool)) { bool value = boost::any_cast(obj); node.append_child(value ? "true" : "false"); } else throw Error((string("Plist Error: Can't serialize type ") + objType.name()).c_str()); } void writeXMLArray( pugi::xml_node& node, const array_type& array) { using namespace std; pugi::xml_node newNode = node.append_child("array"); for(array_type::const_iterator it = array.begin(); it != array.end(); ++it) writeXMLNode(newNode, *it); } void writeXMLDictionary( pugi::xml_node& node, const dictionary_type& message) { using namespace std; pugi::xml_node newNode = node.append_child("dict"); for(dictionary_type::const_iterator it = message.begin(); it != message.end(); ++it) { pugi::xml_node keyNode = newNode.append_child("key"); keyNode.append_child(pugi::node_pcdata).set_value(it->first.c_str()); writeXMLNode(newNode, it->second); } } void writePlistXML( pugi::xml_document& doc, const boost::any& message) // const dictionary_type& message) { // declaration node pugi::xml_node decNode = doc.append_child(pugi::node_declaration); decNode.append_attribute("version") = "1.0"; decNode.append_attribute("encoding") = "UTF-8"; // doctype node doc.append_child(pugi::node_doctype).set_value("plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\""); // root node pugi::xml_node plistNode = doc.append_child("plist"); plistNode.append_attribute("version") = "1.0"; writeXMLNode(plistNode, message); } void writePlistBinary( PlistHelperData& d, const boost::any& message) { using namespace std; //int totalRefs = countDictionary(message); int totalRefs = countAny(message) - 1; d._refCount = totalRefs; d._objRefSize = regulateNullBytes(intToBytes(d._refCount, hostLittleEndian()), 1).size(); //writeBinaryDictionary(d, message); writeBinary(d, message); writeBinaryString(d, "bplist00", false); d._offsetTableOffset = (int64_t) d._objectTable.size(); d._offsetTable.push_back(d._objectTable.size() - 8); d._offsetByteSize = regulateNullBytes(intToBytes(d._offsetTable.back(), hostLittleEndian()), 1).size(); vector offsetBytes; reverse(d._offsetTable.begin(), d._offsetTable.end()); for(unsigned int i = 0; i < d._offsetTable.size(); ++i) { d._offsetTable[i] = d._objectTable.size() - d._offsetTable[i]; vector buffer = regulateNullBytes(intToBytes(d._offsetTable[i], hostLittleEndian()), d._offsetByteSize); //reverse(buffer.begin(), buffer.end()); offsetBytes.insert(offsetBytes.end(), buffer.rbegin(), buffer.rend()); } d._objectTable.insert(d._objectTable.end(), offsetBytes.begin(), offsetBytes.end()); vector dummy(6, 0); d._objectTable.insert(d._objectTable.end(), dummy.begin(), dummy.end()); d._objectTable.push_back((unsigned char) (d._offsetByteSize)); d._objectTable.push_back((unsigned char) (d._objRefSize)); vector temp = intToBytes((int64_t) totalRefs + 1, hostLittleEndian()); d._objectTable.insert(d._objectTable.end(), temp.rbegin(), temp.rend()); temp = intToBytes(0, hostLittleEndian()); d._objectTable.insert(d._objectTable.end(), temp.begin(), temp.end()); temp = intToBytes(d._offsetTableOffset, hostLittleEndian()); d._objectTable.insert(d._objectTable.end(), temp.rbegin(), temp.rend()); } void writePlistBinary(std::vector& plist, const boost::any& message) { PlistHelperData d; writePlistBinary(d, message); plist.resize(d._objectTable.size()); std::copy((const char*) vecData(d._objectTable), (const char*) vecData(d._objectTable) + d._objectTable.size(), plist.begin()); } void writePlistBinary( std::ostream& stream, const boost::any& message) { PlistHelperData d; writePlistBinary(d, message); stream.write((const char*) vecData(d._objectTable), d._objectTable.size()); } void writePlistBinary( const char* filename, const boost::any& message) { std::ofstream stream(filename, std::ios::binary); writePlistBinary(stream, message); stream.close(); } #if defined(_MSC_VER) void writePlistBinary( const wchar_t* filename, const boost::any& message) { std::ofstream stream(filename, std::ios::binary); writePlistBinary(stream, message); stream.close(); } #endif void writePlistXML(std::vector& plist, const boost::any& message) { std::stringstream ss; writePlistXML(ss, message); std::istreambuf_iterator beg(ss); std::istreambuf_iterator end; plist.clear(); plist.insert(plist.begin(), beg, end); } void writePlistXML( std::ostream& stream, const boost::any& message) { pugi::xml_document doc; writePlistXML(doc, message); doc.save(stream); } void writePlistXML( const char* filename, const boost::any& message) { std::ofstream stream(filename, std::ios::binary); writePlistXML(stream, message); stream.close(); } #if defined(_MSC_VER) void writePlistXML( const wchar_t* filename, const boost::any& message) { std::ofstream stream(filename, std::ios::binary); writePlistXML(stream, message); stream.close(); } #endif int countAny(const boost::any& object) { using namespace std; static boost::any dict = dictionary_type(); static boost::any array = array_type(); int count = 0; if(object.type() == dict.type()) count += countDictionary(boost::any_cast(object)) + 1; else if (object.type() == array.type()) count += countArray(boost::any_cast(object)) + 1; else ++count; return count; } std::vector writeBinary(PlistHelperData& d, const boost::any& obj) { using namespace std; const std::type_info &objType = obj.type(); std::vector value; if(objType == typeid(int32_t)) value = writeBinaryInteger(d, boost::any_cast(obj), true); else if(objType == typeid(int64_t)) value = writeBinaryInteger(d, boost::any_cast(obj), true); else if(objType == typeid(long)) value = writeBinaryInteger(d, boost::any_cast(obj), true); else if(objType == typeid(short)) value = writeBinaryInteger(d, boost::any_cast(obj), true); else if(objType == typeid(dictionary_type)) value = writeBinaryDictionary(d, boost::any_cast(obj)); else if(objType == typeid(string)) value = writeBinaryString(d, boost::any_cast(obj), true); else if(objType == typeid(array_type)) value = writeBinaryArray(d, boost::any_cast(obj)); else if(objType == typeid(data_type)) value = writeBinaryByteArray(d, boost::any_cast(obj)); else if(objType == typeid(double)) value = writeBinaryDouble(d, boost::any_cast(obj)); else if(objType == typeid(float)) value = writeBinaryDouble(d, boost::any_cast(obj)); else if(objType == typeid(Date)) value = writeBinaryDate(d, boost::any_cast(obj)); else if(objType == typeid(bool)) value = writeBinaryBool(d, boost::any_cast(obj)); else throw Error((string("Plist Error: Can't serialize type ") + objType.name()).c_str()); return value; } static uint32_t nextpow2(uint32_t x) { --x; x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16; return x + 1; } static uint32_t ilog2(uint32_t x) { uint32_t r = 0; while (x >>= 1) ++r; return r; } std::vector writeBinaryByteArray(PlistHelperData& d, const data_type& byteArray) { using namespace std; vector header; if(byteArray.size() < 15) header.push_back(0x40 | ((unsigned char) byteArray.size())); else { header.push_back(0x40 | 0xf); vector theSize = writeBinaryInteger(d, byteArray.size(), false); header.insert(header.end(), theSize.begin(), theSize.end()); } vector buffer(header); buffer.insert(buffer.end(), (unsigned char*) vecData(byteArray), (unsigned char*) vecData(byteArray) + byteArray.size()); d._objectTable.insert(d._objectTable.begin(), buffer.begin(), buffer.end()); return buffer; } std::vector writeBinaryArray(PlistHelperData& d, const array_type& array) { using namespace std; vector refs; for(array_type::const_reverse_iterator it = array.rbegin(); it != array.rend(); ++it) { writeBinary(d, *it); d._offsetTable.push_back(d._objectTable.size()); refs.push_back(d._refCount); d._refCount--; } vector header; if (array.size() < 15) { header.push_back(0xA0 | ((unsigned char) array.size())); } else { header.push_back(0xA0 | 0xf); vector theSize = writeBinaryInteger(d, array.size(), false); header.insert(header.end(), theSize.begin(), theSize.end()); } // try to do this more efficiently. Not good to insert at the begining of buffer. vector buffer; for(vector::const_iterator it = refs.begin(); it != refs.end(); ++it) { vector refBuffer = regulateNullBytes(intToBytes(*it, hostLittleEndian()), d._objRefSize); // reverse(refBuffer.begin(), refBuffer.end()); buffer.insert(buffer.begin(), refBuffer.rbegin(), refBuffer.rend()); } buffer.insert(buffer.begin(), header.begin(), header.end()); d._objectTable.insert(d._objectTable.begin(), buffer.begin(), buffer.end()); return buffer; } std::vector writeBinaryDictionary(PlistHelperData& d, const dictionary_type& dictionary) { using namespace std; vector refs; for(dictionary_type::const_reverse_iterator it = dictionary.rbegin(); it != dictionary.rend(); ++it) { writeBinary(d, it->second); d._offsetTable.push_back(d._objectTable.size()); refs.push_back(d._refCount); d._refCount--; } for(dictionary_type::const_reverse_iterator it = dictionary.rbegin(); it != dictionary.rend(); ++it) { writeBinary(d, it->first); d._offsetTable.push_back(d._objectTable.size()); refs.push_back(d._refCount); d._refCount--; } vector header; if (dictionary.size() < 15) { header.push_back(0xD0 | ((unsigned char) dictionary.size())); } else { header.push_back(0xD0 | 0xf); vector theSize = writeBinaryInteger(d, dictionary.size(), false); header.insert(header.end(), theSize.begin(), theSize.end()); } // try to do this more efficiently. Not good to insert at the begining of buffer. vector buffer; for(vector::const_iterator it = refs.begin(); it != refs.end(); ++it) { vector refBuffer = regulateNullBytes(intToBytes(*it, hostLittleEndian()), d._objRefSize); // reverse(refBuffer.begin(), refBuffer.end()); buffer.insert(buffer.begin(), refBuffer.rbegin(), refBuffer.rend()); } buffer.insert(buffer.begin(), header.begin(), header.end()); d._objectTable.insert(d._objectTable.begin(), buffer.begin(), buffer.end()); return buffer; } std::vector writeBinaryDouble(PlistHelperData& d, double value) { using namespace std; vector buffer = regulateNullBytes(doubleToBytes(value, hostLittleEndian()), 4); buffer.resize(nextpow2(buffer.size()), 0); unsigned char header = 0x20 | ilog2(buffer.size()); buffer.push_back(header); reverse(buffer.begin(), buffer.end()); d._objectTable.insert(d._objectTable.begin(), buffer.begin(), buffer.end()); return buffer; } std::vector writeBinaryBool(PlistHelperData& d, bool value) { std::vector buffer; if(value) buffer.push_back(0x09); else buffer.push_back(0x08); d._objectTable.insert(d._objectTable.begin(), buffer.begin(), buffer.end()); return buffer; } std::vector writeBinaryDate(PlistHelperData& d, const Date& date) { std::vector buffer; // need to serialize as Apple epoch. double macTime = date.timeAsAppleEpoch(); buffer = doubleToBytes(macTime, false); buffer.insert(buffer.begin(), 0x33); d._objectTable.insert(d._objectTable.begin(), buffer.begin(), buffer.end()); return buffer; } std::vector writeBinaryInteger(PlistHelperData& d, int64_t value, bool write) { using namespace std; // The integer is initially forced to be 64 bit because it must be serialized // as 8 bytes if it is negative. If it is not negative, the // regulateNullBytes step will reduce the representation down to the min // power base 2 bytes needed to store it. vector buffer = intToBytes(value, hostLittleEndian()); buffer = regulateNullBytes(intToBytes(value, hostLittleEndian()), 1); buffer.resize(nextpow2(buffer.size()), 0); unsigned char header = 0x10 | ilog2(buffer.size()); buffer.push_back(header); reverse(buffer.begin(), buffer.end()); if(write) d._objectTable.insert(d._objectTable.begin(), buffer.begin(), buffer.end()); return buffer; } std::vector writeBinaryString(PlistHelperData& d, const std::string& value, bool head) { using namespace std; vector buffer; buffer.reserve(value.size()); for(std::string::const_iterator it = value.begin(); it != value.end(); ++it) buffer.push_back((unsigned char) *it); if(head) { vector header; if (value.length() < 15) header.push_back(0x50 | ((unsigned char) value.length())); else { header.push_back(0x50 | 0xf); vector theSize = writeBinaryInteger(d, buffer.size(), false); header.insert(header.end(), theSize.begin(), theSize.end()); } buffer.insert(buffer.begin(), header.begin(), header.end()); } d._objectTable.insert(d._objectTable.begin(), buffer.begin(), buffer.end()); return buffer; } int countDictionary(const dictionary_type& dictionary) { using namespace std; int count = 0; for(dictionary_type::const_iterator it = dictionary.begin(); it != dictionary.end(); ++it) { ++count; count += countAny(it->second); } return count; } int countArray(const array_type& array) { using namespace std; int count = 0; for(array_type::const_iterator it = array.begin(); it != array.end(); ++it) count += countAny(*it); return count; } void readPlist(std::istream& stream, boost::any& message) { int start = stream.tellg(); stream.seekg(0, std::ifstream::end); int size = ((int) stream.tellg()) - start; if(size > 0) { stream.seekg(0, std::ifstream::beg); std::vector buffer(size); stream.read( (char *)&buffer[0], size ); readPlist(&buffer[0], size, message); } else { throw Error("Can't read zero length data"); } } void readPlist(const char* byteArrayTemp, int64_t size, boost::any& message) { using namespace std; const unsigned char* byteArray = (const unsigned char*) byteArrayTemp; if (!byteArray || (size == 0)) throw Error("Plist: Empty plist data"); // infer plist type from header. If it has the bplist00 header as first 8 // bytes, then it's a binary plist. Otherwise, assume it's XML std::string magicHeader((const char*) byteArray, 8); if(magicHeader == "bplist00") { PlistHelperData d; parseTrailer(d, getRange(byteArray, size - 32, 32)); d._objectTable = getRange(byteArray, 0, d._offsetTableOffset); std::vector offsetTableBytes = getRange(byteArray, d._offsetTableOffset, size - d._offsetTableOffset - 32); parseOffsetTable(d, offsetTableBytes); message = parseBinary(d, 0); } else { pugi::xml_document doc; pugi::xml_parse_result result = doc.load_buffer(byteArray, (size_t)size); if(!result) throw Error((string("Plist: XML parsed with error ") + result.description()).c_str()); pugi::xml_node rootNode = doc.child("plist").first_child(); message = parse(rootNode); } } dictionary_type parseDictionary(pugi::xml_node& node) { using namespace std; dictionary_type dict; for(pugi::xml_node_iterator it = node.begin(); it != node.end(); ++it) { if(string("key") != it->name()) throw Error("Plist: XML dictionary key expected but not found"); string key(it->first_child().value()); ++it; if(it == node.end()) throw Error("Plist: XML dictionary value expected for key " + key + "but not found"); else if(string("key") == it->name()) throw Error("Plist: XML dictionary value expected for key " + key + "but found another key node"); dict[key] = parse(*it); } return dict; } array_type parseArray(pugi::xml_node& node) { using namespace std; array_type array; for(pugi::xml_node_iterator it = node.begin(); it != node.end(); ++it) array.push_back(parse(*it)); return array; } Date parseDate(pugi::xml_node& node) { Date date; date.setTimeFromXMLConvention(node.first_child().value()); return date; } std::vector base64Decode(const char* encodedData) { using namespace std; vector data; insert_iterator > ii(data, data.begin()); base64 b64; int state = 0; b64.get(encodedData, encodedData + strlen(encodedData), ii, state); return data; } void base64Encode(std::string& dataEncoded, const std::vector& data) { using namespace std; dataEncoded.clear(); insert_iterator ii(dataEncoded, dataEncoded.begin()); base64 b64; int state = 0; #if defined(_WIN32) || defined(_WIN64) b64.put(data.begin(), data.end(), ii, state , base64<>::crlf()); #else b64.put(data.begin(), data.end(), ii, state , base64<>::lf()); #endif } boost::any parse(pugi::xml_node& node) { using namespace std; string nodeName = node.name(); boost::any result; if("dict" == nodeName) result = parseDictionary(node); else if("array" == nodeName) result = parseArray(node); else if("string" == nodeName) result = string(node.first_child().value()); else if("integer" == nodeName) result = (int64_t) atoll(node.first_child().value()); else if("real" == nodeName) result = atof(node.first_child().value()); else if("false" == nodeName) result = bool(false); else if("true" == nodeName) result = bool(true); else if("data" == nodeName) result = base64Decode(node.first_child().value()); else if("date" == nodeName) result = parseDate(node); else throw Error(string("Plist: XML unknown node type " + nodeName)); return result; } void parseOffsetTable(PlistHelperData& d, const std::vector& offsetTableBytes) { for (unsigned int i = 0; i < offsetTableBytes.size(); i += d._offsetByteSize) { std::vector temp = getRange(offsetTableBytes, i, d._offsetByteSize); std::reverse(temp.begin(), temp.end()); d._offsetTable.push_back( bytesToInt( vecData(regulateNullBytes(temp, 4)), hostLittleEndian())); } } void parseTrailer(PlistHelperData& d, const std::vector& trailer) { d._offsetByteSize = bytesToInt(vecData(regulateNullBytes(getRange(trailer, 6, 1), 4)), hostLittleEndian()); d._objRefSize = bytesToInt(vecData(regulateNullBytes(getRange(trailer, 7, 1), 4)), hostLittleEndian()); std::vector refCountBytes = getRange(trailer, 12, 4); // std::reverse(refCountBytes.begin(), refCountBytes.end()); d._refCount = bytesToInt(vecData(refCountBytes), false); std::vector offsetTableOffsetBytes = getRange(trailer, 24, 8); // std::reverse(offsetTableOffsetBytes.begin(), offsetTableOffsetBytes.end()); d._offsetTableOffset = bytesToInt(vecData(offsetTableOffsetBytes), false); } std::vector regulateNullBytes(const std::vector& origBytes, unsigned int minBytes) { std::vector bytes(origBytes); while((bytes.back() == 0) && (bytes.size() > minBytes)) bytes.pop_back(); while(bytes.size() < minBytes) bytes.push_back(0); return bytes; } boost::any parseBinary(const PlistHelperData& d, int objRef) { unsigned char header = d._objectTable[d._offsetTable[objRef]]; switch (header & 0xF0) { case 0x00: { return parseBinaryBool(d, d._offsetTable[objRef]); } case 0x10: { int intByteCount; return parseBinaryInt(d, d._offsetTable[objRef], intByteCount); } case 0x20: { return parseBinaryReal(d, d._offsetTable[objRef]); } case 0x30: { return parseBinaryDate(d, d._offsetTable[objRef]); } case 0x40: { return parseBinaryByteArray(d, d._offsetTable[objRef]); } case 0x50: { return parseBinaryString(d, d._offsetTable[objRef]); } case 0x60: { return parseBinaryUnicode(d, d._offsetTable[objRef]); } case 0xD0: { return parseBinaryDictionary(d, objRef); } case 0xA0: { return parseBinaryArray(d, objRef); } } throw Error("This type is not supported"); } std::vector getRefsForContainers(const PlistHelperData& d, int objRef) { using namespace std; int32_t refCount = 0; int refStartPosition; refCount = getCount(d, d._offsetTable[objRef], d._objectTable[d._offsetTable[objRef]], refStartPosition); refStartPosition += d._offsetTable[objRef]; vector refs; int mult = 1; if((((unsigned char) d._objectTable[d._offsetTable[objRef]]) & 0xF0) == 0xD0) mult = 2; for (int i = refStartPosition; i < refStartPosition + refCount * mult * d._objRefSize; i += d._objRefSize) { std::vector refBuffer = getRange(d._objectTable, i, d._objRefSize); reverse(refBuffer.begin(), refBuffer.end()); refs.push_back(bytesToInt(vecData(regulateNullBytes(refBuffer, 4)), hostLittleEndian())); } return refs; } array_type parseBinaryArray(const PlistHelperData& d, int objRef) { using namespace std; vector refs = getRefsForContainers(d, objRef); int32_t refCount = refs.size(); array_type array; for(int i = 0; i < refCount; ++i) array.push_back(parseBinary(d, refs[i])); return array; } dictionary_type parseBinaryDictionary(const PlistHelperData& d, int objRef) { using namespace std; vector refs = getRefsForContainers(d, objRef); int32_t refCount = refs.size() / 2; dictionary_type dict; for (int i = 0; i < refCount; i++) { boost::any keyAny = parseBinary(d, refs[i]); try { std::string& key = boost::any_cast(keyAny); dict[key] = parseBinary(d, refs[i + refCount]); } catch(boost::bad_any_cast& ) { throw Error("Error parsing dictionary. Key can't be parsed as a string"); } } return dict; } std::string parseBinaryString(const PlistHelperData& d, int headerPosition) { unsigned char headerByte = d._objectTable[headerPosition]; int charStartPosition; int32_t charCount = getCount(d, headerPosition, headerByte, charStartPosition); charStartPosition += headerPosition; std::vector characterBytes = getRange(d._objectTable, charStartPosition, charCount); std::string buffer = std::string((char*) vecData(characterBytes), characterBytes.size()); return buffer; } std::string parseBinaryUnicode(const PlistHelperData& d, int headerPosition) { unsigned char headerByte = d._objectTable[headerPosition]; int charStartPosition; int32_t charCount = getCount(d, headerPosition, headerByte, charStartPosition); charStartPosition += headerPosition; std::vector characterBytes = getRange(d._objectTable, charStartPosition, charCount * 2); if (hostLittleEndian()) { if (! characterBytes.empty()) { for (std::size_t i = 0, n = characterBytes.size(); i < n - 1; i += 2) std::swap(characterBytes[i], characterBytes[i + 1]); } } int16_t *u16chars = (int16_t*) vecData(characterBytes); std::size_t u16len = characterBytes.size() / 2; std::string result = boost::locale::conv::utf_to_utf(u16chars, u16chars + u16len, boost::locale::conv::stop); return result; } int64_t parseBinaryInt(const PlistHelperData& d, int headerPosition, int& intByteCount) { unsigned char header = d._objectTable[headerPosition]; intByteCount = 1 << (header & 0xf); std::vector buffer = getRange(d._objectTable, headerPosition + 1, intByteCount); reverse(buffer.begin(), buffer.end()); return bytesToInt(vecData(regulateNullBytes(buffer, 8)), hostLittleEndian()); } double parseBinaryReal(const PlistHelperData& d, int headerPosition) { unsigned char header = d._objectTable[headerPosition]; int byteCount = 1 << (header & 0xf); std::vector buffer = getRange(d._objectTable, headerPosition + 1, byteCount); reverse(buffer.begin(), buffer.end()); return bytesToDouble(vecData(regulateNullBytes(buffer, 8)), hostLittleEndian()); } bool parseBinaryBool(const PlistHelperData& d, int headerPosition) { unsigned char header = d._objectTable[headerPosition]; bool value; if(header == 0x09) value = true; else if (header == 0x08) value = false; else if (header == 0x00) { // null byte, not sure yet what to do with this. It's in the spec but we // have never encountered it. throw Error("Plist: null byte encountered, unsure how to parse"); } else if (header == 0x0F) { // fill byte, not sure yet what to do with this. It's in the spec but we // have never encountered it. throw Error("Plist: fill byte encountered, unsure how to parse"); } else { std::stringstream ss; ss<<"Plist: unknown header "< buffer = getRange(d._objectTable, headerPosition + 1, 8); Date date; // Date is stored as Apple Epoch and big endian. date.setTimeFromAppleEpoch(bytesToDouble(vecData(buffer), false)); return date; } data_type parseBinaryByteArray(const PlistHelperData& d, int headerPosition) { unsigned char headerByte = d._objectTable[headerPosition]; int byteStartPosition; int32_t byteCount = getCount(d, headerPosition, headerByte, byteStartPosition); byteStartPosition += headerPosition; return getRange((const char*) vecData(d._objectTable), byteStartPosition, byteCount); } int32_t getCount(const PlistHelperData& d, int bytePosition, unsigned char headerByte, int& startOffset) { unsigned char headerByteTrail = headerByte & 0xf; if (headerByteTrail < 15) { startOffset = 1; return headerByteTrail; } else { int32_t count = (int32_t)parseBinaryInt(d, bytePosition + 1, startOffset); startOffset += 2; return count; } } template std::string stringFromValue(const T& value) { std::stringstream ss; ss< IntegerType bytesToInt(const unsigned char* bytes, bool littleEndian) { IntegerType result = 0; if (littleEndian) for (int n = sizeof( result ) - 1; n >= 0; n--) result = (result << 8) + bytes[n]; else for (unsigned n = 0; n < sizeof( result ); n++) result = (result << 8) + bytes[n]; return result; } double bytesToDouble(const unsigned char* bytes, bool littleEndian) { double result; int numBytes = sizeof(double); if(littleEndian) memcpy( &result, bytes, numBytes); else { std::vector bytesReverse(numBytes); std::reverse_copy(bytes, bytes + numBytes, bytesReverse.begin()); memcpy( &result, vecData(bytesReverse), numBytes); } return result; } std::vector doubleToBytes(double val, bool littleEndian) { std::vector result(sizeof(double)); memcpy(vecData(result), &val, sizeof(double)); if(!littleEndian) std::reverse(result.begin(), result.end()); return result; } template std::vector intToBytes(IntegerType val, bool littleEndian) { unsigned int numBytes = sizeof(val); std::vector bytes(numBytes); for(unsigned n = 0; n < numBytes; ++n) if(littleEndian) bytes[n] = (val >> 8 * n) & 0xff; else bytes[numBytes - 1 - n] = (val >> 8 * n) & 0xff; return bytes; } std::vector getRange(const unsigned char* origBytes, int64_t index, int64_t size) { std::vector result((std::vector::size_type)size); std::copy(origBytes + index, origBytes + index + size, result.begin()); return result; } std::vector getRange(const char* origBytes, int64_t index, int64_t size) { std::vector result((std::vector::size_type)size); std::copy(origBytes + index, origBytes + index + size, result.begin()); return result; } std::vector getRange(const std::vector& origBytes, int64_t index, int64_t size) { if((index + size) > (int64_t) origBytes.size()) throw Error("Out of bounds getRange"); return getRange(vecData(origBytes), index, size); } } // namespace Plist