+/*
+ * Copyright (c) 2008 Apple Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+/* CFPropertyList.c
+ Copyright 1999-2002, Apple, Inc. All rights reserved.
+ Responsibility: Christopher Kane
+*/
+
+#include <CoreFoundation/CFPropertyList.h>
+#include <CoreFoundation/CFDate.h>
+#include <CoreFoundation/CFNumber.h>
+#include <CoreFoundation/CFSet.h>
+#include "CFPriv.h"
+#include "CFStringEncodingConverter.h"
+#include "CFInternal.h"
+#include <CoreFoundation/CFStream.h>
+#include <CoreFoundation/CFCalendar.h>
+#include <limits.h>
+#include <float.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <ctype.h>
+#if DEPLOYMENT_TARGET_MACOSX
+#include <CoreFoundation/CFPreferences.h>
+#include <mach-o/dyld.h>
+#endif
+
+
+__private_extern__ bool allowMissingSemi = false;
+
+// Should move this somewhere else
+intptr_t _CFDoOperation(intptr_t code, intptr_t subcode1, intptr_t subcode2) {
+ switch (code) {
+ case 15317: allowMissingSemi = subcode1 ? true : false; break;
+ }
+ return code;
+}
+
+#define PLIST_IX 0
+#define ARRAY_IX 1
+#define DICT_IX 2
+#define KEY_IX 3
+#define STRING_IX 4
+#define DATA_IX 5
+#define DATE_IX 6
+#define REAL_IX 7
+#define INTEGER_IX 8
+#define TRUE_IX 9
+#define FALSE_IX 10
+#define DOCTYPE_IX 11
+#define CDSECT_IX 12
+
+#define PLIST_TAG_LENGTH 5
+#define ARRAY_TAG_LENGTH 5
+#define DICT_TAG_LENGTH 4
+#define KEY_TAG_LENGTH 3
+#define STRING_TAG_LENGTH 6
+#define DATA_TAG_LENGTH 4
+#define DATE_TAG_LENGTH 4
+#define REAL_TAG_LENGTH 4
+#define INTEGER_TAG_LENGTH 7
+#define TRUE_TAG_LENGTH 4
+#define FALSE_TAG_LENGTH 5
+#define DOCTYPE_TAG_LENGTH 7
+#define CDSECT_TAG_LENGTH 9
+
+// don't allow _CFKeyedArchiverUID here
+#define __CFAssertIsPList(cf) CFAssert2(CFGetTypeID(cf) == CFStringGetTypeID() || CFGetTypeID(cf) == CFArrayGetTypeID() || CFGetTypeID(cf) == CFBooleanGetTypeID() || CFGetTypeID(cf) == CFNumberGetTypeID() || CFGetTypeID(cf) == CFDictionaryGetTypeID() || CFGetTypeID(cf) == CFDateGetTypeID() || CFGetTypeID(cf) == CFDataGetTypeID(), __kCFLogAssertion, "%s(): %p not of a property list type", __PRETTY_FUNCTION__, cf);
+
+static bool __CFPropertyListIsValidAux(CFPropertyListRef plist, bool recursive, CFMutableSetRef set, CFPropertyListFormat format);
+
+static CFTypeID stringtype = -1, datatype = -1, numbertype = -1, datetype = -1;
+static CFTypeID booltype = -1, nulltype = -1, dicttype = -1, arraytype = -1, settype = -1;
+
+static void initStatics() {
+ if ((CFTypeID)-1 == stringtype) {
+ stringtype = CFStringGetTypeID();
+ }
+ if ((CFTypeID)-1 == datatype) {
+ datatype = CFDataGetTypeID();
+ }
+ if ((CFTypeID)-1 == numbertype) {
+ numbertype = CFNumberGetTypeID();
+ }
+ if ((CFTypeID)-1 == booltype) {
+ booltype = CFBooleanGetTypeID();
+ }
+ if ((CFTypeID)-1 == datetype) {
+ datetype = CFDateGetTypeID();
+ }
+ if ((CFTypeID)-1 == dicttype) {
+ dicttype = CFDictionaryGetTypeID();
+ }
+ if ((CFTypeID)-1 == arraytype) {
+ arraytype = CFArrayGetTypeID();
+ }
+ if ((CFTypeID)-1 == settype) {
+ settype = CFSetGetTypeID();
+ }
+ if ((CFTypeID)-1 == nulltype) {
+ nulltype = CFNullGetTypeID();
+ }
+}
+
+struct context {
+ bool answer;
+ CFMutableSetRef set;
+ CFPropertyListFormat format;
+};
+
+static void __CFPropertyListIsArrayPlistAux(const void *value, void *context) {
+ struct context *ctx = (struct context *)context;
+ if (!ctx->answer) return;
+#if defined(DEBUG)
+ if (!value) CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListIsValid(): property list arrays cannot contain NULL"));
+#endif
+ ctx->answer = value && __CFPropertyListIsValidAux(value, true, ctx->set, ctx->format);
+}
+
+static void __CFPropertyListIsDictPlistAux(const void *key, const void *value, void *context) {
+ struct context *ctx = (struct context *)context;
+ if (!ctx->answer) return;
+#if defined(DEBUG)
+ if (!key) CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListIsValid(): property list dictionaries cannot contain NULL keys"));
+ if (!value) CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListIsValid(): property list dictionaries cannot contain NULL values"));
+ if (stringtype != CFGetTypeID(key)) {
+ CFStringRef desc = CFCopyTypeIDDescription(CFGetTypeID(key));
+ CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListIsValid(): property list dictionaries may only have keys which are CFStrings, not '%@'"), desc);
+ CFRelease(desc);
+ }
+#endif
+ ctx->answer = key && value && (stringtype == CFGetTypeID(key)) && __CFPropertyListIsValidAux(value, true, ctx->set, ctx->format);
+}
+
+static bool __CFPropertyListIsValidAux(CFPropertyListRef plist, bool recursive, CFMutableSetRef set, CFPropertyListFormat format) {
+ CFTypeID type;
+#if defined(DEBUG)
+ if (!plist) CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListIsValid(): property lists cannot contain NULL"));
+#endif
+ if (!plist) return false;
+ type = CFGetTypeID(plist);
+ if (stringtype == type) return true;
+ if (datatype == type) return true;
+ if (kCFPropertyListOpenStepFormat != format) {
+ if (booltype == type) return true;
+ if (numbertype == type) return true;
+ if (datetype == type) return true;
+#if DEPLOYMENT_TARGET_MACOSX
+ if (_CFKeyedArchiverUIDGetTypeID() == type) return true;
+#endif
+ }
+ if (!recursive && arraytype == type) return true;
+ if (!recursive && dicttype == type) return true;
+ // at any one invocation of this function, set should contain the objects in the "path" down to this object
+#if defined(DEBUG)
+ if (CFSetContainsValue(set, plist)) CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListIsValid(): property lists cannot contain recursive container references"));
+#endif
+ if (CFSetContainsValue(set, plist)) return false;
+ if (arraytype == type) {
+ struct context ctx = {true, set, format};
+ CFSetAddValue(set, plist);
+ CFArrayApplyFunction((CFArrayRef)plist, CFRangeMake(0, CFArrayGetCount((CFArrayRef)plist)), __CFPropertyListIsArrayPlistAux, &ctx);
+ CFSetRemoveValue(set, plist);
+ return ctx.answer;
+ }
+ if (dicttype == type) {
+ struct context ctx = {true, set, format};
+ CFSetAddValue(set, plist);
+ CFDictionaryApplyFunction((CFDictionaryRef)plist, __CFPropertyListIsDictPlistAux, &ctx);
+ CFSetRemoveValue(set, plist);
+ return ctx.answer;
+ }
+#if defined(DEBUG)
+ {
+ CFStringRef desc = CFCopyTypeIDDescription(type);
+ CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListIsValid(): property lists cannot contain objects of type '%@'"), desc);
+ CFRelease(desc);
+ }
+#endif
+ return false;
+}
+
+Boolean CFPropertyListIsValid(CFPropertyListRef plist, CFPropertyListFormat format) {
+ initStatics();
+ CFAssert1(plist != NULL, __kCFLogAssertion, "%s(): NULL is not a property list", __PRETTY_FUNCTION__);
+ CFMutableSetRef set = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, NULL);
+ bool result = __CFPropertyListIsValidAux(plist, true, set, format);
+ CFRelease(set);
+ return result;
+}
+
+static const UniChar CFXMLPlistTags[13][10]= {
+{'p', 'l', 'i', 's', 't', '\0', '\0', '\0', '\0', '\0'},
+{'a', 'r', 'r', 'a', 'y', '\0', '\0', '\0', '\0', '\0'},
+{'d', 'i', 'c', 't', '\0', '\0', '\0', '\0', '\0', '\0'},
+{'k', 'e', 'y', '\0', '\0', '\0', '\0', '\0', '\0', '\0'},
+{'s', 't', 'r', 'i', 'n', 'g', '\0', '\0', '\0', '\0'},
+{'d', 'a', 't', 'a', '\0', '\0', '\0', '\0', '\0', '\0'},
+{'d', 'a', 't', 'e', '\0', '\0', '\0', '\0', '\0', '\0'},
+{'r', 'e', 'a', 'l', '\0', '\0', '\0', '\0', '\0', '\0'},
+{'i', 'n', 't', 'e', 'g', 'e', 'r', '\0', '\0', '\0'},
+{'t', 'r', 'u', 'e', '\0', '\0', '\0', '\0', '\0', '\0'},
+{'f', 'a', 'l', 's', 'e', '\0', '\0', '\0', '\0', '\0'},
+{'D', 'O', 'C', 'T', 'Y', 'P', 'E', '\0', '\0', '\0'},
+{'<', '!', '[', 'C', 'D', 'A', 'T', 'A', '[', '\0'}
+};
+
+typedef struct {
+ const UniChar *begin; // first character of the XML to be parsed
+ const UniChar *curr; // current parse location
+ const UniChar *end; // the first character _after_ the end of the XML
+ CFStringRef errorString;
+ CFAllocatorRef allocator;
+ UInt32 mutabilityOption;
+ CFMutableSetRef stringSet; // set of all strings involved in this parse; allows us to share non-mutable strings in the returned plist
+ CFMutableStringRef tmpString; // Mutable string with external characters that functions can feel free to use as temporary storage as the parse progresses
+ Boolean allowNewTypes; // Whether to allow the new types supported by XML property lists, but not by the old, OPENSTEP ASCII property lists (CFNumber, CFBoolean, CFDate)
+ char _padding[3];
+} _CFXMLPlistParseInfo;
+
+static CFTypeRef parseOldStylePropertyListOrStringsFile(_CFXMLPlistParseInfo *pInfo);
+
+
+
+// The following set of _plist... functions append various things to a mutable data which is in UTF8 encoding. These are pretty general. Assumption is call characters and CFStrings can be converted to UTF8 and appeneded.
+
+// Null-terminated, ASCII or UTF8 string
+//
+static void _plistAppendUTF8CString(CFMutableDataRef mData, const char *cString) {
+ CFDataAppendBytes (mData, (const UInt8 *)cString, strlen(cString));
+}
+
+// UniChars
+//
+static void _plistAppendCharacters(CFMutableDataRef mData, const UniChar *chars, CFIndex length) {
+ CFIndex curLoc = 0;
+
+ do { // Flush out ASCII chars, BUFLEN at a time
+ #define BUFLEN 400
+ UInt8 buf[BUFLEN], *bufPtr = buf;
+ CFIndex cnt = 0;
+ while (cnt < length && (cnt - curLoc < BUFLEN) && (chars[cnt] < 128)) *bufPtr++ = (UInt8)(chars[cnt++]);
+ if (cnt > curLoc) { // Flush any ASCII bytes
+ CFDataAppendBytes(mData, buf, cnt - curLoc);
+ curLoc = cnt;
+ }
+ } while (curLoc < length && (chars[curLoc] < 128)); // We will exit out of here when we run out of chars or hit a non-ASCII char
+
+ if (curLoc < length) { // Now deal with non-ASCII chars
+ CFDataRef data = NULL;
+ CFStringRef str = NULL;
+ if ((str = CFStringCreateWithCharactersNoCopy(kCFAllocatorSystemDefault, chars + curLoc, length - curLoc, kCFAllocatorNull))) {
+ if ((data = CFStringCreateExternalRepresentation(kCFAllocatorSystemDefault, str, kCFStringEncodingUTF8, 0))) {
+ CFDataAppendBytes (mData, CFDataGetBytePtr(data), CFDataGetLength(data));
+ CFRelease(data);
+ }
+ CFRelease(str);
+ }
+ CFAssert1(str && data, __kCFLogAssertion, "%s(): Error writing plist", __PRETTY_FUNCTION__);
+ }
+}
+
+// Append CFString
+//
+static void _plistAppendString(CFMutableDataRef mData, CFStringRef str) {
+ const UniChar *chars;
+ const char *cStr;
+ CFDataRef data;
+ if ((chars = CFStringGetCharactersPtr(str))) {
+ _plistAppendCharacters(mData, chars, CFStringGetLength(str));
+ } else if ((cStr = CFStringGetCStringPtr(str, kCFStringEncodingASCII)) || (cStr = CFStringGetCStringPtr(str, kCFStringEncodingUTF8))) {
+ _plistAppendUTF8CString(mData, cStr);
+ } else if ((data = CFStringCreateExternalRepresentation(kCFAllocatorSystemDefault, str, kCFStringEncodingUTF8, 0))) {
+ CFDataAppendBytes (mData, CFDataGetBytePtr(data), CFDataGetLength(data));
+ CFRelease(data);
+ } else {
+ CFAssert1(TRUE, __kCFLogAssertion, "%s(): Error in plist writing", __PRETTY_FUNCTION__);
+ }
+}
+
+
+// Append CFString-style format + arguments
+//
+static void _plistAppendFormat(CFMutableDataRef mData, CFStringRef format, ...) {
+ CFStringRef fStr;
+ va_list argList;
+
+ va_start(argList, format);
+ fStr = CFStringCreateWithFormatAndArguments(kCFAllocatorSystemDefault, NULL, format, argList);
+ va_end(argList);
+
+ CFAssert1(fStr, __kCFLogAssertion, "%s(): Error writing plist", __PRETTY_FUNCTION__);
+ _plistAppendString(mData, fStr);
+ CFRelease(fStr);
+}
+
+
+
+static void _appendIndents(CFIndex numIndents, CFMutableDataRef str) {
+#define NUMTABS 4
+ static const UniChar tabs[NUMTABS] = {'\t','\t','\t','\t'};
+ for (; numIndents > 0; numIndents -= NUMTABS) _plistAppendCharacters(str, tabs, (numIndents >= NUMTABS) ? NUMTABS : numIndents);
+}
+
+/* Append the escaped version of origStr to mStr.
+*/
+static void _appendEscapedString(CFStringRef origStr, CFMutableDataRef mStr) {
+#define BUFSIZE 64
+ CFIndex i, length = CFStringGetLength(origStr);
+ CFIndex bufCnt = 0;
+ UniChar buf[BUFSIZE];
+ CFStringInlineBuffer inlineBuffer;
+
+ CFStringInitInlineBuffer(origStr, &inlineBuffer, CFRangeMake(0, length));
+
+ for (i = 0; i < length; i ++) {
+ UniChar ch = __CFStringGetCharacterFromInlineBufferQuick(&inlineBuffer, i);
+ switch(ch) {
+ case '<':
+ if (bufCnt) _plistAppendCharacters(mStr, buf, bufCnt);
+ bufCnt = 0;
+ _plistAppendUTF8CString(mStr, "<");
+ break;
+ case '>':
+ if (bufCnt) _plistAppendCharacters(mStr, buf, bufCnt);
+ bufCnt = 0;
+ _plistAppendUTF8CString(mStr, ">");
+ break;
+ case '&':
+ if (bufCnt) _plistAppendCharacters(mStr, buf, bufCnt);
+ bufCnt = 0;
+ _plistAppendUTF8CString(mStr, "&");
+ break;
+ default:
+ buf[bufCnt++] = ch;
+ if (bufCnt == BUFSIZE) {
+ _plistAppendCharacters(mStr, buf, bufCnt);
+ bufCnt = 0;
+ }
+ break;
+ }
+ }
+ if (bufCnt) _plistAppendCharacters(mStr, buf, bufCnt);
+}
+
+
+
+/* Base-64 encoding/decoding */
+
+/* The base-64 encoding packs three 8-bit bytes into four 7-bit ASCII
+ * characters. If the number of bytes in the original data isn't divisable
+ * by three, "=" characters are used to pad the encoded data. The complete
+ * set of characters used in base-64 are:
+ *
+ * 'A'..'Z' => 00..25
+ * 'a'..'z' => 26..51
+ * '0'..'9' => 52..61
+ * '+' => 62
+ * '/' => 63
+ * '=' => pad
+ */
+
+// Write the inputData to the mData using Base 64 encoding
+
+static void _XMLPlistAppendDataUsingBase64(CFMutableDataRef mData, CFDataRef inputData, CFIndex indent) {
+ static const char __CFPLDataEncodeTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ #define MAXLINELEN 76
+ char buf[MAXLINELEN + 4 + 2]; // For the slop and carriage return and terminating NULL
+
+ const uint8_t *bytes = CFDataGetBytePtr(inputData);
+ CFIndex length = CFDataGetLength(inputData);
+ CFIndex i, pos;
+ const uint8_t *p;
+
+ if (indent > 8) indent = 8; // refuse to indent more than 64 characters
+
+ pos = 0; // position within buf
+
+ for (i = 0, p = bytes; i < length; i++, p++) {
+ /* 3 bytes are encoded as 4 */
+ switch (i % 3) {
+ case 0:
+ buf[pos++] = __CFPLDataEncodeTable [ ((p[0] >> 2) & 0x3f)];
+ break;
+ case 1:
+ buf[pos++] = __CFPLDataEncodeTable [ ((((p[-1] << 8) | p[0]) >> 4) & 0x3f)];
+ break;
+ case 2:
+ buf[pos++] = __CFPLDataEncodeTable [ ((((p[-1] << 8) | p[0]) >> 6) & 0x3f)];
+ buf[pos++] = __CFPLDataEncodeTable [ (p[0] & 0x3f)];
+ break;
+ }
+ /* Flush the line out every 76 (or fewer) chars --- indents count against the line length*/
+ if (pos >= MAXLINELEN - 8 * indent) {
+ buf[pos++] = '\n';
+ buf[pos++] = 0;
+ _appendIndents(indent, mData);
+ _plistAppendUTF8CString(mData, buf);
+ pos = 0;
+ }
+ }
+
+ switch (i % 3) {
+ case 0:
+ break;
+ case 1:
+ buf[pos++] = __CFPLDataEncodeTable [ ((p[-1] << 4) & 0x30)];
+ buf[pos++] = '=';
+ buf[pos++] = '=';
+ break;
+ case 2:
+ buf[pos++] = __CFPLDataEncodeTable [ ((p[-1] << 2) & 0x3c)];
+ buf[pos++] = '=';
+ break;
+ }
+
+ if (pos > 0) {
+ buf[pos++] = '\n';
+ buf[pos++] = 0;
+ _appendIndents(indent, mData);
+ _plistAppendUTF8CString(mData, buf);
+ }
+}
+
+extern CFStringRef __CFNumberCopyFormattingDescriptionAsFloat64(CFTypeRef cf);
+
+static void _CFAppendXML0(CFTypeRef object, UInt32 indentation, CFMutableDataRef xmlString) {
+ UInt32 typeID = CFGetTypeID(object);
+ _appendIndents(indentation, xmlString);
+ if (typeID == stringtype) {
+ _plistAppendUTF8CString(xmlString, "<");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[STRING_IX], STRING_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">");
+ _appendEscapedString((CFStringRef)object, xmlString);
+ _plistAppendUTF8CString(xmlString, "</");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[STRING_IX], STRING_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">\n");
+#if DEPLOYMENT_TARGET_MACOSX
+ } else if (typeID == _CFKeyedArchiverUIDGetTypeID()) {
+ _plistAppendUTF8CString(xmlString, "<");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">\n");
+ _appendIndents(indentation+1, xmlString);
+ _plistAppendUTF8CString(xmlString, "<");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[KEY_IX], KEY_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">");
+ _appendEscapedString(CFSTR("CF$UID"), xmlString);
+ _plistAppendUTF8CString(xmlString, "</");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[KEY_IX], KEY_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">\n");
+ _appendIndents(indentation + 1, xmlString);
+ _plistAppendUTF8CString(xmlString, "<");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">");
+
+ uint64_t v = _CFKeyedArchiverUIDGetValue((CFKeyedArchiverUIDRef)object);
+ CFNumberRef num = CFNumberCreate(kCFAllocatorSystemDefault, kCFNumberSInt64Type, &v);
+ _plistAppendFormat(xmlString, CFSTR("%@"), num);
+ CFRelease(num);
+
+ _plistAppendUTF8CString(xmlString, "</");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">\n");
+ _appendIndents(indentation, xmlString);
+ _plistAppendUTF8CString(xmlString, "</");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">\n");
+#elif 0
+#else
+#error Unknown or unspecified DEPLOYMENT_TARGET
+#endif
+ } else if (typeID == arraytype) {
+ UInt32 i, count = CFArrayGetCount((CFArrayRef)object);
+ if (count == 0) {
+ _plistAppendUTF8CString(xmlString, "<");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[ARRAY_IX], ARRAY_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, "/>\n");
+ return;
+ }
+ _plistAppendUTF8CString(xmlString, "<");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[ARRAY_IX], ARRAY_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">\n");
+ for (i = 0; i < count; i ++) {
+ _CFAppendXML0(CFArrayGetValueAtIndex((CFArrayRef)object, i), indentation+1, xmlString);
+ }
+ _appendIndents(indentation, xmlString);
+ _plistAppendUTF8CString(xmlString, "</");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[ARRAY_IX], ARRAY_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">\n");
+ } else if (typeID == dicttype) {
+ UInt32 i, count = CFDictionaryGetCount((CFDictionaryRef)object);
+ CFMutableArrayRef keyArray;
+ CFTypeRef *keys;
+ if (count == 0) {
+ _plistAppendUTF8CString(xmlString, "<");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, "/>\n");
+ return;
+ }
+ _plistAppendUTF8CString(xmlString, "<");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">\n");
+ keys = (CFTypeRef *)CFAllocatorAllocate(kCFAllocatorSystemDefault, count * sizeof(CFTypeRef), 0);
+ CFDictionaryGetKeysAndValues((CFDictionaryRef)object, keys, NULL);
+ keyArray = CFArrayCreateMutable(kCFAllocatorSystemDefault, count, &kCFTypeArrayCallBacks);
+ CFArrayReplaceValues(keyArray, CFRangeMake(0, 0), keys, count);
+ CFArraySortValues(keyArray, CFRangeMake(0, count), (CFComparatorFunction)CFStringCompare, NULL);
+ CFArrayGetValues(keyArray, CFRangeMake(0, count), keys);
+ CFRelease(keyArray);
+ for (i = 0; i < count; i ++) {
+ CFTypeRef key = keys[i];
+ _appendIndents(indentation+1, xmlString);
+ _plistAppendUTF8CString(xmlString, "<");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[KEY_IX], KEY_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">");
+ _appendEscapedString((CFStringRef)key, xmlString);
+ _plistAppendUTF8CString(xmlString, "</");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[KEY_IX], KEY_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">\n");
+ _CFAppendXML0(CFDictionaryGetValue((CFDictionaryRef)object, key), indentation+1, xmlString);
+ }
+ CFAllocatorDeallocate(kCFAllocatorSystemDefault, keys);
+ _appendIndents(indentation, xmlString);
+ _plistAppendUTF8CString(xmlString, "</");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">\n");
+ } else if (typeID == datatype) {
+ _plistAppendUTF8CString(xmlString, "<");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[DATA_IX], DATA_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">\n");
+ _XMLPlistAppendDataUsingBase64(xmlString, (CFDataRef)object, indentation);
+ _appendIndents(indentation, xmlString);
+ _plistAppendUTF8CString(xmlString, "</");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[DATA_IX], DATA_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">\n");
+ } else if (typeID == datetype) {
+ // YYYY '-' MM '-' DD 'T' hh ':' mm ':' ss 'Z'
+ int32_t y = 0, M = 0, d = 0, H = 0, m = 0, s = 0;
+#if 1
+ CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(CFDateGetAbsoluteTime((CFDateRef)object), NULL);
+ y = date.year;
+ M = date.month;
+ d = date.day;
+ H = date.hour;
+ m = date.minute;
+ s = (int32_t)date.second;
+#else
+ CFCalendarRef calendar = CFCalendarCreateWithIdentifier(kCFAllocatorSystemDefault, kCFGregorianCalendar);
+ CFTimeZoneRef tz = CFTimeZoneCreateWithName(kCFAllocatorSystemDefault, CFSTR("GMT"), true);
+ CFCalendarSetTimeZone(calendar, tz);
+ CFCalendarDecomposeAbsoluteTime(calendar, CFDateGetAbsoluteTime((CFDateRef)object), (const uint8_t *)"yMdHms", &y, &M, &d, &H, &m, &s);
+ CFRelease(calendar);
+ CFRelease(tz);
+#endif
+ _plistAppendUTF8CString(xmlString, "<");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[DATE_IX], DATE_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">");
+ _plistAppendFormat(xmlString, CFSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), y, M, d, H, m, s);
+ _plistAppendUTF8CString(xmlString, "</");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[DATE_IX], DATE_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">\n");
+ } else if (typeID == numbertype) {
+ if (CFNumberIsFloatType((CFNumberRef)object)) {
+ _plistAppendUTF8CString(xmlString, "<");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">");
+ CFStringRef s = __CFNumberCopyFormattingDescriptionAsFloat64(object);
+ _plistAppendString(xmlString, s);
+ CFRelease(s);
+ _plistAppendUTF8CString(xmlString, "</");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">\n");
+ } else {
+ _plistAppendUTF8CString(xmlString, "<");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">");
+
+ _plistAppendFormat(xmlString, CFSTR("%@"), object);
+
+ _plistAppendUTF8CString(xmlString, "</");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, ">\n");
+ }
+ } else if (typeID == booltype) {
+ if (CFBooleanGetValue((CFBooleanRef)object)) {
+ _plistAppendUTF8CString(xmlString, "<");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[TRUE_IX], TRUE_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, "/>\n");
+ } else {
+ _plistAppendUTF8CString(xmlString, "<");
+ _plistAppendCharacters(xmlString, CFXMLPlistTags[FALSE_IX], FALSE_TAG_LENGTH);
+ _plistAppendUTF8CString(xmlString, "/>\n");
+ }
+ }
+}
+
+static void _CFGenerateXMLPropertyListToData(CFMutableDataRef xml, CFTypeRef propertyList) {
+ _plistAppendUTF8CString(xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE ");
+ _plistAppendCharacters(xml, CFXMLPlistTags[PLIST_IX], PLIST_TAG_LENGTH);
+ _plistAppendUTF8CString(xml, " PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<");
+ _plistAppendCharacters(xml, CFXMLPlistTags[PLIST_IX], PLIST_TAG_LENGTH);
+ _plistAppendUTF8CString(xml, " version=\"1.0\">\n");
+
+ _CFAppendXML0(propertyList, 0, xml);
+
+ _plistAppendUTF8CString(xml, "</");
+ _plistAppendCharacters(xml, CFXMLPlistTags[PLIST_IX], PLIST_TAG_LENGTH);
+ _plistAppendUTF8CString(xml, ">\n");
+}
+
+CFDataRef CFPropertyListCreateXMLData(CFAllocatorRef allocator, CFPropertyListRef propertyList) {
+ initStatics();
+ CFMutableDataRef xml;
+ CFAssert1(propertyList != NULL, __kCFLogAssertion, "%s(): Cannot be called with a NULL property list", __PRETTY_FUNCTION__);
+ __CFAssertIsPList(propertyList);
+ if (!CFPropertyListIsValid(propertyList, kCFPropertyListXMLFormat_v1_0)) return NULL;
+ xml = CFDataCreateMutable(allocator, 0);
+ _CFGenerateXMLPropertyListToData(xml, propertyList);
+ return xml;
+}
+
+CFDataRef _CFPropertyListCreateXMLDataWithExtras(CFAllocatorRef allocator, CFPropertyListRef propertyList) {
+ initStatics();
+ CFMutableDataRef xml;
+ CFAssert1(propertyList != NULL, __kCFLogAssertion, "%s(): Cannot be called with a NULL property list", __PRETTY_FUNCTION__);
+ xml = CFDataCreateMutable(allocator, 0);
+ _CFGenerateXMLPropertyListToData(xml, propertyList);
+ return xml;
+}
+
+// ========================================================================
+
+//
+// ------------------------- Reading plists ------------------
+//
+
+static void skipInlineDTD(_CFXMLPlistParseInfo *pInfo);
+static CFTypeRef parseXMLElement(_CFXMLPlistParseInfo *pInfo, Boolean *isKey);
+
+// warning: doesn't have a good idea of Unicode line separators
+static UInt32 lineNumber(_CFXMLPlistParseInfo *pInfo) {
+ const UniChar *p = pInfo->begin;
+ UInt32 count = 1;
+ while (p < pInfo->curr) {
+ if (*p == '\r') {
+ count ++;
+ if (*(p + 1) == '\n')
+ p ++;
+ } else if (*p == '\n') {
+ count ++;
+ }
+ p ++;
+ }
+ return count;
+}
+
+// warning: doesn't have a good idea of Unicode white space
+CF_INLINE void skipWhitespace(_CFXMLPlistParseInfo *pInfo) {
+ while (pInfo->curr < pInfo->end) {
+ switch (*(pInfo->curr)) {
+ case ' ':
+ case '\t':
+ case '\n':
+ case '\r':
+ pInfo->curr ++;
+ continue;
+ default:
+ return;
+ }
+ }
+}
+
+/* All of these advance to the end of the given construct and return a pointer to the first character beyond the construct. If the construct doesn't parse properly, NULL is returned. */
+
+// pInfo should be just past "<!--"
+static void skipXMLComment(_CFXMLPlistParseInfo *pInfo) {
+ const UniChar *p = pInfo->curr;
+ const UniChar *end = pInfo->end - 3; // Need at least 3 characters to compare against
+ while (p < end) {
+ if (*p == '-' && *(p+1) == '-' && *(p+2) == '>') {
+ pInfo->curr = p+3;
+ return;
+ }
+ p ++;
+ }
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unterminated comment started on line %d"), lineNumber(pInfo));
+}
+
+// stringToMatch and buf must both be of at least len
+static Boolean matchString(const UniChar *buf, const UniChar *stringToMatch, UInt32 len) {
+ switch (len) {
+ case 10: if (buf[9] != stringToMatch[9]) return false;
+ case 9: if (buf[8] != stringToMatch[8]) return false;
+ case 8: if (buf[7] != stringToMatch[7]) return false;
+ case 7: if (buf[6] != stringToMatch[6]) return false;
+ case 6: if (buf[5] != stringToMatch[5]) return false;
+ case 5: if (buf[4] != stringToMatch[4]) return false;
+ case 4: if (buf[3] != stringToMatch[3]) return false;
+ case 3: if (buf[2] != stringToMatch[2]) return false;
+ case 2: if (buf[1] != stringToMatch[1]) return false;
+ case 1: if (buf[0] != stringToMatch[0]) return false;
+ case 0: return true;
+ }
+ return false; // internal error
+}
+
+// pInfo should be set to the first character after "<?"
+static void skipXMLProcessingInstruction(_CFXMLPlistParseInfo *pInfo) {
+ const UniChar *begin = pInfo->curr, *end = pInfo->end - 2; // Looking for "?>" so we need at least 2 characters
+ while (pInfo->curr < end) {
+ if (*(pInfo->curr) == '?' && *(pInfo->curr+1) == '>') {
+ pInfo->curr += 2;
+ return;
+ }
+ pInfo->curr ++;
+ }
+ pInfo->curr = begin;
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected EOF while parsing the processing instruction begun on line %d"), lineNumber(pInfo));
+}
+
+// first character should be immediately after the "<!"
+static void skipDTD(_CFXMLPlistParseInfo *pInfo) {
+ // First pass "DOCTYPE"
+ if (pInfo->end - pInfo->curr < DOCTYPE_TAG_LENGTH || !matchString(pInfo->curr, CFXMLPlistTags[DOCTYPE_IX], DOCTYPE_TAG_LENGTH)) {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Malformed DTD on line %d"), lineNumber(pInfo));
+ return;
+ }
+ pInfo->curr += DOCTYPE_TAG_LENGTH;
+ skipWhitespace(pInfo);
+
+ // Look for either the beginning of a complex DTD or the end of the DOCTYPE structure
+ while (pInfo->curr < pInfo->end) {
+ UniChar ch = *(pInfo->curr);
+ if (ch == '[') break; // inline DTD
+ if (ch == '>') { // End of the DTD
+ pInfo->curr ++;
+ return;
+ }
+ pInfo->curr ++;
+ }
+ if (pInfo->curr == pInfo->end) {
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF while parsing DTD", CFStringGetSystemEncoding());
+ return;
+ }
+
+ // *Sigh* Must parse in-line DTD
+ skipInlineDTD(pInfo);
+ if (pInfo->errorString) return;
+ skipWhitespace(pInfo);
+ if (pInfo->errorString) return;
+ if (pInfo->curr < pInfo->end) {
+ if (*(pInfo->curr) == '>') {
+ pInfo->curr ++;
+ } else {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c on line %d while parsing DTD"), *(pInfo->curr), lineNumber(pInfo));
+ }
+ } else {
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF while parsing DTD", CFStringGetSystemEncoding());
+ }
+}
+
+static void skipPERef(_CFXMLPlistParseInfo *pInfo) {
+ const UniChar *p = pInfo->curr;
+ while (p < pInfo->end) {
+ if (*p == ';') {
+ pInfo->curr = p+1;
+ return;
+ }
+ p ++;
+ }
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected EOF while parsing percent-escape sequence begun on line %d"), lineNumber(pInfo));
+}
+
+// First character should be just past '['
+static void skipInlineDTD(_CFXMLPlistParseInfo *pInfo) {
+ while (!pInfo->errorString && pInfo->curr < pInfo->end) {
+ UniChar ch;
+ skipWhitespace(pInfo);
+ ch = *pInfo->curr;
+ if (ch == '%') {
+ pInfo->curr ++;
+ skipPERef(pInfo);
+ } else if (ch == '<') {
+ pInfo->curr ++;
+ if (pInfo->curr >= pInfo->end) {
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF while parsing inline DTD", CFStringGetSystemEncoding());
+ return;
+ }
+ ch = *(pInfo->curr);
+ if (ch == '?') {
+ pInfo->curr ++;
+ skipXMLProcessingInstruction(pInfo);
+ } else if (ch == '!') {
+ if (pInfo->curr + 2 < pInfo->end && (*(pInfo->curr+1) == '-' && *(pInfo->curr+2) == '-')) {
+ pInfo->curr += 3;
+ skipXMLComment(pInfo);
+ } else {
+ // Skip the myriad of DTD declarations of the form "<!string" ... ">"
+ pInfo->curr ++; // Past both '<' and '!'
+ while (pInfo->curr < pInfo->end) {
+ if (*(pInfo->curr) == '>') break;
+ pInfo->curr ++;
+ }
+ if (*(pInfo->curr) != '>') {
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF while parsing inline DTD", CFStringGetSystemEncoding());
+ return;
+
+ }
+ pInfo->curr ++;
+ }
+ } else {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c on line %d while parsing inline DTD"), ch, lineNumber(pInfo));
+ return;
+ }
+ } else if (ch == ']') {
+ pInfo->curr ++;
+ return;
+ } else {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c on line %d while parsing inline DTD"), ch, lineNumber(pInfo));
+ return;
+ }
+ }
+ if (!pInfo->errorString)
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF while parsing inline DTD", CFStringGetSystemEncoding());
+}
+
+/* A bit wasteful to do everything with unichars (since we know all the characters we're going to see are 7-bit ASCII), but since our data is coming from or going to a CFString, this prevents the extra cost of converting formats. */
+
+static const signed char __CFPLDataDecodeTable[128] = {
+ /* 000 */ -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 010 */ -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 020 */ -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 030 */ -1, -1, -1, -1, -1, -1, -1, -1,
+ /* ' ' */ -1, -1, -1, -1, -1, -1, -1, -1,
+ /* '(' */ -1, -1, -1, 62, -1, -1, -1, 63,
+ /* '0' */ 52, 53, 54, 55, 56, 57, 58, 59,
+ /* '8' */ 60, 61, -1, -1, -1, 0, -1, -1,
+ /* '@' */ -1, 0, 1, 2, 3, 4, 5, 6,
+ /* 'H' */ 7, 8, 9, 10, 11, 12, 13, 14,
+ /* 'P' */ 15, 16, 17, 18, 19, 20, 21, 22,
+ /* 'X' */ 23, 24, 25, -1, -1, -1, -1, -1,
+ /* '`' */ -1, 26, 27, 28, 29, 30, 31, 32,
+ /* 'h' */ 33, 34, 35, 36, 37, 38, 39, 40,
+ /* 'p' */ 41, 42, 43, 44, 45, 46, 47, 48,
+ /* 'x' */ 49, 50, 51, -1, -1, -1, -1, -1
+};
+
+static CFDataRef __CFPLDataDecode(_CFXMLPlistParseInfo *pInfo, Boolean isMutable) {
+ int tmpbufpos = 0;
+ int tmpbuflen = 256;
+ uint8_t *tmpbuf;
+ int numeq = 0;
+ int acc = 0;
+ int cntr = 0;
+
+ tmpbuf = (uint8_t *)CFAllocatorAllocate(pInfo->allocator, tmpbuflen, 0);
+ for (; pInfo->curr < pInfo->end; pInfo->curr++) {
+ UniChar c = *(pInfo->curr);
+ if (c == '<') {
+ break;
+ }
+ if ('=' == c) {
+ numeq++;
+ } else if (!isspace(c)) {
+ numeq = 0;
+ }
+ if (__CFPLDataDecodeTable[c] < 0)
+ continue;
+ cntr++;
+ acc <<= 6;
+ acc += __CFPLDataDecodeTable[c];
+ if (0 == (cntr & 0x3)) {
+ if (tmpbuflen <= tmpbufpos + 2) {
+ if (tmpbuflen < 256 * 1024) {
+ tmpbuflen *= 4;
+ } else if (tmpbuflen < 16 * 1024 * 1024) {
+ tmpbuflen *= 2;
+ } else {
+ // once in this stage, this will be really slow
+ // and really potentially fragment memory
+ tmpbuflen += 256 * 1024;
+ }
+ tmpbuf = (uint8_t *)CFAllocatorReallocate(pInfo->allocator, tmpbuf, tmpbuflen, 0);
+ if (!tmpbuf) HALT;
+ }
+ tmpbuf[tmpbufpos++] = (acc >> 16) & 0xff;
+ if (numeq < 2)
+ tmpbuf[tmpbufpos++] = (acc >> 8) & 0xff;
+ if (numeq < 1)
+ tmpbuf[tmpbufpos++] = acc & 0xff;
+ }
+ }
+ if (isMutable) {
+ CFMutableDataRef result = CFDataCreateMutable(pInfo->allocator, 0);
+ CFDataAppendBytes(result, tmpbuf, tmpbufpos);
+ CFAllocatorDeallocate(pInfo->allocator, tmpbuf);
+ return result;
+ } else {
+ return CFDataCreateWithBytesNoCopy(pInfo->allocator, tmpbuf, tmpbufpos, pInfo->allocator);
+ }
+}
+
+// content ::== (element | CharData | Reference | CDSect | PI | Comment)*
+// In the context of a plist, CharData, Reference and CDSect are not legal (they all resolve to strings). Skipping whitespace, then, the next character should be '<'. From there, we figure out which of the three remaining cases we have (element, PI, or Comment).
+static CFTypeRef getContentObject(_CFXMLPlistParseInfo *pInfo, Boolean *isKey) {
+ if (isKey) *isKey = false;
+ while (!pInfo->errorString && pInfo->curr < pInfo->end) {
+ skipWhitespace(pInfo);
+ if (pInfo->curr >= pInfo->end) {
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
+ return NULL;
+ }
+ if (*(pInfo->curr) != '<') {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c on line %d"), *(pInfo->curr), lineNumber(pInfo));
+ return NULL;
+ }
+ pInfo->curr ++;
+ if (pInfo->curr >= pInfo->end) {
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
+ return NULL;
+ }
+ switch (*(pInfo->curr)) {
+ case '?':
+ // Processing instruction
+ skipXMLProcessingInstruction(pInfo);
+ break;
+ case '!':
+ // Could be a comment
+ if (pInfo->curr+2 >= pInfo->end) {
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
+ return NULL;
+ }
+ if (*(pInfo->curr+1) == '-' && *(pInfo->curr+2) == '-') {
+ pInfo->curr += 2;
+ skipXMLComment(pInfo);
+ } else {
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
+ return NULL;
+ }
+ break;
+ case '/':
+ // Whoops! Looks like we got to the end tag for the element whose content we're parsing
+ pInfo->curr --; // Back off to the '<'
+ return NULL;
+ default:
+ // Should be an element
+ return parseXMLElement(pInfo, isKey);
+ }
+ }
+ // Do not set the error string here; if it wasn't already set by one of the recursive parsing calls, the caller will quickly detect the failure (b/c pInfo->curr >= pInfo->end) and provide a more useful one of the form "end tag for <blah> not found"
+ return NULL;
+}
+
+static void _catFromMarkToBuf(const UniChar *mark, const UniChar *buf, CFMutableStringRef *string, CFAllocatorRef allocator ) {
+ if (!(*string)) {
+ *string = CFStringCreateMutable(allocator, 0);
+ }
+ CFStringAppendCharacters(*string, mark, buf-mark);
+}
+
+static void parseCDSect_pl(_CFXMLPlistParseInfo *pInfo, CFMutableStringRef string) {
+ const UniChar *end, *begin;
+ if (pInfo->end - pInfo->curr < CDSECT_TAG_LENGTH) {
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
+ return;
+ }
+ if (!matchString(pInfo->curr, CFXMLPlistTags[CDSECT_IX], CDSECT_TAG_LENGTH)) {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered improper CDATA opening at line %d"), lineNumber(pInfo));
+ return;
+ }
+ pInfo->curr += CDSECT_TAG_LENGTH;
+ begin = pInfo->curr; // Marks the first character of the CDATA content
+ end = pInfo->end-2; // So we can safely look 2 characters beyond p
+ while (pInfo->curr < end) {
+ if (*(pInfo->curr) == ']' && *(pInfo->curr+1) == ']' && *(pInfo->curr+2) == '>') {
+ // Found the end!
+ CFStringAppendCharacters(string, begin, pInfo->curr-begin);
+ pInfo->curr += 3;
+ return;
+ }
+ pInfo->curr ++;
+ }
+ // Never found the end mark
+ pInfo->curr = begin;
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Could not find end of CDATA started on line %d"), lineNumber(pInfo));
+}
+
+// Only legal references are {lt, gt, amp, apos, quote, #ddd, #xAAA}
+static void parseEntityReference_pl(_CFXMLPlistParseInfo *pInfo, CFMutableStringRef string) {
+ int len;
+ UniChar ch;
+ pInfo->curr ++; // move past the '&';
+ len = pInfo->end - pInfo->curr; // how many characters we can safely scan
+ if (len < 1) {
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
+ return;
+ }
+ switch (*(pInfo->curr)) {
+ case 'l': // "lt"
+ if (len >= 3 && *(pInfo->curr+1) == 't' && *(pInfo->curr+2) == ';') {
+ ch = '<';
+ pInfo->curr += 3;
+ break;
+ }
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo));
+ return;
+ case 'g': // "gt"
+ if (len >= 3 && *(pInfo->curr+1) == 't' && *(pInfo->curr+2) == ';') {
+ ch = '>';
+ pInfo->curr += 3;
+ break;
+ }
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo));
+ return;
+ case 'a': // "apos" or "amp"
+ if (len < 4) { // Not enough characters for either conversion
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
+ return;
+ }
+ if (*(pInfo->curr+1) == 'm') {
+ // "amp"
+ if (*(pInfo->curr+2) == 'p' && *(pInfo->curr+3) == ';') {
+ ch = '&';
+ pInfo->curr += 4;
+ break;
+ }
+ } else if (*(pInfo->curr+1) == 'p') {
+ // "apos"
+ if (len > 4 && *(pInfo->curr+2) == 'o' && *(pInfo->curr+3) == 's' && *(pInfo->curr+4) == ';') {
+ ch = '\'';
+ pInfo->curr += 5;
+ break;
+ }
+ }
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo));
+ return;
+ case 'q': // "quote"
+ if (len >= 5 && *(pInfo->curr+1) == 'u' && *(pInfo->curr+2) == 'o' && *(pInfo->curr+3) == 't' && *(pInfo->curr+4) == ';') {
+ ch = '\"';
+ pInfo->curr += 5;
+ break;
+ }
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo));
+ return;
+ case '#':
+ {
+ uint16_t num = 0;
+ Boolean isHex = false;
+ if ( len < 4) { // Not enough characters to make it all fit! Need at least "&#d;"
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
+ return;
+ }
+ pInfo->curr ++;
+ if (*(pInfo->curr) == 'x') {
+ isHex = true;
+ pInfo->curr ++;
+ }
+ while (pInfo->curr < pInfo->end) {
+ ch = *(pInfo->curr);
+ pInfo->curr ++;
+ if (ch == ';') {
+ CFStringAppendCharacters(string, &num, 1);
+ return;
+ }
+ if (!isHex) num = num*10;
+ else num = num << 4;
+ if (ch <= '9' && ch >= '0') {
+ num += (ch - '0');
+ } else if (!isHex) {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c at line %d"), ch, lineNumber(pInfo));
+ return;
+ } else if (ch >= 'a' && ch <= 'f') {
+ num += 10 + (ch - 'a');
+ } else if (ch >= 'A' && ch <= 'F') {
+ num += 10 + (ch - 'A');
+ } else {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c at line %d"), ch, lineNumber(pInfo));
+ return;
+ }
+ }
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
+ return;
+ }
+ default:
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo));
+ return;
+ }
+ CFStringAppendCharacters(string, &ch, 1);
+}
+
+static CFStringRef _uniqueStringForString(_CFXMLPlistParseInfo *pInfo, CFStringRef stringToUnique) {
+ if (!pInfo->stringSet) {
+ pInfo->stringSet = CFSetCreateMutable(pInfo->allocator, 0, &kCFCopyStringSetCallBacks);
+ _CFSetSetCapacity(pInfo->stringSet, 160); // set capacity high to avoid lots of rehashes, though waste some memory
+ }
+ CFSetAddValue(pInfo->stringSet, stringToUnique);
+ return (CFStringRef)CFSetGetValue(pInfo->stringSet, stringToUnique);
+}
+
+extern void _CFStrSetDesiredCapacity(CFMutableStringRef str, CFIndex len);
+
+static CFStringRef _uniqueStringForCharacters(_CFXMLPlistParseInfo *pInfo, const UniChar *base, CFIndex length) {
+ CFIndex idx;
+ uint8_t *ascii, buffer[1024];
+ bool isASCII;
+ if (!pInfo->stringSet) {
+ pInfo->stringSet = CFSetCreateMutable(pInfo->allocator, 0, &kCFCopyStringSetCallBacks);
+ _CFSetSetCapacity(pInfo->stringSet, 160); // set capacity high to avoid lots of rehashes, though waste some memory
+ }
+ if (pInfo->tmpString) {
+ CFStringDelete(pInfo->tmpString, CFRangeMake(0, CFStringGetLength(pInfo->tmpString)));
+ } else {
+ pInfo->tmpString = CFStringCreateMutable(pInfo->allocator, 0);
+ _CFStrSetDesiredCapacity(pInfo->tmpString, 512);
+ }
+ // This is to avoid having to promote the buffers of all the strings compared against
+ // during the set probe; if a Unicode string is passed in, that's what happens.
+ isASCII = true;
+ for (idx = 0; isASCII && idx < length; idx++) isASCII = isASCII && (base[idx] < 0x80);
+ if (isASCII) {
+ ascii = (length < (CFIndex)sizeof(buffer)) ? buffer : (uint8_t *)CFAllocatorAllocate(kCFAllocatorSystemDefault, length + 1, 0);
+ for (idx = 0; idx < length; idx++) ascii[idx] = (uint8_t)base[idx];
+ ascii[length] = '\0';
+ CFStringAppendCString(pInfo->tmpString, (char *)ascii, kCFStringEncodingASCII);
+ if (ascii != buffer) CFAllocatorDeallocate(kCFAllocatorSystemDefault, ascii);
+ } else {
+ CFStringAppendCharacters(pInfo->tmpString, base, length);
+ }
+ CFSetAddValue(pInfo->stringSet, pInfo->tmpString);
+ return (CFStringRef)CFSetGetValue(pInfo->stringSet, pInfo->tmpString);
+}
+
+
+// String could be comprised of characters, CDSects, or references to one of the "well-known" entities ('<', '>', '&', ''', '"')
+// returns a retained object in *string.
+static CFStringRef getString(_CFXMLPlistParseInfo *pInfo) {
+ const UniChar *mark = pInfo->curr; // At any time in the while loop below, the characters between mark and p have not yet been added to *string
+ CFMutableStringRef string = NULL;
+ while (!pInfo->errorString && pInfo->curr < pInfo->end) {
+ UniChar ch = *(pInfo->curr);
+ if (ch == '<') {
+ if (pInfo->curr + 1 >= pInfo->end) break;
+ // Could be a CDSect; could be the end of the string
+ if (*(pInfo->curr+1) != '!') break; // End of the string
+ _catFromMarkToBuf(mark, pInfo->curr, &string, pInfo->allocator);
+ parseCDSect_pl(pInfo, string);
+ mark = pInfo->curr;
+ } else if (ch == '&') {
+ _catFromMarkToBuf(mark, pInfo->curr, &string, pInfo->allocator);
+ parseEntityReference_pl(pInfo, string);
+ mark = pInfo->curr;
+ } else {
+ pInfo->curr ++;
+ }
+ }
+
+ if (pInfo->errorString) {
+ if (string) CFRelease(string);
+ return NULL;
+ }
+ if (!string) {
+ if (pInfo->mutabilityOption != kCFPropertyListMutableContainersAndLeaves) {
+ CFStringRef uniqueString = _uniqueStringForCharacters(pInfo, mark, pInfo->curr-mark);
+ if (uniqueString) CFRetain(uniqueString);
+ return uniqueString;
+ } else {
+ string = CFStringCreateMutable(pInfo->allocator, 0);
+ CFStringAppendCharacters(string, mark, pInfo->curr - mark);
+ return string;
+ }
+ }
+ _catFromMarkToBuf(mark, pInfo->curr, &string, pInfo->allocator);
+ if (pInfo->mutabilityOption != kCFPropertyListMutableContainersAndLeaves) {
+ CFStringRef uniqueString = _uniqueStringForString(pInfo, string);
+ if (uniqueString) CFRetain(uniqueString);
+ CFRelease(string);
+ return uniqueString;
+ }
+ return string;
+}
+
+static Boolean checkForCloseTag(_CFXMLPlistParseInfo *pInfo, const UniChar *tag, CFIndex tagLen) {
+ if (pInfo->end - pInfo->curr < tagLen + 3) {
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
+ return false;
+ }
+ if (*(pInfo->curr) != '<' || *(++pInfo->curr) != '/') {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c on line %d"), *(pInfo->curr), lineNumber(pInfo));
+ return false;
+ }
+ pInfo->curr ++;
+ if (!matchString(pInfo->curr, tag, tagLen)) {
+ CFStringRef str = CFStringCreateWithCharactersNoCopy(pInfo->allocator, tag, tagLen, kCFAllocatorNull);
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Close tag on line %d does not match open tag %@"), lineNumber(pInfo), str);
+ CFRelease(str);
+ return false;
+ }
+ pInfo->curr += tagLen;
+ skipWhitespace(pInfo);
+ if (pInfo->curr == pInfo->end) {
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
+ return false;
+ }
+ if (*(pInfo->curr) != '>') {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c on line %d"), *(pInfo->curr), lineNumber(pInfo));
+ return false;
+ }
+ pInfo->curr ++;
+ return true;
+}
+
+// pInfo should be set to the first content character of the <plist>
+static CFTypeRef parsePListTag(_CFXMLPlistParseInfo *pInfo) {
+ CFTypeRef result, tmp = NULL;
+ const UniChar *save;
+ result = getContentObject(pInfo, NULL);
+ if (!result) {
+ if (!pInfo->errorString) pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered empty plist tag", CFStringGetSystemEncoding());
+ return NULL;
+ }
+ save = pInfo->curr; // Save this in case the next step fails
+ tmp = getContentObject(pInfo, NULL);
+ if (tmp) {
+ // Got an extra object
+ CFRelease(tmp);
+ CFRelease(result);
+ pInfo->curr = save;
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected element at line %d (plist can only include one object)"), lineNumber(pInfo));
+ return NULL;
+ }
+ if (pInfo->errorString) {
+ // Parse failed catastrophically
+ CFRelease(result);
+ return NULL;
+ }
+ if (checkForCloseTag(pInfo, CFXMLPlistTags[PLIST_IX], PLIST_TAG_LENGTH)) {
+ return result;
+ }
+ CFRelease(result);
+ return NULL;
+}
+
+static int allowImmutableCollections = -1;
+
+static void checkImmutableCollections(void) {
+ allowImmutableCollections = (NULL == getenv("CFPropertyListAllowImmutableCollections")) ? 0 : 1;
+}
+
+static CFTypeRef parseArrayTag(_CFXMLPlistParseInfo *pInfo) {
+ CFMutableArrayRef array = CFArrayCreateMutable(pInfo->allocator, 0, &kCFTypeArrayCallBacks);
+ CFTypeRef tmp = getContentObject(pInfo, NULL);
+ while (tmp) {
+ CFArrayAppendValue(array, tmp);
+ CFRelease(tmp);
+ tmp = getContentObject(pInfo, NULL);
+ }
+ if (pInfo->errorString) { // getContentObject encountered a parse error
+ CFRelease(array);
+ return NULL;
+ }
+ if (checkForCloseTag(pInfo, CFXMLPlistTags[ARRAY_IX], ARRAY_TAG_LENGTH)) {
+ if (-1 == allowImmutableCollections) checkImmutableCollections();
+ if (1 == allowImmutableCollections) {
+ if (pInfo->mutabilityOption == kCFPropertyListImmutable) {
+ CFArrayRef newArray = CFArrayCreateCopy(pInfo->allocator, array);
+ CFRelease(array);
+ array = (CFMutableArrayRef)newArray;
+ }
+ }
+ return array;
+ }
+ CFRelease(array);
+ return NULL;
+}
+
+static CFTypeRef parseDictTag(_CFXMLPlistParseInfo *pInfo) {
+ CFMutableDictionaryRef dict = NULL;
+ CFTypeRef key=NULL, value=NULL;
+ Boolean gotKey;
+ const UniChar *base = pInfo->curr;
+ key = getContentObject(pInfo, &gotKey);
+ while (key) {
+ if (!gotKey) {
+ if (key) CFRelease(key);
+ if (dict) CFRelease(dict);
+ pInfo->curr = base;
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Found non-key inside <dict> at line %d"), lineNumber(pInfo));
+ return NULL;
+ }
+ value = getContentObject(pInfo, NULL);
+ if (!value) {
+ if (key) CFRelease(key);
+ if (dict) CFRelease(dict);
+ if (!pInfo->errorString)
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Value missing for key inside <dict> at line %d"), lineNumber(pInfo));
+ return NULL;
+ }
+ if (NULL == dict) {
+ dict = CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ _CFDictionarySetCapacity(dict, 10);
+ }
+ CFDictionarySetValue(dict, key, value);
+ CFRelease(key);
+ key = NULL;
+ CFRelease(value);
+ value = NULL;
+ base = pInfo->curr;
+ key = getContentObject(pInfo, &gotKey);
+ }
+ if (checkForCloseTag(pInfo, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH)) {
+ if (NULL == dict) {
+ if (pInfo->mutabilityOption == kCFPropertyListImmutable) {
+ dict = (CFMutableDictionaryRef)CFDictionaryCreate(pInfo->allocator, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ } else {
+ dict = CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ }
+ } else {
+ CFIndex cnt = CFDictionaryGetCount(dict);
+#if DEPLOYMENT_TARGET_MACOSX
+ if (1 == cnt) {
+ CFTypeRef val = CFDictionaryGetValue(dict, CFSTR("CF$UID"));
+ if (val && CFGetTypeID(val) == numbertype) {
+ CFTypeRef uid;
+ uint32_t v;
+ CFNumberGetValue((CFNumberRef)val, kCFNumberSInt32Type, &v);
+ uid = (CFTypeRef)_CFKeyedArchiverUIDCreate(pInfo->allocator, v);
+ CFRelease(dict);
+ return uid;
+ }
+ }
+#endif
+ if (-1 == allowImmutableCollections) checkImmutableCollections();
+ if (1 == allowImmutableCollections) {
+ if (pInfo->mutabilityOption == kCFPropertyListImmutable) {
+ CFDictionaryRef newDict = CFDictionaryCreateCopy(pInfo->allocator, dict);
+ CFRelease(dict);
+ dict = (CFMutableDictionaryRef)newDict;
+ }
+ }
+ }
+ return dict;
+ }
+ if (dict) CFRelease(dict);
+ return NULL;
+}
+
+static CFTypeRef parseDataTag(_CFXMLPlistParseInfo *pInfo) {
+ CFDataRef result;
+ const UniChar *base = pInfo->curr;
+ result = __CFPLDataDecode(pInfo, pInfo->mutabilityOption == kCFPropertyListMutableContainersAndLeaves);
+ if (!result) {
+ pInfo->curr = base;
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Could not interpret <data> at line %d (should be base64-encoded)"), lineNumber(pInfo));
+ return NULL;
+ }
+ if (checkForCloseTag(pInfo, CFXMLPlistTags[DATA_IX], DATA_TAG_LENGTH)) return result;
+ CFRelease(result);
+ return NULL;
+}
+
+CF_INLINE Boolean read2DigitNumber(_CFXMLPlistParseInfo *pInfo, int32_t *result) {
+ UniChar ch1, ch2;
+ if (pInfo->curr + 2 >= pInfo->end) return false;
+ ch1 = *pInfo->curr;
+ ch2 = *(pInfo->curr + 1);
+ pInfo->curr += 2;
+ if (!isdigit(ch1) || !isdigit(ch2)) return false;
+ *result = (ch1 - '0')*10 + (ch2 - '0');
+ return true;
+}
+
+// YYYY '-' MM '-' DD 'T' hh ':' mm ':' ss 'Z'
+static CFTypeRef parseDateTag(_CFXMLPlistParseInfo *pInfo) {
+ int32_t year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
+ int32_t num = 0;
+ Boolean badForm = false;
+
+ while (pInfo->curr < pInfo->end && isdigit(*pInfo->curr)) {
+ year = 10*year + (*pInfo->curr) - '0';
+ pInfo->curr ++;
+ }
+ if (pInfo->curr >= pInfo->end || *pInfo->curr != '-') {
+ badForm = true;
+ } else {
+ pInfo->curr ++;
+ }
+
+ if (!badForm && read2DigitNumber(pInfo, &month) && pInfo->curr < pInfo->end && *pInfo->curr == '-') {
+ pInfo->curr ++;
+ } else {
+ badForm = true;
+ }
+
+ if (!badForm && read2DigitNumber(pInfo, &day) && pInfo->curr < pInfo->end && *pInfo->curr == 'T') {
+ pInfo->curr ++;
+ } else {
+ badForm = true;
+ }
+
+ if (!badForm && read2DigitNumber(pInfo, &hour) && pInfo->curr < pInfo->end && *pInfo->curr == ':') {
+ pInfo->curr ++;
+ } else {
+ badForm = true;
+ }
+
+ if (!badForm && read2DigitNumber(pInfo, &minute) && pInfo->curr < pInfo->end && *pInfo->curr == ':') {
+ pInfo->curr ++;
+ } else {
+ badForm = true;
+ }
+
+ if (!badForm && read2DigitNumber(pInfo, &num) && pInfo->curr < pInfo->end && *pInfo->curr == 'Z') {
+ second = num;
+ pInfo->curr ++;
+ } else {
+ badForm = true;
+ }
+
+ if (badForm) {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Could not interpret <date> at line %d"), lineNumber(pInfo));
+ return NULL;
+ }
+ if (!checkForCloseTag(pInfo, CFXMLPlistTags[DATE_IX], DATE_TAG_LENGTH)) return NULL;
+
+ CFAbsoluteTime at = 0.0;
+#if 1
+ CFGregorianDate date = {year, month, day, hour, minute, second};
+ at = CFGregorianDateGetAbsoluteTime(date, NULL);
+#else
+ CFCalendarRef calendar = CFCalendarCreateWithIdentifier(kCFAllocatorSystemDefault, kCFGregorianCalendar);
+ CFTimeZoneRef tz = CFTimeZoneCreateWithName(kCFAllocatorSystemDefault, CFSTR("GMT"), true);
+ CFCalendarSetTimeZone(calendar, tz);
+ CFCalendarComposeAbsoluteTime(calendar, &at, (const uint8_t *)"yMdHms", year, month, day, hour, minute, second);
+ CFRelease(calendar);
+ CFRelease(tz);
+#endif
+ return CFDateCreate(pInfo->allocator, at);
+}
+
+static CFTypeRef parseRealTag(_CFXMLPlistParseInfo *pInfo) {
+ CFStringRef str = getString(pInfo);
+ SInt32 idx, len;
+ double val;
+ CFNumberRef result;
+ CFStringInlineBuffer buf;
+ if (!str) {
+ if (!pInfo->errorString)
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered empty <real> on line %d"), lineNumber(pInfo));
+ return NULL;
+ }
+
+ if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("nan"), kCFCompareCaseInsensitive)) {
+ CFRelease(str);
+ return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberNaN) : NULL;
+ }
+ if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("+infinity"), kCFCompareCaseInsensitive)) {
+ CFRelease(str);
+ return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberPositiveInfinity) : NULL;
+ }
+ if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("-infinity"), kCFCompareCaseInsensitive)) {
+ CFRelease(str);
+ return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberNegativeInfinity) : NULL;
+ }
+ if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("infinity"), kCFCompareCaseInsensitive)) {
+ CFRelease(str);
+ return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberPositiveInfinity) : NULL;
+ }
+ if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("-inf"), kCFCompareCaseInsensitive)) {
+ CFRelease(str);
+ return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberNegativeInfinity) : NULL;
+ }
+ if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("inf"), kCFCompareCaseInsensitive)) {
+ CFRelease(str);
+ return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberPositiveInfinity) : NULL;
+ }
+ if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("+inf"), kCFCompareCaseInsensitive)) {
+ CFRelease(str);
+ return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberPositiveInfinity) : NULL;
+ }
+
+ len = CFStringGetLength(str);
+ CFStringInitInlineBuffer(str, &buf, CFRangeMake(0, len));
+ idx = 0;
+ if (!__CFStringScanDouble(&buf, NULL, &idx, &val) || idx != len) {
+ CFRelease(str);
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered misformatted real on line %d"), lineNumber(pInfo));
+ return NULL;
+ }
+ CFRelease(str);
+ result = CFNumberCreate(pInfo->allocator, kCFNumberDoubleType, &val);
+ if (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) return result;
+ CFRelease(result);
+ return NULL;
+}
+
+#define GET_CH if (pInfo->curr == pInfo->end) { \
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Premature end of file after <integer> on line %d"), lineNumber(pInfo)); \
+ return NULL; \
+ } \
+ ch = *(pInfo->curr)
+
+typedef struct {
+ int64_t high;
+ uint64_t low;
+} CFSInt128Struct;
+
+enum {
+ kCFNumberSInt128Type = 17
+};
+
+static CFTypeRef parseIntegerTag(_CFXMLPlistParseInfo *pInfo) {
+ bool isHex = false, isNeg = false, hadLeadingZero = false;
+ UniChar ch = 0;
+
+ // decimal_constant S*(-|+)?S*[0-9]+ (S == space)
+ // hex_constant S*(-|+)?S*0[xX][0-9a-fA-F]+ (S == space)
+
+ while (pInfo->curr < pInfo->end && __CFIsWhitespace(*(pInfo->curr))) pInfo->curr++;
+ GET_CH;
+ if ('<' == ch) {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered empty <integer> on line %d"), lineNumber(pInfo));
+ return NULL;
+ }
+ if ('-' == ch || '+' == ch) {
+ isNeg = ('-' == ch);
+ pInfo->curr++;
+ while (pInfo->curr < pInfo->end && __CFIsWhitespace(*(pInfo->curr))) pInfo->curr++;
+ }
+ GET_CH;
+ if ('0' == ch) {
+ if (pInfo->curr + 1 < pInfo->end && ('x' == *(pInfo->curr + 1) || 'X' == *(pInfo->curr + 1))) {
+ pInfo->curr++;
+ isHex = true;
+ } else {
+ hadLeadingZero = true;
+ }
+ pInfo->curr++;
+ }
+ GET_CH;
+ while ('0' == ch) {
+ hadLeadingZero = true;
+ pInfo->curr++;
+ GET_CH;
+ }
+ if ('<' == ch && hadLeadingZero) { // nothing but zeros
+ int32_t val = 0;
+ if (!checkForCloseTag(pInfo, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH)) {
+ // checkForCloseTag() sets error string
+ return NULL;
+ }
+ return CFNumberCreate(pInfo->allocator, kCFNumberSInt32Type, &val);
+ }
+ if ('<' == ch) {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Incomplete <integer> on line %d"), lineNumber(pInfo));
+ return NULL;
+ }
+ uint64_t value = 0;
+ uint32_t multiplier = (isHex ? 16 : 10);
+ while ('<' != ch) {
+ uint32_t new_digit = 0;
+ switch (ch) {
+ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
+ new_digit = (ch - '0');
+ break;
+ case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
+ new_digit = (ch - 'a' + 10);
+ break;
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
+ new_digit = (ch - 'A' + 10);
+ break;
+ default: // other character
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unknown character '%c' (0x%x) in <integer> on line %d"), ch, ch, lineNumber(pInfo));
+ return NULL;
+ }
+ if (!isHex && new_digit > 9) {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Hex digit in non-hex <integer> on line %d"), lineNumber(pInfo));
+ return NULL;
+ }
+ if (UINT64_MAX / multiplier < value) {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Integer overflow in <integer> on line %d"), lineNumber(pInfo));
+ return NULL;
+ }
+ value = multiplier * value;
+ if (UINT64_MAX - new_digit < value) {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Integer overflow in <integer> on line %d"), lineNumber(pInfo));
+ return NULL;
+ }
+ value = value + new_digit;
+ if (isNeg && (uint64_t)INT64_MAX + 1 < value) {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Integer underflow in <integer> on line %d"), lineNumber(pInfo));
+ return NULL;
+ }
+ pInfo->curr++;
+ GET_CH;
+ }
+ if (!checkForCloseTag(pInfo, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH)) {
+ // checkForCloseTag() sets error string
+ return NULL;
+ }
+ if (isNeg || value <= INT64_MAX) {
+ int64_t v = value;
+ if (isNeg) v = -v; // no-op if INT64_MIN
+ return CFNumberCreate(pInfo->allocator, kCFNumberSInt64Type, &v);
+ }
+ CFSInt128Struct val;
+ val.high = 0;
+ val.low = value;
+ return CFNumberCreate(pInfo->allocator, kCFNumberSInt128Type, &val);
+}
+
+#undef GET_CH
+
+// Returned object is retained; caller must free. pInfo->curr expected to point to the first character after the '<'
+static CFTypeRef parseXMLElement(_CFXMLPlistParseInfo *pInfo, Boolean *isKey) {
+ const UniChar *marker = pInfo->curr;
+ int markerLength = -1;
+ Boolean isEmpty;
+ int markerIx = -1;
+
+ if (isKey) *isKey = false;
+ while (pInfo->curr < pInfo->end) {
+ UniChar ch = *(pInfo->curr);
+ if (ch == ' ' || ch == '\t' || ch == '\n' || ch =='\r') {
+ if (markerLength == -1) markerLength = pInfo->curr - marker;
+ } else if (ch == '>') {
+ break;
+ }
+ pInfo->curr ++;
+ }
+ if (pInfo->curr >= pInfo->end) return NULL;
+ isEmpty = (*(pInfo->curr-1) == '/');
+ if (markerLength == -1)
+ markerLength = pInfo->curr - (isEmpty ? 1 : 0) - marker;
+ pInfo->curr ++; // Advance past '>'
+ if (markerLength == 0) {
+ // Back up to the beginning of the marker
+ pInfo->curr = marker;
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Malformed tag on line %d"), lineNumber(pInfo));
+ return NULL;
+ }
+ switch (*marker) {
+ case 'a': // Array
+ if (markerLength == ARRAY_TAG_LENGTH && matchString(marker, CFXMLPlistTags[ARRAY_IX], ARRAY_TAG_LENGTH))
+ markerIx = ARRAY_IX;
+ break;
+ case 'd': // Dictionary, data, or date; Fortunately, they all have the same marker length....
+ if (markerLength != DICT_TAG_LENGTH)
+ break;
+ if (matchString(marker, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH))
+ markerIx = DICT_IX;
+ else if (matchString(marker, CFXMLPlistTags[DATA_IX], DATA_TAG_LENGTH))
+ markerIx = DATA_IX;
+ else if (matchString(marker, CFXMLPlistTags[DATE_IX], DATE_TAG_LENGTH))
+ markerIx = DATE_IX;
+ break;
+ case 'f': // false (boolean)
+ if (markerLength == FALSE_TAG_LENGTH && matchString(marker, CFXMLPlistTags[FALSE_IX], FALSE_TAG_LENGTH)) {
+ markerIx = FALSE_IX;
+ }
+ break;
+ case 'i': // integer
+ if (markerLength == INTEGER_TAG_LENGTH && matchString(marker, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH))
+ markerIx = INTEGER_IX;
+ break;
+ case 'k': // Key of a dictionary
+ if (markerLength == KEY_TAG_LENGTH && matchString(marker, CFXMLPlistTags[KEY_IX], KEY_TAG_LENGTH)) {
+ markerIx = KEY_IX;
+ if (isKey) *isKey = true;
+ }
+ break;
+ case 'p': // Plist
+ if (markerLength == PLIST_TAG_LENGTH && matchString(marker, CFXMLPlistTags[PLIST_IX], PLIST_TAG_LENGTH))
+ markerIx = PLIST_IX;
+ break;
+ case 'r': // real
+ if (markerLength == REAL_TAG_LENGTH && matchString(marker, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH))
+ markerIx = REAL_IX;
+ break;
+ case 's': // String
+ if (markerLength == STRING_TAG_LENGTH && matchString(marker, CFXMLPlistTags[STRING_IX], STRING_TAG_LENGTH))
+ markerIx = STRING_IX;
+ break;
+ case 't': // true (boolean)
+ if (markerLength == TRUE_TAG_LENGTH && matchString(marker, CFXMLPlistTags[TRUE_IX], TRUE_TAG_LENGTH))
+ markerIx = TRUE_IX;
+ break;
+ }
+
+ if (!pInfo->allowNewTypes && markerIx != PLIST_IX && markerIx != ARRAY_IX && markerIx != DICT_IX && markerIx != STRING_IX && markerIx != KEY_IX && markerIx != DATA_IX) {
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered new tag when expecting only old-style property list objects", CFStringGetSystemEncoding());
+ return NULL;
+ }
+
+ switch (markerIx) {
+ case PLIST_IX:
+ if (isEmpty) {
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered empty plist tag", CFStringGetSystemEncoding());
+ return NULL;
+ }
+ return parsePListTag(pInfo);
+ case ARRAY_IX:
+ if (isEmpty) {
+ return pInfo->mutabilityOption == kCFPropertyListImmutable ? CFArrayCreate(pInfo->allocator, NULL, 0, &kCFTypeArrayCallBacks) : CFArrayCreateMutable(pInfo->allocator, 0, &kCFTypeArrayCallBacks);
+ } else {
+ return parseArrayTag(pInfo);
+ }
+ case DICT_IX:
+ if (isEmpty) {
+ if (pInfo->mutabilityOption == kCFPropertyListImmutable) {
+ return CFDictionaryCreate(pInfo->allocator, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ } else {
+ return CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ }
+ } else {
+ return parseDictTag(pInfo);
+ }
+ case KEY_IX:
+ case STRING_IX:
+ {
+ CFStringRef str;
+ int tagLen = (markerIx == KEY_IX) ? KEY_TAG_LENGTH : STRING_TAG_LENGTH;
+ if (isEmpty) {
+ return pInfo->mutabilityOption == kCFPropertyListMutableContainersAndLeaves ? CFStringCreateMutable(pInfo->allocator, 0) : CFStringCreateWithCharacters(pInfo->allocator, NULL, 0);
+ }
+ str = getString(pInfo);
+ if (!str) return NULL; // getString will already have set the error string
+ if (!checkForCloseTag(pInfo, CFXMLPlistTags[markerIx], tagLen)) {
+ CFRelease(str);
+ return NULL;
+ }
+ return str;
+ }
+ case DATA_IX:
+ if (isEmpty) {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered empty <data> on line %d"), lineNumber(pInfo));
+ return NULL;
+ } else {
+ return parseDataTag(pInfo);
+ }
+ case DATE_IX:
+ if (isEmpty) {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered empty <date> on line %d"), lineNumber(pInfo));
+ return NULL;
+ } else {
+ return parseDateTag(pInfo);
+ }
+ case TRUE_IX:
+ if (!isEmpty) {
+ if (!checkForCloseTag(pInfo, CFXMLPlistTags[TRUE_IX], TRUE_TAG_LENGTH)) return NULL;
+ }
+ return CFRetain(kCFBooleanTrue);
+ case FALSE_IX:
+ if (!isEmpty) {
+ if (!checkForCloseTag(pInfo, CFXMLPlistTags[FALSE_IX], FALSE_TAG_LENGTH)) return NULL;
+ }
+ return CFRetain(kCFBooleanFalse);
+ case REAL_IX:
+ if (isEmpty) {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered empty <real> on line %d"), lineNumber(pInfo));
+ return NULL;
+ } else {
+ return parseRealTag(pInfo);
+ }
+ case INTEGER_IX:
+ if (isEmpty) {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered empty <integer> on line %d"), lineNumber(pInfo));
+ return NULL;
+ } else {
+ return parseIntegerTag(pInfo);
+ }
+ default: {
+ CFStringRef markerStr = CFStringCreateWithCharacters(pInfo->allocator, marker, markerLength);
+ pInfo->curr = marker;
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unknown tag %@ on line %d"), markerStr, lineNumber(pInfo));
+ CFRelease(markerStr);
+ return NULL;
+ }
+ }
+}
+
+static CFTypeRef parseXMLPropertyList(_CFXMLPlistParseInfo *pInfo) {
+ while (!pInfo->errorString && pInfo->curr < pInfo->end) {
+ UniChar ch;
+ skipWhitespace(pInfo);
+ if (pInfo->curr+1 >= pInfo->end) {
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "No XML content found", CFStringGetSystemEncoding());
+ return NULL;
+ }
+ if (*(pInfo->curr) != '<') {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unexpected character %c at line %d"), *(pInfo->curr), lineNumber(pInfo));
+ return NULL;
+ }
+ ch = *(++ pInfo->curr);
+ if (ch == '!') {
+ // Comment or DTD
+ ++ pInfo->curr;
+ if (pInfo->curr+1 < pInfo->end && *pInfo->curr == '-' && *(pInfo->curr+1) == '-') {
+ // Comment
+ pInfo->curr += 2;
+ skipXMLComment(pInfo);
+ } else {
+ skipDTD(pInfo);
+ }
+ } else if (ch == '?') {
+ // Processing instruction
+ pInfo->curr++;
+ skipXMLProcessingInstruction(pInfo);
+ } else {
+ // Tag or malformed
+ return parseXMLElement(pInfo, NULL);
+ // Note we do not verify that there was only one element, so a file that has garbage after the first element will nonetheless successfully parse
+ }
+ }
+ // Should never get here
+ if (!(pInfo->errorString))
+ pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
+ return NULL;
+}
+
+static CFStringEncoding encodingForXMLData(CFDataRef data, CFStringRef *error) {
+ const uint8_t *bytes = (uint8_t *)CFDataGetBytePtr(data);
+ UInt32 length = CFDataGetLength(data);
+ const uint8_t *idx, *end;
+ char quote;
+
+ // Check for the byte order mark first
+ if (length > 2 &&
+ ((*bytes == 0xFF && *(bytes+1) == 0xFE) ||
+ (*bytes == 0xFE && *(bytes+1) == 0xFF) ||
+ *bytes == 0x00 || *(bytes+1) == 0x00)) // This clause checks for a Unicode sequence lacking the byte order mark; technically an error, but this check is recommended by the XML spec
+ return kCFStringEncodingUnicode;
+
+ // Scan for the <?xml.... ?> opening
+ if (length < 5 || strncmp((char const *) bytes, "<?xml", 5) != 0) return kCFStringEncodingUTF8;
+ idx = bytes + 5;
+ end = bytes + length;
+ // Found "<?xml"; now we scan for "encoding"
+ while (idx < end) {
+ uint8_t ch = *idx;
+ const uint8_t *scan;
+ if ( ch == '?' || ch == '>') return kCFStringEncodingUTF8;
+ idx ++;
+ scan = idx;
+ if (ch == 'e' && *scan++ == 'n' && *scan++ == 'c' && *scan++ == 'o' && *scan++ == 'd' && *scan++ == 'i'
+ && *scan++ == 'n' && *scan++ == 'g' && *scan++ == '=') {
+ idx = scan;
+ break;
+ }
+ }
+ if (idx >= end) return kCFStringEncodingUTF8;
+ quote = *idx;
+ if (quote != '\'' && quote != '\"') return kCFStringEncodingUTF8;
+ else {
+ CFStringRef encodingName;
+ const uint8_t *base = idx+1; // Move past the quote character
+ CFStringEncoding enc;
+ UInt32 len;
+ idx ++;
+ while (idx < end && *idx != quote) idx ++;
+ if (idx >= end) return kCFStringEncodingUTF8;
+ len = idx - base;
+ if (len == 5 && (*base == 'u' || *base == 'U') && (base[1] == 't' || base[1] == 'T') && (base[2] == 'f' || base[2] == 'F') && (base[3] == '-') && (base[4] == '8'))
+ return kCFStringEncodingUTF8;
+ encodingName = CFStringCreateWithBytes(kCFAllocatorSystemDefault, base, len, kCFStringEncodingISOLatin1, false);
+ enc = CFStringConvertIANACharSetNameToEncoding(encodingName);
+ if (enc != kCFStringEncodingInvalidId) {
+ CFRelease(encodingName);
+ return enc;
+ }
+
+ if (error) {
+ *error = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("Encountered unknown encoding (%@)"), encodingName);
+ CFRelease(encodingName);
+ }
+ return 0;
+ }
+}
+
+extern bool __CFTryParseBinaryPlist(CFAllocatorRef allocator, CFDataRef data, CFOptionFlags option, CFPropertyListRef *plist, CFStringRef *errorString);
+int32_t _CFPropertyListAllowNonUTF8 = 0;
+
+#define SAVE_PLISTS 0
+
+#if SAVE_PLISTS
+static Boolean __savePlistData(CFDataRef data, CFOptionFlags opt) {
+ uint8_t pn[2048];
+ uint8_t fn[2048];
+ uint32_t pnlen = sizeof(pn);
+ uint8_t *pnp = NULL;
+ if (0 == _NSGetExecutablePath((char *)pn, &pnlen)) {
+ pnp = strrchr((char *)pn, '/');
+ }
+ if (!pnp) {
+ pnp = pn;
+ } else {
+ pnp++;
+ }
+ if (0 == strcmp((char *)pnp, "parse_plists")) return true;
+ CFUUIDRef r = CFUUIDCreate(kCFAllocatorSystemDefault);
+ CFStringRef s = CFUUIDCreateString(kCFAllocatorSystemDefault, r);
+ CFStringRef p = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("/tmp/plists/%s#%@#0x%x"), pnp, s, opt);
+ _CFStringGetFileSystemRepresentation(p, fn, sizeof(fn));
+ CFRelease(r);
+ CFRelease(s);
+ CFRelease(p);
+ int fd = open((const char *)fn, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+ if (fd < 0) return false;
+ int len = CFDataGetLength(data);
+ int w = write(fd, CFDataGetBytePtr(data), len);
+ fsync(fd);
+ close(fd);
+ if (w != len) return false;
+ return true;
+}
+#endif
+
+CFTypeRef _CFPropertyListCreateFromXMLData(CFAllocatorRef allocator, CFDataRef xmlData, CFOptionFlags option, CFStringRef *errorString, Boolean allowNewTypes, CFPropertyListFormat *format) {
+ initStatics();
+ CFStringEncoding encoding;
+ CFStringRef xmlString;
+ UInt32 length;
+ CFPropertyListRef plist;
+
+ if (errorString) *errorString = NULL;
+ if (!xmlData || CFDataGetLength(xmlData) == 0) {
+ if (errorString) {
+ *errorString = CFSTR("Cannot parse a NULL or zero-length data");
+ CFRetain(*errorString); // Caller expects to release
+ }
+ return NULL;
+ }
+
+#if SAVE_PLISTS
+ __savePlistData(xmlData, option);
+#endif
+
+ if (__CFTryParseBinaryPlist(allocator, xmlData, option, &plist, errorString)) {
+ if (format) *format = kCFPropertyListBinaryFormat_v1_0;
+ return plist;
+ }
+
+ allocator = allocator ? allocator : __CFGetDefaultAllocator();
+ CFRetain(allocator);
+
+ encoding = encodingForXMLData(xmlData, errorString); // 0 is an error return, NOT MacRoman.
+
+ if (encoding == 0) {
+ // Couldn't find an encoding; encodingForXMLData already set *errorString if necessary
+ // Note that encodingForXMLData() will give us the right values for a standard plist, too.
+ if (errorString) *errorString = CFStringCreateWithFormat(allocator, NULL, CFSTR("Could not determine the encoding of the XML data"));
+ return NULL;
+ }
+
+ xmlString = CFStringCreateWithBytes(allocator, CFDataGetBytePtr(xmlData), CFDataGetLength(xmlData), encoding, true);
+ if (NULL == xmlString && (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLeopard) || _CFPropertyListAllowNonUTF8)) { // conversion failed, probably because not in proper encoding
+ // Call __CFStringCreateImmutableFunnel3() the same way CFStringCreateWithBytes() does, except with the addt'l flag
+ if (encoding == kCFStringEncodingUTF8) xmlString = __CFStringCreateImmutableFunnel3(allocator, CFDataGetBytePtr(xmlData), CFDataGetLength(xmlData), kCFStringEncodingUTF8, true, true, false, false, false, (CFAllocatorRef)-1 /* ALLOCATORSFREEFUNC */, kCFStringEncodingLenientUTF8Conversion);
+ }
+ length = xmlString ? CFStringGetLength(xmlString) : 0;
+
+ if (length) {
+ _CFXMLPlistParseInfo pInfoBuf;
+ _CFXMLPlistParseInfo *pInfo = &pInfoBuf;
+ CFTypeRef result;
+ UniChar *buf = (UniChar *)CFStringGetCharactersPtr(xmlString);
+
+ if (errorString) *errorString = NULL;
+ if (!buf) {
+ buf = (UniChar *)CFAllocatorAllocate(allocator, length * sizeof(UniChar), 0);
+ CFStringGetCharacters(xmlString, CFRangeMake(0, length), buf);
+ CFRelease(xmlString);
+ xmlString = NULL;
+ }
+ pInfo->begin = buf;
+ pInfo->end = buf+length;
+ pInfo->curr = buf;
+ pInfo->allocator = allocator;
+ pInfo->errorString = NULL;
+ pInfo->stringSet = NULL;
+ pInfo->tmpString = NULL;
+ pInfo->mutabilityOption = option;
+ pInfo->allowNewTypes = allowNewTypes;
+
+ // Haven't done anything XML-specific to this point. However, the encoding we used to translate the bytes should be kept in mind; we used Unicode if the byte-order mark was present; UTF-8 otherwise. If the system encoding is not UTF-8 or some variant of 7-bit ASCII, we'll be in trouble.....
+ result = parseXMLPropertyList(pInfo);
+ if (result && format) *format = kCFPropertyListXMLFormat_v1_0;
+ if (!result) {
+ CFStringRef err = pInfo->errorString;
+ // Reset pInfo so we can try again
+ pInfo->curr = pInfo->begin;
+ pInfo->errorString = NULL;
+ // Try pList
+ result = parseOldStylePropertyListOrStringsFile(pInfo);
+ if (result && format) *format = kCFPropertyListOpenStepFormat;
+ if (!result) {
+ if (errorString) *errorString = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("XML parser error:\n\t%@\nOld-style plist parser error:\n\t%@\n"), err, pInfo->errorString);
+ }
+ if (err) CFRelease(err);
+ if (pInfo->errorString) CFRelease(pInfo->errorString);
+ }
+ if (xmlString) {
+ CFRelease(xmlString);
+ } else {
+ CFAllocatorDeallocate(allocator, (void *)pInfo->begin);
+ }
+ if (pInfo->stringSet) CFRelease(pInfo->stringSet);
+ if (pInfo->tmpString) CFRelease(pInfo->tmpString);
+ CFRelease(allocator);
+ return result;
+ } else {
+ if (errorString)
+ *errorString = (CFStringRef)CFRetain(CFSTR("Conversion of data failed. The file is not UTF-8, or in the encoding specified in XML header if XML."));
+ return NULL;
+ }
+}
+
+CFTypeRef CFPropertyListCreateFromXMLData(CFAllocatorRef allocator, CFDataRef xmlData, CFOptionFlags option, CFStringRef *errorString) {
+ initStatics();
+ CFAssert1(xmlData != NULL, __kCFLogAssertion, "%s(): NULL data not allowed", __PRETTY_FUNCTION__);
+ CFAssert2(option == kCFPropertyListImmutable || option == kCFPropertyListMutableContainers || option == kCFPropertyListMutableContainersAndLeaves, __kCFLogAssertion, "%s(): Unrecognized option %d", __PRETTY_FUNCTION__, option);
+ return _CFPropertyListCreateFromXMLData(allocator, xmlData, option, errorString, true, NULL);
+}
+
+CFIndex CFPropertyListWriteToStream(CFPropertyListRef propertyList, CFWriteStreamRef stream, CFPropertyListFormat format, CFStringRef *errorString) {
+ initStatics();
+ CFAssert1(stream != NULL, __kCFLogAssertion, "%s(): NULL stream not allowed", __PRETTY_FUNCTION__);
+ CFAssert2(format == kCFPropertyListOpenStepFormat || format == kCFPropertyListXMLFormat_v1_0 || format == kCFPropertyListBinaryFormat_v1_0, __kCFLogAssertion, "%s(): Unrecognized option %d", __PRETTY_FUNCTION__, format);
+ CFAssert1(propertyList != NULL, __kCFLogAssertion, "%s(): Cannot be called with a NULL property list", __PRETTY_FUNCTION__);
+ __CFAssertIsPList(propertyList);
+ CFAssert1(CFWriteStreamGetTypeID() == CFGetTypeID(stream), __kCFLogAssertion, "%s(): stream argument is not a write stream", __PRETTY_FUNCTION__);
+ CFAssert1(kCFStreamStatusOpen == CFWriteStreamGetStatus(stream) || kCFStreamStatusWriting == CFWriteStreamGetStatus(stream), __kCFLogAssertion, "%s(): stream is not open", __PRETTY_FUNCTION__);
+
+ if (errorString) *errorString = NULL;
+ if (!CFPropertyListIsValid(propertyList, format)) {
+ if (errorString) *errorString = (CFStringRef)CFRetain(CFSTR("Property list invalid for format"));
+ return 0;
+ }
+ if (format == kCFPropertyListOpenStepFormat) {
+ if (errorString) *errorString = (CFStringRef)CFRetain(CFSTR("Property list format kCFPropertyListOpenStepFormat not supported for writing"));
+ return 0;
+ }
+ if (format == kCFPropertyListXMLFormat_v1_0) {
+ CFDataRef data = CFPropertyListCreateXMLData(kCFAllocatorSystemDefault, propertyList);
+ if (!data) {
+ if (errorString) *errorString = (CFStringRef)CFRetain(CFSTR("Property list in XML format could not be created for unknown reasons."));
+ return 0;
+ }
+ CFIndex len = CFDataGetLength(data);
+ const uint8_t *ptr = CFDataGetBytePtr(data);
+ while (0 < len) {
+ CFIndex ret = CFWriteStreamWrite(stream, ptr, len);
+ if (ret == 0) {
+ if (errorString) *errorString = (CFStringRef)CFRetain(CFSTR("Property list writing could not be completed because stream is full."));
+ return 0;
+ }
+ if (ret < 0) {
+ CFErrorRef err = CFWriteStreamCopyError(stream);
+ CFStringRef str = err ? CFErrorCopyDescription(err) : NULL;
+ if (errorString) *errorString = str ? str : (CFStringRef)CFRetain(CFSTR("Property list writing could not be completed because the stream had an unknown error."));
+ if (err) CFRelease(err);
+ return 0;
+ }
+ ptr += ret;
+ len -= ret;
+ }
+ len = CFDataGetLength(data);
+ CFRelease(data);
+ return len;
+ }
+ if (format == kCFPropertyListBinaryFormat_v1_0) {
+ CFIndex len = __CFBinaryPlistWriteToStream(propertyList, stream);
+ return len;
+ }
+ if (errorString) *errorString = (CFStringRef)CFRetain(CFSTR("Unknown format option"));
+ return 0;
+}
+
+static void __CFConvertReadStreamToBytes(CFReadStreamRef stream, CFIndex max, uint8_t **buffer, CFIndex *length) {
+ int32_t buflen = 0, bufsize = 0, retlen;
+ uint8_t *buf = NULL, sbuf[8192];
+ for (;;) {
+ retlen = CFReadStreamRead(stream, sbuf, __CFMin(8192, max));
+ if (retlen <= 0) {
+ *buffer = buf;
+ *length = buflen;
+ return;
+ }
+ if (bufsize < buflen + retlen) {
+ if (bufsize < 256 * 1024) {
+ bufsize *= 4;
+ } else if (bufsize < 16 * 1024 * 1024) {
+ bufsize *= 2;
+ } else {
+ // once in this stage, this will be really slow
+ // and really potentially fragment memory
+ bufsize += 256 * 1024;
+ }
+ if (bufsize < buflen + retlen) bufsize = buflen + retlen;
+ buf = (uint8_t *)CFAllocatorReallocate(kCFAllocatorSystemDefault, buf, bufsize, 0);
+ if (!buf) HALT;
+ }
+ memmove(buf + buflen, sbuf, retlen);
+ buflen += retlen;
+ max -= retlen;
+ if (max <= 0) {
+ *buffer = buf;
+ *length = buflen;
+ return;
+ }
+ }
+}
+
+CFPropertyListRef CFPropertyListCreateFromStream(CFAllocatorRef allocator, CFReadStreamRef stream, CFIndex length, CFOptionFlags mutabilityOption, CFPropertyListFormat *format, CFStringRef *errorString) {
+ initStatics();
+ CFPropertyListRef pl;
+ CFDataRef data;
+ CFIndex buflen = 0;
+ uint8_t *buffer = NULL;
+ CFAssert1(stream != NULL, __kCFLogAssertion, "%s(): NULL stream not allowed", __PRETTY_FUNCTION__);
+ CFAssert1(CFReadStreamGetTypeID() == CFGetTypeID(stream), __kCFLogAssertion, "%s(): stream argument is not a read stream", __PRETTY_FUNCTION__);
+ CFAssert1(kCFStreamStatusOpen == CFReadStreamGetStatus(stream) || kCFStreamStatusReading == CFReadStreamGetStatus(stream), __kCFLogAssertion, "%s(): stream is not open", __PRETTY_FUNCTION__);
+ CFAssert2(mutabilityOption == kCFPropertyListImmutable || mutabilityOption == kCFPropertyListMutableContainers || mutabilityOption == kCFPropertyListMutableContainersAndLeaves, __kCFLogAssertion, "%s(): Unrecognized option %d", __PRETTY_FUNCTION__, mutabilityOption);
+
+ if (errorString) *errorString = NULL;
+ if (0 == length) length = INT_MAX;
+ __CFConvertReadStreamToBytes(stream, length, &buffer, &buflen);
+ if (!buffer || buflen < 6) {
+ if (buffer) CFAllocatorDeallocate(kCFAllocatorSystemDefault, buffer);
+ if (errorString) *errorString = (CFStringRef)CFRetain(CFSTR("stream had too few bytes"));
+ return NULL;
+ }
+ data = CFDataCreateWithBytesNoCopy(kCFAllocatorSystemDefault, buffer, buflen, kCFAllocatorSystemDefault);
+ pl = _CFPropertyListCreateFromXMLData(allocator, data, mutabilityOption, errorString, true, format);
+ CFRelease(data);
+ return pl;
+}
+
+// ========================================================================
+
+//
+// Old NeXT-style property lists
+//
+
+static CFTypeRef parsePlistObject(_CFXMLPlistParseInfo *pInfo, bool requireObject);
+
+#define isValidUnquotedStringCharacter(x) (((x) >= 'a' && (x) <= 'z') || ((x) >= 'A' && (x) <= 'Z') || ((x) >= '0' && (x) <= '9') || (x) == '_' || (x) == '$' || (x) == '/' || (x) == ':' || (x) == '.' || (x) == '-')
+
+static void advanceToNonSpace(_CFXMLPlistParseInfo *pInfo) {
+ UniChar ch2;
+ while (pInfo->curr < pInfo->end) {
+ ch2 = *(pInfo->curr);
+ pInfo->curr ++;
+ if (ch2 >= 9 && ch2 <= 0x0d) continue; // tab, newline, vt, form feed, carriage return
+ if (ch2 == ' ' || ch2 == 0x2028 || ch2 == 0x2029) continue; // space and Unicode line sep, para sep
+ if (ch2 == '/') {
+ if (pInfo->curr >= pInfo->end) {
+ // whoops; back up and return
+ pInfo->curr --;
+ return;
+ } else if (*(pInfo->curr) == '/') {
+ pInfo->curr ++;
+ while (pInfo->curr < pInfo->end) { // go to end of comment line
+ UniChar ch3 = *(pInfo->curr);
+ if (ch3 == '\n' || ch3 == '\r' || ch3 == 0x2028 || ch3 == 0x2029) break;
+ pInfo->curr ++;
+ }
+ } else if (*(pInfo->curr) == '*') { // handle /* ... */
+ pInfo->curr ++;
+ while (pInfo->curr < pInfo->end) {
+ ch2 = *(pInfo->curr);
+ pInfo->curr ++;
+ if (ch2 == '*' && pInfo->curr < pInfo->end && *(pInfo->curr) == '/') {
+ pInfo->curr ++; // advance past the '/'
+ break;
+ }
+ }
+ } else {
+ pInfo->curr --;
+ return;
+ }
+ } else {
+ pInfo->curr --;
+ return;
+ }
+ }
+}
+
+static UniChar getSlashedChar(_CFXMLPlistParseInfo *pInfo) {
+ UniChar ch = *(pInfo->curr);
+ pInfo->curr ++;
+ switch (ch) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7': {
+ uint8_t num = ch - '0';
+ UniChar result;
+ CFIndex usedCharLen;
+ /* three digits maximum to avoid reading \000 followed by 5 as \5 ! */
+ if ((ch = *(pInfo->curr)) >= '0' && ch <= '7') { // we use in this test the fact that the buffer is zero-terminated
+ pInfo->curr ++;
+ num = (num << 3) + ch - '0';
+ if ((pInfo->curr < pInfo->end) && (ch = *(pInfo->curr)) >= '0' && ch <= '7') {
+ pInfo->curr ++;
+ num = (num << 3) + ch - '0';
+ }
+ }
+ CFStringEncodingBytesToUnicode(kCFStringEncodingNextStepLatin, 0, &num, sizeof(uint8_t), NULL, &result, 1, &usedCharLen);
+ return (usedCharLen == 1) ? result : 0;
+ }
+ case 'U': {
+ unsigned num = 0, numDigits = 4; /* Parse four digits */
+ while (pInfo->curr < pInfo->end && numDigits--) {
+ if (((ch = *(pInfo->curr)) < 128) && isxdigit(ch)) {
+ pInfo->curr ++;
+ num = (num << 4) + ((ch <= '9') ? (ch - '0') : ((ch <= 'F') ? (ch - 'A' + 10) : (ch - 'a' + 10)));
+ }
+ }
+ return num;
+ }
+ case 'a': return '\a'; // Note: the meaning of '\a' varies with -traditional to gcc
+ case 'b': return '\b';
+ case 'f': return '\f';
+ case 'n': return '\n';
+ case 'r': return '\r';
+ case 't': return '\t';
+ case 'v': return '\v';
+ case '"': return '\"';
+ case '\n': return '\n';
+ }
+ return ch;
+}
+
+static CFStringRef parseQuotedPlistString(_CFXMLPlistParseInfo *pInfo, UniChar quote) {
+ CFMutableStringRef str = NULL;
+ const UniChar *startMark = pInfo->curr;
+ const UniChar *mark = pInfo->curr;
+ while (pInfo->curr < pInfo->end) {
+ UniChar ch = *(pInfo->curr);
+ if (ch == quote) break;
+ if (ch == '\\') {
+ _catFromMarkToBuf(mark, pInfo->curr, &str, pInfo->allocator);
+ pInfo->curr ++;
+ ch = getSlashedChar(pInfo);
+ CFStringAppendCharacters(str, &ch, 1);
+ mark = pInfo->curr;
+ } else {
+ // Note that the original NSParser code was much more complex at this point, but it had to deal with 8-bit characters in a non-UniChar stream. We always have UniChar (we translated the data by the system encoding at the very beginning, hopefully), so this is safe.
+ pInfo->curr ++;
+ }
+ }
+ if (pInfo->end <= pInfo->curr) {
+ if (str) CFRelease(str);
+ pInfo->curr = startMark;
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unterminated quoted string starting on line %d"), lineNumber(pInfo));
+ return NULL;
+ }
+ if (!str) {
+ if (pInfo->mutabilityOption == kCFPropertyListMutableContainersAndLeaves) {
+ _catFromMarkToBuf(mark, pInfo->curr, &str, pInfo->allocator);
+ } else {
+ str = (CFMutableStringRef)_uniqueStringForCharacters(pInfo, mark, pInfo->curr-mark);
+ CFRetain(str);
+ }
+ } else {
+ if (mark != pInfo->curr) {
+ _catFromMarkToBuf(mark, pInfo->curr, &str, pInfo->allocator);
+ }
+ if (pInfo->mutabilityOption != kCFPropertyListMutableContainersAndLeaves) {
+ CFStringRef uniqueString = _uniqueStringForString(pInfo, str);
+ CFRetain(uniqueString);
+ CFRelease(str);
+ str = (CFMutableStringRef)uniqueString;
+ }
+ }
+ pInfo->curr ++; // Advance past the quote character before returning.
+ if (pInfo->errorString) {
+ CFRelease(pInfo->errorString);
+ pInfo->errorString = NULL;
+ }
+ return str;
+}
+
+static CFStringRef parseUnquotedPlistString(_CFXMLPlistParseInfo *pInfo) {
+ const UniChar *mark = pInfo->curr;
+ while (pInfo->curr < pInfo->end) {
+ UniChar ch = *pInfo->curr;
+ if (isValidUnquotedStringCharacter(ch))
+ pInfo->curr ++;
+ else break;
+ }
+ if (pInfo->curr != mark) {
+ if (pInfo->mutabilityOption != kCFPropertyListMutableContainersAndLeaves) {
+ CFStringRef str = _uniqueStringForCharacters(pInfo, mark, pInfo->curr-mark);
+ CFRetain(str);
+ return str;
+ } else {
+ CFMutableStringRef str = CFStringCreateMutable(pInfo->allocator, 0);
+ CFStringAppendCharacters(str, mark, pInfo->curr - mark);
+ return str;
+ }
+ }
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unexpected EOF"));
+ return NULL;
+}
+
+static CFStringRef parsePlistString(_CFXMLPlistParseInfo *pInfo, bool requireObject) {
+ UniChar ch;
+ advanceToNonSpace(pInfo);
+ if (pInfo->curr >= pInfo->end) {
+ if (requireObject) {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unexpected EOF while parsing string"));
+ }
+ return NULL;
+ }
+ ch = *(pInfo->curr);
+ if (ch == '\'' || ch == '\"') {
+ pInfo->curr ++;
+ return parseQuotedPlistString(pInfo, ch);
+ } else if (isValidUnquotedStringCharacter(ch)) {
+ return parseUnquotedPlistString(pInfo);
+ } else {
+ if (requireObject) {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Invalid string character at line %d"), lineNumber(pInfo));
+ }
+ return NULL;
+ }
+}
+
+static CFTypeRef parsePlistArray(_CFXMLPlistParseInfo *pInfo) {
+ CFMutableArrayRef array = CFArrayCreateMutable(pInfo->allocator, 0, &kCFTypeArrayCallBacks);
+ CFTypeRef tmp = parsePlistObject(pInfo, false);
+ while (tmp) {
+ CFArrayAppendValue(array, tmp);
+ CFRelease(tmp);
+ advanceToNonSpace(pInfo);
+ if (*pInfo->curr != ',') {
+ tmp = NULL;
+ } else {
+ pInfo->curr ++;
+ tmp = parsePlistObject(pInfo, false);
+ }
+ }
+ advanceToNonSpace(pInfo);
+ if (*pInfo->curr != ')') {
+ CFRelease(array);
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Expected terminating ')' for array at line %d"), lineNumber(pInfo));
+ return NULL;
+ }
+ if (pInfo->errorString) {
+ CFRelease(pInfo->errorString);
+ pInfo->errorString = NULL;
+ }
+ pInfo->curr ++;
+ return array;
+}
+
+static CFDictionaryRef parsePlistDictContent(_CFXMLPlistParseInfo *pInfo) {
+ CFMutableDictionaryRef dict = CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ CFStringRef key = NULL;
+ Boolean failedParse = false;
+ key = parsePlistString(pInfo, false);
+ while (key) {
+ CFTypeRef value;
+ advanceToNonSpace(pInfo);
+ if (*pInfo->curr == ';') {
+ /* This is a strings file using the shortcut format */
+ /* although this check here really applies to all plists. */
+ value = CFRetain(key);
+ } else if (*pInfo->curr == '=') {
+ pInfo->curr ++;
+ value = parsePlistObject(pInfo, true);
+ if (!value) {
+ failedParse = true;
+ break;
+ }
+ } else {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unexpected ';' or '=' after key at line %d"), lineNumber(pInfo));
+ failedParse = true;
+ break;
+ }
+ CFDictionarySetValue(dict, key, value);
+ CFRelease(key);
+ key = NULL;
+ CFRelease(value);
+ value = NULL;
+ advanceToNonSpace(pInfo);
+ if (*pInfo->curr == ';') {
+ pInfo->curr ++;
+ key = parsePlistString(pInfo, false);
+ } else if (!allowMissingSemi && _CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
+ CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListCreateFromXMLData(): Old-style plist parser: missing semicolon in dictionary."));
+ failedParse = true;
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Missing ';' on line %d"), lineNumber(pInfo));
+ } else {
+ // on pre-Jaguar systems, do nothing except silently ignore the rest
+ // of the dictionary, which is what happened on those systems.
+ }
+ }
+
+ if (failedParse) {
+ if (key) CFRelease(key);
+ CFRelease(dict);
+ return NULL;
+ }
+ if (pInfo->errorString) {
+ CFRelease(pInfo->errorString);
+ pInfo->errorString = NULL;
+ }
+ return dict;
+}
+
+static CFTypeRef parsePlistDict(_CFXMLPlistParseInfo *pInfo) {
+ CFDictionaryRef dict = parsePlistDictContent(pInfo);
+ if (!dict) return NULL;
+ advanceToNonSpace(pInfo);
+ if (*pInfo->curr != '}') {
+ CFRelease(dict);
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Expected terminating '}' for dictionary at line %d"), lineNumber(pInfo));
+ return NULL;
+ }
+ pInfo->curr ++;
+ return dict;
+}
+
+CF_INLINE unsigned char fromHexDigit(unsigned char ch) {
+ if (isdigit(ch)) return ch - '0';
+ if ((ch >= 'a') && (ch <= 'f')) return ch - 'a' + 10;
+ if ((ch >= 'A') && (ch <= 'F')) return ch - 'A' + 10;
+ return 0xff; // Just choose a large number for the error code
+}
+
+/* Gets up to bytesSize bytes from a plist data. Returns number of bytes actually read. Leaves cursor at first non-space, non-hex character.
+ -1 is returned for unexpected char, -2 for uneven number of hex digits
+*/
+static int getDataBytes(_CFXMLPlistParseInfo *pInfo, unsigned char *bytes, int bytesSize) {
+ int numBytesRead = 0;
+ while ((pInfo->curr < pInfo->end) && (numBytesRead < bytesSize)) {
+ int first, second;
+ UniChar ch1 = *pInfo->curr;
+ if (ch1 == '>') return numBytesRead; // Meaning we're done
+ first = fromHexDigit((unsigned char)ch1);
+ if (first != 0xff) { // If the first char is a hex, then try to read a second hex
+ pInfo->curr++;
+ if (pInfo->curr >= pInfo->end) return -2; // Error: uneven number of hex digits
+ UniChar ch2 = *pInfo->curr;
+ second = fromHexDigit((unsigned char)ch2);
+ if (second == 0xff) return -2; // Error: uneven number of hex digits
+ bytes[numBytesRead++] = (first << 4) + second;
+ pInfo->curr++;
+ } else if (ch1 == ' ' || ch1 == '\n' || ch1 == '\t' || ch1 == '\r' || ch1 == 0x2028 || ch1 == 0x2029) {
+ pInfo->curr++;
+ } else {
+ return -1; // Error: unexpected character
+ }
+ }
+ return numBytesRead; // This does likely mean we didn't encounter a '>', but we'll let the caller deal with that
+}
+
+#define numBytes 400
+static CFTypeRef parsePlistData(_CFXMLPlistParseInfo *pInfo) {
+ CFMutableDataRef result = CFDataCreateMutable(pInfo->allocator, 0);
+
+ // Read hex bytes and append them to result
+ while (1) {
+ unsigned char bytes[numBytes];
+ int numBytesRead = getDataBytes(pInfo, bytes, numBytes);
+ if (numBytesRead < 0) {
+ CFRelease(result);
+ switch (numBytesRead) {
+ case -2: pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Malformed data byte group at line %d; uneven length"), lineNumber(pInfo)); break;
+ default: pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Malformed data byte group at line %d; invalid hex"), lineNumber(pInfo)); break;
+ }
+ return NULL;
+ }
+ if (numBytesRead == 0) break;
+ CFDataAppendBytes(result, bytes, numBytesRead);
+ }
+
+ if (pInfo->errorString) {
+ CFRelease(pInfo->errorString);
+ pInfo->errorString = NULL;
+ }
+
+ if (*(pInfo->curr) == '>') {
+ pInfo->curr ++; // Move past '>'
+ return result;
+ } else {
+ CFRelease(result);
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Expected terminating '>' for data at line %d"), lineNumber(pInfo));
+ return NULL;
+ }
+}
+#undef numBytes
+
+// Returned object is retained; caller must free.
+static CFTypeRef parsePlistObject(_CFXMLPlistParseInfo *pInfo, bool requireObject) {
+ UniChar ch;
+ advanceToNonSpace(pInfo);
+ if (pInfo->curr + 1 >= pInfo->end) {
+ if (requireObject) {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unexpected EOF while parsing plist"));
+ }
+ return NULL;
+ }
+ ch = *(pInfo->curr);
+ pInfo->curr ++;
+ if (ch == '{') {
+ return parsePlistDict(pInfo);
+ } else if (ch == '(') {
+ return parsePlistArray(pInfo);
+ } else if (ch == '<') {
+ return parsePlistData(pInfo);
+ } else if (ch == '\'' || ch == '\"') {
+ return parseQuotedPlistString(pInfo, ch);
+ } else if (isValidUnquotedStringCharacter(ch)) {
+ pInfo->curr --;
+ return parseUnquotedPlistString(pInfo);
+ } else {
+ pInfo->curr --; // Must back off the charcter we just read
+ if (requireObject) {
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unexpected character '0x%x' at line %d"), ch, lineNumber(pInfo));
+ }
+ return NULL;
+ }
+}
+
+static CFTypeRef parseOldStylePropertyListOrStringsFile(_CFXMLPlistParseInfo *pInfo) {
+ const UniChar *begin = pInfo->curr;
+ CFTypeRef result;
+ advanceToNonSpace(pInfo);
+ // A file consisting only of whitespace (or empty) is now defined to be an empty dictionary
+ if (pInfo->curr >= pInfo->end) return CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ result = parsePlistObject(pInfo, true);
+ advanceToNonSpace(pInfo);
+ if (pInfo->curr >= pInfo->end) return result;
+ if (!result) return NULL;
+ if (CFGetTypeID(result) != stringtype) {
+ CFRelease(result);
+ pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Junk after plist at line %d"), lineNumber(pInfo));
+ return NULL;
+ }
+ CFRelease(result);
+ // Check for a strings file (looks like a dictionary without the opening/closing curly braces)
+ pInfo->curr = begin;
+ return parsePlistDictContent(pInfo);
+}
+
+#undef isValidUnquotedStringCharacter
+
+static CFArrayRef _arrayDeepImmutableCopy(CFAllocatorRef allocator, CFArrayRef array, CFOptionFlags mutabilityOption) {
+ CFArrayRef result = NULL;
+ CFIndex i, c = CFArrayGetCount(array);
+ CFTypeRef *values;
+ if (c == 0) {
+ result = CFArrayCreate(allocator, NULL, 0, &kCFTypeArrayCallBacks);
+ } else if ((values = (CFTypeRef *)CFAllocatorAllocate(allocator, c*sizeof(CFTypeRef), 0)) != NULL) {
+ CFArrayGetValues(array, CFRangeMake(0, c), values);
+ for (i = 0; i < c; i ++) {
+ values[i] = CFPropertyListCreateDeepCopy(allocator, values[i], mutabilityOption);
+ if (values[i] == NULL) {
+ break;
+ }
+ }
+ result = (i == c) ? CFArrayCreate(allocator, values, c, &kCFTypeArrayCallBacks) : NULL;
+ c = i;
+ for (i = 0; i < c; i ++) {
+ CFRelease(values[i]);
+ }
+ CFAllocatorDeallocate(allocator, values);
+ }
+ return result;
+}
+
+static CFMutableArrayRef _arrayDeepMutableCopy(CFAllocatorRef allocator, CFArrayRef array, CFOptionFlags mutabilityOption) {
+ CFIndex i, c = CFArrayGetCount(array);
+ CFMutableArrayRef result = CFArrayCreateMutable(allocator, 0, &kCFTypeArrayCallBacks);
+ if (result) {
+ for (i = 0; i < c; i ++) {
+ CFTypeRef newValue = CFPropertyListCreateDeepCopy(allocator, CFArrayGetValueAtIndex(array, i), mutabilityOption);
+ if (!newValue) break;
+ CFArrayAppendValue(result, newValue);
+ CFRelease(newValue);
+ }
+ if (i != c) {
+ CFRelease(result);
+ result = NULL;
+ }
+ }
+ return result;
+}
+
+CFPropertyListRef CFPropertyListCreateDeepCopy(CFAllocatorRef allocator, CFPropertyListRef propertyList, CFOptionFlags mutabilityOption) {
+ initStatics();
+ CFTypeID typeID;
+ CFPropertyListRef result = NULL;
+ CFAssert1(propertyList != NULL, __kCFLogAssertion, "%s(): cannot copy a NULL property list", __PRETTY_FUNCTION__);
+ __CFAssertIsPList(propertyList);
+ CFAssert2(mutabilityOption == kCFPropertyListImmutable || mutabilityOption == kCFPropertyListMutableContainers || mutabilityOption == kCFPropertyListMutableContainersAndLeaves, __kCFLogAssertion, "%s(): Unrecognized option %d", __PRETTY_FUNCTION__, mutabilityOption);
+ if (!CFPropertyListIsValid(propertyList, kCFPropertyListBinaryFormat_v1_0)) return NULL;
+
+ if (allocator == NULL) {
+ allocator = (CFAllocatorRef)CFRetain(__CFGetDefaultAllocator());
+ } else {
+ CFRetain(allocator);
+ }
+
+ typeID = CFGetTypeID(propertyList);
+ if (typeID == dicttype) {
+ CFDictionaryRef dict = (CFDictionaryRef)propertyList;
+ Boolean isMutable = (mutabilityOption != kCFPropertyListImmutable);
+ CFIndex count = CFDictionaryGetCount(dict);
+ CFTypeRef *keys, *values;
+ if (count == 0) {
+ result = isMutable ? CFDictionaryCreateMutable(allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks): CFDictionaryCreate(allocator, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ } else if ((keys = (CFTypeRef *)CFAllocatorAllocate(allocator, 2 * count * sizeof(CFTypeRef), 0)) != NULL) {
+ CFIndex i;
+ values = keys+count;
+ CFDictionaryGetKeysAndValues(dict, keys, values);
+ for (i = 0; i < count; i ++) {
+ keys[i] = CFStringCreateCopy(allocator, (CFStringRef)keys[i]);
+ if (keys[i] == NULL) {
+ break;
+ }
+ values[i] = CFPropertyListCreateDeepCopy(allocator, values[i], mutabilityOption);
+ if (values[i] == NULL) {
+ CFRelease(keys[i]);
+ break;
+ }
+ }
+ if (i == count) {
+ result = isMutable ? CFDictionaryCreateMutable(allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks) : CFDictionaryCreate(allocator, keys, values, count, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+ for (i = 0; i < count; i ++) {
+ if (isMutable) {
+ CFDictionarySetValue((CFMutableDictionaryRef)result, keys[i], values[i]);
+ }
+ CFRelease(keys[i]);
+ CFRelease(values[i]);
+ }
+ } else {
+ result = NULL;
+ count = i;
+ for (i = 0; i < count; i ++) {
+ CFRelease(keys[i]);
+ CFRelease(values[i]);
+ }
+ }
+ CFAllocatorDeallocate(allocator, keys);
+ } else {
+ result = NULL;
+ }
+ } else if (typeID == arraytype) {
+ if (mutabilityOption == kCFPropertyListImmutable) {
+ result = _arrayDeepImmutableCopy(allocator, (CFArrayRef)propertyList, mutabilityOption);
+ } else {
+ result = _arrayDeepMutableCopy(allocator, (CFArrayRef)propertyList, mutabilityOption);
+ }
+ } else if (typeID == datatype) {
+ if (mutabilityOption == kCFPropertyListMutableContainersAndLeaves) {
+ result = CFDataCreateMutableCopy(allocator, 0, (CFDataRef)propertyList);
+ } else {
+ result = CFDataCreateCopy(allocator, (CFDataRef)propertyList);
+ }
+ } else if (typeID == numbertype) {
+ // Warning - this will break if byteSize is ever greater than 32
+ uint8_t bytes[32];
+ CFNumberType numType = CFNumberGetType((CFNumberRef)propertyList);
+ CFNumberGetValue((CFNumberRef)propertyList, numType, (void *)bytes);
+ result = CFNumberCreate(allocator, numType, (void *)bytes);
+ } else if (typeID == booltype) {
+ // Booleans are immutable & shared instances
+ CFRetain(propertyList);
+ result = propertyList;
+ } else if (typeID == datetype) {
+ // Dates are immutable
+ result = CFDateCreate(allocator, CFDateGetAbsoluteTime((CFDateRef)propertyList));
+ } else if (typeID == stringtype) {
+ if (mutabilityOption == kCFPropertyListMutableContainersAndLeaves) {
+ result = CFStringCreateMutableCopy(allocator, 0, (CFStringRef)propertyList);
+ } else {
+ result = CFStringCreateCopy(allocator, (CFStringRef)propertyList);
+ }
+ } else {
+ CFAssert2(false, __kCFLogAssertion, "%s(): %p is not a property list type", __PRETTY_FUNCTION__, propertyList);
+ result = NULL;
+ }
+ CFRelease(allocator);
+ return result;
+}
+