blob: a1c99c7e832dd67341db37d3cb7b1b6aa7ef1f4f [file] [log] [blame]
/*
* Copyright (c) 2008-2009 Brent Fulgham <bfulgham@gmail.org>. All rights reserved.
*
* This source code is a modified version of the CoreFoundation sources released by Apple Inc. under
* the terms of the APSL version 2.0 (see below).
*
* For information about changes from the original Apple source release can be found by reviewing the
* source control system for the project at https://sourceforge.net/svn/?group_id=246198.
*
* The original license information is as follows:
*
* 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>
#include <CoreFoundation/CFPreferences.h>
#if DEPLOYMENT_TARGET_MACOSX
#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, (CFIndex)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, "&lt;");
break;
case '>':
if (bufCnt) _plistAppendCharacters(mStr, buf, bufCnt);
bufCnt = 0;
_plistAppendUTF8CString(mStr, "&gt;");
break;
case '&':
if (bufCnt) _plistAppendCharacters(mStr, buf, bufCnt);
bufCnt = 0;
_plistAppendUTF8CString(mStr, "&amp;");
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 || DEPLOYMENT_TARGET_WINDOWS || DEPLOYMENT_TARGET_LINUX
} 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;
}