blob: ad2b6bccef97ea2dc0c34b4983f4931940674317 [file] [log] [blame]
/* CFPropertyList.c
Copyright (c) 1999-2016, Apple Inc. and the Swift project authors
Portions Copyright (c) 2014-2016 Apple Inc. and the Swift project authors
Licensed under Apache License v2.0 with Runtime Library Exception
See http://swift.org/LICENSE.txt for license information
See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
Responsibility: Tony Parker
*/
#include <CoreFoundation/CFPropertyList.h>
#include <CoreFoundation/CFByteOrder.h>
#include <CoreFoundation/CFDate.h>
#include <CoreFoundation/CFNumber.h>
#include <CoreFoundation/CFSet.h>
#include <CoreFoundation/CFError.h>
#include <CoreFoundation/CFError_Private.h>
#include <CoreFoundation/CFPriv.h>
#include <CoreFoundation/CFStringEncodingConverter.h>
#include "CFInternal.h"
#include <CoreFoundation/CFBurstTrie.h>
#include <CoreFoundation/CFString.h>
#include <CoreFoundation/CFStream.h>
#include <CoreFoundation/CFCalendar.h>
#include "CFLocaleInternal.h"
#include <limits.h>
#include <float.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <ctype.h>
CF_EXPORT CFNumberType _CFNumberGetType2(CFNumberRef number);
#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
#if !defined(new_cftype_array)
#define new_cftype_array(N, C) \
size_t N ## _count__ = (C); \
if (N ## _count__ > LONG_MAX / sizeof(CFTypeRef)) { \
CRSetCrashLogMessage("CFPropertyList ran out of memory while attempting to allocate temporary storage."); \
HALT; \
} \
Boolean N ## _is_stack__ = (N ## _count__ <= 256); \
if (N ## _count__ == 0) N ## _count__ = 1; \
STACK_BUFFER_DECL(CFTypeRef, N ## _buffer__, N ## _is_stack__ ? N ## _count__ : 1); \
if (N ## _is_stack__) memset(N ## _buffer__, 0, N ## _count__ * sizeof(CFTypeRef)); \
CFTypeRef * N = N ## _is_stack__ ? N ## _buffer__ : (CFTypeRef *)CFAllocatorAllocate(kCFAllocatorSystemDefault, (N ## _count__) * sizeof(CFTypeRef), 0); \
if (! N) { \
CRSetCrashLogMessage("CFPropertyList ran out of memory while attempting to allocate temporary storage."); \
HALT; \
} \
do {} while (0)
#endif
#if !defined(free_cftype_array)
#define free_cftype_array(N) \
if (! N ## _is_stack__) { \
CFAllocatorDeallocate(kCFAllocatorSystemDefault, N); \
} \
do {} while (0)
#endif
// Used to reference an old-style plist parser error inside of a more general XML error
#define CFPropertyListOldStyleParserErrorKey CFSTR("kCFPropertyListOldStyleParsingError")
static CFTypeID stringtype, datatype, numbertype, datetype;
static CFTypeID booltype, nulltype, dicttype, arraytype, settype;
static void initStatics() {
static dispatch_once_t once;
dispatch_once(&once, ^{
stringtype = CFStringGetTypeID();
datatype = CFDataGetTypeID();
numbertype = CFNumberGetTypeID();
booltype = CFBooleanGetTypeID();
datetype = CFDateGetTypeID();
dicttype = CFDictionaryGetTypeID();
arraytype = CFArrayGetTypeID();
settype = CFSetGetTypeID();
nulltype = CFNullGetTypeID();
});
}
CF_PRIVATE CFErrorRef __CFPropertyListCreateError(CFIndex code, CFStringRef debugString, ...) {
va_list argList;
CFErrorRef error = NULL;
if (debugString != NULL) {
CFStringRef debugMessage = NULL;
va_start(argList, debugString);
debugMessage = CFStringCreateWithFormatAndArguments(kCFAllocatorSystemDefault, NULL, debugString, argList);
va_end(argList);
CFMutableDictionaryRef userInfo = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(userInfo, kCFErrorDebugDescriptionKey, debugMessage);
error = CFErrorCreate(kCFAllocatorSystemDefault, kCFErrorDomainCocoa, code, userInfo);
CFRelease(debugMessage);
CFRelease(userInfo);
} else {
error = CFErrorCreate(kCFAllocatorSystemDefault, kCFErrorDomainCocoa, code, NULL);
}
return error;
}
static CFStringRef __copyErrorDebugDescription(CFErrorRef error) {
CFStringRef result = NULL;
if (error) {
CFDictionaryRef userInfo = CFErrorCopyUserInfo(error);
if (userInfo != NULL) {
CFStringRef desc = (CFStringRef) CFDictionaryGetValue(userInfo, kCFErrorDebugDescriptionKey);
if (desc != NULL) {
result = CFStringCreateCopy(kCFAllocatorSystemDefault, desc);
}
CFRelease(userInfo);
}
}
return result;
}
#pragma mark -
#pragma mark Property List Validation
// 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);
struct context {
bool answer;
CFMutableSetRef set;
CFPropertyListFormat format;
CFStringRef *error;
};
static bool __CFPropertyListIsValidAux(CFPropertyListRef plist, bool recursive, CFMutableSetRef set, CFPropertyListFormat format, CFStringRef *error);
static void __CFPropertyListIsArrayPlistAux(const void *value, void *context) {
struct context *ctx = (struct context *)context;
if (!ctx->answer) return;
if (!value && ctx->error && !*(ctx->error)) {
*(ctx->error) = (CFStringRef)CFRetain(CFSTR("property list arrays cannot contain NULL"));
}
ctx->answer = value && __CFPropertyListIsValidAux(value, true, ctx->set, ctx->format, ctx->error);
}
static void __CFPropertyListIsDictPlistAux(const void *key, const void *value, void *context) {
struct context *ctx = (struct context *)context;
if (!ctx->answer) return;
if (!key && ctx->error && !*(ctx->error)) *(ctx->error) = (CFStringRef)CFRetain(CFSTR("property list dictionaries cannot contain NULL keys"));
if (!value && ctx->error && !*(ctx->error)) *(ctx->error) = (CFStringRef)CFRetain(CFSTR("property list dictionaries cannot contain NULL values"));
if (stringtype != CFGetTypeID(key) && ctx->error && !*(ctx->error)) {
CFStringRef desc = CFCopyTypeIDDescription(CFGetTypeID(key));
*(ctx->error) = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("property list dictionaries may only have keys which are CFStrings, not '%@'"), desc);
CFRelease(desc);
}
ctx->answer = key && value && (stringtype == CFGetTypeID(key)) && __CFPropertyListIsValidAux(value, true, ctx->set, ctx->format, ctx->error);
}
static bool __CFPropertyListIsValidAux(CFPropertyListRef plist, bool recursive, CFMutableSetRef set, CFPropertyListFormat format, CFStringRef *error) {
if (!plist) {
if (error) *error = (CFStringRef)CFRetain(CFSTR("property lists cannot contain NULL"));
return false;
}
CFTypeID 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 (_CFKeyedArchiverUIDGetTypeID() == type) return true;
}
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. For the outermost invocation it can be NULL.
Boolean createdSet = false;
if (!set) {
set = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, NULL);
createdSet = true;
} else if (CFSetContainsValue(set, plist)) {
if (error) *error = (CFStringRef)CFRetain(CFSTR("property lists cannot contain recursive container references"));
return false;
}
Boolean result = false;
if (arraytype == type) {
struct context ctx = {true, set, format, error};
CFSetAddValue(set, plist);
CFArrayApplyFunction((CFArrayRef)plist, CFRangeMake(0, CFArrayGetCount((CFArrayRef)plist)), __CFPropertyListIsArrayPlistAux, &ctx);
CFSetRemoveValue(set, plist);
result = ctx.answer;
} else if (dicttype == type) {
struct context ctx = {true, set, format, error};
CFSetAddValue(set, plist);
CFDictionaryApplyFunction((CFDictionaryRef)plist, __CFPropertyListIsDictPlistAux, &ctx);
CFSetRemoveValue(set, plist);
result = ctx.answer;
} else if (error) {
CFStringRef desc = CFCopyTypeIDDescription(type);
*error = CFStringCreateWithFormat(kCFAllocatorSystemDefault, NULL, CFSTR("property lists cannot contain objects of type '%@'"), desc);
CFRelease(desc);
}
if (createdSet) {
CFRelease(set);
}
return result;
}
static Boolean _CFPropertyListIsValidWithErrorString(CFPropertyListRef plist, CFPropertyListFormat format, CFStringRef *error) {
initStatics();
CFAssert1(plist != NULL, __kCFLogAssertion, "%s(): NULL is not a property list", __PRETTY_FUNCTION__);
return __CFPropertyListIsValidAux(plist, true, NULL, format, error);
}
#pragma mark -
#pragma mark Writing Property Lists
static const char 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'}
};
static const UniChar CFXMLPlistTagsUnicode[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 char *begin; // first character of the XML to be parsed
const char *curr; // current parse location
const char *end; // the first character _after_ the end of the XML
CFErrorRef error;
CFAllocatorRef allocator;
UInt32 mutabilityOption;
CFBurstTrieRef stringTrie; // map of cached strings
CFMutableArrayRef stringCache; // retaining array of strings
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)
CFSetRef keyPaths; // if NULL, no filtering
Boolean skip; // if true, do not create any objects.
} _CFXMLPlistParseInfo;
CF_PRIVATE CFTypeRef __CFCreateOldStylePropertyListOrStringsFile(CFAllocatorRef allocator, CFDataRef xmlData, CFStringRef originalString, CFStringEncoding guessedEncoding, CFOptionFlags option, CFErrorRef *outError,CFPropertyListFormat *format);
CF_INLINE void __CFPListRelease(CFTypeRef cf, CFAllocatorRef allocator) {
if (cf) CFRelease(cf);
}
// The following set of _plist... functions append various things to a mutable data which is in UTF8 encoding. These are pretty general. Assumption is call characters and CFStrings can be converted to UTF8 and appeneded.
// Null-terminated, ASCII or UTF8 string
//
static void _plistAppendUTF8CString(CFMutableDataRef mData, const char *cString) {
CFDataAppendBytes (mData, (const UInt8 *)cString, strlen(cString));
}
// UniChars
//
static void _plistAppendCharacters(CFMutableDataRef mData, const UniChar *chars, CFIndex length) {
CFIndex curLoc = 0;
do { // Flush out ASCII chars, BUFLEN at a time
#define BUFLEN 400
UInt8 buf[BUFLEN], *bufPtr = buf;
CFIndex cnt = 0;
while (cnt < length && (cnt - curLoc < BUFLEN) && (chars[cnt] < 128)) *bufPtr++ = (UInt8)(chars[cnt++]);
if (cnt > curLoc) { // Flush any ASCII bytes
CFDataAppendBytes(mData, buf, cnt - curLoc);
curLoc = cnt;
}
} while (curLoc < length && (chars[curLoc] < 128)); // We will exit out of here when we run out of chars or hit a non-ASCII char
if (curLoc < length) { // Now deal with non-ASCII chars
CFDataRef data = NULL;
CFStringRef str = NULL;
if ((str = CFStringCreateWithCharactersNoCopy(kCFAllocatorSystemDefault, chars + curLoc, length - curLoc, kCFAllocatorNull))) {
if ((data = CFStringCreateExternalRepresentation(kCFAllocatorSystemDefault, str, kCFStringEncodingUTF8, 0))) {
CFDataAppendBytes (mData, CFDataGetBytePtr(data), CFDataGetLength(data));
CFRelease(data);
}
CFRelease(str);
}
CFAssert1(str && data, __kCFLogAssertion, "%s(): Error writing plist", __PRETTY_FUNCTION__);
}
}
// Append CFString
//
static void _plistAppendString(CFMutableDataRef mData, CFStringRef str) {
const UniChar *chars;
const char *cStr;
CFDataRef data;
if ((chars = CFStringGetCharactersPtr(str))) {
_plistAppendCharacters(mData, chars, CFStringGetLength(str));
} else if ((cStr = CFStringGetCStringPtr(str, kCFStringEncodingASCII)) || (cStr = CFStringGetCStringPtr(str, kCFStringEncodingUTF8))) {
_plistAppendUTF8CString(mData, cStr);
} else if ((data = CFStringCreateExternalRepresentation(kCFAllocatorSystemDefault, str, kCFStringEncodingUTF8, 0))) {
CFDataAppendBytes (mData, CFDataGetBytePtr(data), CFDataGetLength(data));
CFRelease(data);
} else {
CFAssert1(TRUE, __kCFLogAssertion, "%s(): Error in plist writing", __PRETTY_FUNCTION__);
}
}
// Append CFString-style format + arguments
//
static void _plistAppendFormat(CFMutableDataRef mData, CFStringRef format, ...) {
CFStringRef fStr;
va_list argList;
va_start(argList, format);
fStr = CFStringCreateWithFormatAndArguments(kCFAllocatorSystemDefault, NULL, format, argList);
va_end(argList);
CFAssert1(fStr, __kCFLogAssertion, "%s(): Error writing plist", __PRETTY_FUNCTION__);
_plistAppendString(mData, fStr);
CFRelease(fStr);
}
static void _appendIndents(CFIndex numIndents, CFMutableDataRef str) {
#define NUMTABS 4
static const UniChar tabs[NUMTABS] = {'\t','\t','\t','\t'};
for (; numIndents > 0; numIndents -= NUMTABS) _plistAppendCharacters(str, tabs, (numIndents >= NUMTABS) ? NUMTABS : numIndents);
}
/* Append the escaped version of origStr to mStr.
*/
static void _appendEscapedString(CFStringRef origStr, CFMutableDataRef mStr) {
#define BUFSIZE 64
CFIndex i, length = CFStringGetLength(origStr);
CFIndex bufCnt = 0;
UniChar buf[BUFSIZE];
CFStringInlineBuffer inlineBuffer;
CFStringInitInlineBuffer(origStr, &inlineBuffer, CFRangeMake(0, length));
for (i = 0; i < length; i ++) {
UniChar ch = __CFStringGetCharacterFromInlineBufferQuick(&inlineBuffer, i);
if (CFStringIsSurrogateHighCharacter(ch) && (bufCnt + 2 >= BUFSIZE)) {
// flush the buffer first so we have room for a low/high pair and do not split them
_plistAppendCharacters(mStr, buf, bufCnt);
bufCnt = 0;
}
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, CFXMLPlistTagsUnicode[STRING_IX], STRING_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, ">");
_appendEscapedString((CFStringRef)object, xmlString);
_plistAppendUTF8CString(xmlString, "</");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[STRING_IX], STRING_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, ">\n");
} else if (typeID == _CFKeyedArchiverUIDGetTypeID()) {
// This is only used for the keyed archiver
_plistAppendUTF8CString(xmlString, "<");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[DICT_IX], DICT_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, ">\n");
_appendIndents(indentation+1, xmlString);
_plistAppendUTF8CString(xmlString, "<");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[KEY_IX], KEY_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, ">");
_appendEscapedString(CFSTR("CF$UID"), xmlString);
_plistAppendUTF8CString(xmlString, "</");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[KEY_IX], KEY_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, ">\n");
_appendIndents(indentation + 1, xmlString);
_plistAppendUTF8CString(xmlString, "<");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[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, CFXMLPlistTagsUnicode[INTEGER_IX], INTEGER_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, ">\n");
_appendIndents(indentation, xmlString);
_plistAppendUTF8CString(xmlString, "</");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[DICT_IX], DICT_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, ">\n");
} else if (typeID == arraytype) {
UInt32 i, count = CFArrayGetCount((CFArrayRef)object);
if (count == 0) {
_plistAppendUTF8CString(xmlString, "<");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[ARRAY_IX], ARRAY_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, "/>\n");
return;
}
_plistAppendUTF8CString(xmlString, "<");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[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, CFXMLPlistTagsUnicode[ARRAY_IX], ARRAY_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, ">\n");
} else if (typeID == dicttype) {
UInt32 i, count = CFDictionaryGetCount((CFDictionaryRef)object);
CFMutableArrayRef keyArray;
if (count == 0) {
_plistAppendUTF8CString(xmlString, "<");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[DICT_IX], DICT_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, "/>\n");
return;
}
_plistAppendUTF8CString(xmlString, "<");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[DICT_IX], DICT_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, ">\n");
new_cftype_array(keys, count);
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, CFXMLPlistTagsUnicode[KEY_IX], KEY_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, ">");
_appendEscapedString((CFStringRef)key, xmlString);
_plistAppendUTF8CString(xmlString, "</");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[KEY_IX], KEY_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, ">\n");
_CFAppendXML0(CFDictionaryGetValue((CFDictionaryRef)object, key), indentation+1, xmlString);
}
free_cftype_array(keys);
_appendIndents(indentation, xmlString);
_plistAppendUTF8CString(xmlString, "</");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[DICT_IX], DICT_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, ">\n");
} else if (typeID == datatype) {
_plistAppendUTF8CString(xmlString, "<");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[DATA_IX], DATA_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, ">\n");
_XMLPlistAppendDataUsingBase64(xmlString, (CFDataRef)object, indentation);
_appendIndents(indentation, xmlString);
_plistAppendUTF8CString(xmlString, "</");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[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;
CFAbsoluteTime at = CFDateGetAbsoluteTime((CFDateRef)object);
#if 0
// Alternative to the CFAbsoluteTimeGetGregorianDate() code which works well
struct timeval tv;
struct tm mine;
tv.tv_sec = floor(at + kCFAbsoluteTimeIntervalSince1970);
gmtime_r(&tv.tv_sec, &mine);
y = mine.tm_year + 1900;
M = mine.tm_mon + 1;
d = mine.tm_mday;
H = mine.tm_hour;
m = mine.tm_min;
s = mine.tm_sec;
#endif
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated"
CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(at, NULL);
#pragma GCC diagnostic pop
#if 0
if (date.year != y || date.month != M || date.day != d || date.hour != H || date.minute != m || (int32_t)date.second != s) {
CFLog(4, CFSTR("DATE ERROR {%d, %d, %d, %d, %d, %d} != {%d, %d, %d, %d, %d, %d}\n"), (int)date.year, (int)date.month, (int)date.day, (int)date.hour, (int)date.minute, (int32_t)date.second, y, M, d, H, m, s);
}
#endif
y = date.year;
M = date.month;
d = date.day;
H = date.hour;
m = date.minute;
s = (int32_t)date.second;
_plistAppendUTF8CString(xmlString, "<");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[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, CFXMLPlistTagsUnicode[DATE_IX], DATE_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, ">\n");
} else if (typeID == numbertype) {
if (CFNumberIsFloatType((CFNumberRef)object)) {
_plistAppendUTF8CString(xmlString, "<");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[REAL_IX], REAL_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, ">");
CFStringRef s = __CFNumberCopyFormattingDescriptionAsFloat64(object);
_plistAppendString(xmlString, s);
CFRelease(s);
_plistAppendUTF8CString(xmlString, "</");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[REAL_IX], REAL_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, ">\n");
} else {
_plistAppendUTF8CString(xmlString, "<");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[INTEGER_IX], INTEGER_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, ">");
_plistAppendFormat(xmlString, CFSTR("%@"), object);
_plistAppendUTF8CString(xmlString, "</");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[INTEGER_IX], INTEGER_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, ">\n");
}
} else if (typeID == booltype) {
if (CFBooleanGetValue((CFBooleanRef)object)) {
_plistAppendUTF8CString(xmlString, "<");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[TRUE_IX], TRUE_TAG_LENGTH);
_plistAppendUTF8CString(xmlString, "/>\n");
} else {
_plistAppendUTF8CString(xmlString, "<");
_plistAppendCharacters(xmlString, CFXMLPlistTagsUnicode[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, CFXMLPlistTagsUnicode[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, CFXMLPlistTagsUnicode[PLIST_IX], PLIST_TAG_LENGTH);
_plistAppendUTF8CString(xml, " version=\"1.0\">\n");
_CFAppendXML0(propertyList, 0, xml);
_plistAppendUTF8CString(xml, "</");
_plistAppendCharacters(xml, CFXMLPlistTagsUnicode[PLIST_IX], PLIST_TAG_LENGTH);
_plistAppendUTF8CString(xml, ">\n");
}
// ========================================================================
#pragma mark -
#pragma mark Exported Creation Functions
CFDataRef _CFPropertyListCreateXMLData(CFAllocatorRef allocator, CFPropertyListRef propertyList, Boolean checkValidPlist) {
initStatics();
CFMutableDataRef xml;
CFAssert1(propertyList != NULL, __kCFLogAssertion, "%s(): Cannot be called with a NULL property list", __PRETTY_FUNCTION__);
if (checkValidPlist && !CFPropertyListIsValid(propertyList, kCFPropertyListXMLFormat_v1_0)) {
__CFAssertIsPList(propertyList);
return NULL;
}
xml = CFDataCreateMutable(allocator, 0);
_CFGenerateXMLPropertyListToData(xml, propertyList);
return xml;
}
CFDataRef CFPropertyListCreateXMLData(CFAllocatorRef allocator, CFPropertyListRef propertyList) {
return _CFPropertyListCreateXMLData(allocator, propertyList, true);
}
CF_EXPORT CFDataRef _CFPropertyListCreateXMLDataWithExtras(CFAllocatorRef allocator, CFPropertyListRef propertyList) {
return _CFPropertyListCreateXMLData(allocator, propertyList, false);
}
Boolean CFPropertyListIsValid(CFPropertyListRef plist, CFPropertyListFormat format) {
initStatics();
CFAssert1(plist != NULL, __kCFLogAssertion, "%s(): NULL is not a property list", __PRETTY_FUNCTION__);
return __CFPropertyListIsValidAux(plist, true, NULL, format, NULL);
#if defined(DEBUG)
CFStringRef error = NULL;
bool result = __CFPropertyListIsValidAux(plist, true, NULL, format, &error);
if (error) {
CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListIsValid(): %@"), error);
CFRelease(error);
}
return result;
#endif
}
// ========================================================================
#pragma mark -
#pragma mark Reading Plists
//
// ------------------------- Reading plists ------------------
//
static void skipInlineDTD(_CFXMLPlistParseInfo *pInfo);
static Boolean parseXMLElement(_CFXMLPlistParseInfo *pInfo, Boolean *isKey, CFTypeRef *out);
// warning: doesn't have a good idea of Unicode line separators
static UInt32 lineNumber(_CFXMLPlistParseInfo *pInfo) {
const char *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 char *p = pInfo->curr;
const char *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->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unterminated comment started on line %d"), lineNumber(pInfo));
}
// pInfo should be set to the first character after "<?"
static void skipXMLProcessingInstruction(_CFXMLPlistParseInfo *pInfo) {
const char *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->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, 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 || memcmp(pInfo->curr, CFXMLPlistTags[DOCTYPE_IX], DOCTYPE_TAG_LENGTH)) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, 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) {
char 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->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF while parsing DTD"));
return;
}
// *Sigh* Must parse in-line DTD
skipInlineDTD(pInfo);
if (pInfo->error) return;
skipWhitespace(pInfo);
if (pInfo->error) return;
if (pInfo->curr < pInfo->end) {
if (*(pInfo->curr) == '>') {
pInfo->curr ++;
} else {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected character %c on line %d while parsing DTD"), *(pInfo->curr), lineNumber(pInfo));
}
} else {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF while parsing DTD"));
}
}
static void skipPERef(_CFXMLPlistParseInfo *pInfo) {
const char *p = pInfo->curr;
while (p < pInfo->end) {
if (*p == ';') {
pInfo->curr = p+1;
return;
}
p ++;
}
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, 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->error && 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->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF while parsing inline DTD"));
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->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF while parsing inline DTD"));
return;
}
pInfo->curr ++;
}
} else {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected character %c on line %d while parsing inline DTD"), ch, lineNumber(pInfo));
return;
}
} else if (ch == ']') {
pInfo->curr ++;
return;
} else {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected character %c on line %d while parsing inline DTD"), ch, lineNumber(pInfo));
return;
}
}
if (!pInfo->error) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF while parsing inline DTD"));
}
}
// 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 Boolean getContentObject(_CFXMLPlistParseInfo *pInfo, Boolean *isKey, CFTypeRef *out) {
if (isKey) *isKey = false;
while (!pInfo->error && pInfo->curr < pInfo->end) {
skipWhitespace(pInfo);
if (pInfo->curr >= pInfo->end) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF"));
return false;
}
if (*(pInfo->curr) != '<') {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected character %c on line %d while looking for open tag"), *(pInfo->curr), lineNumber(pInfo));
return false;
}
pInfo->curr ++;
if (pInfo->curr >= pInfo->end) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF"));
return false;
}
switch (*(pInfo->curr)) {
case '?':
// Processing instruction
skipXMLProcessingInstruction(pInfo);
break;
case '!':
// Could be a comment
if (pInfo->curr+2 >= pInfo->end) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF"));
return false;
}
if (*(pInfo->curr+1) == '-' && *(pInfo->curr+2) == '-') {
pInfo->curr += 2;
skipXMLComment(pInfo);
} else {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF"));
return false;
}
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 false;
default:
// Should be an element
return parseXMLElement(pInfo, isKey, out);
}
}
// 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 false;
}
static void parseCDSect_pl(_CFXMLPlistParseInfo *pInfo, CFMutableDataRef stringData) {
const char *end, *begin;
if (pInfo->end - pInfo->curr < CDSECT_TAG_LENGTH) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF"));
return;
}
if (memcmp(pInfo->curr, CFXMLPlistTags[CDSECT_IX], CDSECT_TAG_LENGTH)) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, 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!
CFDataAppendBytes(stringData, (const UInt8 *)begin, pInfo->curr-begin);
pInfo->curr += 3;
return;
}
pInfo->curr ++;
}
// Never found the end mark
pInfo->curr = begin;
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, 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, CFMutableDataRef stringData) {
int len;
pInfo->curr ++; // move past the '&';
len = pInfo->end - pInfo->curr; // how many bytes we can safely scan
if (len < 1) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF"));
return;
}
char ch;
switch (*(pInfo->curr)) {
case 'l': // "lt"
if (len >= 3 && *(pInfo->curr+1) == 't' && *(pInfo->curr+2) == ';') {
ch = '<';
pInfo->curr += 3;
break;
}
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, 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->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, 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->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF"));
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->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, 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->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo));
return;
case '#':
{
uint32_t num = 0;
Boolean isHex = false;
if ( len < 4) { // Not enough characters to make it all fit! Need at least "&#d;"
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF"));
return;
}
pInfo->curr ++;
if (*(pInfo->curr) == 'x') {
isHex = true;
pInfo->curr ++;
}
int numberOfCharactersSoFar = 0;
while (pInfo->curr < pInfo->end) {
ch = *(pInfo->curr);
pInfo->curr ++;
numberOfCharactersSoFar++;
if (ch == ';') {
// The value in num always refers to the unicode code point. We'll have to convert since the calling function expects UTF8 data. Swap to BE always so we know what encoding to use.
num = CFSwapInt32HostToBig(num);
CFStringRef oneChar = CFStringCreateWithBytes(pInfo->allocator, (const uint8_t *)&num, sizeof(num), kCFStringEncodingUTF32BE, NO);
if (!oneChar) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unparseable Unicode sequence at line %d while parsing data (input did not result in a real string)"), lineNumber(pInfo));
return;
}
uint8_t tmpBuf[6]; // max of 6 bytes for UTF8
CFIndex tmpBufLength = 0;
CFStringGetBytes(oneChar, CFRangeMake(0, CFStringGetLength(oneChar)), kCFStringEncodingUTF8, 0, NO, tmpBuf, 6, &tmpBufLength);
CFDataAppendBytes(stringData, tmpBuf, tmpBufLength);
__CFPListRelease(oneChar, pInfo->allocator);
return;
} else if (numberOfCharactersSoFar > 8) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unparseable unicode sequence at line %d while parsing data (too large of a value for a Unicode sequence)"), lineNumber(pInfo));
return;
}
if (!isHex) num = num*10;
else num = num << 4;
if (ch <= '9' && ch >= '0') {
num += (ch - '0');
} else if (!isHex) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected character %c at line %d while parsing data"), 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->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected character %c at line %d while parsing data"), ch, lineNumber(pInfo));
return;
}
}
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF"));
return;
}
default:
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo));
return;
}
CFDataAppendBytes(stringData, (const UInt8 *)&ch, 1);
}
static void _createStringMap(_CFXMLPlistParseInfo *pInfo) {
pInfo->stringTrie = CFBurstTrieCreate();
pInfo->stringCache = CFArrayCreateMutable(pInfo->allocator, 0, &kCFTypeArrayCallBacks);
}
static void _cleanupStringMap(_CFXMLPlistParseInfo *pInfo) {
CFBurstTrieRelease(pInfo->stringTrie);
CFRelease(pInfo->stringCache);
pInfo->stringTrie = NULL;
pInfo->stringCache = NULL;
}
static CFStringRef _createUniqueStringWithUTF8Bytes(_CFXMLPlistParseInfo *pInfo, const char *base, CFIndex length) {
if (length == 0) return (CFStringRef)CFRetain(CFSTR(""));
CFStringRef result = NULL;
uint32_t payload = 0;
Boolean uniqued = CFBurstTrieContainsUTF8String(pInfo->stringTrie, (UInt8 *)base, length, &payload);
if (uniqued && payload > 0) {
// The index is at payload - 1 (see below).
result = (CFStringRef)CFArrayGetValueAtIndex(pInfo->stringCache, (CFIndex)payload - 1);
CFRetain(result);
} else {
result = CFStringCreateWithBytes(pInfo->allocator, (const UInt8 *)base, length, kCFStringEncodingUTF8, NO);
if (!result) return NULL;
// Payload must be >0, so the actual index of the value is at payload - 1
// We also get add to the array after we make sure that CFBurstTrieAddUTF8String succeeds (it can fail, if the string is too large, for example)
payload = CFArrayGetCount(pInfo->stringCache) + 1;
Boolean didAddToBurstTrie = CFBurstTrieAddUTF8String(pInfo->stringTrie, (UInt8 *)base, length, payload);
if (didAddToBurstTrie) {
CFArrayAppendValue(pInfo->stringCache, result);
}
}
return result;
}
// String could be comprised of characters, CDSects, or references to one of the "well-known" entities ('<', '>', '&', ''', '"')
static Boolean parseStringTag(_CFXMLPlistParseInfo *pInfo, CFStringRef *out) {
const char *mark = pInfo->curr;
CFMutableDataRef stringData = NULL;
while (!pInfo->error && pInfo->curr < pInfo->end) {
char 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
if (!stringData) stringData = CFDataCreateMutable(pInfo->allocator, 0);
CFDataAppendBytes(stringData, (const UInt8 *)mark, pInfo->curr - mark);
parseCDSect_pl(pInfo, stringData); // TODO: move to return boolean
mark = pInfo->curr;
} else if (ch == '&') {
if (!stringData) stringData = CFDataCreateMutable(pInfo->allocator, 0);
CFDataAppendBytes(stringData, (const UInt8 *)mark, pInfo->curr - mark);
parseEntityReference_pl(pInfo, stringData); // TODO: move to return boolean
mark = pInfo->curr;
} else {
pInfo->curr ++;
}
}
if (pInfo->error) {
__CFPListRelease(stringData, pInfo->allocator);
return false;
}
if (!stringData) {
if (pInfo->skip) {
*out = NULL;
} else {
if (pInfo->mutabilityOption != kCFPropertyListMutableContainersAndLeaves) {
CFStringRef s = _createUniqueStringWithUTF8Bytes(pInfo, mark, pInfo->curr - mark);
if (!s) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unable to convert string to correct encoding"));
return false;
}
*out = s;
} else {
CFStringRef s = CFStringCreateWithBytes(pInfo->allocator, (const UInt8 *)mark, pInfo->curr - mark, kCFStringEncodingUTF8, NO);
if (!s) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unable to convert string to correct encoding"));
return false;
}
*out = CFStringCreateMutableCopy(pInfo->allocator, 0, s);
__CFPListRelease(s, pInfo->allocator);
}
}
return true;
} else {
if (pInfo->skip) {
*out = NULL;
} else {
CFDataAppendBytes(stringData, (const UInt8 *)mark, pInfo->curr - mark);
if (pInfo->mutabilityOption != kCFPropertyListMutableContainersAndLeaves) {
CFStringRef s = _createUniqueStringWithUTF8Bytes(pInfo, (const char *)CFDataGetBytePtr(stringData), CFDataGetLength(stringData));
if (!s) {
CFRelease(stringData);
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unable to convert string to correct encoding"));
return false;
}
*out = s;
} else {
CFStringRef s = CFStringCreateWithBytes(pInfo->allocator, (const UInt8 *)CFDataGetBytePtr(stringData), CFDataGetLength(stringData), kCFStringEncodingUTF8, NO);
if (!s) {
CFRelease(stringData);
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unable to convert string to correct encoding"));
return false;
}
*out = CFStringCreateMutableCopy(pInfo->allocator, 0, s);
__CFPListRelease(s, pInfo->allocator);
}
}
__CFPListRelease(stringData, pInfo->allocator);
return true;
}
}
static Boolean checkForCloseTag(_CFXMLPlistParseInfo *pInfo, const char *tag, CFIndex tagLen) {
if (pInfo->end - pInfo->curr < tagLen + 3) {
if (!pInfo->error) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF"));
}
return false;
}
if (*(pInfo->curr) != '<' || *(++pInfo->curr) != '/') {
if (!pInfo->error) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected character %c on line %d while looking for close tag"), *(pInfo->curr), lineNumber(pInfo));
}
return false;
}
pInfo->curr ++;
if (memcmp(pInfo->curr, tag, tagLen)) {
CFStringRef str = CFStringCreateWithBytes(kCFAllocatorSystemDefault, (const UInt8 *)tag, tagLen, kCFStringEncodingUTF8, NO);
if (!pInfo->error) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, 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) {
if (!pInfo->error) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected EOF"));
}
return false;
}
if (*(pInfo->curr) != '>') {
if (!pInfo->error) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected character %c on line %d while looking for close tag"), *(pInfo->curr), lineNumber(pInfo));
}
return false;
}
pInfo->curr ++;
return true;
}
// pInfo should be set to the first content character of the <plist>
static Boolean parsePListTag(_CFXMLPlistParseInfo *pInfo, CFTypeRef *out) {
CFTypeRef result = NULL;
if (!getContentObject(pInfo, NULL, &result)) {
if (!pInfo->error) pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered empty plist tag"));
return false;
}
const char *save = pInfo->curr; // Save this in case the next step fails
CFTypeRef tmp = NULL;
if (getContentObject(pInfo, NULL, &tmp)) {
// Got an extra object
__CFPListRelease(tmp, pInfo->allocator);
__CFPListRelease(result, pInfo->allocator);
pInfo->curr = save;
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered unexpected element at line %d (plist can only include one object)"), lineNumber(pInfo));
return false;
}
if (pInfo->error) {
// Parse failed catastrophically
__CFPListRelease(result, pInfo->allocator);
return false;
}
if (!checkForCloseTag(pInfo, CFXMLPlistTags[PLIST_IX], PLIST_TAG_LENGTH)) {
__CFPListRelease(result, pInfo->allocator);
return false;
}
*out = result;
return true;
}
static int allowImmutableCollections = -1;
static void checkImmutableCollections(void) {
allowImmutableCollections = (NULL == __CFgetenv("CFPropertyListAllowImmutableCollections")) ? 0 : 1;
}
// This converts the input value, a set of strings, into the form that's efficient for using during recursive decent parsing, a set of arrays
static CFSetRef createTopLevelKeypaths(CFAllocatorRef allocator, CFSetRef keyPaths) {
if (!keyPaths) return NULL;
CFIndex count = CFSetGetCount(keyPaths);
new_cftype_array(keyPathValues, count);
CFSetGetValues(keyPaths, keyPathValues);
CFMutableSetRef splitKeyPathSet = CFSetCreateMutable(allocator, count, &kCFTypeSetCallBacks);
for (CFIndex i = 0; i < count; i++) {
// Split each key path and add it to the split path set, which we will reference throughout parsing
CFArrayRef split = CFStringCreateArrayBySeparatingStrings(allocator, (CFStringRef)(keyPathValues[i]), CFSTR(":"));
CFSetAddValue(splitKeyPathSet, split);
__CFPListRelease(split, allocator);
}
free_cftype_array(keyPathValues);
return splitKeyPathSet;
}
// This splits up the keypaths into the ones relevant for this level (of array or dictionary), and the ones for the next level (of array or dictionary)
CF_PRIVATE void __CFPropertyListCreateSplitKeypaths(CFAllocatorRef allocator, CFSetRef currentKeys, CFSetRef *theseKeys, CFSetRef *nextKeys) {
if (!currentKeys) { *theseKeys = NULL; *nextKeys = NULL; return; }
CFIndex count = CFSetGetCount(currentKeys);
// For each array in the current key path set, grab the item at the start of the list and put it into theseKeys. The rest of the array goes into nextKeys.
CFMutableSetRef outTheseKeys = NULL;
CFMutableSetRef outNextKeys = NULL;
new_cftype_array(currentKeyPaths, count);
CFSetGetValues(currentKeys, currentKeyPaths);
for (CFIndex i = 0; i < count; i++) {
CFArrayRef oneKeyPath = (CFArrayRef)currentKeyPaths[i];
CFIndex keyPathCount = CFArrayGetCount(oneKeyPath);
if (keyPathCount > 0) {
if (!outTheseKeys) outTheseKeys = CFSetCreateMutable(allocator, 0, &kCFTypeSetCallBacks);
CFSetAddValue(outTheseKeys, CFArrayGetValueAtIndex(oneKeyPath, 0));
}
if (keyPathCount > 1) {
if (!outNextKeys) outNextKeys = CFSetCreateMutable(allocator, 0, &kCFTypeSetCallBacks);
// Create an array with values from 1 - end of list
new_cftype_array(restOfKeys, keyPathCount - 1);
CFArrayGetValues(oneKeyPath, CFRangeMake(1, CFArrayGetCount(oneKeyPath) - 1), restOfKeys);
CFArrayRef newNextKeys = CFArrayCreate(allocator, restOfKeys, CFArrayGetCount(oneKeyPath) - 1, &kCFTypeArrayCallBacks);
CFSetAddValue(outNextKeys, newNextKeys);
__CFPListRelease(newNextKeys, allocator);
free_cftype_array(restOfKeys);
}
}
*theseKeys = outTheseKeys;
*nextKeys = outNextKeys;
}
static Boolean parseArrayTag(_CFXMLPlistParseInfo *pInfo, CFTypeRef *out) {
CFTypeRef tmp = NULL;
if (pInfo->skip) {
Boolean result = getContentObject(pInfo, NULL, &tmp);
while (result) {
if (tmp) {
// Shouldn't happen (if skipping, all content values should be null), but just in case
__CFPListRelease(tmp, pInfo->allocator);
}
result = getContentObject(pInfo, NULL, &tmp);
}
if (pInfo->error) {
// getContentObject encountered a parse error
return false;
}
if (!checkForCloseTag(pInfo, CFXMLPlistTags[ARRAY_IX], ARRAY_TAG_LENGTH)) {
return false;
} else {
*out = NULL;
return true;
}
}
CFMutableArrayRef array = CFArrayCreateMutable(pInfo->allocator, 0, &kCFTypeArrayCallBacks);
Boolean result;
CFIndex count = 0;
CFSetRef oldKeyPaths = pInfo->keyPaths;
CFSetRef newKeyPaths, keys;
__CFPropertyListCreateSplitKeypaths(pInfo->allocator, pInfo->keyPaths, &keys, &newKeyPaths);
if (keys) {
CFStringRef countString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("%ld"), count);
if (!CFSetContainsValue(keys, countString)) pInfo->skip = true;
__CFPListRelease(countString, pInfo->allocator);
count++;
pInfo->keyPaths = newKeyPaths;
}
result = getContentObject(pInfo, NULL, &tmp);
if (keys) {
pInfo->keyPaths = oldKeyPaths;
pInfo->skip = false;
}
while (result) {
if (tmp) {
CFArrayAppendValue(array, tmp);
__CFPListRelease(tmp, pInfo->allocator);
}
if (keys) {
// prep for getting next object
CFStringRef countString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("%ld"), count);
if (!CFSetContainsValue(keys, countString)) pInfo->skip = true;
__CFPListRelease(countString, pInfo->allocator);
count++;
pInfo->keyPaths = newKeyPaths;
}
result = getContentObject(pInfo, NULL, &tmp);
if (keys) {
// reset after getting object
pInfo->keyPaths = oldKeyPaths;
pInfo->skip = false;
}
}
__CFPListRelease(newKeyPaths, pInfo->allocator);
__CFPListRelease(keys, pInfo->allocator);
if (pInfo->error) { // getContentObject encountered a parse error
__CFPListRelease(array, pInfo->allocator);
return false;
}
if (!checkForCloseTag(pInfo, CFXMLPlistTags[ARRAY_IX], ARRAY_TAG_LENGTH)) {
__CFPListRelease(array, pInfo->allocator);
return false;
}
if (-1 == allowImmutableCollections) {
checkImmutableCollections();
}
if (1 == allowImmutableCollections) {
if (pInfo->mutabilityOption == kCFPropertyListImmutable) {
CFArrayRef newArray = CFArrayCreateCopy(pInfo->allocator, array);
__CFPListRelease(array, pInfo->allocator);
array = (CFMutableArrayRef)newArray;
}
}
*out = array;
return true;
}
static Boolean parseDictTag(_CFXMLPlistParseInfo *pInfo, CFTypeRef *out) {
Boolean gotKey;
Boolean result;
CFTypeRef key = NULL, value = NULL;
if (pInfo->skip) {
result = getContentObject(pInfo, &gotKey, &key);
while (result) {
if (!gotKey) {
if (!pInfo->error) pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Found non-key inside <dict> at line %d"), lineNumber(pInfo));
return false;
}
result = getContentObject(pInfo, NULL, &value);
if (!result) {
if (!pInfo->error) pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Value missing for key inside <dict> at line %d"), lineNumber(pInfo));
return false;
}
// key and value should be null, but we'll release just in case here
__CFPListRelease(key, pInfo->allocator);
key = NULL;
__CFPListRelease(value, pInfo->allocator);
value = NULL;
result = getContentObject(pInfo, &gotKey, &key);
}
if (checkForCloseTag(pInfo, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH)) {
*out = NULL;
return true;
} else {
return false;
}
}
CFSetRef oldKeyPaths = pInfo->keyPaths;
CFSetRef nextKeyPaths, theseKeyPaths;
__CFPropertyListCreateSplitKeypaths(pInfo->allocator, pInfo->keyPaths, &theseKeyPaths, &nextKeyPaths);
CFMutableDictionaryRef dict = NULL;
result = getContentObject(pInfo, &gotKey, &key);
while (result && key) {
if (!gotKey) {
if (!pInfo->error) pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Found non-key inside <dict> at line %d"), lineNumber(pInfo));
__CFPListRelease(key, pInfo->allocator);
__CFPListRelease(nextKeyPaths, pInfo->allocator);
__CFPListRelease(theseKeyPaths, pInfo->allocator);
__CFPListRelease(dict, pInfo->allocator);
return false;
}
if (theseKeyPaths) {
if (!CFSetContainsValue(theseKeyPaths, key)) pInfo->skip = true;
pInfo->keyPaths = nextKeyPaths;
}
result = getContentObject(pInfo, NULL, &value);
if (theseKeyPaths) {
pInfo->keyPaths = oldKeyPaths;
pInfo->skip = false;
}
if (!result) {
if (!pInfo->error) pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Value missing for key inside <dict> at line %d"), lineNumber(pInfo));
__CFPListRelease(key, pInfo->allocator);
__CFPListRelease(nextKeyPaths, pInfo->allocator);
__CFPListRelease(theseKeyPaths, pInfo->allocator);
__CFPListRelease(dict, pInfo->allocator);
return false;
}
if (key && value) {
if (NULL == dict) {
dict = CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
_CFDictionarySetCapacity(dict, 10);
}
CFDictionarySetValue(dict, key, value);
}
__CFPListRelease(key, pInfo->allocator);
key = NULL;
__CFPListRelease(value, pInfo->allocator);
value = NULL;
result = getContentObject(pInfo, &gotKey, &key);
}
__CFPListRelease(nextKeyPaths, pInfo->allocator);
__CFPListRelease(theseKeyPaths, pInfo->allocator);
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 (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);
__CFPListRelease(dict, pInfo->allocator);
*out = uid;
return true;
}
}
if (-1 == allowImmutableCollections) checkImmutableCollections();
if (1 == allowImmutableCollections) {
if (pInfo->mutabilityOption == kCFPropertyListImmutable) {
CFDictionaryRef newDict = CFDictionaryCreateCopy(pInfo->allocator, dict);
__CFPListRelease(dict, pInfo->allocator);
dict = (CFMutableDictionaryRef)newDict;
}
}
}
*out = dict;
return true;
}
if (dict) __CFPListRelease(dict, pInfo->allocator);
return false;
}
static Boolean parseDataTag(_CFXMLPlistParseInfo *pInfo, CFTypeRef *out) {
const char *base = pInfo->curr;
static const signed char dataDecodeTable[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
};
int tmpbufpos = 0;
int tmpbuflen = 256;
uint8_t *tmpbuf = pInfo->skip ? NULL : (uint8_t *)CFAllocatorAllocate(pInfo->allocator, tmpbuflen, 0);
int numeq = 0;
int acc = 0;
int cntr = 0;
for (; pInfo->curr < pInfo->end; pInfo->curr++) {
signed char c = *(pInfo->curr);
if (c == '<') {
break;
}
if ('=' == c) {
numeq++;
} else if (!isspace(c)) {
numeq = 0;
}
if (dataDecodeTable[c] < 0)
continue;
cntr++;
acc <<= 6;
acc += dataDecodeTable[c];
if (!pInfo->skip && 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; // out of memory
}
tmpbuf[tmpbufpos++] = (acc >> 16) & 0xff;
if (numeq < 2) tmpbuf[tmpbufpos++] = (acc >> 8) & 0xff;
if (numeq < 1) tmpbuf[tmpbufpos++] = acc & 0xff;
}
}
CFDataRef result = NULL;
if (!pInfo->skip) {
if (pInfo->mutabilityOption == kCFPropertyListMutableContainersAndLeaves) {
result = (CFDataRef)CFDataCreateMutable(pInfo->allocator, 0);
CFDataAppendBytes((CFMutableDataRef)result, tmpbuf, tmpbufpos);
CFAllocatorDeallocate(pInfo->allocator, tmpbuf);
} else {
result = CFDataCreateWithBytesNoCopy(pInfo->allocator, tmpbuf, tmpbufpos, pInfo->allocator);
}
if (!result) {
pInfo->curr = base;
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Could not interpret <data> at line %d (should be base64-encoded)"), lineNumber(pInfo));
return false;
}
}
if (checkForCloseTag(pInfo, CFXMLPlistTags[DATA_IX], DATA_TAG_LENGTH)) {
*out = result;
return true;
} else {
__CFPListRelease(result, pInfo->allocator);
return false;
}
}
CF_INLINE Boolean read2DigitNumber(_CFXMLPlistParseInfo *pInfo, int32_t *result) {
char 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 Boolean parseDateTag(_CFXMLPlistParseInfo *pInfo, CFTypeRef *out) {
int32_t year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
int32_t num = 0;
Boolean badForm = false;
Boolean yearIsNegative = false;
if (pInfo->curr < pInfo->end && *pInfo->curr == '-') {
yearIsNegative = true;
pInfo->curr++;
}
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 || !checkForCloseTag(pInfo, CFXMLPlistTags[DATE_IX], DATE_TAG_LENGTH)) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Could not interpret <date> at line %d"), lineNumber(pInfo));
return false;
}
CFAbsoluteTime at = 0.0;
#if 0
{ // alternative to CFGregorianDateGetAbsoluteTime() below; also, cheaper than CFCalendar would be;
// clearly not thread-safe with that environment variable having to be set;
// timegm() could be used instead of mktime(), on platforms which have it
struct tm mine;
mine.tm_year = (yearIsNegative ? -year : year) - 1900;
mine.tm_mon = month - 1;
mine.tm_mday = day;
mine.tm_hour = hour;
mine.tm_min = minute;
mine.tm_sec = second;
char *tz = getenv("TZ");
setenv("TZ", "", 1);
tzset();
at = mktime(tm) - kCFAbsoluteTimeIntervalSince1970;
if (tz) {
setenv("TZ", tz, 1);
} else {
unsetenv("TZ");
}
tzset();
}
#endif
// See <rdar://problem/5052483> Revisit the CFGregorianDate -> CFCalendar change in CFPropertyList.c
// for why we can't use CFCalendar
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated"
CFGregorianDate date = {yearIsNegative ? -year : year, month, day, hour, minute, second};
at = CFGregorianDateGetAbsoluteTime(date, NULL);
#pragma GCC diagnostic pop
if (pInfo->skip) {
*out = NULL;
} else {
*out = CFDateCreate(pInfo->allocator, at);
}
return true;
}
static Boolean parseRealTag(_CFXMLPlistParseInfo *pInfo, CFTypeRef *out) {
CFStringRef str = NULL;
if (!parseStringTag(pInfo, &str)) {
if (!pInfo->error) pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered empty <real> on line %d"), lineNumber(pInfo));
return false;
}
CFNumberRef result = NULL;
if (!pInfo->skip) {
if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("nan"), kCFCompareCaseInsensitive)) result = kCFNumberNaN;
else if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("+infinity"), kCFCompareCaseInsensitive)) result = kCFNumberPositiveInfinity;
else if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("-infinity"), kCFCompareCaseInsensitive)) result = kCFNumberNegativeInfinity;
else if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("infinity"), kCFCompareCaseInsensitive)) result = kCFNumberPositiveInfinity;
else if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("-inf"), kCFCompareCaseInsensitive)) result = kCFNumberNegativeInfinity;
else if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("inf"), kCFCompareCaseInsensitive)) result = kCFNumberPositiveInfinity;
else if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("+inf"), kCFCompareCaseInsensitive)) result = kCFNumberPositiveInfinity;
if (result) {
CFRetain(result);
} else {
CFIndex len = CFStringGetLength(str);
CFStringInlineBuffer buf;
CFStringInitInlineBuffer(str, &buf, CFRangeMake(0, len));
SInt32 idx = 0;
double val;
if (!__CFStringScanDouble(&buf, NULL, &idx, &val) || idx != len) {
__CFPListRelease(str, pInfo->allocator);
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered misformatted real on line %d"), lineNumber(pInfo));
return false;
}
result = CFNumberCreate(pInfo->allocator, kCFNumberDoubleType, &val);
}
}
__CFPListRelease(str, pInfo->allocator);
if (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) {
*out = result;
return true;
} else {
__CFPListRelease(result, pInfo->allocator);
return false;
}
}
#define GET_CH if (pInfo->curr == pInfo->end) { \
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Premature end of file after <integer> on line %d"), lineNumber(pInfo)); \
return false; \
} \
ch = *(pInfo->curr)
typedef struct {
int64_t high;
uint64_t low;
} CFSInt128Struct;
enum {
kCFNumberSInt128Type = 17
};
CF_INLINE bool isWhitespace(const char *utf8bytes, const char *end) {
// Converted UTF-16 isWhitespace from CFString to UTF8 bytes to get full list of UTF8 whitespace
/*
0020 -> <20>
0009 -> <09>
00a0 -> <c2a0>
1680 -> <e19a80>
2000 -> <e28080>
2001 -> <e28081>
2002 -> <e28082>
2003 -> <e28083>
2004 -> <e28084>
2005 -> <e28085>
2006 -> <e28086>
2007 -> <e28087>
2008 -> <e28088>
2009 -> <e28089>
200a -> <e2808a>
200b -> <e2808b>
202f -> <e280af>
205f -> <e2819f>
3000 -> <e38080>
*/
// Except we consider some additional values from 0x0 to 0x21 and 0x7E to 0xA1 as whitespace, for compatability
unsigned char byte1 = *utf8bytes;
if (byte1 < 0x21 || (byte1 > 0x7E && byte1 < 0xA1)) return true;
if ((byte1 == 0xe2 || byte1 == 0xe3) && (end - utf8bytes >= 3)) {
// Check other possibilities in the 3-bytes range
unsigned char byte2 = *(utf8bytes + 1);
unsigned char byte3 = *(utf8bytes + 2);
if (byte1 == 0xe2 && byte2 == 0x80) {
return ((byte3 >= 80 && byte3 <= 0x8b) || byte3 == 0xaf);
} else if (byte1 == 0xe2 && byte2 == 0x81) {
return byte3 == 0x9f;
} else if (byte1 == 0xe3 && byte2 == 0x80 && byte3 == 0x80) {
return true;
}
}
return false;
}
static Boolean parseIntegerTag(_CFXMLPlistParseInfo *pInfo, CFTypeRef *out) {
bool isHex = false, isNeg = false, hadLeadingZero = false;
char 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 && isWhitespace(pInfo->curr, pInfo->end)) pInfo->curr++;
GET_CH;
if ('<' == ch) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered empty <integer> on line %d"), lineNumber(pInfo));
return false;
}
if ('-' == ch || '+' == ch) {
isNeg = ('-' == ch);
pInfo->curr++;
while (pInfo->curr < pInfo->end && isWhitespace(pInfo->curr, pInfo->end)) 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 false;
}
if (pInfo->skip) {
*out = NULL;
} else {
*out = CFNumberCreate(pInfo->allocator, kCFNumberSInt32Type, &val);
}
return true;
}
if ('<' == ch) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Incomplete <integer> on line %d"), lineNumber(pInfo));
return false;
}
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->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unknown character '%c' (0x%x) in <integer> on line %d"), ch, ch, lineNumber(pInfo));
return false;
}
if (!isHex && new_digit > 9) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Hex digit in non-hex <integer> on line %d"), lineNumber(pInfo));
return false;
}
if (UINT64_MAX / multiplier < value) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Integer overflow in <integer> on line %d"), lineNumber(pInfo));
return false;
}
value = multiplier * value;
if (UINT64_MAX - new_digit < value) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Integer overflow in <integer> on line %d"), lineNumber(pInfo));
return false;
}
value = value + new_digit;
if (isNeg && (uint64_t)INT64_MAX + 1 < value) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Integer underflow in <integer> on line %d"), lineNumber(pInfo));
return false;
}
pInfo->curr++;
GET_CH;
}
if (!checkForCloseTag(pInfo, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH)) {
// checkForCloseTag() sets error string
return false;
}
if (pInfo->skip) {
*out = NULL;
} else {
if (isNeg || value <= INT64_MAX) {
int64_t v = value;
if (isNeg) v = -v; // no-op if INT64_MIN
*out = CFNumberCreate(pInfo->allocator, kCFNumberSInt64Type, &v);
} else {
CFSInt128Struct val;
val.high = 0;
val.low = value;
*out = CFNumberCreate(pInfo->allocator, kCFNumberSInt128Type, &val);
}
}
return true;
}
#undef GET_CH
// Returned object is retained; caller must free. pInfo->curr expected to point to the first character after the '<'
static Boolean parseXMLElement(_CFXMLPlistParseInfo *pInfo, Boolean *isKey, CFTypeRef *out) {
const char *marker = pInfo->curr;
int markerLength = -1;
Boolean isEmpty;
int markerIx = -1;
if (isKey) *isKey = false;
while (pInfo->curr < pInfo->end) {
char 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 false;
}
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->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Malformed tag on line %d"), lineNumber(pInfo));
return false;
}
switch (*marker) {
case 'a': // Array
if (markerLength == ARRAY_TAG_LENGTH && !memcmp(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 (!memcmp(marker, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH))
markerIx = DICT_IX;
else if (!memcmp(marker, CFXMLPlistTags[DATA_IX], DATA_TAG_LENGTH))
markerIx = DATA_IX;
else if (!memcmp(marker, CFXMLPlistTags[DATE_IX], DATE_TAG_LENGTH))
markerIx = DATE_IX;
break;
case 'f': // false (boolean)
if (markerLength == FALSE_TAG_LENGTH && !memcmp(marker, CFXMLPlistTags[FALSE_IX], FALSE_TAG_LENGTH)) {
markerIx = FALSE_IX;
}
break;
case 'i': // integer
if (markerLength == INTEGER_TAG_LENGTH && !memcmp(marker, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH))
markerIx = INTEGER_IX;
break;
case 'k': // Key of a dictionary
if (markerLength == KEY_TAG_LENGTH && !memcmp(marker, CFXMLPlistTags[KEY_IX], KEY_TAG_LENGTH)) {
markerIx = KEY_IX;
if (isKey) *isKey = true;
}
break;
case 'p': // Plist
if (markerLength == PLIST_TAG_LENGTH && !memcmp(marker, CFXMLPlistTags[PLIST_IX], PLIST_TAG_LENGTH))
markerIx = PLIST_IX;
break;
case 'r': // real
if (markerLength == REAL_TAG_LENGTH && !memcmp(marker, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH))
markerIx = REAL_IX;
break;
case 's': // String
if (markerLength == STRING_TAG_LENGTH && !memcmp(marker, CFXMLPlistTags[STRING_IX], STRING_TAG_LENGTH))
markerIx = STRING_IX;
break;
case 't': // true (boolean)
if (markerLength == TRUE_TAG_LENGTH && !memcmp(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->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered new tag when expecting only old-style property list objects"));
return false;
}
switch (markerIx) {
case PLIST_IX:
if (isEmpty) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered empty plist tag"));
return false;
}
return parsePListTag(pInfo, out);
case ARRAY_IX:
if (isEmpty) {
if (pInfo->skip) {
*out = NULL;
} else {
if (pInfo->mutabilityOption == kCFPropertyListImmutable) {
*out = CFArrayCreate(pInfo->allocator, NULL, 0, &kCFTypeArrayCallBacks);
} else {
*out = CFArrayCreateMutable(pInfo->allocator, 0, &kCFTypeArrayCallBacks);
}
}
return true;
} else {
return parseArrayTag(pInfo, out);
}
case DICT_IX:
if (isEmpty) {
if (pInfo->skip) {
*out = NULL;
} else {
if (pInfo->mutabilityOption == kCFPropertyListImmutable) {
*out = CFDictionaryCreate(pInfo->allocator, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
} else {
*out = CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}
}
return true;
} else {
return parseDictTag(pInfo, out);
}
case KEY_IX:
case STRING_IX:
{
int tagLen = (markerIx == KEY_IX) ? KEY_TAG_LENGTH : STRING_TAG_LENGTH;
if (isEmpty) {
if (pInfo->skip) {
*out = NULL;
} else {
if (pInfo->mutabilityOption == kCFPropertyListMutableContainersAndLeaves) {
*out = CFStringCreateMutable(pInfo->allocator, 0);
} else {
*out = CFStringCreateWithCharacters(pInfo->allocator, NULL, 0);
}
}
return true;
}
if (!parseStringTag(pInfo, (CFStringRef *)out)) {
return false; // parseStringTag will already have set the error string
}
if (!checkForCloseTag(pInfo, CFXMLPlistTags[markerIx], tagLen)) {
__CFPListRelease(*out, pInfo->allocator);
return false;
} else {
return true;
}
}
case DATA_IX:
if (isEmpty) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered empty <data> on line %d"), lineNumber(pInfo));
return false;
} else {
return parseDataTag(pInfo, out);
}
case DATE_IX:
if (isEmpty) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered empty <date> on line %d"), lineNumber(pInfo));
return false;
} else {
return parseDateTag(pInfo, out);
}
case TRUE_IX:
if (!isEmpty) {
if (!checkForCloseTag(pInfo, CFXMLPlistTags[TRUE_IX], TRUE_TAG_LENGTH)) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered non-empty <true> on line %d"), lineNumber(pInfo));
return false;
}
}
if (pInfo->skip) {
*out = NULL;
} else {
*out = CFRetain(kCFBooleanTrue);
}
return true;
case FALSE_IX:
if (!isEmpty) {
if (!checkForCloseTag(pInfo, CFXMLPlistTags[FALSE_IX], FALSE_TAG_LENGTH)) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered non-empty <false> on line %d"), lineNumber(pInfo));
return false;
}
}
if (pInfo->skip) {
*out = NULL;
} else {
*out = CFRetain(kCFBooleanFalse);
}
return true;
case REAL_IX:
if (isEmpty) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered empty <real> on line %d"), lineNumber(pInfo));
return false;
} else {
return parseRealTag(pInfo, out);
}
case INTEGER_IX:
if (isEmpty) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Encountered empty <integer> on line %d"), lineNumber(pInfo));
return false;
} else {
return parseIntegerTag(pInfo, out);
}
default: {