2 * Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 Copyright 1999-2002, Apple, Inc. All rights reserved.
25 Responsibility: Christopher Kane
28 #include <CoreFoundation/CFPropertyList.h>
29 #include <CoreFoundation/CFDate.h>
30 #include <CoreFoundation/CFNumber.h>
31 #include <CoreFoundation/CFSet.h>
32 #include "CFUtilitiesPriv.h"
33 #include "CFStringEncodingConverter.h"
34 #include "CFInternal.h"
35 #include <CoreFoundation/CFStream.h>
37 #include <CoreFoundation/CFPreferences.h>
47 __private_extern__
bool allowMissingSemi
= false;
49 // Should move this somewhere else
50 intptr_t _CFDoOperation(intptr_t code
, intptr_t subcode1
, intptr_t subcode2
) {
52 case 15317: allowMissingSemi
= subcode1
? true : false; break;
71 #define PLIST_TAG_LENGTH 5
72 #define ARRAY_TAG_LENGTH 5
73 #define DICT_TAG_LENGTH 4
74 #define KEY_TAG_LENGTH 3
75 #define STRING_TAG_LENGTH 6
76 #define DATA_TAG_LENGTH 4
77 #define DATE_TAG_LENGTH 4
78 #define REAL_TAG_LENGTH 4
79 #define INTEGER_TAG_LENGTH 7
80 #define TRUE_TAG_LENGTH 4
81 #define FALSE_TAG_LENGTH 5
82 #define DOCTYPE_TAG_LENGTH 7
83 #define CDSECT_TAG_LENGTH 9
85 // don't allow _CFKeyedArchiverUID here
86 #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(): 0x%x not of a property list type", __PRETTY_FUNCTION__, (UInt32)cf);
88 static bool __CFPropertyListIsValidAux(CFPropertyListRef plist
, bool recursive
, CFMutableSetRef set
, CFPropertyListFormat format
);
93 CFPropertyListFormat format
;
96 static void __CFPropertyListIsArrayPlistAux(const void *value
, void *context
) {
97 struct context
*ctx
= (struct context
*)context
;
98 if (!ctx
->answer
) return;
100 if (!value
) CFLog(0, CFSTR("CFPropertyListIsValid(): property list arrays cannot contain NULL"));
102 ctx
->answer
= value
&& __CFPropertyListIsValidAux(value
, true, ctx
->set
, ctx
->format
);
105 static void __CFPropertyListIsDictPlistAux(const void *key
, const void *value
, void *context
) {
106 struct context
*ctx
= (struct context
*)context
;
107 if (!ctx
->answer
) return;
109 if (!key
) CFLog(0, CFSTR("CFPropertyListIsValid(): property list dictionaries cannot contain NULL keys"));
110 if (!value
) CFLog(0, CFSTR("CFPropertyListIsValid(): property list dictionaries cannot contain NULL values"));
111 if (CFStringGetTypeID() != CFGetTypeID(key
)) {
112 CFStringRef desc
= CFCopyTypeIDDescription(CFGetTypeID(key
));
113 CFLog(0, CFSTR("CFPropertyListIsValid(): property list dictionaries may only have keys which are CFStrings, not '%@'"), desc
);
117 ctx
->answer
= key
&& value
&& (CFStringGetTypeID() == CFGetTypeID(key
)) && __CFPropertyListIsValidAux(value
, true, ctx
->set
, ctx
->format
);
120 static bool __CFPropertyListIsValidAux(CFPropertyListRef plist
, bool recursive
, CFMutableSetRef set
, CFPropertyListFormat format
) {
123 if (!plist
) CFLog(0, CFSTR("CFPropertyListIsValid(): property lists cannot contain NULL"));
125 if (!plist
) return false;
126 type
= CFGetTypeID(plist
);
127 if (CFStringGetTypeID() == type
) return true;
128 if (CFDataGetTypeID() == type
) return true;
129 if (kCFPropertyListOpenStepFormat
!= format
) {
130 if (CFBooleanGetTypeID() == type
) return true;
131 if (CFNumberGetTypeID() == type
) return true;
132 if (CFDateGetTypeID() == type
) return true;
133 if (_CFKeyedArchiverUIDGetTypeID() == type
) return true;
135 if (!recursive
&& CFArrayGetTypeID() == type
) return true;
136 if (!recursive
&& CFDictionaryGetTypeID() == type
) return true;
137 // at any one invocation of this function, set should contain the objects in the "path" down to this object
139 if (CFSetContainsValue(set
, plist
)) CFLog(0, CFSTR("CFPropertyListIsValid(): property lists cannot contain recursive container references"));
141 if (CFSetContainsValue(set
, plist
)) return false;
142 if (CFArrayGetTypeID() == type
) {
143 struct context ctx
= {true, set
, format
};
144 CFSetAddValue(set
, plist
);
145 CFArrayApplyFunction(plist
, CFRangeMake(0, CFArrayGetCount(plist
)), __CFPropertyListIsArrayPlistAux
, &ctx
);
146 CFSetRemoveValue(set
, plist
);
149 if (CFDictionaryGetTypeID() == type
) {
150 struct context ctx
= {true, set
, format
};
151 CFSetAddValue(set
, plist
);
152 CFDictionaryApplyFunction(plist
, __CFPropertyListIsDictPlistAux
, &ctx
);
153 CFSetRemoveValue(set
, plist
);
158 CFStringRef desc
= CFCopyTypeIDDescription(type
);
159 CFLog(0, CFSTR("CFPropertyListIsValid(): property lists cannot contain objects of type '%@'"), desc
);
166 Boolean
CFPropertyListIsValid(CFPropertyListRef plist
, CFPropertyListFormat format
) {
169 CFAssert1(plist
!= NULL
, __kCFLogAssertion
, "%s(): NULL is not a property list", __PRETTY_FUNCTION__
);
170 set
= CFSetCreateMutable(kCFAllocatorSystemDefault
, 0, NULL
);
171 result
= __CFPropertyListIsValidAux(plist
, true, set
, format
);
176 static const UniChar CFXMLPlistTags
[13][10]= {
177 {'p', 'l', 'i', 's', 't', '\0', '\0', '\0', '\0', '\0'},
178 {'a', 'r', 'r', 'a', 'y', '\0', '\0', '\0', '\0', '\0'},
179 {'d', 'i', 'c', 't', '\0', '\0', '\0', '\0', '\0', '\0'},
180 {'k', 'e', 'y', '\0', '\0', '\0', '\0', '\0', '\0', '\0'},
181 {'s', 't', 'r', 'i', 'n', 'g', '\0', '\0', '\0', '\0'},
182 {'d', 'a', 't', 'a', '\0', '\0', '\0', '\0', '\0', '\0'},
183 {'d', 'a', 't', 'e', '\0', '\0', '\0', '\0', '\0', '\0'},
184 {'r', 'e', 'a', 'l', '\0', '\0', '\0', '\0', '\0', '\0'},
185 {'i', 'n', 't', 'e', 'g', 'e', 'r', '\0', '\0', '\0'},
186 {'t', 'r', 'u', 'e', '\0', '\0', '\0', '\0', '\0', '\0'},
187 {'f', 'a', 'l', 's', 'e', '\0', '\0', '\0', '\0', '\0'},
188 {'D', 'O', 'C', 'T', 'Y', 'P', 'E', '\0', '\0', '\0'},
189 {'<', '!', '[', 'C', 'D', 'A', 'T', 'A', '[', '\0'}
193 const UniChar
*begin
; // first character of the XML to be parsed
194 const UniChar
*curr
; // current parse location
195 const UniChar
*end
; // the first character _after_ the end of the XML
196 CFStringRef errorString
;
197 CFAllocatorRef allocator
;
198 UInt32 mutabilityOption
;
199 CFMutableSetRef stringSet
; // set of all strings involved in this parse; allows us to share non-mutable strings in the returned plist
200 CFMutableStringRef tmpString
; // Mutable string with external characters that functions can feel free to use as temporary storage as the parse progresses
201 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)
203 } _CFXMLPlistParseInfo
;
205 static CFTypeRef
parseOldStylePropertyListOrStringsFile(_CFXMLPlistParseInfo
*pInfo
);
209 // 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.
211 // Null-terminated, ASCII or UTF8 string
213 static void _plistAppendUTF8CString(CFMutableDataRef mData
, const char *cString
) {
214 CFDataAppendBytes (mData
, (const UInt8
*)cString
, strlen(cString
));
219 static void _plistAppendCharacters(CFMutableDataRef mData
, const UniChar
*chars
, CFIndex length
) {
222 do { // Flush out ASCII chars, BUFLEN at a time
224 UInt8 buf
[BUFLEN
], *bufPtr
= buf
;
226 while (cnt
< length
&& (cnt
- curLoc
< BUFLEN
) && (chars
[cnt
] < 128)) *bufPtr
++ = (UInt8
)(chars
[cnt
++]);
227 if (cnt
> curLoc
) { // Flush any ASCII bytes
228 CFDataAppendBytes(mData
, buf
, cnt
- curLoc
);
231 } while (curLoc
< length
&& (chars
[curLoc
] < 128)); // We will exit out of here when we run out of chars or hit a non-ASCII char
233 if (curLoc
< length
) { // Now deal with non-ASCII chars
234 CFDataRef data
= NULL
;
235 CFStringRef str
= NULL
;
236 if ((str
= CFStringCreateWithCharactersNoCopy(NULL
, chars
+ curLoc
, length
- curLoc
, kCFAllocatorNull
))) {
237 if ((data
= CFStringCreateExternalRepresentation(NULL
, str
, kCFStringEncodingUTF8
, 0))) {
238 CFDataAppendBytes (mData
, CFDataGetBytePtr(data
), CFDataGetLength(data
));
243 CFAssert1(str
&& data
, __kCFLogAssertion
, "%s(): Error writing plist", __PRETTY_FUNCTION__
);
249 static void _plistAppendString(CFMutableDataRef mData
, CFStringRef str
) {
250 const UniChar
*chars
;
253 if ((chars
= CFStringGetCharactersPtr(str
))) {
254 _plistAppendCharacters(mData
, chars
, CFStringGetLength(str
));
255 } else if ((cStr
= CFStringGetCStringPtr(str
, kCFStringEncodingASCII
)) || (cStr
= CFStringGetCStringPtr(str
, kCFStringEncodingUTF8
))) {
256 _plistAppendUTF8CString(mData
, cStr
);
257 } else if ((data
= CFStringCreateExternalRepresentation(NULL
, str
, kCFStringEncodingUTF8
, 0))) {
258 CFDataAppendBytes (mData
, CFDataGetBytePtr(data
), CFDataGetLength(data
));
261 CFAssert1(TRUE
, __kCFLogAssertion
, "%s(): Error in plist writing", __PRETTY_FUNCTION__
);
266 // Append CFString-style format + arguments
268 static void _plistAppendFormat(CFMutableDataRef mData
, CFStringRef format
, ...) {
272 va_start(argList
, format
);
273 fStr
= CFStringCreateWithFormatAndArguments(NULL
, NULL
, format
, argList
);
276 CFAssert1(fStr
, __kCFLogAssertion
, "%s(): Error writing plist", __PRETTY_FUNCTION__
);
277 _plistAppendString(mData
, fStr
);
283 static void _appendIndents(CFIndex numIndents
, CFMutableDataRef str
) {
285 static const UniChar tabs
[NUMTABS
] = {'\t','\t','\t','\t'};
286 for (; numIndents
> 0; numIndents
-= NUMTABS
) _plistAppendCharacters(str
, tabs
, (numIndents
>= NUMTABS
) ? NUMTABS
: numIndents
);
289 /* Append the escaped version of origStr to mStr.
291 static void _appendEscapedString(CFStringRef origStr
, CFMutableDataRef mStr
) {
293 CFIndex i
, length
= CFStringGetLength(origStr
);
295 UniChar buf
[BUFSIZE
];
296 CFStringInlineBuffer inlineBuffer
;
298 CFStringInitInlineBuffer(origStr
, &inlineBuffer
, CFRangeMake(0, length
));
300 for (i
= 0; i
< length
; i
++) {
301 UniChar ch
= __CFStringGetCharacterFromInlineBufferQuick(&inlineBuffer
, i
);
304 if (bufCnt
) _plistAppendCharacters(mStr
, buf
, bufCnt
);
306 _plistAppendUTF8CString(mStr
, "<");
309 if (bufCnt
) _plistAppendCharacters(mStr
, buf
, bufCnt
);
311 _plistAppendUTF8CString(mStr
, ">");
314 if (bufCnt
) _plistAppendCharacters(mStr
, buf
, bufCnt
);
316 _plistAppendUTF8CString(mStr
, "&");
320 if (bufCnt
== BUFSIZE
) {
321 _plistAppendCharacters(mStr
, buf
, bufCnt
);
327 if (bufCnt
) _plistAppendCharacters(mStr
, buf
, bufCnt
);
332 /* Base-64 encoding/decoding */
334 /* The base-64 encoding packs three 8-bit bytes into four 7-bit ASCII
335 * characters. If the number of bytes in the original data isn't divisable
336 * by three, "=" characters are used to pad the encoded data. The complete
337 * set of characters used in base-64 are:
347 // Write the inputData to the mData using Base 64 encoding
349 static void _XMLPlistAppendDataUsingBase64(CFMutableDataRef mData
, CFDataRef inputData
, CFIndex indent
) {
350 static const char __CFPLDataEncodeTable
[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
351 #define MAXLINELEN 76
352 char buf
[MAXLINELEN
+ 4 + 2]; // For the slop and carriage return and terminating NULL
354 const uint8_t *bytes
= CFDataGetBytePtr(inputData
);
355 CFIndex length
= CFDataGetLength(inputData
);
359 if (indent
> 8) indent
= 8; // refuse to indent more than 64 characters
361 pos
= 0; // position within buf
363 for (i
= 0, p
= bytes
; i
< length
; i
++, p
++) {
364 /* 3 bytes are encoded as 4 */
367 buf
[pos
++] = __CFPLDataEncodeTable
[ ((p
[0] >> 2) & 0x3f)];
370 buf
[pos
++] = __CFPLDataEncodeTable
[ ((((p
[-1] << 8) | p
[0]) >> 4) & 0x3f)];
373 buf
[pos
++] = __CFPLDataEncodeTable
[ ((((p
[-1] << 8) | p
[0]) >> 6) & 0x3f)];
374 buf
[pos
++] = __CFPLDataEncodeTable
[ (p
[0] & 0x3f)];
377 /* Flush the line out every 76 (or fewer) chars --- indents count against the line length*/
378 if (pos
>= MAXLINELEN
- 8 * indent
) {
381 _appendIndents(indent
, mData
);
382 _plistAppendUTF8CString(mData
, buf
);
391 buf
[pos
++] = __CFPLDataEncodeTable
[ ((p
[-1] << 4) & 0x30)];
396 buf
[pos
++] = __CFPLDataEncodeTable
[ ((p
[-1] << 2) & 0x3c)];
404 _appendIndents(indent
, mData
);
405 _plistAppendUTF8CString(mData
, buf
);
409 extern CFStringRef
__CFNumberCopyFormattingDescriptionAsFloat64(CFTypeRef cf
);
411 static void _CFAppendXML0(CFTypeRef object
, UInt32 indentation
, CFMutableDataRef xmlString
) {
412 UInt32 typeID
= CFGetTypeID(object
);
413 _appendIndents(indentation
, xmlString
);
414 if (typeID
== CFStringGetTypeID()) {
415 _plistAppendUTF8CString(xmlString
, "<");
416 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[STRING_IX
], STRING_TAG_LENGTH
);
417 _plistAppendUTF8CString(xmlString
, ">");
418 _appendEscapedString(object
, xmlString
);
419 _plistAppendUTF8CString(xmlString
, "</");
420 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[STRING_IX
], STRING_TAG_LENGTH
);
421 _plistAppendUTF8CString(xmlString
, ">\n");
422 } else if (typeID
== _CFKeyedArchiverUIDGetTypeID()) {
423 _plistAppendUTF8CString(xmlString
, "<");
424 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[DICT_IX
], DICT_TAG_LENGTH
);
425 _plistAppendUTF8CString(xmlString
, ">\n");
426 _appendIndents(indentation
+1, xmlString
);
427 _plistAppendUTF8CString(xmlString
, "<");
428 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[KEY_IX
], KEY_TAG_LENGTH
);
429 _plistAppendUTF8CString(xmlString
, ">");
430 _appendEscapedString(CFSTR("CF$UID"), xmlString
);
431 _plistAppendUTF8CString(xmlString
, "</");
432 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[KEY_IX
], KEY_TAG_LENGTH
);
433 _plistAppendUTF8CString(xmlString
, ">\n");
434 _appendIndents(indentation
+ 1, xmlString
);
435 _plistAppendUTF8CString(xmlString
, "<");
436 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[INTEGER_IX
], INTEGER_TAG_LENGTH
);
437 _plistAppendUTF8CString(xmlString
, ">");
439 uint64_t v
= _CFKeyedArchiverUIDGetValue(object
);
440 CFNumberRef num
= CFNumberCreate(kCFAllocatorSystemDefault
, kCFNumberSInt64Type
, &v
);
441 _plistAppendFormat(xmlString
, CFSTR("%@"), num
);
444 _plistAppendUTF8CString(xmlString
, "</");
445 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[INTEGER_IX
], INTEGER_TAG_LENGTH
);
446 _plistAppendUTF8CString(xmlString
, ">\n");
447 _appendIndents(indentation
, xmlString
);
448 _plistAppendUTF8CString(xmlString
, "</");
449 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[DICT_IX
], DICT_TAG_LENGTH
);
450 _plistAppendUTF8CString(xmlString
, ">\n");
451 } else if (typeID
== CFArrayGetTypeID()) {
452 UInt32 i
, count
= CFArrayGetCount(object
);
454 _plistAppendUTF8CString(xmlString
, "<");
455 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[ARRAY_IX
], ARRAY_TAG_LENGTH
);
456 _plistAppendUTF8CString(xmlString
, "/>\n");
459 _plistAppendUTF8CString(xmlString
, "<");
460 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[ARRAY_IX
], ARRAY_TAG_LENGTH
);
461 _plistAppendUTF8CString(xmlString
, ">\n");
462 for (i
= 0; i
< count
; i
++) {
463 _CFAppendXML0(CFArrayGetValueAtIndex(object
, i
), indentation
+1, xmlString
);
465 _appendIndents(indentation
, xmlString
);
466 _plistAppendUTF8CString(xmlString
, "</");
467 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[ARRAY_IX
], ARRAY_TAG_LENGTH
);
468 _plistAppendUTF8CString(xmlString
, ">\n");
469 } else if (typeID
== CFDictionaryGetTypeID()) {
470 UInt32 i
, count
= CFDictionaryGetCount(object
);
471 CFAllocatorRef allocator
= CFGetAllocator(xmlString
);
472 CFMutableArrayRef keyArray
;
475 _plistAppendUTF8CString(xmlString
, "<");
476 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[DICT_IX
], DICT_TAG_LENGTH
);
477 _plistAppendUTF8CString(xmlString
, "/>\n");
480 _plistAppendUTF8CString(xmlString
, "<");
481 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[DICT_IX
], DICT_TAG_LENGTH
);
482 _plistAppendUTF8CString(xmlString
, ">\n");
483 keys
= (CFTypeRef
*)CFAllocatorAllocate(allocator
, count
* sizeof(CFTypeRef
), 0);
484 CFDictionaryGetKeysAndValues(object
, keys
, NULL
);
485 keyArray
= CFArrayCreateMutable(allocator
, count
, &kCFTypeArrayCallBacks
);
486 CFArrayReplaceValues(keyArray
, CFRangeMake(0, 0), keys
, count
);
487 CFArraySortValues(keyArray
, CFRangeMake(0, count
), (CFComparatorFunction
)CFStringCompare
, NULL
);
488 CFArrayGetValues(keyArray
, CFRangeMake(0, count
), keys
);
490 for (i
= 0; i
< count
; i
++) {
491 CFTypeRef key
= keys
[i
];
492 _appendIndents(indentation
+1, xmlString
);
493 _plistAppendUTF8CString(xmlString
, "<");
494 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[KEY_IX
], KEY_TAG_LENGTH
);
495 _plistAppendUTF8CString(xmlString
, ">");
496 _appendEscapedString(key
, xmlString
);
497 _plistAppendUTF8CString(xmlString
, "</");
498 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[KEY_IX
], KEY_TAG_LENGTH
);
499 _plistAppendUTF8CString(xmlString
, ">\n");
500 _CFAppendXML0(CFDictionaryGetValue(object
, key
), indentation
+1, xmlString
);
502 CFAllocatorDeallocate(allocator
, keys
);
503 _appendIndents(indentation
, xmlString
);
504 _plistAppendUTF8CString(xmlString
, "</");
505 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[DICT_IX
], DICT_TAG_LENGTH
);
506 _plistAppendUTF8CString(xmlString
, ">\n");
507 } else if (typeID
== CFDataGetTypeID()) {
508 _plistAppendUTF8CString(xmlString
, "<");
509 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[DATA_IX
], DATA_TAG_LENGTH
);
510 _plistAppendUTF8CString(xmlString
, ">\n");
511 _XMLPlistAppendDataUsingBase64(xmlString
, object
, indentation
);
512 _appendIndents(indentation
, xmlString
);
513 _plistAppendUTF8CString(xmlString
, "</");
514 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[DATA_IX
], DATA_TAG_LENGTH
);
515 _plistAppendUTF8CString(xmlString
, ">\n");
516 } else if (typeID
== CFDateGetTypeID()) {
517 // YYYY '-' MM '-' DD 'T' hh ':' mm ':' ss 'Z'
518 CFGregorianDate date
= CFAbsoluteTimeGetGregorianDate(CFDateGetAbsoluteTime(object
), NULL
);
520 _plistAppendUTF8CString(xmlString
, "<");
521 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[DATE_IX
], DATE_TAG_LENGTH
);
522 _plistAppendUTF8CString(xmlString
, ">");
524 _plistAppendFormat(xmlString
, CFSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), date
.year
, date
.month
, date
.day
, date
.hour
, date
.minute
, (int)date
.second
);
526 _plistAppendUTF8CString(xmlString
, "</");
527 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[DATE_IX
], DATE_TAG_LENGTH
);
528 _plistAppendUTF8CString(xmlString
, ">\n");
529 } else if (typeID
== CFNumberGetTypeID()) {
530 if (CFNumberIsFloatType(object
)) {
531 _plistAppendUTF8CString(xmlString
, "<");
532 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
);
533 _plistAppendUTF8CString(xmlString
, ">");
535 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
536 CFStringRef s
= __CFNumberCopyFormattingDescriptionAsFloat64(object
);
537 _plistAppendString(xmlString
, s
);
539 } else if (CFNumberGetType(object
) == kCFNumberFloat64Type
|| CFNumberGetType(object
) == kCFNumberDoubleType
) {
541 static CFStringRef doubleFormatString
= NULL
;
542 CFNumberGetValue(object
, kCFNumberDoubleType
, &doubleVal
);
543 if (!doubleFormatString
) {
544 doubleFormatString
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%%.%de"), DBL_DIG
);
546 _plistAppendFormat(xmlString
, doubleFormatString
, doubleVal
);
549 static CFStringRef floatFormatString
= NULL
;
550 CFNumberGetValue(object
, kCFNumberFloatType
, &floatVal
);
551 if (!floatFormatString
) {
552 floatFormatString
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%%.%de"), FLT_DIG
);
554 _plistAppendFormat(xmlString
, floatFormatString
, floatVal
);
557 _plistAppendUTF8CString(xmlString
, "</");
558 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
);
559 _plistAppendUTF8CString(xmlString
, ">\n");
561 _plistAppendUTF8CString(xmlString
, "<");
562 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[INTEGER_IX
], INTEGER_TAG_LENGTH
);
563 _plistAppendUTF8CString(xmlString
, ">");
565 _plistAppendFormat(xmlString
, CFSTR("%@"), object
);
567 _plistAppendUTF8CString(xmlString
, "</");
568 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[INTEGER_IX
], INTEGER_TAG_LENGTH
);
569 _plistAppendUTF8CString(xmlString
, ">\n");
571 } else if (typeID
== CFBooleanGetTypeID()) {
572 if (CFBooleanGetValue(object
)) {
573 _plistAppendUTF8CString(xmlString
, "<");
574 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[TRUE_IX
], TRUE_TAG_LENGTH
);
575 _plistAppendUTF8CString(xmlString
, "/>\n");
577 _plistAppendUTF8CString(xmlString
, "<");
578 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[FALSE_IX
], FALSE_TAG_LENGTH
);
579 _plistAppendUTF8CString(xmlString
, "/>\n");
584 static void _CFGenerateXMLPropertyListToData(CFMutableDataRef xml
, CFTypeRef propertyList
) {
585 _plistAppendUTF8CString(xml
, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE ");
586 _plistAppendCharacters(xml
, CFXMLPlistTags
[PLIST_IX
], PLIST_TAG_LENGTH
);
587 _plistAppendUTF8CString(xml
, " PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<");
588 _plistAppendCharacters(xml
, CFXMLPlistTags
[PLIST_IX
], PLIST_TAG_LENGTH
);
589 _plistAppendUTF8CString(xml
, " version=\"1.0\">\n");
591 _CFAppendXML0(propertyList
, 0, xml
);
593 _plistAppendUTF8CString(xml
, "</");
594 _plistAppendCharacters(xml
, CFXMLPlistTags
[PLIST_IX
], PLIST_TAG_LENGTH
);
595 _plistAppendUTF8CString(xml
, ">\n");
598 CFDataRef
CFPropertyListCreateXMLData(CFAllocatorRef allocator
, CFPropertyListRef propertyList
) {
599 CFMutableDataRef xml
;
600 CFAssert1(propertyList
!= NULL
, __kCFLogAssertion
, "%s(): Cannot be called with a NULL property list", __PRETTY_FUNCTION__
);
601 __CFAssertIsPList(propertyList
);
602 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
603 if (!CFPropertyListIsValid(propertyList
, kCFPropertyListXMLFormat_v1_0
)) return NULL
;
605 xml
= CFDataCreateMutable(allocator
, 0);
606 _CFGenerateXMLPropertyListToData(xml
, propertyList
);
610 CFDataRef
_CFPropertyListCreateXMLDataWithExtras(CFAllocatorRef allocator
, CFPropertyListRef propertyList
) {
611 CFMutableDataRef xml
;
612 CFAssert1(propertyList
!= NULL
, __kCFLogAssertion
, "%s(): Cannot be called with a NULL property list", __PRETTY_FUNCTION__
);
613 xml
= CFDataCreateMutable(allocator
, 0);
614 _CFGenerateXMLPropertyListToData(xml
, propertyList
);
618 // ========================================================================
621 // ------------------------- Reading plists ------------------
624 static void skipInlineDTD(_CFXMLPlistParseInfo
*pInfo
);
625 static CFTypeRef
parseXMLElement(_CFXMLPlistParseInfo
*pInfo
, Boolean
*isKey
);
627 // warning: doesn't have a good idea of Unicode line separators
628 static UInt32
lineNumber(_CFXMLPlistParseInfo
*pInfo
) {
629 const UniChar
*p
= pInfo
->begin
;
631 while (p
< pInfo
->curr
) {
634 if (*(p
+ 1) == '\n')
636 } else if (*p
== '\n') {
644 // warning: doesn't have a good idea of Unicode white space
645 CF_INLINE
void skipWhitespace(_CFXMLPlistParseInfo
*pInfo
) {
646 while (pInfo
->curr
< pInfo
->end
) {
647 switch (*(pInfo
->curr
)) {
660 /* 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. */
662 // pInfo should be just past "<!--"
663 static void skipXMLComment(_CFXMLPlistParseInfo
*pInfo
) {
664 const UniChar
*p
= pInfo
->curr
;
665 const UniChar
*end
= pInfo
->end
- 3; // Need at least 3 characters to compare against
667 if (*p
== '-' && *(p
+1) == '-' && *(p
+2) == '>') {
673 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Unterminated comment started on line %d"), lineNumber(pInfo
));
676 // stringToMatch and buf must both be of at least len
677 static Boolean
matchString(const UniChar
*buf
, const UniChar
*stringToMatch
, UInt32 len
) {
679 case 10: if (buf
[9] != stringToMatch
[9]) return false;
680 case 9: if (buf
[8] != stringToMatch
[8]) return false;
681 case 8: if (buf
[7] != stringToMatch
[7]) return false;
682 case 7: if (buf
[6] != stringToMatch
[6]) return false;
683 case 6: if (buf
[5] != stringToMatch
[5]) return false;
684 case 5: if (buf
[4] != stringToMatch
[4]) return false;
685 case 4: if (buf
[3] != stringToMatch
[3]) return false;
686 case 3: if (buf
[2] != stringToMatch
[2]) return false;
687 case 2: if (buf
[1] != stringToMatch
[1]) return false;
688 case 1: if (buf
[0] != stringToMatch
[0]) return false;
691 return false; // internal error
694 // pInfo should be set to the first character after "<?"
695 static void skipXMLProcessingInstruction(_CFXMLPlistParseInfo
*pInfo
) {
696 const UniChar
*begin
= pInfo
->curr
, *end
= pInfo
->end
- 2; // Looking for "?>" so we need at least 2 characters
697 while (pInfo
->curr
< end
) {
698 if (*(pInfo
->curr
) == '?' && *(pInfo
->curr
+1) == '>') {
705 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected EOF while parsing the processing instruction begun on line %d"), lineNumber(pInfo
));
708 // first character should be immediately after the "<!"
709 static void skipDTD(_CFXMLPlistParseInfo
*pInfo
) {
710 // First pass "DOCTYPE"
711 if (pInfo
->end
- pInfo
->curr
< DOCTYPE_TAG_LENGTH
|| !matchString(pInfo
->curr
, CFXMLPlistTags
[DOCTYPE_IX
], DOCTYPE_TAG_LENGTH
)) {
712 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Malformed DTD on line %d"), lineNumber(pInfo
));
715 pInfo
->curr
+= DOCTYPE_TAG_LENGTH
;
716 skipWhitespace(pInfo
);
718 // Look for either the beginning of a complex DTD or the end of the DOCTYPE structure
719 while (pInfo
->curr
< pInfo
->end
) {
720 UniChar ch
= *(pInfo
->curr
);
721 if (ch
== '[') break; // inline DTD
722 if (ch
== '>') { // End of the DTD
728 if (pInfo
->curr
== pInfo
->end
) {
729 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF while parsing DTD", CFStringGetSystemEncoding());
733 // *Sigh* Must parse in-line DTD
734 skipInlineDTD(pInfo
);
735 if (pInfo
->errorString
) return;
736 skipWhitespace(pInfo
);
737 if (pInfo
->errorString
) return;
738 if (pInfo
->curr
< pInfo
->end
) {
739 if (*(pInfo
->curr
) == '>') {
742 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected character %c on line %d while parsing DTD"), *(pInfo
->curr
), lineNumber(pInfo
));
745 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF while parsing DTD", CFStringGetSystemEncoding());
749 static void skipPERef(_CFXMLPlistParseInfo
*pInfo
) {
750 const UniChar
*p
= pInfo
->curr
;
751 while (p
< pInfo
->end
) {
758 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected EOF while parsing percent-escape sequence begun on line %d"), lineNumber(pInfo
));
761 // First character should be just past '['
762 static void skipInlineDTD(_CFXMLPlistParseInfo
*pInfo
) {
763 while (!pInfo
->errorString
&& pInfo
->curr
< pInfo
->end
) {
765 skipWhitespace(pInfo
);
770 } else if (ch
== '<') {
772 if (pInfo
->curr
>= pInfo
->end
) {
773 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF while parsing inline DTD", CFStringGetSystemEncoding());
779 skipXMLProcessingInstruction(pInfo
);
780 } else if (ch
== '!') {
781 if (pInfo
->curr
+ 2 < pInfo
->end
&& (*(pInfo
->curr
+1) == '-' && *(pInfo
->curr
+2) == '-')) {
783 skipXMLComment(pInfo
);
785 // Skip the myriad of DTD declarations of the form "<!string" ... ">"
786 pInfo
->curr
++; // Past both '<' and '!'
787 while (pInfo
->curr
< pInfo
->end
) {
788 if (*(pInfo
->curr
) == '>') break;
791 if (*(pInfo
->curr
) != '>') {
792 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF while parsing inline DTD", CFStringGetSystemEncoding());
799 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected character %c on line %d while parsing inline DTD"), ch
, lineNumber(pInfo
));
802 } else if (ch
== ']') {
806 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected character %c on line %d while parsing inline DTD"), ch
, lineNumber(pInfo
));
810 if (!pInfo
->errorString
)
811 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF while parsing inline DTD", CFStringGetSystemEncoding());
814 /* A bit wasteful to do everything with unichars (since we know all the characters we're going to see are 7-bit ASCII), but since our data is coming from or going to a CFString, this prevents the extra cost of converting formats. */
816 static const signed char __CFPLDataDecodeTable
[128] = {
817 /* 000 */ -1, -1, -1, -1, -1, -1, -1, -1,
818 /* 010 */ -1, -1, -1, -1, -1, -1, -1, -1,
819 /* 020 */ -1, -1, -1, -1, -1, -1, -1, -1,
820 /* 030 */ -1, -1, -1, -1, -1, -1, -1, -1,
821 /* ' ' */ -1, -1, -1, -1, -1, -1, -1, -1,
822 /* '(' */ -1, -1, -1, 62, -1, -1, -1, 63,
823 /* '0' */ 52, 53, 54, 55, 56, 57, 58, 59,
824 /* '8' */ 60, 61, -1, -1, -1, 0, -1, -1,
825 /* '@' */ -1, 0, 1, 2, 3, 4, 5, 6,
826 /* 'H' */ 7, 8, 9, 10, 11, 12, 13, 14,
827 /* 'P' */ 15, 16, 17, 18, 19, 20, 21, 22,
828 /* 'X' */ 23, 24, 25, -1, -1, -1, -1, -1,
829 /* '`' */ -1, 26, 27, 28, 29, 30, 31, 32,
830 /* 'h' */ 33, 34, 35, 36, 37, 38, 39, 40,
831 /* 'p' */ 41, 42, 43, 44, 45, 46, 47, 48,
832 /* 'x' */ 49, 50, 51, -1, -1, -1, -1, -1
835 static CFDataRef
__CFPLDataDecode(_CFXMLPlistParseInfo
*pInfo
, Boolean
mutable) {
843 // GrP GC: collector shouldn't scan this raw data
844 tmpbuf
= CFAllocatorAllocate(pInfo
->allocator
, tmpbuflen
, AUTO_MEMORY_UNSCANNED
);
845 for (; pInfo
->curr
< pInfo
->end
; pInfo
->curr
++) {
846 UniChar c
= *(pInfo
->curr
);
852 } else if (!isspace(c
)) {
855 if (__CFPLDataDecodeTable
[c
] < 0)
859 acc
+= __CFPLDataDecodeTable
[c
];
860 if (0 == (cntr
& 0x3)) {
861 if (tmpbuflen
<= tmpbufpos
+ 2) {
863 tmpbuf
= CFAllocatorReallocate(pInfo
->allocator
, tmpbuf
, tmpbuflen
, AUTO_MEMORY_UNSCANNED
);
865 tmpbuf
[tmpbufpos
++] = (acc
>> 16) & 0xff;
867 tmpbuf
[tmpbufpos
++] = (acc
>> 8) & 0xff;
869 tmpbuf
[tmpbufpos
++] = acc
& 0xff;
873 CFMutableDataRef result
= CFDataCreateMutable(pInfo
->allocator
, 0);
874 CFDataAppendBytes(result
, tmpbuf
, tmpbufpos
);
875 CFAllocatorDeallocate(pInfo
->allocator
, tmpbuf
);
878 return CFDataCreateWithBytesNoCopy(pInfo
->allocator
, (char const *) tmpbuf
, tmpbufpos
, pInfo
->allocator
);
882 // content ::== (element | CharData | Reference | CDSect | PI | Comment)*
883 // 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).
884 static CFTypeRef
getContentObject(_CFXMLPlistParseInfo
*pInfo
, Boolean
*isKey
) {
885 if (isKey
) *isKey
= false;
886 while (!pInfo
->errorString
&& pInfo
->curr
< pInfo
->end
) {
887 skipWhitespace(pInfo
);
888 if (pInfo
->curr
>= pInfo
->end
) {
889 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
892 if (*(pInfo
->curr
) != '<') {
893 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected character %c on line %d"), *(pInfo
->curr
), lineNumber(pInfo
));
897 if (pInfo
->curr
>= pInfo
->end
) {
898 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
901 switch (*(pInfo
->curr
)) {
903 // Processing instruction
904 skipXMLProcessingInstruction(pInfo
);
907 // Could be a comment
908 if (pInfo
->curr
+2 >= pInfo
->end
) {
909 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
912 if (*(pInfo
->curr
+1) == '-' && *(pInfo
->curr
+2) == '-') {
914 skipXMLComment(pInfo
);
916 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
921 // Whoops! Looks like we got to the end tag for the element whose content we're parsing
922 pInfo
->curr
--; // Back off to the '<'
925 // Should be an element
926 return parseXMLElement(pInfo
, isKey
);
929 // 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"
933 static void _catFromMarkToBuf(const UniChar
*mark
, const UniChar
*buf
, CFMutableStringRef
*string
, CFAllocatorRef allocator
) {
935 *string
= CFStringCreateMutable(allocator
, 0);
937 CFStringAppendCharacters(*string
, mark
, buf
-mark
);
940 static void parseCDSect_pl(_CFXMLPlistParseInfo
*pInfo
, CFMutableStringRef string
) {
941 const UniChar
*end
, *begin
;
942 if (pInfo
->end
- pInfo
->curr
< CDSECT_TAG_LENGTH
) {
943 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
946 if (!matchString(pInfo
->curr
, CFXMLPlistTags
[CDSECT_IX
], CDSECT_TAG_LENGTH
)) {
947 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered improper CDATA opening at line %d"), lineNumber(pInfo
));
950 pInfo
->curr
+= CDSECT_TAG_LENGTH
;
951 begin
= pInfo
->curr
; // Marks the first character of the CDATA content
952 end
= pInfo
->end
-2; // So we can safely look 2 characters beyond p
953 while (pInfo
->curr
< end
) {
954 if (*(pInfo
->curr
) == ']' && *(pInfo
->curr
+1) == ']' && *(pInfo
->curr
+2) == '>') {
956 CFStringAppendCharacters(string
, begin
, pInfo
->curr
-begin
);
962 // Never found the end mark
964 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Could not find end of CDATA started on line %d"), lineNumber(pInfo
));
967 // Only legal references are {lt, gt, amp, apos, quote, #ddd, #xAAA}
968 static void parseEntityReference_pl(_CFXMLPlistParseInfo
*pInfo
, CFMutableStringRef string
) {
971 pInfo
->curr
++; // move past the '&';
972 len
= pInfo
->end
- pInfo
->curr
; // how many characters we can safely scan
974 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
977 switch (*(pInfo
->curr
)) {
979 if (len
>= 3 && *(pInfo
->curr
+1) == 't' && *(pInfo
->curr
+2) == ';') {
984 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo
));
987 if (len
>= 3 && *(pInfo
->curr
+1) == 't' && *(pInfo
->curr
+2) == ';') {
992 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo
));
994 case 'a': // "apos" or "amp"
995 if (len
< 4) { // Not enough characters for either conversion
996 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
999 if (*(pInfo
->curr
+1) == 'm') {
1001 if (*(pInfo
->curr
+2) == 'p' && *(pInfo
->curr
+3) == ';') {
1006 } else if (*(pInfo
->curr
+1) == 'p') {
1008 if (len
> 4 && *(pInfo
->curr
+2) == 'o' && *(pInfo
->curr
+3) == 's' && *(pInfo
->curr
+4) == ';') {
1014 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo
));
1016 case 'q': // "quote"
1017 if (len
>= 5 && *(pInfo
->curr
+1) == 'u' && *(pInfo
->curr
+2) == 'o' && *(pInfo
->curr
+3) == 't' && *(pInfo
->curr
+4) == ';') {
1022 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo
));
1027 Boolean isHex
= false;
1028 if ( len
< 4) { // Not enough characters to make it all fit! Need at least "&#d;"
1029 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
1033 if (*(pInfo
->curr
) == 'x') {
1037 while (pInfo
->curr
< pInfo
->end
) {
1038 ch
= *(pInfo
->curr
);
1041 CFStringAppendCharacters(string
, &num
, 1);
1044 if (!isHex
) num
= num
*10;
1045 else num
= num
<< 4;
1046 if (ch
<= '9' && ch
>= '0') {
1048 } else if (!isHex
) {
1049 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected character %c at line %d"), ch
, lineNumber(pInfo
));
1051 } else if (ch
>= 'a' && ch
<= 'f') {
1052 num
+= 10 + (ch
- 'a');
1053 } else if (ch
>= 'A' && ch
<= 'F') {
1054 num
+= 10 + (ch
- 'A');
1056 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected character %c at line %d"), ch
, lineNumber(pInfo
));
1060 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
1064 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo
));
1067 CFStringAppendCharacters(string
, &ch
, 1);
1070 extern const void *__CFSetAddValueAndReturn(CFMutableSetRef set
, const void *value
);
1072 static CFStringRef
_uniqueStringForString(_CFXMLPlistParseInfo
*pInfo
, CFStringRef stringToUnique
) {
1073 if (!pInfo
->stringSet
) {
1074 pInfo
->stringSet
= CFSetCreateMutable(pInfo
->allocator
, 0, &kCFCopyStringSetCallBacks
);
1075 _CFSetSetCapacity(pInfo
->stringSet
, 160); // set capacity high to avoid lots of rehashes, though waste some memory
1077 return __CFSetAddValueAndReturn(pInfo
->stringSet
, stringToUnique
);
1080 extern void _CFStrSetDesiredCapacity(CFMutableStringRef str
, CFIndex len
);
1082 static CFStringRef
_uniqueStringForCharacters(_CFXMLPlistParseInfo
*pInfo
, const UniChar
*base
, CFIndex length
) {
1084 uint8_t *ascii
, buffer
[1024];
1086 if (!pInfo
->stringSet
) {
1087 pInfo
->stringSet
= CFSetCreateMutable(pInfo
->allocator
, 0, &kCFCopyStringSetCallBacks
);
1088 _CFSetSetCapacity(pInfo
->stringSet
, 160); // set capacity high to avoid lots of rehashes, though waste some memory
1090 if (pInfo
->tmpString
) {
1091 CFStringDelete(pInfo
->tmpString
, CFRangeMake(0, CFStringGetLength(pInfo
->tmpString
)));
1093 pInfo
->tmpString
= CFStringCreateMutable(pInfo
->allocator
, 0);
1094 _CFStrSetDesiredCapacity(pInfo
->tmpString
, 512);
1096 // This is to avoid having to promote the buffers of all the strings compared against
1097 // during the set probe; if a Unicode string is passed in, that's what happens.
1099 for (idx
= 0; isASCII
&& idx
< length
; idx
++) isASCII
= isASCII
&& (base
[idx
] < 0x80);
1101 ascii
= (length
< (CFIndex
)sizeof(buffer
)) ? buffer
: CFAllocatorAllocate(kCFAllocatorSystemDefault
, length
+ 1, 0);
1102 for (idx
= 0; idx
< length
; idx
++) ascii
[idx
] = (uint8_t)base
[idx
];
1103 ascii
[length
] = '\0';
1104 CFStringAppendCString(pInfo
->tmpString
, ascii
, kCFStringEncodingASCII
);
1105 if (ascii
!= buffer
) CFAllocatorDeallocate(kCFAllocatorSystemDefault
, ascii
);
1107 CFStringAppendCharacters(pInfo
->tmpString
, base
, length
);
1109 return __CFSetAddValueAndReturn(pInfo
->stringSet
, pInfo
->tmpString
);
1113 // String could be comprised of characters, CDSects, or references to one of the "well-known" entities ('<', '>', '&', ''', '"')
1114 // returns a retained object in *string.
1115 static CFStringRef
getString(_CFXMLPlistParseInfo
*pInfo
) {
1116 const UniChar
*mark
= pInfo
->curr
; // At any time in the while loop below, the characters between mark and p have not yet been added to *string
1117 CFMutableStringRef string
= NULL
;
1118 while (!pInfo
->errorString
&& pInfo
->curr
< pInfo
->end
) {
1119 UniChar ch
= *(pInfo
->curr
);
1121 if (pInfo
->curr
+ 1 >= pInfo
->end
) break;
1122 // Could be a CDSect; could be the end of the string
1123 if (*(pInfo
->curr
+1) != '!') break; // End of the string
1124 _catFromMarkToBuf(mark
, pInfo
->curr
, &string
, pInfo
->allocator
);
1125 parseCDSect_pl(pInfo
, string
);
1127 } else if (ch
== '&') {
1128 _catFromMarkToBuf(mark
, pInfo
->curr
, &string
, pInfo
->allocator
);
1129 parseEntityReference_pl(pInfo
, string
);
1136 if (pInfo
->errorString
) {
1137 if (string
) CFRelease(string
);
1141 if (pInfo
->mutabilityOption
!= kCFPropertyListMutableContainersAndLeaves
) {
1142 CFStringRef uniqueString
= _uniqueStringForCharacters(pInfo
, mark
, pInfo
->curr
-mark
);
1143 CFRetain(uniqueString
);
1144 return uniqueString
;
1146 string
= CFStringCreateMutable(pInfo
->allocator
, 0);
1147 CFStringAppendCharacters(string
, mark
, pInfo
->curr
- mark
);
1151 _catFromMarkToBuf(mark
, pInfo
->curr
, &string
, pInfo
->allocator
);
1152 if (pInfo
->mutabilityOption
!= kCFPropertyListMutableContainersAndLeaves
) {
1153 CFStringRef uniqueString
= _uniqueStringForString(pInfo
, string
);
1154 CFRetain(uniqueString
);
1156 return uniqueString
;
1161 static Boolean
checkForCloseTag(_CFXMLPlistParseInfo
*pInfo
, const UniChar
*tag
, CFIndex tagLen
) {
1162 if (pInfo
->end
- pInfo
->curr
< tagLen
+ 3) {
1163 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
1166 if (*(pInfo
->curr
) != '<' || *(++pInfo
->curr
) != '/') {
1167 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected character %c on line %d"), *(pInfo
->curr
), lineNumber(pInfo
));
1171 if (!matchString(pInfo
->curr
, tag
, tagLen
)) {
1172 CFStringRef str
= CFStringCreateWithCharactersNoCopy(pInfo
->allocator
, tag
, tagLen
, kCFAllocatorNull
);
1173 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Close tag on line %d does not match open tag %@"), lineNumber(pInfo
), str
);
1177 pInfo
->curr
+= tagLen
;
1178 skipWhitespace(pInfo
);
1179 if (pInfo
->curr
== pInfo
->end
) {
1180 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
1183 if (*(pInfo
->curr
) != '>') {
1184 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected character %c on line %d"), *(pInfo
->curr
), lineNumber(pInfo
));
1191 // pInfo should be set to the first content character of the <plist>
1192 static CFTypeRef
parsePListTag(_CFXMLPlistParseInfo
*pInfo
) {
1193 CFTypeRef result
, tmp
= NULL
;
1194 const UniChar
*save
;
1195 result
= getContentObject(pInfo
, NULL
);
1197 if (!pInfo
->errorString
) pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered empty plist tag", CFStringGetSystemEncoding());
1200 save
= pInfo
->curr
; // Save this in case the next step fails
1201 tmp
= getContentObject(pInfo
, NULL
);
1203 // Got an extra object
1207 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected element at line %d (plist can only include one object)"), lineNumber(pInfo
));
1210 if (pInfo
->errorString
) {
1211 // Parse failed catastrophically
1215 if (checkForCloseTag(pInfo
, CFXMLPlistTags
[PLIST_IX
], PLIST_TAG_LENGTH
)) {
1222 static int allowImmutableCollections
= -1;
1224 static void checkImmutableCollections(void) {
1225 allowImmutableCollections
= (NULL
== getenv("CFPropertyListAllowImmutableCollections")) ? 0 : 1;
1228 static CFTypeRef
parseArrayTag(_CFXMLPlistParseInfo
*pInfo
) {
1229 CFMutableArrayRef array
= CFArrayCreateMutable(pInfo
->allocator
, 0, &kCFTypeArrayCallBacks
);
1230 CFTypeRef tmp
= getContentObject(pInfo
, NULL
);
1232 CFArrayAppendValue(array
, tmp
);
1234 tmp
= getContentObject(pInfo
, NULL
);
1236 if (pInfo
->errorString
) { // getContentObject encountered a parse error
1240 if (checkForCloseTag(pInfo
, CFXMLPlistTags
[ARRAY_IX
], ARRAY_TAG_LENGTH
)) {
1241 if (-1 == allowImmutableCollections
) checkImmutableCollections();
1242 if (1 == allowImmutableCollections
) {
1243 if (pInfo
->mutabilityOption
== kCFPropertyListImmutable
) {
1244 CFArrayRef newArray
= CFArrayCreateCopy(pInfo
->allocator
, array
);
1246 array
= (CFMutableArrayRef
)newArray
;
1255 static CFTypeRef
parseDictTag(_CFXMLPlistParseInfo
*pInfo
) {
1256 CFMutableDictionaryRef dict
= NULL
;
1257 CFTypeRef key
=NULL
, value
=NULL
;
1259 const UniChar
*base
= pInfo
->curr
;
1260 key
= getContentObject(pInfo
, &gotKey
);
1263 if (key
) CFRelease(key
);
1264 if (dict
) CFRelease(dict
);
1266 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Found non-key inside <dict> at line %d"), lineNumber(pInfo
));
1269 value
= getContentObject(pInfo
, NULL
);
1271 if (key
) CFRelease(key
);
1272 if (dict
) CFRelease(dict
);
1273 if (!pInfo
->errorString
)
1274 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Value missing for key inside <dict> at line %d"), lineNumber(pInfo
));
1278 dict
= CFDictionaryCreateMutable(pInfo
->allocator
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1279 _CFDictionarySetCapacity(dict
, 10);
1281 CFDictionarySetValue(dict
, key
, value
);
1287 key
= getContentObject(pInfo
, &gotKey
);
1289 if (checkForCloseTag(pInfo
, CFXMLPlistTags
[DICT_IX
], DICT_TAG_LENGTH
)) {
1291 if (pInfo
->mutabilityOption
== kCFPropertyListImmutable
) {
1292 dict
= (CFMutableDictionaryRef
)CFDictionaryCreate(pInfo
->allocator
, NULL
, NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1294 dict
= CFDictionaryCreateMutable(pInfo
->allocator
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1297 CFIndex cnt
= CFDictionaryGetCount(dict
);
1299 CFTypeRef val
= CFDictionaryGetValue(dict
, CFSTR("CF$UID"));
1300 if (val
&& CFGetTypeID(val
) == CFNumberGetTypeID()) {
1303 CFNumberGetValue(val
, kCFNumberSInt32Type
, &v
);
1304 uid
= (CFTypeRef
)_CFKeyedArchiverUIDCreate(pInfo
->allocator
, v
);
1309 if (-1 == allowImmutableCollections
) checkImmutableCollections();
1310 if (1 == allowImmutableCollections
) {
1311 if (pInfo
->mutabilityOption
== kCFPropertyListImmutable
) {
1312 CFDictionaryRef newDict
= CFDictionaryCreateCopy(pInfo
->allocator
, dict
);
1314 dict
= (CFMutableDictionaryRef
)newDict
;
1320 if (dict
) CFRelease(dict
);
1324 static CFTypeRef
parseDataTag(_CFXMLPlistParseInfo
*pInfo
) {
1326 const UniChar
*base
= pInfo
->curr
;
1327 result
= __CFPLDataDecode(pInfo
, pInfo
->mutabilityOption
== kCFPropertyListMutableContainersAndLeaves
);
1330 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Could not interpret <data> at line %d (should be base64-encoded)"), lineNumber(pInfo
));
1333 if (checkForCloseTag(pInfo
, CFXMLPlistTags
[DATA_IX
], DATA_TAG_LENGTH
)) return result
;
1338 CF_INLINE Boolean
read2DigitNumber(_CFXMLPlistParseInfo
*pInfo
, int8_t *result
) {
1340 if (pInfo
->curr
+ 2 >= pInfo
->end
) return false;
1342 ch2
= *(pInfo
->curr
+ 1);
1344 if (!isdigit(ch1
) || !isdigit(ch2
)) return false;
1345 *result
= (ch1
- '0')*10 + (ch2
- '0');
1349 // YYYY '-' MM '-' DD 'T' hh ':' mm ':' ss 'Z'
1350 static CFTypeRef
parseDateTag(_CFXMLPlistParseInfo
*pInfo
) {
1351 CFGregorianDate date
;
1353 Boolean badForm
= false;
1356 while (pInfo
->curr
< pInfo
->end
&& isdigit(*pInfo
->curr
)) {
1357 date
.year
= 10*date
.year
+ (*pInfo
->curr
) - '0';
1360 if (pInfo
->curr
>= pInfo
->end
|| *pInfo
->curr
!= '-') {
1366 if (!badForm
&& read2DigitNumber(pInfo
, &date
.month
) && pInfo
->curr
< pInfo
->end
&& *pInfo
->curr
== '-') {
1372 if (!badForm
&& read2DigitNumber(pInfo
, &date
.day
) && pInfo
->curr
< pInfo
->end
&& *pInfo
->curr
== 'T') {
1378 if (!badForm
&& read2DigitNumber(pInfo
, &date
.hour
) && pInfo
->curr
< pInfo
->end
&& *pInfo
->curr
== ':') {
1384 if (!badForm
&& read2DigitNumber(pInfo
, &date
.minute
) && pInfo
->curr
< pInfo
->end
&& *pInfo
->curr
== ':') {
1390 if (!badForm
&& read2DigitNumber(pInfo
, &num
) && pInfo
->curr
< pInfo
->end
&& *pInfo
->curr
== 'Z') {
1398 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Could not interpret <date> at line %d"), lineNumber(pInfo
));
1401 if (!checkForCloseTag(pInfo
, CFXMLPlistTags
[DATE_IX
], DATE_TAG_LENGTH
)) return NULL
;
1402 return CFDateCreate(pInfo
->allocator
, CFGregorianDateGetAbsoluteTime(date
, NULL
));
1405 static CFTypeRef
parseRealTag(_CFXMLPlistParseInfo
*pInfo
) {
1406 CFStringRef str
= getString(pInfo
);
1410 CFStringInlineBuffer buf
;
1412 if (!pInfo
->errorString
)
1413 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered empty <real> on line %d"), lineNumber(pInfo
));
1417 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
1418 if (kCFCompareEqualTo
== CFStringCompare(str
, CFSTR("nan"), kCFCompareCaseInsensitive
)) {
1420 return (checkForCloseTag(pInfo
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
)) ? CFRetain(kCFNumberNaN
) : NULL
;
1422 if (kCFCompareEqualTo
== CFStringCompare(str
, CFSTR("+infinity"), kCFCompareCaseInsensitive
)) {
1424 return (checkForCloseTag(pInfo
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
)) ? CFRetain(kCFNumberPositiveInfinity
) : NULL
;
1426 if (kCFCompareEqualTo
== CFStringCompare(str
, CFSTR("-infinity"), kCFCompareCaseInsensitive
)) {
1428 return (checkForCloseTag(pInfo
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
)) ? CFRetain(kCFNumberNegativeInfinity
) : NULL
;
1430 if (kCFCompareEqualTo
== CFStringCompare(str
, CFSTR("infinity"), kCFCompareCaseInsensitive
)) {
1432 return (checkForCloseTag(pInfo
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
)) ? CFRetain(kCFNumberPositiveInfinity
) : NULL
;
1434 if (kCFCompareEqualTo
== CFStringCompare(str
, CFSTR("-inf"), kCFCompareCaseInsensitive
)) {
1436 return (checkForCloseTag(pInfo
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
)) ? CFRetain(kCFNumberNegativeInfinity
) : NULL
;
1438 if (kCFCompareEqualTo
== CFStringCompare(str
, CFSTR("inf"), kCFCompareCaseInsensitive
)) {
1440 return (checkForCloseTag(pInfo
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
)) ? CFRetain(kCFNumberPositiveInfinity
) : NULL
;
1442 if (kCFCompareEqualTo
== CFStringCompare(str
, CFSTR("+inf"), kCFCompareCaseInsensitive
)) {
1444 return (checkForCloseTag(pInfo
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
)) ? CFRetain(kCFNumberPositiveInfinity
) : NULL
;
1448 len
= CFStringGetLength(str
);
1449 CFStringInitInlineBuffer(str
, &buf
, CFRangeMake(0, len
));
1451 if (!__CFStringScanDouble(&buf
, NULL
, &idx
, &val
) || idx
!= len
) {
1453 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered misformatted real on line %d"), lineNumber(pInfo
));
1457 result
= CFNumberCreate(pInfo
->allocator
, kCFNumberDoubleType
, &val
);
1458 if (checkForCloseTag(pInfo
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
)) return result
;
1463 #define GET_CH if (pInfo->curr == pInfo->end) { \
1464 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Premature end of file after <integer> on line %d"), lineNumber(pInfo)); \
1469 static CFTypeRef
parseIntegerTag(_CFXMLPlistParseInfo
*pInfo
) {
1470 bool isHex
= false, isNeg
= false, hadLeadingZero
= false;
1471 int64_t value
= (int64_t)0;
1474 // decimal_constant S*(-|+)?S*[0-9]+ (S == space)
1475 // hex_constant S*(-|+)?S*0[xX][0-9a-fA-F]+ (S == space)
1477 while (pInfo
->curr
< pInfo
->end
&& __CFIsWhitespace(*(pInfo
->curr
))) pInfo
->curr
++;
1480 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered empty <integer> on line %d"), lineNumber(pInfo
));
1483 if ('-' == ch
|| '+' == ch
) {
1484 isNeg
= ('-' == ch
);
1486 while (pInfo
->curr
< pInfo
->end
&& __CFIsWhitespace(*(pInfo
->curr
))) pInfo
->curr
++;
1490 if (pInfo
->curr
+ 1 < pInfo
->end
&& ('x' == *(pInfo
->curr
+ 1) || 'X' == *(pInfo
->curr
+ 1))) {
1494 hadLeadingZero
= true;
1500 hadLeadingZero
= true;
1504 if ('<' == ch
&& hadLeadingZero
) { // nothing but zeros
1506 if (!checkForCloseTag(pInfo
, CFXMLPlistTags
[INTEGER_IX
], INTEGER_TAG_LENGTH
)) {
1507 // checkForCloseTag() sets error string
1510 return CFNumberCreate(pInfo
->allocator
, kCFNumberSInt32Type
, &val
);
1513 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Incomplete <integer> on line %d"), lineNumber(pInfo
));
1517 int64_t old_value
= value
;
1519 case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
1520 value
= (isHex
? 16 : 10) * value
+ (ch
- '0');
1522 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
1524 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Hex digit in non-hex <integer> on line %d"), lineNumber(pInfo
));
1527 value
= 16 * value
+ (ch
- 'a' + 10);
1529 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
1531 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Hex digit in non-hex <integer> on line %d"), lineNumber(pInfo
));
1534 value
= 16 * value
+ (ch
- 'A' + 10);
1536 default: // other character
1537 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Unknown character '%c' (0x%x) in <integer> on line %d"), ch
, ch
, lineNumber(pInfo
));
1540 if (isNeg
&& LLONG_MIN
== value
) {
1541 // overflow by one when isNeg gives the proper value, if we're done with the number
1542 if (pInfo
->curr
+ 1 < pInfo
->end
&& '<' == *(pInfo
->curr
+ 1)) {
1548 if (value
< old_value
) {
1549 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered <integer> too large to represent on line %d"), lineNumber(pInfo
));
1555 if (!checkForCloseTag(pInfo
, CFXMLPlistTags
[INTEGER_IX
], INTEGER_TAG_LENGTH
)) {
1556 // checkForCloseTag() sets error string
1559 if (isNeg
) value
= -value
;
1560 return CFNumberCreate(pInfo
->allocator
, kCFNumberSInt64Type
, &value
);
1565 // Returned object is retained; caller must free. pInfo->curr expected to point to the first character after the '<'
1566 static CFTypeRef
parseXMLElement(_CFXMLPlistParseInfo
*pInfo
, Boolean
*isKey
) {
1567 const UniChar
*marker
= pInfo
->curr
;
1568 int markerLength
= -1;
1572 if (isKey
) *isKey
= false;
1573 while (pInfo
->curr
< pInfo
->end
) {
1574 UniChar ch
= *(pInfo
->curr
);
1575 if (ch
== ' ' || ch
== '\t' || ch
== '\n' || ch
=='\r') {
1576 if (markerLength
== -1) markerLength
= pInfo
->curr
- marker
;
1577 } else if (ch
== '>') {
1582 if (pInfo
->curr
>= pInfo
->end
) return NULL
;
1583 isEmpty
= (*(pInfo
->curr
-1) == '/');
1584 if (markerLength
== -1)
1585 markerLength
= pInfo
->curr
- (isEmpty
? 1 : 0) - marker
;
1586 pInfo
->curr
++; // Advance past '>'
1587 if (markerLength
== 0) {
1588 // Back up to the beginning of the marker
1589 pInfo
->curr
= marker
;
1590 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Malformed tag on line %d"), lineNumber(pInfo
));
1595 if (markerLength
== ARRAY_TAG_LENGTH
&& matchString(marker
, CFXMLPlistTags
[ARRAY_IX
], ARRAY_TAG_LENGTH
))
1596 markerIx
= ARRAY_IX
;
1598 case 'd': // Dictionary, data, or date; Fortunately, they all have the same marker length....
1599 if (markerLength
!= DICT_TAG_LENGTH
)
1601 if (matchString(marker
, CFXMLPlistTags
[DICT_IX
], DICT_TAG_LENGTH
))
1603 else if (matchString(marker
, CFXMLPlistTags
[DATA_IX
], DATA_TAG_LENGTH
))
1605 else if (matchString(marker
, CFXMLPlistTags
[DATE_IX
], DATE_TAG_LENGTH
))
1608 case 'f': // false (boolean)
1609 if (markerLength
== FALSE_TAG_LENGTH
&& matchString(marker
, CFXMLPlistTags
[FALSE_IX
], FALSE_TAG_LENGTH
)) {
1610 markerIx
= FALSE_IX
;
1613 case 'i': // integer
1614 if (markerLength
== INTEGER_TAG_LENGTH
&& matchString(marker
, CFXMLPlistTags
[INTEGER_IX
], INTEGER_TAG_LENGTH
))
1615 markerIx
= INTEGER_IX
;
1617 case 'k': // Key of a dictionary
1618 if (markerLength
== KEY_TAG_LENGTH
&& matchString(marker
, CFXMLPlistTags
[KEY_IX
], KEY_TAG_LENGTH
)) {
1620 if (isKey
) *isKey
= true;
1624 if (markerLength
== PLIST_TAG_LENGTH
&& matchString(marker
, CFXMLPlistTags
[PLIST_IX
], PLIST_TAG_LENGTH
))
1625 markerIx
= PLIST_IX
;
1628 if (markerLength
== REAL_TAG_LENGTH
&& matchString(marker
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
))
1632 if (markerLength
== STRING_TAG_LENGTH
&& matchString(marker
, CFXMLPlistTags
[STRING_IX
], STRING_TAG_LENGTH
))
1633 markerIx
= STRING_IX
;
1635 case 't': // true (boolean)
1636 if (markerLength
== TRUE_TAG_LENGTH
&& matchString(marker
, CFXMLPlistTags
[TRUE_IX
], TRUE_TAG_LENGTH
))
1641 if (!pInfo
->allowNewTypes
&& markerIx
!= PLIST_IX
&& markerIx
!= ARRAY_IX
&& markerIx
!= DICT_IX
&& markerIx
!= STRING_IX
&& markerIx
!= KEY_IX
&& markerIx
!= DATA_IX
) {
1642 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered new tag when expecting only old-style property list objects", CFStringGetSystemEncoding());
1649 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered empty plist tag", CFStringGetSystemEncoding());
1652 return parsePListTag(pInfo
);
1655 return pInfo
->mutabilityOption
== kCFPropertyListImmutable
? CFArrayCreate(pInfo
->allocator
, NULL
, 0, &kCFTypeArrayCallBacks
) : CFArrayCreateMutable(pInfo
->allocator
, 0, &kCFTypeArrayCallBacks
);
1657 return parseArrayTag(pInfo
);
1661 if (pInfo
->mutabilityOption
== kCFPropertyListImmutable
) {
1662 return CFDictionaryCreate(pInfo
->allocator
, NULL
, NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1664 return CFDictionaryCreateMutable(pInfo
->allocator
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1667 return parseDictTag(pInfo
);
1673 int tagLen
= (markerIx
== KEY_IX
) ? KEY_TAG_LENGTH
: STRING_TAG_LENGTH
;
1675 return pInfo
->mutabilityOption
== kCFPropertyListMutableContainersAndLeaves
? CFStringCreateMutable(pInfo
->allocator
, 0) : CFStringCreateWithCharacters(pInfo
->allocator
, NULL
, 0);
1677 str
= getString(pInfo
);
1678 if (!str
) return NULL
; // getString will already have set the error string
1679 if (!checkForCloseTag(pInfo
, CFXMLPlistTags
[markerIx
], tagLen
)) {
1687 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered empty <data> on line %d"), lineNumber(pInfo
));
1690 return parseDataTag(pInfo
);
1694 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered empty <date> on line %d"), lineNumber(pInfo
));
1697 return parseDateTag(pInfo
);
1701 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered non-empty <true> tag on line %d"), lineNumber(pInfo
));
1704 return CFRetain(kCFBooleanTrue
);
1708 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered non-empty <false> tag on line %d"), lineNumber(pInfo
));
1711 return CFRetain(kCFBooleanFalse
);
1715 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered empty <real> on line %d"), lineNumber(pInfo
));
1718 return parseRealTag(pInfo
);
1722 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered empty <integer> on line %d"), lineNumber(pInfo
));
1725 return parseIntegerTag(pInfo
);
1728 CFStringRef markerStr
= CFStringCreateWithCharacters(pInfo
->allocator
, marker
, markerLength
);
1729 pInfo
->curr
= marker
;
1730 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unknown tag %@ on line %d"), markerStr
, lineNumber(pInfo
));
1731 CFRelease(markerStr
);
1737 static CFTypeRef
parseXMLPropertyList(_CFXMLPlistParseInfo
*pInfo
) {
1738 while (!pInfo
->errorString
&& pInfo
->curr
< pInfo
->end
) {
1740 skipWhitespace(pInfo
);
1741 if (pInfo
->curr
+1 >= pInfo
->end
) {
1742 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "No XML content found", CFStringGetSystemEncoding());
1745 if (*(pInfo
->curr
) != '<') {
1746 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Unexpected character %c at line %d"), *(pInfo
->curr
), lineNumber(pInfo
));
1749 ch
= *(++ pInfo
->curr
);
1753 if (pInfo
->curr
+1 < pInfo
->end
&& *pInfo
->curr
== '-' && *(pInfo
->curr
+1) == '-') {
1756 skipXMLComment(pInfo
);
1760 } else if (ch
== '?') {
1761 // Processing instruction
1763 skipXMLProcessingInstruction(pInfo
);
1766 return parseXMLElement(pInfo
, NULL
);
1767 // Note we do not verify that there was only one element, so a file that has garbage after the first element will nonetheless successfully parse
1770 // Should never get here
1771 if (!(pInfo
->errorString
))
1772 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
1776 static CFStringEncoding
encodingForXMLData(CFDataRef data
, CFStringRef
*error
) {
1777 const uint8_t *bytes
= (uint8_t *)CFDataGetBytePtr(data
);
1778 UInt32 length
= CFDataGetLength(data
);
1779 const uint8_t *idx
, *end
;
1782 // Check for the byte order mark first
1784 ((*bytes
== 0xFF && *(bytes
+1) == 0xFE) ||
1785 (*bytes
== 0xFE && *(bytes
+1) == 0xFF) ||
1786 *bytes
== 0x00 || *(bytes
+1) == 0x00)) // This clause checks for a Unicode sequence lacking the byte order mark; technically an error, but this check is recommended by the XML spec
1787 return kCFStringEncodingUnicode
;
1789 // Scan for the <?xml.... ?> opening
1790 if (length
< 5 || strncmp((char const *) bytes
, "<?xml", 5) != 0) return kCFStringEncodingUTF8
;
1792 end
= bytes
+ length
;
1793 // Found "<?xml"; now we scan for "encoding"
1796 const uint8_t *scan
;
1797 if ( ch
== '?' || ch
== '>') return kCFStringEncodingUTF8
;
1800 if (ch
== 'e' && *scan
++ == 'n' && *scan
++ == 'c' && *scan
++ == 'o' && *scan
++ == 'd' && *scan
++ == 'i'
1801 && *scan
++ == 'n' && *scan
++ == 'g' && *scan
++ == '=') {
1806 if (idx
>= end
) return kCFStringEncodingUTF8
;
1808 if (quote
!= '\'' && quote
!= '\"') return kCFStringEncodingUTF8
;
1810 CFStringRef encodingName
;
1811 const uint8_t *base
= idx
+1; // Move past the quote character
1812 CFStringEncoding enc
;
1815 while (idx
< end
&& *idx
!= quote
) idx
++;
1816 if (idx
>= end
) return kCFStringEncodingUTF8
;
1818 if (len
== 5 && (*base
== 'u' || *base
== 'U') && (base
[1] == 't' || base
[1] == 'T') && (base
[2] == 'f' || base
[2] == 'F') && (base
[3] == '-') && (base
[4] == '8'))
1819 return kCFStringEncodingUTF8
;
1820 encodingName
= CFStringCreateWithBytes(NULL
, base
, len
, kCFStringEncodingISOLatin1
, false);
1821 enc
= CFStringConvertIANACharSetNameToEncoding(encodingName
);
1822 if (enc
!= kCFStringEncodingInvalidId
) {
1823 CFRelease(encodingName
);
1828 *error
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("Encountered unknown encoding (%@)"), encodingName
);
1829 CFRelease(encodingName
);
1835 CFTypeRef __CFNastyFile__
= NULL
;
1836 static CFSpinLock_t __CFNastyFileLock__
= 0;
1838 void __CFSetNastyFile(CFTypeRef cf
) {
1839 __CFSpinLock(&__CFNastyFileLock__
);
1840 if (__CFNastyFile__
) CFRelease(__CFNastyFile__
);
1841 __CFNastyFile__
= cf
? CFRetain(cf
) : cf
;
1842 __CFSpinUnlock(&__CFNastyFileLock__
);
1845 extern bool __CFTryParseBinaryPlist(CFAllocatorRef allocator
, CFDataRef data
, CFOptionFlags option
, CFPropertyListRef
*plist
, CFStringRef
*errorString
);
1846 int32_t _CFPropertyListAllowNonUTF8
= 0;
1848 CFTypeRef
_CFPropertyListCreateFromXMLData(CFAllocatorRef allocator
, CFDataRef xmlData
, CFOptionFlags option
, CFStringRef
*errorString
, Boolean allowNewTypes
, CFPropertyListFormat
*format
) {
1849 CFStringEncoding encoding
;
1850 CFStringRef xmlString
;
1852 CFPropertyListRef plist
;
1854 if (errorString
) *errorString
= NULL
;
1855 if (!xmlData
|| CFDataGetLength(xmlData
) == 0) {
1857 *errorString
= CFSTR("Cannot parse a NULL or zero-length data");
1858 CFRetain(*errorString
); // Caller expects to release
1863 if (__CFTryParseBinaryPlist(allocator
, xmlData
, option
, &plist
, errorString
)) {
1864 if (format
) *format
= kCFPropertyListBinaryFormat_v1_0
;
1868 allocator
= allocator
? allocator
: __CFGetDefaultAllocator();
1869 CFRetain(allocator
);
1871 encoding
= encodingForXMLData(xmlData
, errorString
); // 0 is an error return, NOT MacRoman.
1873 if (encoding
== 0) {
1874 // Couldn't find an encoding; encodingForXMLData already set *errorString if necessary
1875 // Note that encodingForXMLData() will give us the right values for a standard plist, too.
1876 if (errorString
) *errorString
= CFStringCreateWithFormat(allocator
, NULL
, CFSTR("Could not determine the encoding of the XML data"));
1880 xmlString
= CFStringCreateWithBytes(allocator
, CFDataGetBytePtr(xmlData
), CFDataGetLength(xmlData
), encoding
, true);
1881 if (NULL
== xmlString
&& (!_CFExecutableLinkedOnOrAfter(CFSystemVersionChablis
) || _CFPropertyListAllowNonUTF8
)) { // conversion failed, probably because not in proper encoding
1882 CFTypeRef f
= (__CFNastyFile__
) ? (__CFNastyFile__
) : CFSTR("(UNKNOWN)");
1883 if (encoding
== kCFStringEncodingUTF8
) {
1884 CFLog(0, CFSTR("\n\tCFPropertyListCreateFromXMLData(): plist parse failed; the data is not proper UTF-8. The file name for this data could be:\n\t%@\n\tThe parser will retry as in 10.2, but the problem should be corrected in the plist."), f
);
1887 CFLog(0, CFSTR("\n\tCFPropertyListCreateFromXMLData(): conversion of data failed.\n\tThe file is not in the encoding specified in XML header if XML.\n\tThe file name for this data could be:\n\t\t%@\n."), f
);
1890 // Call __CFStringCreateImmutableFunnel3() the same way CFStringCreateWithBytes() does, except with the addt'l flag
1891 if (encoding
== kCFStringEncodingUTF8
) xmlString
= __CFStringCreateImmutableFunnel3(allocator
, CFDataGetBytePtr(xmlData
), CFDataGetLength(xmlData
), kCFStringEncodingUTF8
, true, true, false, false, false, (void *)-1 /* ALLOCATORSFREEFUNC */, kCFStringEncodingLenientUTF8Conversion
);
1893 length
= xmlString
? CFStringGetLength(xmlString
) : 0;
1896 _CFXMLPlistParseInfo pInfoBuf
;
1897 _CFXMLPlistParseInfo
*pInfo
= &pInfoBuf
;
1899 UniChar
*buf
= (UniChar
*)CFStringGetCharactersPtr(xmlString
);
1901 if (errorString
) *errorString
= NULL
;
1903 buf
= (UniChar
*)CFAllocatorAllocate(allocator
, length
* sizeof(UniChar
), 0);
1904 CFStringGetCharacters(xmlString
, CFRangeMake(0, length
), buf
);
1905 CFRelease(xmlString
);
1909 pInfo
->end
= buf
+length
;
1911 pInfo
->allocator
= allocator
;
1912 pInfo
->errorString
= NULL
;
1913 pInfo
->stringSet
= NULL
;
1914 pInfo
->tmpString
= NULL
;
1915 pInfo
->mutabilityOption
= option
;
1916 pInfo
->allowNewTypes
= allowNewTypes
;
1918 // Haven't done anything XML-specific to this point. However, the encoding we used to translate the bytes should be kept in mind; we used Unicode if the byte-order mark was present; UTF-8 otherwise. If the system encoding is not UTF-8 or some variant of 7-bit ASCII, we'll be in trouble.....
1919 result
= parseXMLPropertyList(pInfo
);
1920 if (result
&& format
) *format
= kCFPropertyListXMLFormat_v1_0
;
1922 CFStringRef err
= pInfo
->errorString
;
1923 // Reset pInfo so we can try again
1924 pInfo
->curr
= pInfo
->begin
;
1925 pInfo
->errorString
= NULL
;
1927 result
= parseOldStylePropertyListOrStringsFile(pInfo
);
1928 if (result
&& format
) *format
= kCFPropertyListOpenStepFormat
;
1930 if (errorString
) *errorString
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("XML parser error:\n\t%@\nOld-style plist parser error:\n\t%@\n"), err
, pInfo
->errorString
);
1932 if (err
) CFRelease(err
);
1933 if (pInfo
->errorString
) CFRelease(pInfo
->errorString
);
1936 CFRelease(xmlString
);
1938 CFAllocatorDeallocate(allocator
, (void *)pInfo
->begin
);
1940 if (pInfo
->stringSet
) CFRelease(pInfo
->stringSet
);
1941 if (pInfo
->tmpString
) CFRelease(pInfo
->tmpString
);
1942 CFRelease(allocator
);
1946 *errorString
= CFRetain(CFSTR("Conversion of data failed. The file is not UTF-8, or in the encoding specified in XML header if XML."));
1951 CFTypeRef
CFPropertyListCreateFromXMLData(CFAllocatorRef allocator
, CFDataRef xmlData
, CFOptionFlags option
, CFStringRef
*errorString
) {
1952 CFAssert1(xmlData
!= NULL
, __kCFLogAssertion
, "%s(): NULL data not allowed", __PRETTY_FUNCTION__
);
1953 CFAssert2(option
== kCFPropertyListImmutable
|| option
== kCFPropertyListMutableContainers
|| option
== kCFPropertyListMutableContainersAndLeaves
, __kCFLogAssertion
, "%s(): Unrecognized option %d", __PRETTY_FUNCTION__
, option
);
1954 return _CFPropertyListCreateFromXMLData(allocator
, xmlData
, option
, errorString
, true, NULL
);
1957 CFIndex
CFPropertyListWriteToStream(CFPropertyListRef propertyList
, CFWriteStreamRef stream
, CFPropertyListFormat format
, CFStringRef
*errorString
) {
1958 CFAssert1(stream
!= NULL
, __kCFLogAssertion
, "%s(): NULL stream not allowed", __PRETTY_FUNCTION__
);
1959 CFAssert2(format
== kCFPropertyListOpenStepFormat
|| format
== kCFPropertyListXMLFormat_v1_0
|| format
== kCFPropertyListBinaryFormat_v1_0
, __kCFLogAssertion
, "%s(): Unrecognized option %d", __PRETTY_FUNCTION__
, format
);
1960 CFAssert1(propertyList
!= NULL
, __kCFLogAssertion
, "%s(): Cannot be called with a NULL property list", __PRETTY_FUNCTION__
);
1961 __CFAssertIsPList(propertyList
);
1962 CFAssert1(CFWriteStreamGetTypeID() == CFGetTypeID(stream
), __kCFLogAssertion
, "%s(): stream argument is not a write stream", __PRETTY_FUNCTION__
);
1963 CFAssert1(kCFStreamStatusOpen
== CFWriteStreamGetStatus(stream
) || kCFStreamStatusWriting
== CFWriteStreamGetStatus(stream
), __kCFLogAssertion
, "%s(): stream is not open", __PRETTY_FUNCTION__
);
1965 if (errorString
) *errorString
= NULL
;
1966 if (!CFPropertyListIsValid(propertyList
, format
)) {
1967 if (errorString
) *errorString
= CFRetain(CFSTR("Property list invalid for format"));
1970 if (format
== kCFPropertyListOpenStepFormat
) {
1971 if (errorString
) *errorString
= CFRetain(CFSTR("Property list format kCFPropertyListOpenStepFormat not supported for writing"));
1974 if (format
== kCFPropertyListXMLFormat_v1_0
) {
1975 CFDataRef data
= CFPropertyListCreateXMLData(kCFAllocatorSystemDefault
, propertyList
);
1976 CFIndex len
= data
? CFDataGetLength(data
) : 0;
1977 CFIndex ret
= CFWriteStreamWrite(stream
, CFDataGetBytePtr(data
), len
);
1983 if (format
== kCFPropertyListBinaryFormat_v1_0
) {
1984 CFIndex len
= __CFBinaryPlistWriteToStream(propertyList
, stream
);
1987 if (errorString
) *errorString
= CFRetain(CFSTR("Unknown format option"));
1991 static void __CFConvertReadStreamToBytes(CFReadStreamRef stream
, CFIndex max
, uint8_t **buffer
, CFIndex
*length
) {
1992 int32_t buflen
= 0, bufsize
= 0, retlen
;
1993 uint8_t *buf
= NULL
, sbuf
[8192];
1995 retlen
= CFReadStreamRead(stream
, sbuf
, __CFMin(8192, max
));
1997 if (retlen
<= 0 || max
<= 0) {
2002 if (bufsize
< buflen
+ retlen
) {
2003 bufsize
= 2 * bufsize
;
2004 if (bufsize
< buflen
+ retlen
) bufsize
= buflen
+ retlen
;
2005 buf
= CFAllocatorReallocate(kCFAllocatorSystemDefault
, buf
, bufsize
, 0);
2007 memmove(buf
+ buflen
, sbuf
, retlen
);
2012 CFPropertyListRef
CFPropertyListCreateFromStream(CFAllocatorRef allocator
, CFReadStreamRef stream
, CFIndex length
, CFOptionFlags mutabilityOption
, CFPropertyListFormat
*format
, CFStringRef
*errorString
) {
2013 CFPropertyListRef pl
;
2016 uint8_t *buffer
= NULL
;
2017 CFAssert1(stream
!= NULL
, __kCFLogAssertion
, "%s(): NULL stream not allowed", __PRETTY_FUNCTION__
);
2018 CFAssert1(CFReadStreamGetTypeID() == CFGetTypeID(stream
), __kCFLogAssertion
, "%s(): stream argument is not a read stream", __PRETTY_FUNCTION__
);
2019 CFAssert1(kCFStreamStatusOpen
== CFReadStreamGetStatus(stream
) || kCFStreamStatusReading
== CFReadStreamGetStatus(stream
), __kCFLogAssertion
, "%s(): stream is not open", __PRETTY_FUNCTION__
);
2020 CFAssert2(mutabilityOption
== kCFPropertyListImmutable
|| mutabilityOption
== kCFPropertyListMutableContainers
|| mutabilityOption
== kCFPropertyListMutableContainersAndLeaves
, __kCFLogAssertion
, "%s(): Unrecognized option %d", __PRETTY_FUNCTION__
, mutabilityOption
);
2022 if (errorString
) *errorString
= NULL
;
2023 if (0 == length
) length
= INT_MAX
;
2024 __CFConvertReadStreamToBytes(stream
, length
, &buffer
, &buflen
);
2025 if (!buffer
|| buflen
< 6) {
2026 if (buffer
) CFAllocatorDeallocate(kCFAllocatorSystemDefault
, buffer
);
2027 if (errorString
) *errorString
= CFRetain(CFSTR("stream had too few bytes"));
2030 data
= CFDataCreateWithBytesNoCopy(kCFAllocatorSystemDefault
, buffer
, buflen
, kCFAllocatorSystemDefault
);
2031 pl
= _CFPropertyListCreateFromXMLData(allocator
, data
, mutabilityOption
, errorString
, true, format
);
2036 // ========================================================================
2039 // Old NeXT-style property lists
2042 static CFTypeRef
parsePlistObject(_CFXMLPlistParseInfo
*pInfo
, bool requireObject
);
2044 #define isValidUnquotedStringCharacter(x) (((x) >= 'a' && (x) <= 'z') || ((x) >= 'A' && (x) <= 'Z') || ((x) >= '0' && (x) <= '9') || (x) == '_' || (x) == '$' || (x) == '/' || (x) == ':' || (x) == '.' || (x) == '-')
2046 static void advanceToNonSpace(_CFXMLPlistParseInfo
*pInfo
) {
2048 while (pInfo
->curr
< pInfo
->end
) {
2049 ch2
= *(pInfo
->curr
);
2051 if (ch2
>= 9 && ch2
<= 0x0d) continue; // tab, newline, vt, form feed, carriage return
2052 if (ch2
== ' ' || ch2
== 0x2028 || ch2
== 0x2029) continue; // space and Unicode line sep, para sep
2054 if (pInfo
->curr
>= pInfo
->end
) {
2055 // whoops; back up and return
2058 } else if (*(pInfo
->curr
) == '/') {
2060 while (pInfo
->curr
< pInfo
->end
) { // go to end of comment line
2061 UniChar ch3
= *(pInfo
->curr
);
2062 if (ch3
== '\n' || ch3
== '\r' || ch3
== 0x2028 || ch3
== 0x2029) break;
2065 } else if (*(pInfo
->curr
) == '*') { // handle /* ... */
2067 while (pInfo
->curr
< pInfo
->end
) {
2068 ch2
= *(pInfo
->curr
);
2070 if (ch2
== '*' && pInfo
->curr
< pInfo
->end
&& *(pInfo
->curr
) == '/') {
2071 pInfo
->curr
++; // advance past the '/'
2086 static UniChar
getSlashedChar(_CFXMLPlistParseInfo
*pInfo
) {
2087 UniChar ch
= *(pInfo
->curr
);
2098 uint8_t num
= ch
- '0';
2101 /* three digits maximum to avoid reading \000 followed by 5 as \5 ! */
2102 if ((ch
= *(pInfo
->curr
)) >= '0' && ch
<= '7') { // we use in this test the fact that the buffer is zero-terminated
2104 num
= (num
<< 3) + ch
- '0';
2105 if ((pInfo
->curr
< pInfo
->end
) && (ch
= *(pInfo
->curr
)) >= '0' && ch
<= '7') {
2107 num
= (num
<< 3) + ch
- '0';
2110 CFStringEncodingBytesToUnicode(kCFStringEncodingNextStepLatin
, 0, &num
, sizeof(uint8_t), NULL
, &result
, 1, &usedCharLen
);
2111 return (usedCharLen
== 1) ? result
: 0;
2114 unsigned num
= 0, numDigits
= 4; /* Parse four digits */
2115 while (pInfo
->curr
< pInfo
->end
&& numDigits
--) {
2116 if (((ch
= *(pInfo
->curr
)) < 128) && isxdigit(ch
)) {
2118 num
= (num
<< 4) + ((ch
<= '9') ? (ch
- '0') : ((ch
<= 'F') ? (ch
- 'A' + 10) : (ch
- 'a' + 10)));
2123 case 'a': return '\a'; // Note: the meaning of '\a' varies with -traditional to gcc
2124 case 'b': return '\b';
2125 case 'f': return '\f';
2126 case 'n': return '\n';
2127 case 'r': return '\r';
2128 case 't': return '\t';
2129 case 'v': return '\v';
2130 case '"': return '\"';
2131 case '\n': return '\n';
2136 static CFStringRef
parseQuotedPlistString(_CFXMLPlistParseInfo
*pInfo
, UniChar quote
) {
2137 CFMutableStringRef str
= NULL
;
2138 const UniChar
*startMark
= pInfo
->curr
;
2139 const UniChar
*mark
= pInfo
->curr
;
2140 while (pInfo
->curr
< pInfo
->end
) {
2141 UniChar ch
= *(pInfo
->curr
);
2142 if (ch
== quote
) break;
2144 _catFromMarkToBuf(mark
, pInfo
->curr
, &str
, pInfo
->allocator
);
2146 ch
= getSlashedChar(pInfo
);
2147 CFStringAppendCharacters(str
, &ch
, 1);
2150 // 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.
2154 if (pInfo
->end
<= pInfo
->curr
) {
2155 if (str
) CFRelease(str
);
2156 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2157 pInfo
->curr
= startMark
;
2158 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Unterminated quoted string starting on line %d"), lineNumber(pInfo
));
2163 if (pInfo
->mutabilityOption
== kCFPropertyListMutableContainersAndLeaves
) {
2164 _catFromMarkToBuf(mark
, pInfo
->curr
, &str
, pInfo
->allocator
);
2166 str
= (CFMutableStringRef
)_uniqueStringForCharacters(pInfo
, mark
, pInfo
->curr
-mark
);
2170 if (mark
!= pInfo
->curr
) {
2171 _catFromMarkToBuf(mark
, pInfo
->curr
, &str
, pInfo
->allocator
);
2173 if (pInfo
->mutabilityOption
!= kCFPropertyListMutableContainersAndLeaves
) {
2174 CFStringRef uniqueString
= _uniqueStringForString(pInfo
, str
);
2176 CFRetain(uniqueString
);
2177 str
= (CFMutableStringRef
)uniqueString
;
2180 pInfo
->curr
++; // Advance past the quote character before returning.
2181 if (pInfo
->errorString
) {
2182 CFRelease(pInfo
->errorString
);
2183 pInfo
->errorString
= NULL
;
2188 static CFStringRef
parseUnquotedPlistString(_CFXMLPlistParseInfo
*pInfo
) {
2189 const UniChar
*mark
= pInfo
->curr
;
2190 while (pInfo
->curr
< pInfo
->end
) {
2191 UniChar ch
= *pInfo
->curr
;
2192 if (isValidUnquotedStringCharacter(ch
))
2196 if (pInfo
->curr
!= mark
) {
2197 if (pInfo
->mutabilityOption
!= kCFPropertyListMutableContainersAndLeaves
) {
2198 CFStringRef str
= _uniqueStringForCharacters(pInfo
, mark
, pInfo
->curr
-mark
);
2202 CFMutableStringRef str
= CFStringCreateMutable(pInfo
->allocator
, 0);
2203 CFStringAppendCharacters(str
, mark
, pInfo
->curr
- mark
);
2207 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2208 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Unexpected EOF"));
2213 static CFStringRef
parsePlistString(_CFXMLPlistParseInfo
*pInfo
, bool requireObject
) {
2215 advanceToNonSpace(pInfo
);
2216 if (pInfo
->curr
>= pInfo
->end
) {
2217 if (requireObject
&& _CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2218 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Unexpected EOF while parsing string"));
2222 ch
= *(pInfo
->curr
);
2223 if (ch
== '\'' || ch
== '\"') {
2225 return parseQuotedPlistString(pInfo
, ch
);
2226 } else if (isValidUnquotedStringCharacter(ch
)) {
2227 return parseUnquotedPlistString(pInfo
);
2229 if (requireObject
&& _CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2230 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Invalid string character at line %d"), lineNumber(pInfo
));
2236 static CFTypeRef
parsePlistArray(_CFXMLPlistParseInfo
*pInfo
) {
2237 CFMutableArrayRef array
= CFArrayCreateMutable(pInfo
->allocator
, 0, &kCFTypeArrayCallBacks
);
2238 CFTypeRef tmp
= parsePlistObject(pInfo
, false);
2240 CFArrayAppendValue(array
, tmp
);
2242 advanceToNonSpace(pInfo
);
2243 if (*pInfo
->curr
!= ',') {
2247 tmp
= parsePlistObject(pInfo
, false);
2250 advanceToNonSpace(pInfo
);
2251 if (*pInfo
->curr
!= ')') {
2253 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2254 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Expected terminating ')' for array at line %d"), lineNumber(pInfo
));
2258 if (pInfo
->errorString
) {
2259 CFRelease(pInfo
->errorString
);
2260 pInfo
->errorString
= NULL
;
2266 static CFDictionaryRef
parsePlistDictContent(_CFXMLPlistParseInfo
*pInfo
) {
2267 CFMutableDictionaryRef dict
= CFDictionaryCreateMutable(pInfo
->allocator
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2268 CFStringRef key
= NULL
;
2269 Boolean failedParse
= false;
2270 key
= parsePlistString(pInfo
, false);
2273 advanceToNonSpace(pInfo
);
2274 if (*pInfo
->curr
== ';') {
2275 /* This is a strings file using the shortcut format */
2276 /* although this check here really applies to all plists. */
2277 value
= CFRetain(key
);
2278 } else if (*pInfo
->curr
== '=') {
2280 value
= parsePlistObject(pInfo
, true);
2286 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2287 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Unexpected ';' or '=' after key at line %d"), lineNumber(pInfo
));
2292 CFDictionarySetValue(dict
, key
, value
);
2297 advanceToNonSpace(pInfo
);
2298 if (*pInfo
->curr
== ';') {
2300 key
= parsePlistString(pInfo
, false);
2301 } else if (!allowMissingSemi
&& _CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2302 static int yanmode
= -1;
2303 if (-1 == yanmode
) yanmode
= (getenv("YanMode") != NULL
);
2305 CFLog(0, CFSTR("CFPropertyListCreateFromXMLData(): Old-style plist parser: missing semicolon in dictionary."));
2306 if (__CFNastyFile__
) {
2307 CFLog(0, CFSTR("CFPropertyListCreateFromXMLData(): The file name for this data might be (or it might not): %@"), __CFNastyFile__
);
2311 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Missing ';' on line %d"), lineNumber(pInfo
));
2313 // on pre-Jaguar systems, do nothing except silently ignore the rest
2314 // of the dictionary, which is what happened on those systems.
2319 if (key
) CFRelease(key
);
2323 if (pInfo
->errorString
) {
2324 CFRelease(pInfo
->errorString
);
2325 pInfo
->errorString
= NULL
;
2330 static CFTypeRef
parsePlistDict(_CFXMLPlistParseInfo
*pInfo
) {
2331 CFDictionaryRef dict
= parsePlistDictContent(pInfo
);
2332 if (!dict
) return NULL
;
2333 advanceToNonSpace(pInfo
);
2334 if (*pInfo
->curr
!= '}') {
2336 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2337 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Expected terminating '}' for dictionary at line %d"), lineNumber(pInfo
));
2345 CF_INLINE
unsigned char fromHexDigit(unsigned char ch
) {
2346 if (isdigit(ch
)) return ch
- '0';
2347 if ((ch
>= 'a') && (ch
<= 'f')) return ch
- 'a' + 10;
2348 if ((ch
>= 'A') && (ch
<= 'F')) return ch
- 'A' + 10;
2349 return 0xff; // Just choose a large number for the error code
2352 /* Gets up to bytesSize bytes from a plist data. Returns number of bytes actually read. Leaves cursor at first non-space, non-hex character.
2353 -1 is returned for unexpected char, -2 for uneven number of hex digits
2355 static int getDataBytes(_CFXMLPlistParseInfo
*pInfo
, unsigned char *bytes
, int bytesSize
) {
2356 int numBytesRead
= 0;
2357 while ((pInfo
->curr
< pInfo
->end
) && (numBytesRead
< bytesSize
)) {
2359 UniChar ch1
= *pInfo
->curr
;
2360 if (ch1
== '>') return numBytesRead
; // Meaning we're done
2361 first
= fromHexDigit(ch1
);
2362 if (first
!= 0xff) { // If the first char is a hex, then try to read a second hex
2364 if (pInfo
->curr
>= pInfo
->end
) return -2; // Error: uneven number of hex digits
2365 UniChar ch2
= *pInfo
->curr
;
2366 second
= fromHexDigit(ch2
);
2367 if (second
== 0xff) return -2; // Error: uneven number of hex digits
2368 bytes
[numBytesRead
++] = (first
<< 4) + second
;
2370 } else if (ch1
== ' ' || ch1
== '\n' || ch1
== '\t' || ch1
== '\r' || ch1
== 0x2028 || ch1
== 0x2029) {
2373 return -1; // Error: unexpected character
2376 return numBytesRead
; // This does likely mean we didn't encounter a '>', but we'll let the caller deal with that
2379 static CFTypeRef
parsePlistData(_CFXMLPlistParseInfo
*pInfo
) {
2380 CFMutableDataRef result
= CFDataCreateMutable(pInfo
->allocator
, 0);
2382 // Read hex bytes and append them to result
2384 #define numBytes 400
2385 unsigned char bytes
[numBytes
];
2386 int numBytesRead
= getDataBytes(pInfo
, bytes
, numBytes
);
2387 if (numBytesRead
< 0) {
2389 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2390 switch (numBytesRead
) {
2391 case -2: pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Malformed data byte group at line %d; uneven length"), lineNumber(pInfo
)); break;
2392 default: pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Malformed data byte group at line %d; invalid hex"), lineNumber(pInfo
)); break;
2397 if (numBytesRead
== 0) break;
2398 CFDataAppendBytes(result
, bytes
, numBytesRead
);
2401 if (pInfo
->errorString
) {
2402 CFRelease(pInfo
->errorString
);
2403 pInfo
->errorString
= NULL
;
2406 if (*(pInfo
->curr
) == '>') {
2407 pInfo
->curr
++; // Move past '>'
2411 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2412 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Expected terminating '>' for data at line %d"), lineNumber(pInfo
));
2418 // Returned object is retained; caller must free.
2419 static CFTypeRef
parsePlistObject(_CFXMLPlistParseInfo
*pInfo
, bool requireObject
) {
2421 advanceToNonSpace(pInfo
);
2422 if (pInfo
->curr
+ 1 >= pInfo
->end
) {
2423 if (requireObject
&& _CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2424 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Unexpected EOF while parsing plist"));
2428 ch
= *(pInfo
->curr
);
2431 return parsePlistDict(pInfo
);
2432 } else if (ch
== '(') {
2433 return parsePlistArray(pInfo
);
2434 } else if (ch
== '<') {
2435 return parsePlistData(pInfo
);
2436 } else if (ch
== '\'' || ch
== '\"') {
2437 return parseQuotedPlistString(pInfo
, ch
);
2438 } else if (isValidUnquotedStringCharacter(ch
)) {
2440 return parseUnquotedPlistString(pInfo
);
2442 pInfo
->curr
--; // Must back off the charcter we just read
2443 if (requireObject
&& _CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2444 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Unexpected character '0x%x' at line %d"), ch
, lineNumber(pInfo
));
2450 static CFTypeRef
parseOldStylePropertyListOrStringsFile(_CFXMLPlistParseInfo
*pInfo
) {
2451 const UniChar
*begin
= pInfo
->curr
;
2453 advanceToNonSpace(pInfo
);
2454 // A file consisting only of whitespace (or empty) is now defined to be an empty dictionary
2455 if (pInfo
->curr
>= pInfo
->end
) return CFDictionaryCreateMutable(pInfo
->allocator
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2456 result
= parsePlistObject(pInfo
, true);
2457 advanceToNonSpace(pInfo
);
2458 if (pInfo
->curr
>= pInfo
->end
) return result
;
2459 if (!result
) return NULL
;
2460 if (CFGetTypeID(result
) != CFStringGetTypeID()) {
2462 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2463 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Junk after plist at line %d"), lineNumber(pInfo
));
2468 // Check for a strings file (looks like a dictionary without the opening/closing curly braces)
2469 pInfo
->curr
= begin
;
2470 return parsePlistDictContent(pInfo
);
2473 #undef isValidUnquotedStringCharacter
2475 static CFArrayRef
_arrayDeepImmutableCopy(CFAllocatorRef allocator
, CFArrayRef array
, CFOptionFlags mutabilityOption
) {
2476 CFArrayRef result
= NULL
;
2477 CFIndex i
, c
= CFArrayGetCount(array
);
2480 result
= CFArrayCreate(allocator
, NULL
, 0, &kCFTypeArrayCallBacks
);
2481 } else if ((values
= CFAllocatorAllocate(allocator
, c
*sizeof(CFTypeRef
), 0)) != NULL
) {
2482 CFArrayGetValues(array
, CFRangeMake(0, c
), values
);
2483 for (i
= 0; i
< c
; i
++) {
2484 values
[i
] = CFPropertyListCreateDeepCopy(allocator
, values
[i
], mutabilityOption
);
2485 if (values
[i
] == NULL
) {
2489 result
= (i
== c
) ? CFArrayCreate(allocator
, values
, c
, &kCFTypeArrayCallBacks
) : NULL
;
2491 for (i
= 0; i
< c
; i
++) {
2492 CFRelease(values
[i
]);
2494 CFAllocatorDeallocate(allocator
, values
);
2499 static CFMutableArrayRef
_arrayDeepMutableCopy(CFAllocatorRef allocator
, CFArrayRef array
, CFOptionFlags mutabilityOption
) {
2500 CFIndex i
, c
= CFArrayGetCount(array
);
2501 CFMutableArrayRef result
= CFArrayCreateMutable(allocator
, 0, &kCFTypeArrayCallBacks
);
2503 for (i
= 0; i
< c
; i
++) {
2504 CFTypeRef newValue
= CFPropertyListCreateDeepCopy(allocator
, CFArrayGetValueAtIndex(array
, i
), mutabilityOption
);
2505 if (!newValue
) break;
2506 CFArrayAppendValue(result
, newValue
);
2507 CFRelease(newValue
);
2517 CFPropertyListRef
CFPropertyListCreateDeepCopy(CFAllocatorRef allocator
, CFPropertyListRef propertyList
, CFOptionFlags mutabilityOption
) {
2519 CFPropertyListRef result
= NULL
;
2520 CFAssert1(propertyList
!= NULL
, __kCFLogAssertion
, "%s(): cannot copy a NULL property list", __PRETTY_FUNCTION__
);
2521 __CFAssertIsPList(propertyList
);
2522 CFAssert2(mutabilityOption
== kCFPropertyListImmutable
|| mutabilityOption
== kCFPropertyListMutableContainers
|| mutabilityOption
== kCFPropertyListMutableContainersAndLeaves
, __kCFLogAssertion
, "%s(): Unrecognized option %d", __PRETTY_FUNCTION__
, mutabilityOption
);
2523 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2524 if (!CFPropertyListIsValid(propertyList
, kCFPropertyListBinaryFormat_v1_0
)) return NULL
;
2527 if (allocator
== NULL
) {
2528 allocator
= CFRetain(__CFGetDefaultAllocator());
2530 CFRetain(allocator
);
2533 typeID
= CFGetTypeID(propertyList
);
2534 if (typeID
== CFDictionaryGetTypeID()) {
2535 CFDictionaryRef dict
= (CFDictionaryRef
)propertyList
;
2536 Boolean
mutable = (mutabilityOption
!= kCFPropertyListImmutable
);
2537 CFIndex count
= CFDictionaryGetCount(dict
);
2538 CFTypeRef
*keys
, *values
;
2540 result
= mutable ? CFDictionaryCreateMutable(allocator
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
): CFDictionaryCreate(allocator
, NULL
, NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2541 } else if ((keys
= CFAllocatorAllocate(allocator
, 2 * count
* sizeof(CFTypeRef
), 0)) != NULL
) {
2543 values
= keys
+count
;
2544 CFDictionaryGetKeysAndValues(dict
, keys
, values
);
2545 for (i
= 0; i
< count
; i
++) {
2546 keys
[i
] = CFStringCreateCopy(allocator
, keys
[i
]);
2547 if (keys
[i
] == NULL
) {
2550 values
[i
] = CFPropertyListCreateDeepCopy(allocator
, values
[i
], mutabilityOption
);
2551 if (values
[i
] == NULL
) {
2557 result
= mutable ? CFDictionaryCreateMutable(allocator
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
) : CFDictionaryCreate(allocator
, keys
, values
, count
, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2558 for (i
= 0; i
< count
; i
++) {
2560 CFDictionarySetValue((CFMutableDictionaryRef
)result
, keys
[i
], values
[i
]);
2563 CFRelease(values
[i
]);
2568 for (i
= 0; i
< count
; i
++) {
2570 CFRelease(values
[i
]);
2573 CFAllocatorDeallocate(allocator
, keys
);
2577 } else if (typeID
== CFArrayGetTypeID()) {
2578 if (mutabilityOption
== kCFPropertyListImmutable
) {
2579 result
= _arrayDeepImmutableCopy(allocator
, (CFArrayRef
)propertyList
, mutabilityOption
);
2581 result
= _arrayDeepMutableCopy(allocator
, (CFArrayRef
)propertyList
, mutabilityOption
);
2583 } else if (typeID
== CFDataGetTypeID()) {
2584 if (mutabilityOption
== kCFPropertyListMutableContainersAndLeaves
) {
2585 result
= CFDataCreateMutableCopy(allocator
, 0, (CFDataRef
)propertyList
);
2587 result
= CFDataCreateCopy(allocator
, (CFDataRef
)propertyList
);
2589 } else if (typeID
== CFNumberGetTypeID()) {
2590 // Warning - this will break if byteSize is ever greater than 16
2592 CFNumberType numType
= CFNumberGetType((CFNumberRef
)propertyList
);
2593 CFNumberGetValue((CFNumberRef
)propertyList
, numType
, (void *)bytes
);
2594 result
= CFNumberCreate(allocator
, numType
, (void *)bytes
);
2595 } else if (typeID
== CFBooleanGetTypeID()) {
2596 // Booleans are immutable & shared instances
2597 CFRetain(propertyList
);
2598 result
= propertyList
;
2599 } else if (typeID
== CFDateGetTypeID()) {
2600 // Dates are immutable
2601 result
= CFDateCreate(allocator
, CFDateGetAbsoluteTime((CFDateRef
)propertyList
));
2602 } else if (typeID
== CFStringGetTypeID()) {
2603 if (mutabilityOption
== kCFPropertyListMutableContainersAndLeaves
) {
2604 result
= CFStringCreateMutableCopy(allocator
, 0, (CFStringRef
)propertyList
);
2606 result
= CFStringCreateCopy(allocator
, (CFStringRef
)propertyList
);
2609 CFAssert2(false, __kCFLogAssertion
, "%s(): 0x%x is not a property list type", __PRETTY_FUNCTION__
, propertyList
);
2612 CFRelease(allocator
);