blob: e782489cb195b0bb58739647f14c438b89707c91 [file] [log] [blame]
/* CFOldStylePList.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/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>
//
// Old NeXT-style property lists
//
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; // set of all strings involved in this parse; allows us to share non-mutable strings in the returned plist
} _CFStringsFileParseInfo;
// warning: doesn't have a good idea of Unicode line separators
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) CF_RETURNS_RETAINED;
#define isValidUnquotedStringCharacter(x) (((x) >= 'a' && (x) <= 'z') || ((x) >= 'A' && (x) <= 'Z') || ((x) >= '0' && (x) <= '9') || (x) == '_' || (x) == '$' || (x) == '/' || (x) == ':' || (x) == '.' || (x) == '-')
// Returns true if the advance found something before the end of the buffer, false otherwise
static Boolean advanceToNonSpace(_CFStringsFileParseInfo *pInfo) {
UniChar ch2;
while (pInfo->curr < pInfo->end) {
ch2 = *(pInfo->curr);
pInfo->curr ++;
if (ch2 >= 9 && ch2 <= 0x0d) continue; // tab, newline, vt, form feed, carriage return
if (ch2 == ' ' || ch2 == 0x2028 || ch2 == 0x2029) continue; // space and Unicode line sep, para sep
if (ch2 == '/') {
if (pInfo->curr >= pInfo->end) {
// whoops; back up and return
pInfo->curr --;
return true;
} else if (*(pInfo->curr) == '/') {
pInfo->curr ++;
while (pInfo->curr < pInfo->end) { // go to end of comment line
UniChar ch3 = *(pInfo->curr);
if (ch3 == '\n' || ch3 == '\r' || ch3 == 0x2028 || ch3 == 0x2029) break;
pInfo->curr ++;
}
} else if (*(pInfo->curr) == '*') { // handle /* ... */
pInfo->curr ++;
while (pInfo->curr < pInfo->end) {
ch2 = *(pInfo->curr);
pInfo->curr ++;
if (ch2 == '*' && pInfo->curr < pInfo->end && *(pInfo->curr) == '/') {
pInfo->curr ++; // advance past the '/'
break;
}
}
} else {
pInfo->curr --;
return 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;
/* three digits maximum to avoid reading \000 followed by 5 as \5 ! */
if ((ch = *(pInfo->curr)) >= '0' && ch <= '7') { // we use in this test the fact that the buffer is zero-terminated
pInfo->curr ++;
num = (num << 3) + ch - '0';
if ((pInfo->curr < pInfo->end) && (ch = *(pInfo->curr)) >= '0' && ch <= '7') {
pInfo->curr ++;
num = (num << 3) + ch - '0';
}
}
CFStringEncodingBytesToUnicode(kCFStringEncodingNextStepLatin, 0, &num, sizeof(uint8_t), NULL, &result, 1, &usedCharLen);
return (usedCharLen == 1) ? result : 0;
}
case 'U': {
unsigned num = 0, numDigits = 4; /* Parse four digits */
while (pInfo->curr < pInfo->end && numDigits--) {
if (((ch = *(pInfo->curr)) < 128) && isxdigit(ch)) {
pInfo->curr ++;
num = (num << 4) + ((ch <= '9') ? (ch - '0') : ((ch <= 'F') ? (ch - 'A' + 10) : (ch - 'a' + 10)));
}
}
return num;
}
case 'a': return '\a'; // Note: the meaning of '\a' varies with -traditional to gcc
case 'b': return '\b';
case 'f': return '\f';
case 'n': return '\n';
case 'r': return '\r';
case 't': return '\t';
case 'v': return '\v';
case '"': return '\"';
case '\n': return '\n';
}
return ch;
}
static CFStringRef _uniqueStringForCharacters(_CFStringsFileParseInfo *pInfo, const UniChar *base, CFIndex length) CF_RETURNS_RETAINED {
if (0 == length) return (CFStringRef)CFRetain(CFSTR(""));
// This is to avoid having to promote the buffers of all the strings compared against
// during the set probe; if a Unicode string is passed in, that's what happens.
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;
}
if (stringToUnique) CFRelease(stringToUnique);
if (uniqued) CFRetain(uniqued);
return uniqued;
}
static CFStringRef _uniqueStringForString(_CFStringsFileParseInfo *pInfo, CFStringRef stringToUnique) CF_RETURNS_RETAINED {
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) CFRetain(uniqued);
return uniqued;
}
static CFStringRef parseQuotedPlistString(_CFStringsFileParseInfo *pInfo, UniChar quote) CF_RETURNS_RETAINED {
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 {
// Note that the original NSParser code was much more complex at this point, but it had to deal with 8-bit characters in a non-UniChar stream. We always have UniChar (we translated the data by the system encoding at the very beginning, hopefully), so this is safe.
pInfo->curr ++;
}
}
if (pInfo->end <= pInfo->curr) {
if (str) CFRelease(str);
pInfo->curr = startMark;
pInfo->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);
if (str) CFRelease(str);
str = (CFMutableStringRef)uniqueString;
}
}
pInfo->curr ++; // Advance past the quote character before returning.
if (pInfo->error) {
CFRelease(pInfo->error);
pInfo->error = NULL;
}
return str;
}
static CFStringRef parseUnquotedPlistString(_CFStringsFileParseInfo *pInfo) CF_RETURNS_RETAINED {
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) CF_RETURNS_RETAINED {
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) CF_RETURNS_RETAINED {
CFMutableArrayRef array = CFArrayCreateMutable(pInfo->allocator, 0, &kCFTypeArrayCallBacks);
CFTypeRef tmp = parsePlistObject(pInfo, false);
Boolean foundChar;
while (tmp) {
CFArrayAppendValue(array, tmp);
if (tmp) CFRelease(tmp);
foundChar = advanceToNonSpace(pInfo);
if (!foundChar) {
if (array) CFRelease(array);
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 != ')') {
if (array) CFRelease(array);
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."), (unsigned int)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."), (unsigned int)line);
}
static CFDictionaryRef parsePlistDictContent(_CFStringsFileParseInfo *pInfo) CF_RETURNS_RETAINED {
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 == ';') {
/* This is a strings file using the shortcut format */
/* although this check here really applies to all plists. */
value = CFRetain(key);
} else if (*pInfo->curr == '=') {
pInfo->curr ++;
value = parsePlistObject(pInfo, true);
if (!value) {
failedParse = true;
break;
}
} else {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unexpected ';' or '=' after key at line %d"), lineNumberStrings(pInfo));
failedParse = true;
break;
}
CFDictionarySetValue(dict, key, value);
if (key) CFRelease(key);
key = NULL;
if (value) CFRelease(value);
value = NULL;
foundChar = advanceToNonSpace(pInfo);
if (foundChar && *pInfo->curr == ';') {
pInfo->curr ++;
key = parsePlistString(pInfo, false);
} else {
UInt32 line = lineNumberStrings(pInfo);
_CFPropertyListMissingSemicolon(line);
failedParse = true;
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Missing ';' on line %d"), line);
}
}
if (failedParse) {
if (key) CFRelease(key);
if (dict) CFRelease(dict);
return NULL;
}
if (pInfo->error) {
CFRelease(pInfo->error);
pInfo->error = NULL;
}
return dict;
}
static CFTypeRef parsePlistDict(_CFStringsFileParseInfo *pInfo) CF_RETURNS_RETAINED {
CFDictionaryRef dict = parsePlistDictContent(pInfo);
if (!dict) return NULL;
Boolean foundChar = advanceToNonSpace(pInfo);
if (!foundChar || *pInfo->curr != '}') {
if (dict) CFRelease(dict);
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; // Just choose a large number for the error code
}
/* Gets up to bytesSize bytes from a plist data. Returns number of bytes actually read. Leaves cursor at first non-space, non-hex character.
-1 is returned for unexpected char, -2 for uneven number of hex digits
*/
static int getDataBytes(_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; // Meaning we're done
first = fromHexDigit((unsigned char)ch1);
if (first != 0xff) { // If the first char is a hex, then try to read a second hex
pInfo->curr++;
if (pInfo->curr >= pInfo->end) return -2; // Error: uneven number of hex digits
UniChar ch2 = *pInfo->curr;
second = fromHexDigit((unsigned char)ch2);
if (second == 0xff) return -2; // Error: uneven number of hex digits
bytes[numBytesRead++] = (first << 4) + second;
pInfo->curr++;
} else if (ch1 == ' ' || ch1 == '\n' || ch1 == '\t' || ch1 == '\r' || ch1 == 0x2028 || ch1 == 0x2029) {
pInfo->curr++;
} else {
return -1; // Error: unexpected character
}
}
return numBytesRead; // This does likely mean we didn't encounter a '>', but we'll let the caller deal with that
}
#define numBytes 400
static CFTypeRef parsePlistData(_CFStringsFileParseInfo *pInfo) CF_RETURNS_RETAINED {
CFMutableDataRef result = CFDataCreateMutable(pInfo->allocator, 0);
// Read hex bytes and append them to result
while (1) {
unsigned char bytes[numBytes];
int numBytesRead = getDataBytes(pInfo, bytes, numBytes);
if (numBytesRead < 0) {
if (result) CFRelease(result);
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 ++; // Move past '>'
return result;
} else {
if (result) CFRelease(result);
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Expected terminating '>' for data at line %d"), lineNumberStrings(pInfo));
return NULL;
}
}
#undef numBytes
// Returned object is retained; caller must free.
static CFTypeRef parsePlistObject(_CFStringsFileParseInfo *pInfo, bool requireObject) CF_RETURNS_RETAINED {
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 --; // Must back off the charcter we just read
if (requireObject) {
pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unexpected character '0x%x' at line %d"), ch, lineNumberStrings(pInfo));
}
return NULL;
}
}
// CFAllocatorRef allocator, CFDataRef xmlData, CFStringRef originalString, CFStringEncoding guessedEncoding, CFOptionFlags option, CFErrorRef *outError, Boolean allowNewTypes, CFPropertyListFormat *format, CFSetRef keyPaths
CF_PRIVATE CFTypeRef __CFCreateOldStylePropertyListOrStringsFile(CFAllocatorRef allocator, CFDataRef xmlData, CFStringRef originalString, CFStringEncoding guessedEncoding, CFOptionFlags option, CFErrorRef *outError,CFPropertyListFormat *format) {
// Convert the string to UTF16 for parsing old-style
if (originalString) {
// Ensure that originalString is not collected while we are using it
CFRetain(originalString);
} else {
originalString = CFStringCreateWithBytes(kCFAllocatorSystemDefault, CFDataGetBytePtr(xmlData), CFDataGetLength(xmlData), guessedEncoding, NO);
if (!originalString) {
// Couldn't convert
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) {
// A file consisting only of whitespace (or empty) is now defined to be an empty dictionary
result = CFDictionaryCreateMutable(allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
} else {
result = parsePlistObject(&stringsPInfo, true);
if (result) {
foundChar = advanceToNonSpace(&stringsPInfo);
if (foundChar) {
if (CFGetTypeID(result) != CFStringGetTypeID()) {
if (result) CFRelease(result);
result = NULL;
if (stringsPInfo.error) CFRelease(stringsPInfo.error);
stringsPInfo.error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Junk after plist at line %d"), lineNumberStrings(&stringsPInfo));
} else {
// Reset info and keep parsing
if (result) CFRelease(result);
if (stringsPInfo.error) CFRelease(stringsPInfo.error);
stringsPInfo.error = NULL;
// Check for a strings file (looks like a dictionary without the opening/closing curly braces)
stringsPInfo.curr = begin;
result = parsePlistDictContent(&stringsPInfo);
}
}
}
}
if (!result) {
// Must return some kind of error if requested
if (outError) {
if (stringsPInfo.error) {
// Transfer ownership
*outError = stringsPInfo.error;
} else {
*outError = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unknown error parsing property list around line %d"), lineNumberStrings(&stringsPInfo));
}
} else if (stringsPInfo.error) {
// Caller doesn't want it, so we need to free it
CFRelease(stringsPInfo.error);
}
}
if (result && format) *format = kCFPropertyListOpenStepFormat;
if (createdBuffer) CFAllocatorDeallocate(allocator, buf);
CFRelease(stringsPInfo.stringSet);
CFRelease(originalString);
return result;
}
#undef isValidUnquotedStringCharacter