#include <CoreFoundation/CFPropertyList.h>
#include <CoreFoundation/CFDate.h>
#include <CoreFoundation/CFNumber.h>
#include <CoreFoundation/CFError.h>
#include <CoreFoundation/CFStringEncodingConverter.h>
#include "CFInternal.h"
#include <CoreFoundation/CFCalendar.h>
#include <CoreFoundation/CFSet.h>
#include <ctype.h>
CF_INLINE void __CFPListRelease(CFTypeRef cf, CFAllocatorRef allocator) {
if (cf && !(0)) CFRelease(cf);
}
CF_PRIVATE CFErrorRef __CFPropertyListCreateError(CFIndex code, CFStringRef debugString, ...);
typedef struct {
const UniChar *begin;
const UniChar *curr;
const UniChar *end;
CFErrorRef error;
CFAllocatorRef allocator;
UInt32 mutabilityOption;
CFMutableSetRef stringSet; } _CFStringsFileParseInfo;
static UInt32 lineNumberStrings(_CFStringsFileParseInfo *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;
}
static CFTypeRef parsePlistObject(_CFStringsFileParseInfo *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 Boolean advanceToNonSpace(_CFStringsFileParseInfo *pInfo) {
UniChar ch2;
while (pInfo->curr < pInfo->end) {
ch2 = *(pInfo->curr);
pInfo->curr ++;
if (ch2 >= 9 && ch2 <= 0x0d) continue; if (ch2 == ' ' || ch2 == 0x2028 || ch2 == 0x2029) continue; if (ch2 == '/') {
if (pInfo->curr >= pInfo->end) {
pInfo->curr --;
return true;
} else if (*(pInfo->curr) == '/') {
pInfo->curr ++;
while (pInfo->curr < pInfo->end) { UniChar ch3 = *(pInfo->curr);
if (ch3 == '\n' || ch3 == '\r' || ch3 == 0x2028 || ch3 == 0x2029) break;
pInfo->curr ++;
}
} else if (*(pInfo->curr) == '*') { pInfo->curr ++;
while (pInfo->curr < pInfo->end) {
ch2 = *(pInfo->curr);
pInfo->curr ++;
if (ch2 == '*' && pInfo->curr < pInfo->end && *(pInfo->curr) == '/') {
pInfo->curr ++; break;
}
}
} else {
pInfo->curr --;
return true;
}
} else {
pInfo->curr --;
return true;
}
}
return false;
}
static UniChar getSlashedChar(_CFStringsFileParseInfo *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;
if ((ch = *(pInfo->curr)) >= '0' && ch <= '7') { 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;
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'; 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 _uniqueStringForCharacters(_CFStringsFileParseInfo *pInfo, const UniChar *base, CFIndex length) {
if (0 == length) return !(0) ? (CFStringRef)CFRetain(CFSTR("")) : CFSTR("");
CFStringRef stringToUnique = NULL;
Boolean use_stack = (length < 2048);
STACK_BUFFER_DECL(uint8_t, buffer, use_stack ? length + 1 : 1);
uint8_t *ascii = use_stack ? buffer : (uint8_t *)CFAllocatorAllocate(kCFAllocatorSystemDefault, length + 1, 0);
for (CFIndex idx = 0; idx < length; idx++) {
UniChar ch = base[idx];
if (ch < 0x80) {
ascii[idx] = (uint8_t)ch;
} else {
stringToUnique = CFStringCreateWithCharacters(pInfo->allocator, base, length);
break;
}
}
if (!stringToUnique) {
ascii[length] = '\0';
stringToUnique = CFStringCreateWithBytes(pInfo->allocator, ascii, length, kCFStringEncodingASCII, false);
}
if (ascii != buffer) CFAllocatorDeallocate(kCFAllocatorSystemDefault, ascii);
CFStringRef uniqued = (CFStringRef)CFSetGetValue(pInfo->stringSet, stringToUnique);
if (!uniqued) {
CFSetAddValue(pInfo->stringSet, stringToUnique);
uniqued = stringToUnique;
}
__CFPListRelease(stringToUnique, pInfo->allocator);
if (uniqued && !(0)) CFRetain(uniqued);
return uniqued;
}
static CFStringRef _uniqueStringForString(_CFStringsFileParseInfo *pInfo, CFStringRef stringToUnique) {
CFStringRef uniqued = (CFStringRef)CFSetGetValue(pInfo->stringSet, stringToUnique);
if (!uniqued) {
uniqued = (CFStringRef)__CFStringCollectionCopy(pInfo->allocator, stringToUnique);
CFSetAddValue(pInfo->stringSet, uniqued);
__CFTypeCollectionRelease(pInfo->allocator, uniqued);
}
if (uniqued && !(0)) CFRetain(uniqued);
return uniqued;
}
static CFStringRef parseQuotedPlistString(_CFStringsFileParseInfo *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 == '\\') {
if (!str) str = CFStringCreateMutable(pInfo->allocator, 0);
CFStringAppendCharacters(str, mark, pInfo->curr - mark);
pInfo->curr ++;
ch = getSlashedChar(pInfo);
CFStringAppendCharacters(str, &ch, 1);
mark = pInfo->curr;
} else {
pInfo->curr ++;
}
}
if (pInfo->end <= pInfo->curr) {
__CFPListRelease(str, pInfo->allocator);
pInfo->curr = startMark;
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unterminated quoted string starting on line %d"), lineNumberStrings(pInfo));
return NULL;
}
if (!str) {
if (pInfo->mutabilityOption == kCFPropertyListMutableContainersAndLeaves) {
str = CFStringCreateMutable(pInfo->allocator, 0);
CFStringAppendCharacters(str, mark, pInfo->curr - mark);
} else {
str = (CFMutableStringRef)_uniqueStringForCharacters(pInfo, mark, pInfo->curr-mark);
}
} else {
if (mark != pInfo->curr) {
CFStringAppendCharacters(str, mark, pInfo->curr - mark);
}
if (pInfo->mutabilityOption != kCFPropertyListMutableContainersAndLeaves) {
CFStringRef uniqueString = _uniqueStringForString(pInfo, str);
__CFPListRelease(str, pInfo->allocator);
str = (CFMutableStringRef)uniqueString;
}
}
pInfo->curr ++; if (pInfo->error) {
CFRelease(pInfo->error);
pInfo->error = NULL;
}
return str;
}
static CFStringRef parseUnquotedPlistString(_CFStringsFileParseInfo *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);
return str;
} else {
CFMutableStringRef str = CFStringCreateMutable(pInfo->allocator, 0);
CFStringAppendCharacters(str, mark, pInfo->curr - mark);
return str;
}
}
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unexpected EOF"));
return NULL;
}
static CFStringRef parsePlistString(_CFStringsFileParseInfo *pInfo, bool requireObject) {
UniChar ch;
Boolean foundChar = advanceToNonSpace(pInfo);
if (!foundChar) {
if (requireObject) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, 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->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Invalid string character at line %d"), lineNumberStrings(pInfo));
}
return NULL;
}
}
static CFTypeRef parsePlistArray(_CFStringsFileParseInfo *pInfo) {
CFMutableArrayRef array = CFArrayCreateMutable(pInfo->allocator, 0, &kCFTypeArrayCallBacks);
CFTypeRef tmp = parsePlistObject(pInfo, false);
Boolean foundChar;
while (tmp) {
CFArrayAppendValue(array, tmp);
__CFPListRelease(tmp, pInfo->allocator);
foundChar = advanceToNonSpace(pInfo);
if (!foundChar) {
__CFPListRelease(array, pInfo->allocator);
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Expected ',' for array at line %d"), lineNumberStrings(pInfo));
return NULL;
}
if (*pInfo->curr != ',') {
tmp = NULL;
} else {
pInfo->curr ++;
tmp = parsePlistObject(pInfo, false);
}
}
foundChar = advanceToNonSpace(pInfo);
if (!foundChar || *pInfo->curr != ')') {
__CFPListRelease(array, pInfo->allocator);
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Expected terminating ')' for array at line %d"), lineNumberStrings(pInfo));
return NULL;
}
if (pInfo->error) {
CFRelease(pInfo->error);
pInfo->error = NULL;
}
pInfo->curr ++;
return array;
}
__attribute__((noinline)) void _CFPropertyListMissingSemicolon(UInt32 line) {
CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListCreateFromXMLData(): Old-style plist parser: missing semicolon in dictionary on line %d. Parsing will be abandoned. Break on _CFPropertyListMissingSemicolon to debug."), line);
}
__attribute__((noinline)) void _CFPropertyListMissingSemicolonOrValue(UInt32 line) {
CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListCreateFromXMLData(): Old-style plist parser: missing semicolon or value in dictionary on line %d. Parsing will be abandoned. Break on _CFPropertyListMissingSemicolonOrValue to debug."), line);
}
static CFDictionaryRef parsePlistDictContent(_CFStringsFileParseInfo *pInfo) {
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFStringRef key = NULL;
Boolean failedParse = false;
key = parsePlistString(pInfo, false);
while (key) {
CFTypeRef value;
Boolean foundChar = advanceToNonSpace(pInfo);
if (!foundChar) {
UInt32 line = lineNumberStrings(pInfo);
_CFPropertyListMissingSemicolonOrValue(line);
failedParse = true;
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Missing ';' on line %d"), line);
break;
}
if (*pInfo->curr == ';') {
value = CFRetain(key);
} else if (*pInfo->curr == '=') {
pInfo->curr ++;
value = parsePlistObject(pInfo, true);
if (!value) {
failedParse = true;
break;
}
} else {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unexpected ';' or '=' after key at line %d"), lineNumberStrings(pInfo));
failedParse = true;
break;
}
CFDictionarySetValue(dict, key, value);
__CFPListRelease(key, pInfo->allocator);
key = NULL;
__CFPListRelease(value, pInfo->allocator);
value = NULL;
foundChar = advanceToNonSpace(pInfo);
if (foundChar && *pInfo->curr == ';') {
pInfo->curr ++;
key = parsePlistString(pInfo, false);
} else if (true || !foundChar) {
UInt32 line = lineNumberStrings(pInfo);
_CFPropertyListMissingSemicolon(line);
failedParse = true;
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Missing ';' on line %d"), line);
}
}
if (failedParse) {
__CFPListRelease(key, pInfo->allocator);
__CFPListRelease(dict, pInfo->allocator);
return NULL;
}
if (pInfo->error) {
CFRelease(pInfo->error);
pInfo->error = NULL;
}
return dict;
}
static CFTypeRef parsePlistDict(_CFStringsFileParseInfo *pInfo) {
CFDictionaryRef dict = parsePlistDictContent(pInfo);
if (!dict) return NULL;
Boolean foundChar = advanceToNonSpace(pInfo);
if (!foundChar || *pInfo->curr != '}') {
__CFPListRelease(dict, pInfo->allocator);
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Expected terminating '}' for dictionary at line %d"), lineNumberStrings(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; }
static int getDataBytes(_CFStringsFileParseInfo *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; first = fromHexDigit((unsigned char)ch1);
if (first != 0xff) { pInfo->curr++;
if (pInfo->curr >= pInfo->end) return -2; UniChar ch2 = *pInfo->curr;
second = fromHexDigit((unsigned char)ch2);
if (second == 0xff) return -2; 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; }
}
return numBytesRead; }
#define numBytes 400
static CFTypeRef parsePlistData(_CFStringsFileParseInfo *pInfo) {
CFMutableDataRef result = CFDataCreateMutable(pInfo->allocator, 0);
while (1) {
unsigned char bytes[numBytes];
int numBytesRead = getDataBytes(pInfo, bytes, numBytes);
if (numBytesRead < 0) {
__CFPListRelease(result, pInfo->allocator);
switch (numBytesRead) {
case -2:
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Malformed data byte group at line %d; uneven length"), lineNumberStrings(pInfo));
break;
default:
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Malformed data byte group at line %d; invalid hex"), lineNumberStrings(pInfo));
break;
}
return NULL;
}
if (numBytesRead == 0) break;
CFDataAppendBytes(result, bytes, numBytesRead);
}
if (pInfo->error) {
CFRelease(pInfo->error);
pInfo->error = NULL;
}
if (*(pInfo->curr) == '>') {
pInfo->curr ++; return result;
} else {
__CFPListRelease(result, pInfo->allocator);
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Expected terminating '>' for data at line %d"), lineNumberStrings(pInfo));
return NULL;
}
}
#undef numBytes
static CFTypeRef parsePlistObject(_CFStringsFileParseInfo *pInfo, bool requireObject) {
UniChar ch;
Boolean foundChar = advanceToNonSpace(pInfo);
if (!foundChar) {
if (requireObject) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, 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 --; if (requireObject) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unexpected character '0x%x' at line %d"), ch, lineNumberStrings(pInfo));
}
return NULL;
}
}
CF_PRIVATE CFTypeRef __CFCreateOldStylePropertyListOrStringsFile(CFAllocatorRef allocator, CFDataRef xmlData, CFStringRef originalString, CFStringEncoding guessedEncoding, CFOptionFlags option, CFErrorRef *outError,CFPropertyListFormat *format) {
if (originalString) {
CFRetain(originalString);
} else {
originalString = CFStringCreateWithBytes(kCFAllocatorSystemDefault, CFDataGetBytePtr(xmlData), CFDataGetLength(xmlData), guessedEncoding, NO);
if (!originalString) {
if (outError) *outError = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Conversion of string failed."));
return NULL;
}
}
UInt32 length;
Boolean createdBuffer = false;
length = CFStringGetLength(originalString);
if (!length) {
if (outError) *outError = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Conversion of string failed. The string is empty."));
return NULL;
}
UniChar *buf = (UniChar *)CFStringGetCharactersPtr(originalString);
if (!buf) {
buf = (UniChar *)CFAllocatorAllocate(allocator, length * sizeof(UniChar), 0);
if (!buf) {
CRSetCrashLogMessage("CFPropertyList ran out of memory while attempting to allocate temporary storage.");
return NULL;
}
CFStringGetCharacters(originalString, CFRangeMake(0, length), buf);
createdBuffer = true;
}
_CFStringsFileParseInfo stringsPInfo;
stringsPInfo.begin = buf;
stringsPInfo.end = buf+length;
stringsPInfo.curr = buf;
stringsPInfo.allocator = allocator;
stringsPInfo.mutabilityOption = option;
stringsPInfo.stringSet = CFSetCreateMutable(allocator, 0, &kCFTypeSetCallBacks);
stringsPInfo.error = NULL;
const UniChar *begin = stringsPInfo.curr;
CFTypeRef result = NULL;
Boolean foundChar = advanceToNonSpace(&stringsPInfo);
if (!foundChar) {
result = CFDictionaryCreateMutable(allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
} else {
result = parsePlistObject(&stringsPInfo, true);
if (result) {
foundChar = advanceToNonSpace(&stringsPInfo);
if (foundChar) {
if (CFGetTypeID(result) != CFStringGetTypeID()) {
__CFPListRelease(result, allocator);
result = NULL;
if (stringsPInfo.error) CFRelease(stringsPInfo.error);
stringsPInfo.error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Junk after plist at line %d"), lineNumberStrings(&stringsPInfo));
} else {
__CFPListRelease(result, allocator);
if (stringsPInfo.error) CFRelease(stringsPInfo.error);
stringsPInfo.error = NULL;
stringsPInfo.curr = begin;
result = parsePlistDictContent(&stringsPInfo);
}
}
}
}
if (!result) {
if (outError) {
if (stringsPInfo.error) {
*outError = stringsPInfo.error;
} else {
*outError = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unknown error parsing property list around line %d"), lineNumberStrings(&stringsPInfo));
}
} else if (stringsPInfo.error) {
CFRelease(stringsPInfo.error);
}
}
if (result && format) *format = kCFPropertyListOpenStepFormat;
if (createdBuffer && !(0)) CFAllocatorDeallocate(allocator, buf);
CFRelease(stringsPInfo.stringSet);
CFRelease(originalString);
return result;
}
#undef isValidUnquotedStringCharacter