1/*
2 * Copyright (c) 2014 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24/*	CFOldStylePList.c
25	Copyright (c) 1999-2013, Apple Inc. All rights reserved.
26	Responsibility: Tony Parker
27*/
28
29#include <CoreFoundation/CFPropertyList.h>
30#include <CoreFoundation/CFDate.h>
31#include <CoreFoundation/CFNumber.h>
32#include <CoreFoundation/CFError.h>
33#include <CoreFoundation/CFStringEncodingConverter.h>
34#include "CFInternal.h"
35#include <CoreFoundation/CFCalendar.h>
36#include <CoreFoundation/CFSet.h>
37
38#include <ctype.h>
39
40//
41// Old NeXT-style property lists
42//
43
44CF_INLINE void __CFPListRelease(CFTypeRef cf, CFAllocatorRef allocator) {
45    if (cf && !(0)) CFRelease(cf);
46}
47
48CF_PRIVATE CFErrorRef __CFPropertyListCreateError(CFIndex code, CFStringRef debugString, ...);
49
50typedef struct {
51    const UniChar *begin;
52    const UniChar *curr;
53    const UniChar *end;
54    CFErrorRef error;
55    CFAllocatorRef allocator;
56    UInt32 mutabilityOption;
57    CFMutableSetRef stringSet;  // set of all strings involved in this parse; allows us to share non-mutable strings in the returned plist
58} _CFStringsFileParseInfo;
59
60// warning: doesn't have a good idea of Unicode line separators
61static UInt32 lineNumberStrings(_CFStringsFileParseInfo *pInfo) {
62    const UniChar *p = pInfo->begin;
63    UInt32 count = 1;
64    while (p < pInfo->curr) {
65        if (*p == '\r') {
66            count ++;
67            if (*(p + 1) == '\n')
68                p ++;
69        } else if (*p == '\n') {
70            count ++;
71        }
72        p ++;
73    }
74    return count;
75}
76
77static CFTypeRef parsePlistObject(_CFStringsFileParseInfo *pInfo, bool requireObject);
78
79#define isValidUnquotedStringCharacter(x) (((x) >= 'a' && (x) <= 'z') || ((x) >= 'A' && (x) <= 'Z') || ((x) >= '0' && (x) <= '9') || (x) == '_' || (x) == '$' || (x) == '/' || (x) == ':' || (x) == '.' || (x) == '-')
80
81// Returns true if the advance found something before the end of the buffer, false otherwise
82static Boolean advanceToNonSpace(_CFStringsFileParseInfo *pInfo) {
83    UniChar ch2;
84    while (pInfo->curr < pInfo->end) {
85	ch2 = *(pInfo->curr);
86        pInfo->curr ++;
87        if (ch2 >= 9 && ch2 <= 0x0d) continue;	// tab, newline, vt, form feed, carriage return
88        if (ch2 == ' ' || ch2 == 0x2028 || ch2 == 0x2029) continue;	// space and Unicode line sep, para sep
89	if (ch2 == '/') {
90            if (pInfo->curr >= pInfo->end) {
91                // whoops; back up and return
92                pInfo->curr --;
93                return true;
94            } else if (*(pInfo->curr) == '/') {
95                pInfo->curr ++;
96                while (pInfo->curr < pInfo->end) {	// go to end of comment line
97                    UniChar ch3 = *(pInfo->curr);
98                    if (ch3 == '\n' || ch3 == '\r' || ch3 == 0x2028 || ch3 == 0x2029) break;
99                    pInfo->curr ++;
100		}
101	    } else if (*(pInfo->curr) == '*') {		// handle /* ... */
102                pInfo->curr ++;
103		while (pInfo->curr < pInfo->end) {
104		    ch2 = *(pInfo->curr);
105                    pInfo->curr ++;
106		    if (ch2 == '*' && pInfo->curr < pInfo->end && *(pInfo->curr) == '/') {
107                        pInfo->curr ++; // advance past the '/'
108                        break;
109                    }
110                }
111            } else {
112                pInfo->curr --;
113                return true;
114	    }
115        } else {
116            pInfo->curr --;
117            return true;
118        }
119    }
120    return false;
121}
122
123static UniChar getSlashedChar(_CFStringsFileParseInfo *pInfo) {
124    UniChar ch = *(pInfo->curr);
125    pInfo->curr ++;
126    switch (ch) {
127	case '0':
128	case '1':
129	case '2':
130	case '3':
131	case '4':
132	case '5':
133	case '6':
134	case '7':  {
135            uint8_t num = ch - '0';
136            UniChar result;
137            CFIndex usedCharLen;
138	    /* three digits maximum to avoid reading \000 followed by 5 as \5 ! */
139	    if ((ch = *(pInfo->curr)) >= '0' && ch <= '7') { // we use in this test the fact that the buffer is zero-terminated
140                pInfo->curr ++;
141		num = (num << 3) + ch - '0';
142		if ((pInfo->curr < pInfo->end) && (ch = *(pInfo->curr)) >= '0' && ch <= '7') {
143                    pInfo->curr ++;
144		    num = (num << 3) + ch - '0';
145		}
146	    }
147            CFStringEncodingBytesToUnicode(kCFStringEncodingNextStepLatin, 0, &num, sizeof(uint8_t), NULL,  &result, 1, &usedCharLen);
148            return (usedCharLen == 1) ? result : 0;
149	}
150	case 'U': {
151	    unsigned num = 0, numDigits = 4;	/* Parse four digits */
152	    while (pInfo->curr < pInfo->end && numDigits--) {
153                if (((ch = *(pInfo->curr)) < 128) && isxdigit(ch)) {
154                    pInfo->curr ++;
155		    num = (num << 4) + ((ch <= '9') ? (ch - '0') : ((ch <= 'F') ? (ch - 'A' + 10) : (ch - 'a' + 10)));
156		}
157	    }
158	    return num;
159	}
160	case 'a':	return '\a';	// Note: the meaning of '\a' varies with -traditional to gcc
161	case 'b':	return '\b';
162	case 'f':	return '\f';
163	case 'n':	return '\n';
164	case 'r':	return '\r';
165	case 't':	return '\t';
166	case 'v':	return '\v';
167	case '"':	return '\"';
168	case '\n':	return '\n';
169    }
170    return ch;
171}
172
173static CFStringRef _uniqueStringForCharacters(_CFStringsFileParseInfo *pInfo, const UniChar *base, CFIndex length) {
174    if (0 == length) return !(0) ? (CFStringRef)CFRetain(CFSTR("")) : CFSTR("");
175    // This is to avoid having to promote the buffers of all the strings compared against
176    // during the set probe; if a Unicode string is passed in, that's what happens.
177    CFStringRef stringToUnique = NULL;
178    Boolean use_stack = (length < 2048);
179    STACK_BUFFER_DECL(uint8_t, buffer, use_stack ? length + 1 : 1);
180    uint8_t *ascii = use_stack ? buffer : (uint8_t *)CFAllocatorAllocate(kCFAllocatorSystemDefault, length + 1, 0);
181    for (CFIndex idx = 0; idx < length; idx++) {
182        UniChar ch = base[idx];
183	if (ch < 0x80) {
184	    ascii[idx] = (uint8_t)ch;
185        } else {
186	    stringToUnique = CFStringCreateWithCharacters(pInfo->allocator, base, length);
187	    break;
188	}
189    }
190    if (!stringToUnique) {
191        ascii[length] = '\0';
192        stringToUnique = CFStringCreateWithBytes(pInfo->allocator, ascii, length, kCFStringEncodingASCII, false);
193    }
194    if (ascii != buffer) CFAllocatorDeallocate(kCFAllocatorSystemDefault, ascii);
195    CFStringRef uniqued = (CFStringRef)CFSetGetValue(pInfo->stringSet, stringToUnique);
196    if (!uniqued) {
197        CFSetAddValue(pInfo->stringSet, stringToUnique);
198	uniqued = stringToUnique;
199    }
200    __CFPListRelease(stringToUnique, pInfo->allocator);
201    if (uniqued && !(0)) CFRetain(uniqued);
202    return uniqued;
203}
204
205static CFStringRef _uniqueStringForString(_CFStringsFileParseInfo *pInfo, CFStringRef stringToUnique) {
206    CFStringRef uniqued = (CFStringRef)CFSetGetValue(pInfo->stringSet, stringToUnique);
207    if (!uniqued) {
208        uniqued = (CFStringRef)__CFStringCollectionCopy(pInfo->allocator, stringToUnique);
209        CFSetAddValue(pInfo->stringSet, uniqued);
210        __CFTypeCollectionRelease(pInfo->allocator, uniqued);
211    }
212    if (uniqued && !(0)) CFRetain(uniqued);
213    return uniqued;
214}
215
216static CFStringRef parseQuotedPlistString(_CFStringsFileParseInfo *pInfo, UniChar quote) {
217    CFMutableStringRef str = NULL;
218    const UniChar *startMark = pInfo->curr;
219    const UniChar *mark = pInfo->curr;
220    while (pInfo->curr < pInfo->end) {
221	UniChar ch = *(pInfo->curr);
222        if (ch == quote) break;
223        if (ch == '\\') {
224            if (!str) str = CFStringCreateMutable(pInfo->allocator, 0);
225            CFStringAppendCharacters(str, mark, pInfo->curr - mark);
226            pInfo->curr ++;
227            ch = getSlashedChar(pInfo);
228            CFStringAppendCharacters(str, &ch, 1);
229            mark = pInfo->curr;
230	} else {
231            // 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.
232            pInfo->curr ++;
233        }
234    }
235    if (pInfo->end <= pInfo->curr) {
236        __CFPListRelease(str, pInfo->allocator);
237        pInfo->curr = startMark;
238        pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unterminated quoted string starting on line %d"), lineNumberStrings(pInfo));
239        return NULL;
240    }
241    if (!str) {
242        if (pInfo->mutabilityOption == kCFPropertyListMutableContainersAndLeaves) {
243            str = CFStringCreateMutable(pInfo->allocator, 0);
244            CFStringAppendCharacters(str, mark, pInfo->curr - mark);
245        } else {
246            str = (CFMutableStringRef)_uniqueStringForCharacters(pInfo, mark, pInfo->curr-mark);
247        }
248    } else {
249        if (mark != pInfo->curr) {
250            CFStringAppendCharacters(str, mark, pInfo->curr - mark);
251        }
252        if (pInfo->mutabilityOption != kCFPropertyListMutableContainersAndLeaves) {
253            CFStringRef uniqueString = _uniqueStringForString(pInfo, str);
254            __CFPListRelease(str, pInfo->allocator);
255            str = (CFMutableStringRef)uniqueString;
256        }
257    }
258    pInfo->curr ++;  // Advance past the quote character before returning.
259    if (pInfo->error) {
260        CFRelease(pInfo->error);
261        pInfo->error = NULL;
262    }
263    return str;
264}
265
266static CFStringRef parseUnquotedPlistString(_CFStringsFileParseInfo *pInfo) {
267    const UniChar *mark = pInfo->curr;
268    while (pInfo->curr < pInfo->end) {
269        UniChar ch = *pInfo->curr;
270        if (isValidUnquotedStringCharacter(ch))
271            pInfo->curr ++;
272        else break;
273    }
274    if (pInfo->curr != mark) {
275        if (pInfo->mutabilityOption != kCFPropertyListMutableContainersAndLeaves) {
276            CFStringRef str = _uniqueStringForCharacters(pInfo, mark, pInfo->curr-mark);
277            return str;
278        } else {
279            CFMutableStringRef str = CFStringCreateMutable(pInfo->allocator, 0);
280            CFStringAppendCharacters(str, mark, pInfo->curr - mark);
281            return str;
282        }
283    }
284    pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unexpected EOF"));
285    return NULL;
286}
287
288static CFStringRef parsePlistString(_CFStringsFileParseInfo *pInfo, bool requireObject) {
289    UniChar ch;
290    Boolean foundChar = advanceToNonSpace(pInfo);
291    if (!foundChar) {
292        if (requireObject) {
293            pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unexpected EOF while parsing string"));
294        }
295        return NULL;
296    }
297    ch = *(pInfo->curr);
298    if (ch == '\'' || ch == '\"') {
299        pInfo->curr ++;
300        return parseQuotedPlistString(pInfo, ch);
301    } else if (isValidUnquotedStringCharacter(ch)) {
302        return parseUnquotedPlistString(pInfo);
303    } else {
304        if (requireObject) {
305            pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Invalid string character at line %d"), lineNumberStrings(pInfo));
306	}
307        return NULL;
308    }
309}
310
311static CFTypeRef parsePlistArray(_CFStringsFileParseInfo *pInfo) {
312    CFMutableArrayRef array = CFArrayCreateMutable(pInfo->allocator, 0, &kCFTypeArrayCallBacks);
313    CFTypeRef tmp = parsePlistObject(pInfo, false);
314    Boolean foundChar;
315    while (tmp) {
316        CFArrayAppendValue(array, tmp);
317        __CFPListRelease(tmp, pInfo->allocator);
318        foundChar = advanceToNonSpace(pInfo);
319	if (!foundChar) {
320	    __CFPListRelease(array, pInfo->allocator);
321	    pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Expected ',' for array at line %d"), lineNumberStrings(pInfo));
322	    return NULL;
323	}
324        if (*pInfo->curr != ',') {
325            tmp = NULL;
326        } else {
327            pInfo->curr ++;
328            tmp = parsePlistObject(pInfo, false);
329        }
330    }
331    foundChar = advanceToNonSpace(pInfo);
332    if (!foundChar || *pInfo->curr != ')') {
333        __CFPListRelease(array, pInfo->allocator);
334        pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Expected terminating ')' for array at line %d"), lineNumberStrings(pInfo));
335        return NULL;
336    }
337    if (pInfo->error) {
338        CFRelease(pInfo->error);
339        pInfo->error = NULL;
340    }
341    pInfo->curr ++;
342    return array;
343}
344
345__attribute__((noinline)) void _CFPropertyListMissingSemicolon(UInt32 line) {
346    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);
347}
348
349__attribute__((noinline)) void _CFPropertyListMissingSemicolonOrValue(UInt32 line) {
350    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);
351}
352
353static CFDictionaryRef parsePlistDictContent(_CFStringsFileParseInfo *pInfo) {
354    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
355    CFStringRef key = NULL;
356    Boolean failedParse = false;
357    key = parsePlistString(pInfo, false);
358    while (key) {
359        CFTypeRef value;
360        Boolean foundChar = advanceToNonSpace(pInfo);
361        if (!foundChar) {
362            UInt32 line = lineNumberStrings(pInfo);
363            _CFPropertyListMissingSemicolonOrValue(line);
364            failedParse = true;
365            pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Missing ';' on line %d"), line);
366            break;
367        }
368
369	if (*pInfo->curr == ';') {
370	    /* This is a strings file using the shortcut format */
371	    /* although this check here really applies to all plists. */
372	    value = CFRetain(key);
373	} else if (*pInfo->curr == '=') {
374	    pInfo->curr ++;
375	    value = parsePlistObject(pInfo, true);
376	    if (!value) {
377		failedParse = true;
378		break;
379	    }
380	} else {
381            pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unexpected ';' or '=' after key at line %d"), lineNumberStrings(pInfo));
382	    failedParse = true;
383	    break;
384	}
385	CFDictionarySetValue(dict, key, value);
386	__CFPListRelease(key, pInfo->allocator);
387	key = NULL;
388	__CFPListRelease(value, pInfo->allocator);
389	value = NULL;
390	foundChar = advanceToNonSpace(pInfo);
391	if (foundChar && *pInfo->curr == ';') {
392	    pInfo->curr ++;
393	    key = parsePlistString(pInfo, false);
394	} else if (true || !foundChar) {
395            UInt32 line = lineNumberStrings(pInfo);
396            _CFPropertyListMissingSemicolon(line);
397	    failedParse = true;
398	    pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Missing ';' on line %d"), line);
399	}
400    }
401
402    if (failedParse) {
403        __CFPListRelease(key, pInfo->allocator);
404        __CFPListRelease(dict, pInfo->allocator);
405        return NULL;
406    }
407    if (pInfo->error) {
408        CFRelease(pInfo->error);
409        pInfo->error = NULL;
410    }
411    return dict;
412}
413
414static CFTypeRef parsePlistDict(_CFStringsFileParseInfo *pInfo) {
415    CFDictionaryRef dict = parsePlistDictContent(pInfo);
416    if (!dict) return NULL;
417    Boolean foundChar = advanceToNonSpace(pInfo);
418    if (!foundChar || *pInfo->curr != '}') {
419        __CFPListRelease(dict, pInfo->allocator);
420        pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Expected terminating '}' for dictionary at line %d"), lineNumberStrings(pInfo));
421        return NULL;
422    }
423    pInfo->curr ++;
424    return dict;
425}
426
427CF_INLINE unsigned char fromHexDigit(unsigned char ch) {
428    if (isdigit(ch)) return ch - '0';
429    if ((ch >= 'a') && (ch <= 'f')) return ch - 'a' + 10;
430    if ((ch >= 'A') && (ch <= 'F')) return ch - 'A' + 10;
431    return 0xff; // Just choose a large number for the error code
432}
433
434/* Gets up to bytesSize bytes from a plist data. Returns number of bytes actually read. Leaves cursor at first non-space, non-hex character.
435 -1 is returned for unexpected char, -2 for uneven number of hex digits
436 */
437static int getDataBytes(_CFStringsFileParseInfo *pInfo, unsigned char *bytes, int bytesSize) {
438    int numBytesRead = 0;
439    while ((pInfo->curr < pInfo->end) && (numBytesRead < bytesSize)) {
440	int first, second;
441	UniChar ch1 = *pInfo->curr;
442	if (ch1 == '>') return numBytesRead;  // Meaning we're done
443	first = fromHexDigit((unsigned char)ch1);
444	if (first != 0xff) {	// If the first char is a hex, then try to read a second hex
445	    pInfo->curr++;
446	    if (pInfo->curr >= pInfo->end) return -2;   // Error: uneven number of hex digits
447	    UniChar ch2 = *pInfo->curr;
448	    second = fromHexDigit((unsigned char)ch2);
449	    if (second == 0xff) return -2;  // Error: uneven number of hex digits
450	    bytes[numBytesRead++] = (first << 4) + second;
451	    pInfo->curr++;
452	} else if (ch1 == ' ' || ch1 == '\n' || ch1 == '\t' || ch1 == '\r' || ch1 == 0x2028 || ch1 == 0x2029) {
453	    pInfo->curr++;
454	} else {
455	    return -1;  // Error: unexpected character
456	}
457    }
458    return numBytesRead;    // This does likely mean we didn't encounter a '>', but we'll let the caller deal with that
459}
460
461#define numBytes 400
462static CFTypeRef parsePlistData(_CFStringsFileParseInfo *pInfo) {
463    CFMutableDataRef result = CFDataCreateMutable(pInfo->allocator, 0);
464
465    // Read hex bytes and append them to result
466    while (1) {
467	unsigned char bytes[numBytes];
468	int numBytesRead = getDataBytes(pInfo, bytes, numBytes);
469	if (numBytesRead < 0) {
470	    __CFPListRelease(result, pInfo->allocator);
471            switch (numBytesRead) {
472                case -2:
473                    pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Malformed data byte group at line %d; uneven length"), lineNumberStrings(pInfo));
474                    break;
475                default:
476                    pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Malformed data byte group at line %d; invalid hex"), lineNumberStrings(pInfo));
477                    break;
478            }
479	    return NULL;
480	}
481	if (numBytesRead == 0) break;
482	CFDataAppendBytes(result, bytes, numBytesRead);
483    }
484
485    if (pInfo->error) {
486        CFRelease(pInfo->error);
487        pInfo->error = NULL;
488    }
489
490    if (*(pInfo->curr) == '>') {
491        pInfo->curr ++; // Move past '>'
492        return result;
493    } else {
494        __CFPListRelease(result, pInfo->allocator);
495        pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Expected terminating '>' for data at line %d"), lineNumberStrings(pInfo));
496        return NULL;
497    }
498}
499#undef numBytes
500
501// Returned object is retained; caller must free.
502static CFTypeRef parsePlistObject(_CFStringsFileParseInfo *pInfo, bool requireObject) {
503    UniChar ch;
504    Boolean foundChar = advanceToNonSpace(pInfo);
505    if (!foundChar) {
506        if (requireObject) {
507            pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unexpected EOF while parsing plist"));
508        }
509        return NULL;
510    }
511    ch = *(pInfo->curr);
512    pInfo->curr ++;
513    if (ch == '{') {
514        return parsePlistDict(pInfo);
515    } else if (ch == '(') {
516        return parsePlistArray(pInfo);
517    } else if (ch == '<') {
518        return parsePlistData(pInfo);
519    } else if (ch == '\'' || ch == '\"') {
520        return parseQuotedPlistString(pInfo, ch);
521    } else if (isValidUnquotedStringCharacter(ch)) {
522        pInfo->curr --;
523        return parseUnquotedPlistString(pInfo);
524    } else {
525        pInfo->curr --;  // Must back off the charcter we just read
526        if (requireObject) {
527            pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unexpected character '0x%x' at line %d"), ch, lineNumberStrings(pInfo));
528        }
529        return NULL;
530    }
531}
532
533// CFAllocatorRef allocator, CFDataRef xmlData, CFStringRef originalString, CFStringEncoding guessedEncoding, CFOptionFlags option, CFErrorRef *outError, Boolean allowNewTypes, CFPropertyListFormat *format, CFSetRef keyPaths
534
535CF_PRIVATE CFTypeRef __CFCreateOldStylePropertyListOrStringsFile(CFAllocatorRef allocator, CFDataRef xmlData, CFStringRef originalString, CFStringEncoding guessedEncoding, CFOptionFlags option, CFErrorRef *outError,CFPropertyListFormat *format) {
536
537    // Convert the string to UTF16 for parsing old-style
538    if (originalString) {
539        // Ensure that originalString is not collected while we are using it
540        CFRetain(originalString);
541    } else {
542        originalString = CFStringCreateWithBytes(kCFAllocatorSystemDefault, CFDataGetBytePtr(xmlData), CFDataGetLength(xmlData), guessedEncoding, NO);
543        if (!originalString) {
544            // Couldn't convert
545            if (outError) *outError = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Conversion of string failed."));
546            return NULL;
547        }
548    }
549
550    UInt32 length;
551    Boolean createdBuffer = false;
552    length = CFStringGetLength(originalString);
553    if (!length) {
554        if (outError) *outError = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Conversion of string failed. The string is empty."));
555        return NULL;
556    }
557
558    UniChar *buf = (UniChar *)CFStringGetCharactersPtr(originalString);
559    if (!buf) {
560        buf = (UniChar *)CFAllocatorAllocate(allocator, length * sizeof(UniChar), 0);
561        if (!buf) {
562            CRSetCrashLogMessage("CFPropertyList ran out of memory while attempting to allocate temporary storage.");
563            return NULL;
564        }
565        CFStringGetCharacters(originalString, CFRangeMake(0, length), buf);
566        createdBuffer = true;
567    }
568
569    _CFStringsFileParseInfo stringsPInfo;
570    stringsPInfo.begin = buf;
571    stringsPInfo.end = buf+length;
572    stringsPInfo.curr = buf;
573    stringsPInfo.allocator = allocator;
574    stringsPInfo.mutabilityOption = option;
575    stringsPInfo.stringSet = CFSetCreateMutable(allocator, 0, &kCFTypeSetCallBacks);
576    stringsPInfo.error = NULL;
577
578    const UniChar *begin = stringsPInfo.curr;
579    CFTypeRef result = NULL;
580    Boolean foundChar = advanceToNonSpace(&stringsPInfo);
581    if (!foundChar) {
582        // A file consisting only of whitespace (or empty) is now defined to be an empty dictionary
583        result = CFDictionaryCreateMutable(allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
584    } else {
585        result = parsePlistObject(&stringsPInfo, true);
586        if (result) {
587            foundChar = advanceToNonSpace(&stringsPInfo);
588            if (foundChar) {
589                if (CFGetTypeID(result) != CFStringGetTypeID()) {
590                    __CFPListRelease(result, allocator);
591                    result = NULL;
592                    if (stringsPInfo.error) CFRelease(stringsPInfo.error);
593                    stringsPInfo.error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Junk after plist at line %d"), lineNumberStrings(&stringsPInfo));
594                } else {
595                    // Reset info and keep parsing
596                    __CFPListRelease(result, allocator);
597                    if (stringsPInfo.error) CFRelease(stringsPInfo.error);
598                    stringsPInfo.error = NULL;
599
600                    // Check for a strings file (looks like a dictionary without the opening/closing curly braces)
601                    stringsPInfo.curr = begin;
602                    result = parsePlistDictContent(&stringsPInfo);
603                }
604            }
605        }
606    }
607
608    if (!result) {
609        // Must return some kind of error if requested
610        if (outError) {
611            if (stringsPInfo.error) {
612                // Transfer ownership
613                *outError = stringsPInfo.error;
614            } else {
615                *outError = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unknown error parsing property list around line %d"), lineNumberStrings(&stringsPInfo));
616            }
617        } else if (stringsPInfo.error) {
618            // Caller doesn't want it, so we need to free it
619            CFRelease(stringsPInfo.error);
620        }
621    }
622
623    if (result && format) *format = kCFPropertyListOpenStepFormat;
624
625    if (createdBuffer && !(0)) CFAllocatorDeallocate(allocator, buf);
626    CFRelease(stringsPInfo.stringSet);
627    CFRelease(originalString);
628    return result;
629}
630
631#undef isValidUnquotedStringCharacter
632