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>
46 __private_extern__
bool allowMissingSemi
= false;
48 // Should move this somewhere else
49 intptr_t _CFDoOperation(intptr_t code
, intptr_t subcode1
, intptr_t subcode2
) {
51 case 15317: allowMissingSemi
= subcode1
? true : false; break;
70 #define PLIST_TAG_LENGTH 5
71 #define ARRAY_TAG_LENGTH 5
72 #define DICT_TAG_LENGTH 4
73 #define KEY_TAG_LENGTH 3
74 #define STRING_TAG_LENGTH 6
75 #define DATA_TAG_LENGTH 4
76 #define DATE_TAG_LENGTH 4
77 #define REAL_TAG_LENGTH 4
78 #define INTEGER_TAG_LENGTH 7
79 #define TRUE_TAG_LENGTH 4
80 #define FALSE_TAG_LENGTH 5
81 #define DOCTYPE_TAG_LENGTH 7
82 #define CDSECT_TAG_LENGTH 9
84 // don't allow _CFKeyedArchiverUID here
85 #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);
87 static bool __CFPropertyListIsValidAux(CFPropertyListRef plist
, bool recursive
, CFMutableSetRef set
, CFPropertyListFormat format
);
92 CFPropertyListFormat format
;
95 static void __CFPropertyListIsArrayPlistAux(const void *value
, void *context
) {
96 struct context
*ctx
= (struct context
*)context
;
97 if (!ctx
->answer
) return;
99 if (!value
) CFLog(0, CFSTR("CFPropertyListIsValid(): property list arrays cannot contain NULL"));
101 ctx
->answer
= value
&& __CFPropertyListIsValidAux(value
, true, ctx
->set
, ctx
->format
);
104 static void __CFPropertyListIsDictPlistAux(const void *key
, const void *value
, void *context
) {
105 struct context
*ctx
= (struct context
*)context
;
106 if (!ctx
->answer
) return;
108 if (!key
) CFLog(0, CFSTR("CFPropertyListIsValid(): property list dictionaries cannot contain NULL keys"));
109 if (!value
) CFLog(0, CFSTR("CFPropertyListIsValid(): property list dictionaries cannot contain NULL values"));
110 if (CFStringGetTypeID() != CFGetTypeID(key
)) {
111 CFStringRef desc
= CFCopyTypeIDDescription(CFGetTypeID(key
));
112 CFLog(0, CFSTR("CFPropertyListIsValid(): property list dictionaries may only have keys which are CFStrings, not '%@'"), desc
);
116 ctx
->answer
= key
&& value
&& (CFStringGetTypeID() == CFGetTypeID(key
)) && __CFPropertyListIsValidAux(value
, true, ctx
->set
, ctx
->format
);
119 static bool __CFPropertyListIsValidAux(CFPropertyListRef plist
, bool recursive
, CFMutableSetRef set
, CFPropertyListFormat format
) {
122 if (!plist
) CFLog(0, CFSTR("CFPropertyListIsValid(): property lists cannot contain NULL"));
124 if (!plist
) return false;
125 type
= CFGetTypeID(plist
);
126 if (CFStringGetTypeID() == type
) return true;
127 if (CFDataGetTypeID() == type
) return true;
128 if (kCFPropertyListOpenStepFormat
!= format
) {
129 if (CFBooleanGetTypeID() == type
) return true;
130 if (CFNumberGetTypeID() == type
) return true;
131 if (CFDateGetTypeID() == type
) return true;
132 if (_CFKeyedArchiverUIDGetTypeID() == type
) return true;
134 if (!recursive
&& CFArrayGetTypeID() == type
) return true;
135 if (!recursive
&& CFDictionaryGetTypeID() == type
) return true;
136 // at any one invocation of this function, set should contain the objects in the "path" down to this object
138 if (CFSetContainsValue(set
, plist
)) CFLog(0, CFSTR("CFPropertyListIsValid(): property lists cannot contain recursive container references"));
140 if (CFSetContainsValue(set
, plist
)) return false;
141 if (CFArrayGetTypeID() == type
) {
142 struct context ctx
= {true, set
, format
};
143 CFSetAddValue(set
, plist
);
144 CFArrayApplyFunction(plist
, CFRangeMake(0, CFArrayGetCount(plist
)), __CFPropertyListIsArrayPlistAux
, &ctx
);
145 CFSetRemoveValue(set
, plist
);
148 if (CFDictionaryGetTypeID() == type
) {
149 struct context ctx
= {true, set
, format
};
150 CFSetAddValue(set
, plist
);
151 CFDictionaryApplyFunction(plist
, __CFPropertyListIsDictPlistAux
, &ctx
);
152 CFSetRemoveValue(set
, plist
);
157 CFStringRef desc
= CFCopyTypeIDDescription(type
);
158 CFLog(0, CFSTR("CFPropertyListIsValid(): property lists cannot contain objects of type '%@'"), desc
);
165 Boolean
CFPropertyListIsValid(CFPropertyListRef plist
, CFPropertyListFormat format
) {
168 CFAssert1(plist
!= NULL
, __kCFLogAssertion
, "%s(): NULL is not a property list", __PRETTY_FUNCTION__
);
169 set
= CFSetCreateMutable(kCFAllocatorSystemDefault
, 0, NULL
);
170 result
= __CFPropertyListIsValidAux(plist
, true, set
, format
);
175 static const UniChar CFXMLPlistTags
[13][10]= {
176 {'p', 'l', 'i', 's', 't', '\0', '\0', '\0', '\0', '\0'},
177 {'a', 'r', 'r', 'a', 'y', '\0', '\0', '\0', '\0', '\0'},
178 {'d', 'i', 'c', 't', '\0', '\0', '\0', '\0', '\0', '\0'},
179 {'k', 'e', 'y', '\0', '\0', '\0', '\0', '\0', '\0', '\0'},
180 {'s', 't', 'r', 'i', 'n', 'g', '\0', '\0', '\0', '\0'},
181 {'d', 'a', 't', 'a', '\0', '\0', '\0', '\0', '\0', '\0'},
182 {'d', 'a', 't', 'e', '\0', '\0', '\0', '\0', '\0', '\0'},
183 {'r', 'e', 'a', 'l', '\0', '\0', '\0', '\0', '\0', '\0'},
184 {'i', 'n', 't', 'e', 'g', 'e', 'r', '\0', '\0', '\0'},
185 {'t', 'r', 'u', 'e', '\0', '\0', '\0', '\0', '\0', '\0'},
186 {'f', 'a', 'l', 's', 'e', '\0', '\0', '\0', '\0', '\0'},
187 {'D', 'O', 'C', 'T', 'Y', 'P', 'E', '\0', '\0', '\0'},
188 {'<', '!', '[', 'C', 'D', 'A', 'T', 'A', '[', '\0'}
192 const UniChar
*begin
; // first character of the XML to be parsed
193 const UniChar
*curr
; // current parse location
194 const UniChar
*end
; // the first character _after_ the end of the XML
195 CFStringRef errorString
;
196 CFAllocatorRef allocator
;
197 UInt32 mutabilityOption
;
198 CFMutableSetRef stringSet
; // set of all strings involved in this parse; allows us to share non-mutable strings in the returned plist
199 CFMutableStringRef tmpString
; // Mutable string with external characters that functions can feel free to use as temporary storage as the parse progresses
200 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)
202 } _CFXMLPlistParseInfo
;
204 static CFTypeRef
parseOldStylePropertyListOrStringsFile(_CFXMLPlistParseInfo
*pInfo
);
208 // 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.
210 // Null-terminated, ASCII or UTF8 string
212 static void _plistAppendUTF8CString(CFMutableDataRef mData
, const char *cString
) {
213 CFDataAppendBytes (mData
, (const UInt8
*)cString
, strlen(cString
));
218 static void _plistAppendCharacters(CFMutableDataRef mData
, const UniChar
*chars
, CFIndex length
) {
221 do { // Flush out ASCII chars, BUFLEN at a time
223 UInt8 buf
[BUFLEN
], *bufPtr
= buf
;
225 while (cnt
< length
&& (cnt
- curLoc
< BUFLEN
) && (chars
[cnt
] < 128)) *bufPtr
++ = (UInt8
)(chars
[cnt
++]);
226 if (cnt
> curLoc
) { // Flush any ASCII bytes
227 CFDataAppendBytes(mData
, buf
, cnt
- curLoc
);
230 } while (curLoc
< length
&& (chars
[curLoc
] < 128)); // We will exit out of here when we run out of chars or hit a non-ASCII char
232 if (curLoc
< length
) { // Now deal with non-ASCII chars
233 CFDataRef data
= NULL
;
234 CFStringRef str
= NULL
;
235 if ((str
= CFStringCreateWithCharactersNoCopy(NULL
, chars
+ curLoc
, length
- curLoc
, kCFAllocatorNull
))) {
236 if ((data
= CFStringCreateExternalRepresentation(NULL
, str
, kCFStringEncodingUTF8
, 0))) {
237 CFDataAppendBytes (mData
, CFDataGetBytePtr(data
), CFDataGetLength(data
));
242 CFAssert1(str
&& data
, __kCFLogAssertion
, "%s(): Error writing plist", __PRETTY_FUNCTION__
);
248 static void _plistAppendString(CFMutableDataRef mData
, CFStringRef str
) {
249 const UniChar
*chars
;
252 if ((chars
= CFStringGetCharactersPtr(str
))) {
253 _plistAppendCharacters(mData
, chars
, CFStringGetLength(str
));
254 } else if ((cStr
= CFStringGetCStringPtr(str
, kCFStringEncodingASCII
)) || (cStr
= CFStringGetCStringPtr(str
, kCFStringEncodingUTF8
))) {
255 _plistAppendUTF8CString(mData
, cStr
);
256 } else if ((data
= CFStringCreateExternalRepresentation(NULL
, str
, kCFStringEncodingUTF8
, 0))) {
257 CFDataAppendBytes (mData
, CFDataGetBytePtr(data
), CFDataGetLength(data
));
260 CFAssert1(TRUE
, __kCFLogAssertion
, "%s(): Error in plist writing", __PRETTY_FUNCTION__
);
265 // Append CFString-style format + arguments
267 static void _plistAppendFormat(CFMutableDataRef mData
, CFStringRef format
, ...) {
271 va_start(argList
, format
);
272 fStr
= CFStringCreateWithFormatAndArguments(NULL
, NULL
, format
, argList
);
275 CFAssert1(fStr
, __kCFLogAssertion
, "%s(): Error writing plist", __PRETTY_FUNCTION__
);
276 _plistAppendString(mData
, fStr
);
282 static void _appendIndents(CFIndex numIndents
, CFMutableDataRef str
) {
284 static const UniChar tabs
[NUMTABS
] = {'\t','\t','\t','\t'};
285 for (; numIndents
> 0; numIndents
-= NUMTABS
) _plistAppendCharacters(str
, tabs
, (numIndents
>= NUMTABS
) ? NUMTABS
: numIndents
);
288 /* Append the escaped version of origStr to mStr.
290 static void _appendEscapedString(CFStringRef origStr
, CFMutableDataRef mStr
) {
292 CFIndex i
, length
= CFStringGetLength(origStr
);
294 UniChar buf
[BUFSIZE
];
295 CFStringInlineBuffer inlineBuffer
;
297 CFStringInitInlineBuffer(origStr
, &inlineBuffer
, CFRangeMake(0, length
));
299 for (i
= 0; i
< length
; i
++) {
300 UniChar ch
= __CFStringGetCharacterFromInlineBufferQuick(&inlineBuffer
, i
);
303 if (bufCnt
) _plistAppendCharacters(mStr
, buf
, bufCnt
);
305 _plistAppendUTF8CString(mStr
, "<");
308 if (bufCnt
) _plistAppendCharacters(mStr
, buf
, bufCnt
);
310 _plistAppendUTF8CString(mStr
, ">");
313 if (bufCnt
) _plistAppendCharacters(mStr
, buf
, bufCnt
);
315 _plistAppendUTF8CString(mStr
, "&");
319 if (bufCnt
== BUFSIZE
) {
320 _plistAppendCharacters(mStr
, buf
, bufCnt
);
326 if (bufCnt
) _plistAppendCharacters(mStr
, buf
, bufCnt
);
331 /* Base-64 encoding/decoding */
333 /* The base-64 encoding packs three 8-bit bytes into four 7-bit ASCII
334 * characters. If the number of bytes in the original data isn't divisable
335 * by three, "=" characters are used to pad the encoded data. The complete
336 * set of characters used in base-64 are:
346 // Write the inputData to the mData using Base 64 encoding
348 static void _XMLPlistAppendDataUsingBase64(CFMutableDataRef mData
, CFDataRef inputData
, CFIndex indent
) {
349 static const char __CFPLDataEncodeTable
[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
350 #define MAXLINELEN 76
351 char buf
[MAXLINELEN
+ 4 + 2]; // For the slop and carriage return and terminating NULL
353 const uint8_t *bytes
= CFDataGetBytePtr(inputData
);
354 CFIndex length
= CFDataGetLength(inputData
);
358 if (indent
> 8) indent
= 8; // refuse to indent more than 64 characters
360 pos
= 0; // position within buf
362 for (i
= 0, p
= bytes
; i
< length
; i
++, p
++) {
363 /* 3 bytes are encoded as 4 */
366 buf
[pos
++] = __CFPLDataEncodeTable
[ ((p
[0] >> 2) & 0x3f)];
369 buf
[pos
++] = __CFPLDataEncodeTable
[ ((((p
[-1] << 8) | p
[0]) >> 4) & 0x3f)];
372 buf
[pos
++] = __CFPLDataEncodeTable
[ ((((p
[-1] << 8) | p
[0]) >> 6) & 0x3f)];
373 buf
[pos
++] = __CFPLDataEncodeTable
[ (p
[0] & 0x3f)];
376 /* Flush the line out every 76 (or fewer) chars --- indents count against the line length*/
377 if (pos
>= MAXLINELEN
- 8 * indent
) {
380 _appendIndents(indent
, mData
);
381 _plistAppendUTF8CString(mData
, buf
);
390 buf
[pos
++] = __CFPLDataEncodeTable
[ ((p
[-1] << 4) & 0x30)];
395 buf
[pos
++] = __CFPLDataEncodeTable
[ ((p
[-1] << 2) & 0x3c)];
403 _appendIndents(indent
, mData
);
404 _plistAppendUTF8CString(mData
, buf
);
408 extern CFStringRef
__CFNumberCopyFormattingDescriptionAsFloat64(CFTypeRef cf
);
410 static void _CFAppendXML0(CFTypeRef object
, UInt32 indentation
, CFMutableDataRef xmlString
) {
411 UInt32 typeID
= CFGetTypeID(object
);
412 _appendIndents(indentation
, xmlString
);
413 if (typeID
== CFStringGetTypeID()) {
414 _plistAppendUTF8CString(xmlString
, "<");
415 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[STRING_IX
], STRING_TAG_LENGTH
);
416 _plistAppendUTF8CString(xmlString
, ">");
417 _appendEscapedString(object
, xmlString
);
418 _plistAppendUTF8CString(xmlString
, "</");
419 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[STRING_IX
], STRING_TAG_LENGTH
);
420 _plistAppendUTF8CString(xmlString
, ">\n");
421 } else if (typeID
== _CFKeyedArchiverUIDGetTypeID()) {
422 _plistAppendUTF8CString(xmlString
, "<");
423 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[DICT_IX
], DICT_TAG_LENGTH
);
424 _plistAppendUTF8CString(xmlString
, ">\n");
425 _appendIndents(indentation
+1, xmlString
);
426 _plistAppendUTF8CString(xmlString
, "<");
427 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[KEY_IX
], KEY_TAG_LENGTH
);
428 _plistAppendUTF8CString(xmlString
, ">");
429 _appendEscapedString(CFSTR("CF$UID"), xmlString
);
430 _plistAppendUTF8CString(xmlString
, "</");
431 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[KEY_IX
], KEY_TAG_LENGTH
);
432 _plistAppendUTF8CString(xmlString
, ">\n");
433 _appendIndents(indentation
+ 1, xmlString
);
434 _plistAppendUTF8CString(xmlString
, "<");
435 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[INTEGER_IX
], INTEGER_TAG_LENGTH
);
436 _plistAppendUTF8CString(xmlString
, ">");
438 uint64_t v
= _CFKeyedArchiverUIDGetValue(object
);
439 CFNumberRef num
= CFNumberCreate(kCFAllocatorSystemDefault
, kCFNumberSInt64Type
, &v
);
440 _plistAppendFormat(xmlString
, CFSTR("%@"), num
);
443 _plistAppendUTF8CString(xmlString
, "</");
444 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[INTEGER_IX
], INTEGER_TAG_LENGTH
);
445 _plistAppendUTF8CString(xmlString
, ">\n");
446 _appendIndents(indentation
, xmlString
);
447 _plistAppendUTF8CString(xmlString
, "</");
448 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[DICT_IX
], DICT_TAG_LENGTH
);
449 _plistAppendUTF8CString(xmlString
, ">\n");
450 } else if (typeID
== CFArrayGetTypeID()) {
451 UInt32 i
, count
= CFArrayGetCount(object
);
453 _plistAppendUTF8CString(xmlString
, "<");
454 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[ARRAY_IX
], ARRAY_TAG_LENGTH
);
455 _plistAppendUTF8CString(xmlString
, "/>\n");
458 _plistAppendUTF8CString(xmlString
, "<");
459 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[ARRAY_IX
], ARRAY_TAG_LENGTH
);
460 _plistAppendUTF8CString(xmlString
, ">\n");
461 for (i
= 0; i
< count
; i
++) {
462 _CFAppendXML0(CFArrayGetValueAtIndex(object
, i
), indentation
+1, xmlString
);
464 _appendIndents(indentation
, xmlString
);
465 _plistAppendUTF8CString(xmlString
, "</");
466 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[ARRAY_IX
], ARRAY_TAG_LENGTH
);
467 _plistAppendUTF8CString(xmlString
, ">\n");
468 } else if (typeID
== CFDictionaryGetTypeID()) {
469 UInt32 i
, count
= CFDictionaryGetCount(object
);
470 CFAllocatorRef allocator
= CFGetAllocator(xmlString
);
471 CFMutableArrayRef keyArray
;
474 _plistAppendUTF8CString(xmlString
, "<");
475 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[DICT_IX
], DICT_TAG_LENGTH
);
476 _plistAppendUTF8CString(xmlString
, "/>\n");
479 _plistAppendUTF8CString(xmlString
, "<");
480 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[DICT_IX
], DICT_TAG_LENGTH
);
481 _plistAppendUTF8CString(xmlString
, ">\n");
482 keys
= (CFTypeRef
*)CFAllocatorAllocate(allocator
, count
* sizeof(CFTypeRef
), 0);
483 CFDictionaryGetKeysAndValues(object
, keys
, NULL
);
484 keyArray
= CFArrayCreateMutable(allocator
, count
, &kCFTypeArrayCallBacks
);
485 CFArrayReplaceValues(keyArray
, CFRangeMake(0, 0), keys
, count
);
486 CFArraySortValues(keyArray
, CFRangeMake(0, count
), (CFComparatorFunction
)CFStringCompare
, NULL
);
487 CFArrayGetValues(keyArray
, CFRangeMake(0, count
), keys
);
489 for (i
= 0; i
< count
; i
++) {
490 CFTypeRef key
= keys
[i
];
491 _appendIndents(indentation
+1, xmlString
);
492 _plistAppendUTF8CString(xmlString
, "<");
493 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[KEY_IX
], KEY_TAG_LENGTH
);
494 _plistAppendUTF8CString(xmlString
, ">");
495 _appendEscapedString(key
, xmlString
);
496 _plistAppendUTF8CString(xmlString
, "</");
497 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[KEY_IX
], KEY_TAG_LENGTH
);
498 _plistAppendUTF8CString(xmlString
, ">\n");
499 _CFAppendXML0(CFDictionaryGetValue(object
, key
), indentation
+1, xmlString
);
501 CFAllocatorDeallocate(allocator
, keys
);
502 _appendIndents(indentation
, xmlString
);
503 _plistAppendUTF8CString(xmlString
, "</");
504 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[DICT_IX
], DICT_TAG_LENGTH
);
505 _plistAppendUTF8CString(xmlString
, ">\n");
506 } else if (typeID
== CFDataGetTypeID()) {
507 _plistAppendUTF8CString(xmlString
, "<");
508 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[DATA_IX
], DATA_TAG_LENGTH
);
509 _plistAppendUTF8CString(xmlString
, ">\n");
510 _XMLPlistAppendDataUsingBase64(xmlString
, object
, indentation
);
511 _appendIndents(indentation
, xmlString
);
512 _plistAppendUTF8CString(xmlString
, "</");
513 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[DATA_IX
], DATA_TAG_LENGTH
);
514 _plistAppendUTF8CString(xmlString
, ">\n");
515 } else if (typeID
== CFDateGetTypeID()) {
516 // YYYY '-' MM '-' DD 'T' hh ':' mm ':' ss 'Z'
517 CFGregorianDate date
= CFAbsoluteTimeGetGregorianDate(CFDateGetAbsoluteTime(object
), NULL
);
519 _plistAppendUTF8CString(xmlString
, "<");
520 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[DATE_IX
], DATE_TAG_LENGTH
);
521 _plistAppendUTF8CString(xmlString
, ">");
523 _plistAppendFormat(xmlString
, CFSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), date
.year
, date
.month
, date
.day
, date
.hour
, date
.minute
, (int)date
.second
);
525 _plistAppendUTF8CString(xmlString
, "</");
526 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[DATE_IX
], DATE_TAG_LENGTH
);
527 _plistAppendUTF8CString(xmlString
, ">\n");
528 } else if (typeID
== CFNumberGetTypeID()) {
529 if (CFNumberIsFloatType(object
)) {
530 _plistAppendUTF8CString(xmlString
, "<");
531 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
);
532 _plistAppendUTF8CString(xmlString
, ">");
534 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
535 CFStringRef s
= __CFNumberCopyFormattingDescriptionAsFloat64(object
);
536 _plistAppendString(xmlString
, s
);
538 } else if (CFNumberGetType(object
) == kCFNumberFloat64Type
|| CFNumberGetType(object
) == kCFNumberDoubleType
) {
540 static CFStringRef doubleFormatString
= NULL
;
541 CFNumberGetValue(object
, kCFNumberDoubleType
, &doubleVal
);
542 if (!doubleFormatString
) {
543 doubleFormatString
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%%.%de"), DBL_DIG
);
545 _plistAppendFormat(xmlString
, doubleFormatString
, doubleVal
);
548 static CFStringRef floatFormatString
= NULL
;
549 CFNumberGetValue(object
, kCFNumberFloatType
, &floatVal
);
550 if (!floatFormatString
) {
551 floatFormatString
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("%%.%de"), FLT_DIG
);
553 _plistAppendFormat(xmlString
, floatFormatString
, floatVal
);
556 _plistAppendUTF8CString(xmlString
, "</");
557 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
);
558 _plistAppendUTF8CString(xmlString
, ">\n");
560 _plistAppendUTF8CString(xmlString
, "<");
561 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[INTEGER_IX
], INTEGER_TAG_LENGTH
);
562 _plistAppendUTF8CString(xmlString
, ">");
564 _plistAppendFormat(xmlString
, CFSTR("%@"), object
);
566 _plistAppendUTF8CString(xmlString
, "</");
567 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[INTEGER_IX
], INTEGER_TAG_LENGTH
);
568 _plistAppendUTF8CString(xmlString
, ">\n");
570 } else if (typeID
== CFBooleanGetTypeID()) {
571 if (CFBooleanGetValue(object
)) {
572 _plistAppendUTF8CString(xmlString
, "<");
573 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[TRUE_IX
], TRUE_TAG_LENGTH
);
574 _plistAppendUTF8CString(xmlString
, "/>\n");
576 _plistAppendUTF8CString(xmlString
, "<");
577 _plistAppendCharacters(xmlString
, CFXMLPlistTags
[FALSE_IX
], FALSE_TAG_LENGTH
);
578 _plistAppendUTF8CString(xmlString
, "/>\n");
583 static void _CFGenerateXMLPropertyListToData(CFMutableDataRef xml
, CFTypeRef propertyList
) {
584 _plistAppendUTF8CString(xml
, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE ");
585 _plistAppendCharacters(xml
, CFXMLPlistTags
[PLIST_IX
], PLIST_TAG_LENGTH
);
586 _plistAppendUTF8CString(xml
, " PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<");
587 _plistAppendCharacters(xml
, CFXMLPlistTags
[PLIST_IX
], PLIST_TAG_LENGTH
);
588 _plistAppendUTF8CString(xml
, " version=\"1.0\">\n");
590 _CFAppendXML0(propertyList
, 0, xml
);
592 _plistAppendUTF8CString(xml
, "</");
593 _plistAppendCharacters(xml
, CFXMLPlistTags
[PLIST_IX
], PLIST_TAG_LENGTH
);
594 _plistAppendUTF8CString(xml
, ">\n");
597 CFDataRef
CFPropertyListCreateXMLData(CFAllocatorRef allocator
, CFPropertyListRef propertyList
) {
598 CFMutableDataRef xml
;
599 CFAssert1(propertyList
!= NULL
, __kCFLogAssertion
, "%s(): Cannot be called with a NULL property list", __PRETTY_FUNCTION__
);
600 __CFAssertIsPList(propertyList
);
601 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
602 if (!CFPropertyListIsValid(propertyList
, kCFPropertyListXMLFormat_v1_0
)) return NULL
;
604 xml
= CFDataCreateMutable(allocator
, 0);
605 _CFGenerateXMLPropertyListToData(xml
, propertyList
);
609 CFDataRef
_CFPropertyListCreateXMLDataWithExtras(CFAllocatorRef allocator
, CFPropertyListRef propertyList
) {
610 CFMutableDataRef xml
;
611 CFAssert1(propertyList
!= NULL
, __kCFLogAssertion
, "%s(): Cannot be called with a NULL property list", __PRETTY_FUNCTION__
);
612 xml
= CFDataCreateMutable(allocator
, 0);
613 _CFGenerateXMLPropertyListToData(xml
, propertyList
);
617 // ========================================================================
620 // ------------------------- Reading plists ------------------
623 static void skipInlineDTD(_CFXMLPlistParseInfo
*pInfo
);
624 static CFTypeRef
parseXMLElement(_CFXMLPlistParseInfo
*pInfo
, Boolean
*isKey
);
626 // warning: doesn't have a good idea of Unicode line separators
627 static UInt32
lineNumber(_CFXMLPlistParseInfo
*pInfo
) {
628 const UniChar
*p
= pInfo
->begin
;
630 while (p
< pInfo
->curr
) {
633 if (*(p
+ 1) == '\n')
635 } else if (*p
== '\n') {
643 // warning: doesn't have a good idea of Unicode white space
644 CF_INLINE
void skipWhitespace(_CFXMLPlistParseInfo
*pInfo
) {
645 while (pInfo
->curr
< pInfo
->end
) {
646 switch (*(pInfo
->curr
)) {
659 /* 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. */
661 // pInfo should be just past "<!--"
662 static void skipXMLComment(_CFXMLPlistParseInfo
*pInfo
) {
663 const UniChar
*p
= pInfo
->curr
;
664 const UniChar
*end
= pInfo
->end
- 3; // Need at least 3 characters to compare against
666 if (*p
== '-' && *(p
+1) == '-' && *(p
+2) == '>') {
672 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Unterminated comment started on line %d"), lineNumber(pInfo
));
675 // stringToMatch and buf must both be of at least len
676 static Boolean
matchString(const UniChar
*buf
, const UniChar
*stringToMatch
, UInt32 len
) {
678 case 10: if (buf
[9] != stringToMatch
[9]) return false;
679 case 9: if (buf
[8] != stringToMatch
[8]) return false;
680 case 8: if (buf
[7] != stringToMatch
[7]) return false;
681 case 7: if (buf
[6] != stringToMatch
[6]) return false;
682 case 6: if (buf
[5] != stringToMatch
[5]) return false;
683 case 5: if (buf
[4] != stringToMatch
[4]) return false;
684 case 4: if (buf
[3] != stringToMatch
[3]) return false;
685 case 3: if (buf
[2] != stringToMatch
[2]) return false;
686 case 2: if (buf
[1] != stringToMatch
[1]) return false;
687 case 1: if (buf
[0] != stringToMatch
[0]) return false;
690 return false; // internal error
693 // pInfo should be set to the first character after "<?"
694 static void skipXMLProcessingInstruction(_CFXMLPlistParseInfo
*pInfo
) {
695 const UniChar
*begin
= pInfo
->curr
, *end
= pInfo
->end
- 2; // Looking for "?>" so we need at least 2 characters
696 while (pInfo
->curr
< end
) {
697 if (*(pInfo
->curr
) == '?' && *(pInfo
->curr
+1) == '>') {
704 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected EOF while parsing the processing instruction begun on line %d"), lineNumber(pInfo
));
707 // first character should be immediately after the "<!"
708 static void skipDTD(_CFXMLPlistParseInfo
*pInfo
) {
709 // First pass "DOCTYPE"
710 if (pInfo
->end
- pInfo
->curr
< DOCTYPE_TAG_LENGTH
|| !matchString(pInfo
->curr
, CFXMLPlistTags
[DOCTYPE_IX
], DOCTYPE_TAG_LENGTH
)) {
711 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Malformed DTD on line %d"), lineNumber(pInfo
));
714 pInfo
->curr
+= DOCTYPE_TAG_LENGTH
;
715 skipWhitespace(pInfo
);
717 // Look for either the beginning of a complex DTD or the end of the DOCTYPE structure
718 while (pInfo
->curr
< pInfo
->end
) {
719 UniChar ch
= *(pInfo
->curr
);
720 if (ch
== '[') break; // inline DTD
721 if (ch
== '>') { // End of the DTD
727 if (pInfo
->curr
== pInfo
->end
) {
728 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF while parsing DTD", CFStringGetSystemEncoding());
732 // *Sigh* Must parse in-line DTD
733 skipInlineDTD(pInfo
);
734 if (pInfo
->errorString
) return;
735 skipWhitespace(pInfo
);
736 if (pInfo
->errorString
) return;
737 if (pInfo
->curr
< pInfo
->end
) {
738 if (*(pInfo
->curr
) == '>') {
741 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected character %c on line %d while parsing DTD"), *(pInfo
->curr
), lineNumber(pInfo
));
744 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF while parsing DTD", CFStringGetSystemEncoding());
748 static void skipPERef(_CFXMLPlistParseInfo
*pInfo
) {
749 const UniChar
*p
= pInfo
->curr
;
750 while (p
< pInfo
->end
) {
757 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected EOF while parsing percent-escape sequence begun on line %d"), lineNumber(pInfo
));
760 // First character should be just past '['
761 static void skipInlineDTD(_CFXMLPlistParseInfo
*pInfo
) {
762 while (!pInfo
->errorString
&& pInfo
->curr
< pInfo
->end
) {
764 skipWhitespace(pInfo
);
769 } else if (ch
== '<') {
771 if (pInfo
->curr
>= pInfo
->end
) {
772 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF while parsing inline DTD", CFStringGetSystemEncoding());
778 skipXMLProcessingInstruction(pInfo
);
779 } else if (ch
== '!') {
780 if (pInfo
->curr
+ 2 < pInfo
->end
&& (*(pInfo
->curr
+1) == '-' && *(pInfo
->curr
+2) == '-')) {
782 skipXMLComment(pInfo
);
784 // Skip the myriad of DTD declarations of the form "<!string" ... ">"
785 pInfo
->curr
++; // Past both '<' and '!'
786 while (pInfo
->curr
< pInfo
->end
) {
787 if (*(pInfo
->curr
) == '>') break;
790 if (*(pInfo
->curr
) != '>') {
791 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF while parsing inline DTD", CFStringGetSystemEncoding());
798 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected character %c on line %d while parsing inline DTD"), ch
, lineNumber(pInfo
));
801 } else if (ch
== ']') {
805 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected character %c on line %d while parsing inline DTD"), ch
, lineNumber(pInfo
));
809 if (!pInfo
->errorString
)
810 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF while parsing inline DTD", CFStringGetSystemEncoding());
813 /* 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. */
815 static const signed char __CFPLDataDecodeTable
[128] = {
816 /* 000 */ -1, -1, -1, -1, -1, -1, -1, -1,
817 /* 010 */ -1, -1, -1, -1, -1, -1, -1, -1,
818 /* 020 */ -1, -1, -1, -1, -1, -1, -1, -1,
819 /* 030 */ -1, -1, -1, -1, -1, -1, -1, -1,
820 /* ' ' */ -1, -1, -1, -1, -1, -1, -1, -1,
821 /* '(' */ -1, -1, -1, 62, -1, -1, -1, 63,
822 /* '0' */ 52, 53, 54, 55, 56, 57, 58, 59,
823 /* '8' */ 60, 61, -1, -1, -1, 0, -1, -1,
824 /* '@' */ -1, 0, 1, 2, 3, 4, 5, 6,
825 /* 'H' */ 7, 8, 9, 10, 11, 12, 13, 14,
826 /* 'P' */ 15, 16, 17, 18, 19, 20, 21, 22,
827 /* 'X' */ 23, 24, 25, -1, -1, -1, -1, -1,
828 /* '`' */ -1, 26, 27, 28, 29, 30, 31, 32,
829 /* 'h' */ 33, 34, 35, 36, 37, 38, 39, 40,
830 /* 'p' */ 41, 42, 43, 44, 45, 46, 47, 48,
831 /* 'x' */ 49, 50, 51, -1, -1, -1, -1, -1
834 static CFDataRef
__CFPLDataDecode(_CFXMLPlistParseInfo
*pInfo
, Boolean
mutable) {
842 // GrP GC: collector shouldn't scan this raw data
843 tmpbuf
= CFAllocatorAllocate(pInfo
->allocator
, tmpbuflen
, AUTO_MEMORY_UNSCANNED
);
844 for (; pInfo
->curr
< pInfo
->end
; pInfo
->curr
++) {
845 UniChar c
= *(pInfo
->curr
);
851 } else if (!isspace(c
)) {
854 if (__CFPLDataDecodeTable
[c
] < 0)
858 acc
+= __CFPLDataDecodeTable
[c
];
859 if (0 == (cntr
& 0x3)) {
860 if (tmpbuflen
<= tmpbufpos
+ 2) {
862 tmpbuf
= CFAllocatorReallocate(pInfo
->allocator
, tmpbuf
, tmpbuflen
, AUTO_MEMORY_UNSCANNED
);
864 tmpbuf
[tmpbufpos
++] = (acc
>> 16) & 0xff;
866 tmpbuf
[tmpbufpos
++] = (acc
>> 8) & 0xff;
868 tmpbuf
[tmpbufpos
++] = acc
& 0xff;
872 CFMutableDataRef result
= CFDataCreateMutable(pInfo
->allocator
, 0);
873 CFDataAppendBytes(result
, tmpbuf
, tmpbufpos
);
874 CFAllocatorDeallocate(pInfo
->allocator
, tmpbuf
);
877 return CFDataCreateWithBytesNoCopy(pInfo
->allocator
, (char const *) tmpbuf
, tmpbufpos
, pInfo
->allocator
);
881 // content ::== (element | CharData | Reference | CDSect | PI | Comment)*
882 // 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).
883 static CFTypeRef
getContentObject(_CFXMLPlistParseInfo
*pInfo
, Boolean
*isKey
) {
884 if (isKey
) *isKey
= false;
885 while (!pInfo
->errorString
&& pInfo
->curr
< pInfo
->end
) {
886 skipWhitespace(pInfo
);
887 if (pInfo
->curr
>= pInfo
->end
) {
888 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
891 if (*(pInfo
->curr
) != '<') {
892 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected character %c on line %d"), *(pInfo
->curr
), lineNumber(pInfo
));
896 if (pInfo
->curr
>= pInfo
->end
) {
897 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
900 switch (*(pInfo
->curr
)) {
902 // Processing instruction
903 skipXMLProcessingInstruction(pInfo
);
906 // Could be a comment
907 if (pInfo
->curr
+2 >= pInfo
->end
) {
908 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
911 if (*(pInfo
->curr
+1) == '-' && *(pInfo
->curr
+2) == '-') {
913 skipXMLComment(pInfo
);
915 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
920 // Whoops! Looks like we got to the end tag for the element whose content we're parsing
921 pInfo
->curr
--; // Back off to the '<'
924 // Should be an element
925 return parseXMLElement(pInfo
, isKey
);
928 // 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"
932 static void _catFromMarkToBuf(const UniChar
*mark
, const UniChar
*buf
, CFMutableStringRef
*string
, CFAllocatorRef allocator
) {
934 *string
= CFStringCreateMutable(allocator
, 0);
936 CFStringAppendCharacters(*string
, mark
, buf
-mark
);
939 static void parseCDSect_pl(_CFXMLPlistParseInfo
*pInfo
, CFMutableStringRef string
) {
940 const UniChar
*end
, *begin
;
941 if (pInfo
->end
- pInfo
->curr
< CDSECT_TAG_LENGTH
) {
942 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
945 if (!matchString(pInfo
->curr
, CFXMLPlistTags
[CDSECT_IX
], CDSECT_TAG_LENGTH
)) {
946 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered improper CDATA opening at line %d"), lineNumber(pInfo
));
949 pInfo
->curr
+= CDSECT_TAG_LENGTH
;
950 begin
= pInfo
->curr
; // Marks the first character of the CDATA content
951 end
= pInfo
->end
-2; // So we can safely look 2 characters beyond p
952 while (pInfo
->curr
< end
) {
953 if (*(pInfo
->curr
) == ']' && *(pInfo
->curr
+1) == ']' && *(pInfo
->curr
+2) == '>') {
955 CFStringAppendCharacters(string
, begin
, pInfo
->curr
-begin
);
961 // Never found the end mark
963 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Could not find end of CDATA started on line %d"), lineNumber(pInfo
));
966 // Only legal references are {lt, gt, amp, apos, quote, #ddd, #xAAA}
967 static void parseEntityReference_pl(_CFXMLPlistParseInfo
*pInfo
, CFMutableStringRef string
) {
970 pInfo
->curr
++; // move past the '&';
971 len
= pInfo
->end
- pInfo
->curr
; // how many characters we can safely scan
973 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
976 switch (*(pInfo
->curr
)) {
978 if (len
>= 3 && *(pInfo
->curr
+1) == 't' && *(pInfo
->curr
+2) == ';') {
983 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo
));
986 if (len
>= 3 && *(pInfo
->curr
+1) == 't' && *(pInfo
->curr
+2) == ';') {
991 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo
));
993 case 'a': // "apos" or "amp"
994 if (len
< 4) { // Not enough characters for either conversion
995 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
998 if (*(pInfo
->curr
+1) == 'm') {
1000 if (*(pInfo
->curr
+2) == 'p' && *(pInfo
->curr
+3) == ';') {
1005 } else if (*(pInfo
->curr
+1) == 'p') {
1007 if (len
> 4 && *(pInfo
->curr
+2) == 'o' && *(pInfo
->curr
+3) == 's' && *(pInfo
->curr
+4) == ';') {
1013 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo
));
1015 case 'q': // "quote"
1016 if (len
>= 5 && *(pInfo
->curr
+1) == 'u' && *(pInfo
->curr
+2) == 'o' && *(pInfo
->curr
+3) == 't' && *(pInfo
->curr
+4) == ';') {
1021 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo
));
1026 Boolean isHex
= false;
1027 if ( len
< 4) { // Not enough characters to make it all fit! Need at least "&#d;"
1028 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
1032 if (*(pInfo
->curr
) == 'x') {
1036 while (pInfo
->curr
< pInfo
->end
) {
1037 ch
= *(pInfo
->curr
);
1040 CFStringAppendCharacters(string
, &num
, 1);
1043 if (!isHex
) num
= num
*10;
1044 else num
= num
<< 4;
1045 if (ch
<= '9' && ch
>= '0') {
1047 } else if (!isHex
) {
1048 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected character %c at line %d"), ch
, lineNumber(pInfo
));
1050 } else if (ch
>= 'a' && ch
<= 'f') {
1051 num
+= 10 + (ch
- 'a');
1052 } else if (ch
>= 'A' && ch
<= 'F') {
1053 num
+= 10 + (ch
- 'A');
1055 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected character %c at line %d"), ch
, lineNumber(pInfo
));
1059 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
1063 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo
));
1066 CFStringAppendCharacters(string
, &ch
, 1);
1069 extern const void *__CFSetAddValueAndReturn(CFMutableSetRef set
, const void *value
);
1071 static CFStringRef
_uniqueStringForString(_CFXMLPlistParseInfo
*pInfo
, CFStringRef stringToUnique
) {
1072 if (!pInfo
->stringSet
) {
1073 pInfo
->stringSet
= CFSetCreateMutable(pInfo
->allocator
, 0, &kCFCopyStringSetCallBacks
);
1074 _CFSetSetCapacity(pInfo
->stringSet
, 160); // set capacity high to avoid lots of rehashes, though waste some memory
1076 return __CFSetAddValueAndReturn(pInfo
->stringSet
, stringToUnique
);
1079 extern void _CFStrSetDesiredCapacity(CFMutableStringRef str
, CFIndex len
);
1081 static CFStringRef
_uniqueStringForCharacters(_CFXMLPlistParseInfo
*pInfo
, const UniChar
*base
, CFIndex length
) {
1083 uint8_t *ascii
, buffer
[1024];
1085 if (!pInfo
->stringSet
) {
1086 pInfo
->stringSet
= CFSetCreateMutable(pInfo
->allocator
, 0, &kCFCopyStringSetCallBacks
);
1087 _CFSetSetCapacity(pInfo
->stringSet
, 160); // set capacity high to avoid lots of rehashes, though waste some memory
1089 if (pInfo
->tmpString
) {
1090 CFStringDelete(pInfo
->tmpString
, CFRangeMake(0, CFStringGetLength(pInfo
->tmpString
)));
1092 pInfo
->tmpString
= CFStringCreateMutable(pInfo
->allocator
, 0);
1093 _CFStrSetDesiredCapacity(pInfo
->tmpString
, 512);
1095 // This is to avoid having to promote the buffers of all the strings compared against
1096 // during the set probe; if a Unicode string is passed in, that's what happens.
1098 for (idx
= 0; isASCII
&& idx
< length
; idx
++) isASCII
= isASCII
&& (base
[idx
] < 0x80);
1100 ascii
= (length
< (CFIndex
)sizeof(buffer
)) ? buffer
: CFAllocatorAllocate(kCFAllocatorSystemDefault
, length
+ 1, 0);
1101 for (idx
= 0; idx
< length
; idx
++) ascii
[idx
] = (uint8_t)base
[idx
];
1102 ascii
[length
] = '\0';
1103 CFStringAppendCString(pInfo
->tmpString
, ascii
, kCFStringEncodingASCII
);
1104 if (ascii
!= buffer
) CFAllocatorDeallocate(kCFAllocatorSystemDefault
, ascii
);
1106 CFStringAppendCharacters(pInfo
->tmpString
, base
, length
);
1108 return __CFSetAddValueAndReturn(pInfo
->stringSet
, pInfo
->tmpString
);
1112 // String could be comprised of characters, CDSects, or references to one of the "well-known" entities ('<', '>', '&', ''', '"')
1113 // returns a retained object in *string.
1114 static CFStringRef
getString(_CFXMLPlistParseInfo
*pInfo
) {
1115 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
1116 CFMutableStringRef string
= NULL
;
1117 while (!pInfo
->errorString
&& pInfo
->curr
< pInfo
->end
) {
1118 UniChar ch
= *(pInfo
->curr
);
1120 if (pInfo
->curr
+ 1 >= pInfo
->end
) break;
1121 // Could be a CDSect; could be the end of the string
1122 if (*(pInfo
->curr
+1) != '!') break; // End of the string
1123 _catFromMarkToBuf(mark
, pInfo
->curr
, &string
, pInfo
->allocator
);
1124 parseCDSect_pl(pInfo
, string
);
1126 } else if (ch
== '&') {
1127 _catFromMarkToBuf(mark
, pInfo
->curr
, &string
, pInfo
->allocator
);
1128 parseEntityReference_pl(pInfo
, string
);
1135 if (pInfo
->errorString
) {
1136 if (string
) CFRelease(string
);
1140 if (pInfo
->mutabilityOption
!= kCFPropertyListMutableContainersAndLeaves
) {
1141 CFStringRef uniqueString
= _uniqueStringForCharacters(pInfo
, mark
, pInfo
->curr
-mark
);
1142 CFRetain(uniqueString
);
1143 return uniqueString
;
1145 string
= CFStringCreateMutable(pInfo
->allocator
, 0);
1146 CFStringAppendCharacters(string
, mark
, pInfo
->curr
- mark
);
1150 _catFromMarkToBuf(mark
, pInfo
->curr
, &string
, pInfo
->allocator
);
1151 if (pInfo
->mutabilityOption
!= kCFPropertyListMutableContainersAndLeaves
) {
1152 CFStringRef uniqueString
= _uniqueStringForString(pInfo
, string
);
1153 CFRetain(uniqueString
);
1155 return uniqueString
;
1160 static Boolean
checkForCloseTag(_CFXMLPlistParseInfo
*pInfo
, const UniChar
*tag
, CFIndex tagLen
) {
1161 if (pInfo
->end
- pInfo
->curr
< tagLen
+ 3) {
1162 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
1165 if (*(pInfo
->curr
) != '<' || *(++pInfo
->curr
) != '/') {
1166 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected character %c on line %d"), *(pInfo
->curr
), lineNumber(pInfo
));
1170 if (!matchString(pInfo
->curr
, tag
, tagLen
)) {
1171 CFStringRef str
= CFStringCreateWithCharactersNoCopy(pInfo
->allocator
, tag
, tagLen
, kCFAllocatorNull
);
1172 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Close tag on line %d does not match open tag %@"), lineNumber(pInfo
), str
);
1176 pInfo
->curr
+= tagLen
;
1177 skipWhitespace(pInfo
);
1178 if (pInfo
->curr
== pInfo
->end
) {
1179 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
1182 if (*(pInfo
->curr
) != '>') {
1183 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected character %c on line %d"), *(pInfo
->curr
), lineNumber(pInfo
));
1190 // pInfo should be set to the first content character of the <plist>
1191 static CFTypeRef
parsePListTag(_CFXMLPlistParseInfo
*pInfo
) {
1192 CFTypeRef result
, tmp
= NULL
;
1193 const UniChar
*save
;
1194 result
= getContentObject(pInfo
, NULL
);
1196 if (!pInfo
->errorString
) pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered empty plist tag", CFStringGetSystemEncoding());
1199 save
= pInfo
->curr
; // Save this in case the next step fails
1200 tmp
= getContentObject(pInfo
, NULL
);
1202 // Got an extra object
1206 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unexpected element at line %d (plist can only include one object)"), lineNumber(pInfo
));
1209 if (pInfo
->errorString
) {
1210 // Parse failed catastrophically
1214 if (checkForCloseTag(pInfo
, CFXMLPlistTags
[PLIST_IX
], PLIST_TAG_LENGTH
)) {
1221 static int allowImmutableCollections
= -1;
1223 static void checkImmutableCollections(void) {
1224 allowImmutableCollections
= (NULL
== getenv("CFPropertyListAllowImmutableCollections")) ? 0 : 1;
1227 static CFTypeRef
parseArrayTag(_CFXMLPlistParseInfo
*pInfo
) {
1228 CFMutableArrayRef array
= CFArrayCreateMutable(pInfo
->allocator
, 0, &kCFTypeArrayCallBacks
);
1229 CFTypeRef tmp
= getContentObject(pInfo
, NULL
);
1231 CFArrayAppendValue(array
, tmp
);
1233 tmp
= getContentObject(pInfo
, NULL
);
1235 if (pInfo
->errorString
) { // getContentObject encountered a parse error
1239 if (checkForCloseTag(pInfo
, CFXMLPlistTags
[ARRAY_IX
], ARRAY_TAG_LENGTH
)) {
1240 if (-1 == allowImmutableCollections
) checkImmutableCollections();
1241 if (1 == allowImmutableCollections
) {
1242 if (pInfo
->mutabilityOption
== kCFPropertyListImmutable
) {
1243 CFArrayRef newArray
= CFArrayCreateCopy(pInfo
->allocator
, array
);
1245 array
= (CFMutableArrayRef
)newArray
;
1254 static CFTypeRef
parseDictTag(_CFXMLPlistParseInfo
*pInfo
) {
1255 CFMutableDictionaryRef dict
= NULL
;
1256 CFTypeRef key
=NULL
, value
=NULL
;
1258 const UniChar
*base
= pInfo
->curr
;
1259 key
= getContentObject(pInfo
, &gotKey
);
1262 if (key
) CFRelease(key
);
1263 if (dict
) CFRelease(dict
);
1265 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Found non-key inside <dict> at line %d"), lineNumber(pInfo
));
1268 value
= getContentObject(pInfo
, NULL
);
1270 if (key
) CFRelease(key
);
1271 if (dict
) CFRelease(dict
);
1272 if (!pInfo
->errorString
)
1273 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Value missing for key inside <dict> at line %d"), lineNumber(pInfo
));
1277 dict
= CFDictionaryCreateMutable(pInfo
->allocator
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1278 _CFDictionarySetCapacity(dict
, 10);
1280 CFDictionarySetValue(dict
, key
, value
);
1286 key
= getContentObject(pInfo
, &gotKey
);
1288 if (checkForCloseTag(pInfo
, CFXMLPlistTags
[DICT_IX
], DICT_TAG_LENGTH
)) {
1290 if (pInfo
->mutabilityOption
== kCFPropertyListImmutable
) {
1291 dict
= (CFMutableDictionaryRef
)CFDictionaryCreate(pInfo
->allocator
, NULL
, NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1293 dict
= CFDictionaryCreateMutable(pInfo
->allocator
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1296 CFIndex cnt
= CFDictionaryGetCount(dict
);
1298 CFTypeRef val
= CFDictionaryGetValue(dict
, CFSTR("CF$UID"));
1299 if (val
&& CFGetTypeID(val
) == CFNumberGetTypeID()) {
1302 CFNumberGetValue(val
, kCFNumberSInt32Type
, &v
);
1303 uid
= (CFTypeRef
)_CFKeyedArchiverUIDCreate(pInfo
->allocator
, v
);
1308 if (-1 == allowImmutableCollections
) checkImmutableCollections();
1309 if (1 == allowImmutableCollections
) {
1310 if (pInfo
->mutabilityOption
== kCFPropertyListImmutable
) {
1311 CFDictionaryRef newDict
= CFDictionaryCreateCopy(pInfo
->allocator
, dict
);
1313 dict
= (CFMutableDictionaryRef
)newDict
;
1319 if (dict
) CFRelease(dict
);
1323 static CFTypeRef
parseDataTag(_CFXMLPlistParseInfo
*pInfo
) {
1325 const UniChar
*base
= pInfo
->curr
;
1326 result
= __CFPLDataDecode(pInfo
, pInfo
->mutabilityOption
== kCFPropertyListMutableContainersAndLeaves
);
1329 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Could not interpret <data> at line %d (should be base64-encoded)"), lineNumber(pInfo
));
1332 if (checkForCloseTag(pInfo
, CFXMLPlistTags
[DATA_IX
], DATA_TAG_LENGTH
)) return result
;
1337 CF_INLINE Boolean
read2DigitNumber(_CFXMLPlistParseInfo
*pInfo
, int8_t *result
) {
1339 if (pInfo
->curr
+ 2 >= pInfo
->end
) return false;
1341 ch2
= *(pInfo
->curr
+ 1);
1343 if (!isdigit(ch1
) || !isdigit(ch2
)) return false;
1344 *result
= (ch1
- '0')*10 + (ch2
- '0');
1348 // YYYY '-' MM '-' DD 'T' hh ':' mm ':' ss 'Z'
1349 static CFTypeRef
parseDateTag(_CFXMLPlistParseInfo
*pInfo
) {
1350 CFGregorianDate date
;
1352 Boolean badForm
= false;
1355 while (pInfo
->curr
< pInfo
->end
&& isdigit(*pInfo
->curr
)) {
1356 date
.year
= 10*date
.year
+ (*pInfo
->curr
) - '0';
1359 if (pInfo
->curr
>= pInfo
->end
|| *pInfo
->curr
!= '-') {
1365 if (!badForm
&& read2DigitNumber(pInfo
, &date
.month
) && pInfo
->curr
< pInfo
->end
&& *pInfo
->curr
== '-') {
1371 if (!badForm
&& read2DigitNumber(pInfo
, &date
.day
) && pInfo
->curr
< pInfo
->end
&& *pInfo
->curr
== 'T') {
1377 if (!badForm
&& read2DigitNumber(pInfo
, &date
.hour
) && pInfo
->curr
< pInfo
->end
&& *pInfo
->curr
== ':') {
1383 if (!badForm
&& read2DigitNumber(pInfo
, &date
.minute
) && pInfo
->curr
< pInfo
->end
&& *pInfo
->curr
== ':') {
1389 if (!badForm
&& read2DigitNumber(pInfo
, &num
) && pInfo
->curr
< pInfo
->end
&& *pInfo
->curr
== 'Z') {
1397 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Could not interpret <date> at line %d"), lineNumber(pInfo
));
1400 if (!checkForCloseTag(pInfo
, CFXMLPlistTags
[DATE_IX
], DATE_TAG_LENGTH
)) return NULL
;
1401 return CFDateCreate(pInfo
->allocator
, CFGregorianDateGetAbsoluteTime(date
, NULL
));
1404 static CFTypeRef
parseRealTag(_CFXMLPlistParseInfo
*pInfo
) {
1405 CFStringRef str
= getString(pInfo
);
1409 CFStringInlineBuffer buf
;
1411 if (!pInfo
->errorString
)
1412 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered empty <real> on line %d"), lineNumber(pInfo
));
1416 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
1417 if (kCFCompareEqualTo
== CFStringCompare(str
, CFSTR("nan"), kCFCompareCaseInsensitive
)) {
1419 return (checkForCloseTag(pInfo
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
)) ? CFRetain(kCFNumberNaN
) : NULL
;
1421 if (kCFCompareEqualTo
== CFStringCompare(str
, CFSTR("+infinity"), kCFCompareCaseInsensitive
)) {
1423 return (checkForCloseTag(pInfo
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
)) ? CFRetain(kCFNumberPositiveInfinity
) : NULL
;
1425 if (kCFCompareEqualTo
== CFStringCompare(str
, CFSTR("-infinity"), kCFCompareCaseInsensitive
)) {
1427 return (checkForCloseTag(pInfo
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
)) ? CFRetain(kCFNumberNegativeInfinity
) : NULL
;
1429 if (kCFCompareEqualTo
== CFStringCompare(str
, CFSTR("infinity"), kCFCompareCaseInsensitive
)) {
1431 return (checkForCloseTag(pInfo
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
)) ? CFRetain(kCFNumberPositiveInfinity
) : NULL
;
1433 if (kCFCompareEqualTo
== CFStringCompare(str
, CFSTR("-inf"), kCFCompareCaseInsensitive
)) {
1435 return (checkForCloseTag(pInfo
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
)) ? CFRetain(kCFNumberNegativeInfinity
) : NULL
;
1437 if (kCFCompareEqualTo
== CFStringCompare(str
, CFSTR("inf"), kCFCompareCaseInsensitive
)) {
1439 return (checkForCloseTag(pInfo
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
)) ? CFRetain(kCFNumberPositiveInfinity
) : NULL
;
1441 if (kCFCompareEqualTo
== CFStringCompare(str
, CFSTR("+inf"), kCFCompareCaseInsensitive
)) {
1443 return (checkForCloseTag(pInfo
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
)) ? CFRetain(kCFNumberPositiveInfinity
) : NULL
;
1447 len
= CFStringGetLength(str
);
1448 CFStringInitInlineBuffer(str
, &buf
, CFRangeMake(0, len
));
1450 if (!__CFStringScanDouble(&buf
, NULL
, &idx
, &val
) || idx
!= len
) {
1452 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered misformatted real on line %d"), lineNumber(pInfo
));
1456 result
= CFNumberCreate(pInfo
->allocator
, kCFNumberDoubleType
, &val
);
1457 if (checkForCloseTag(pInfo
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
)) return result
;
1462 #define GET_CH if (pInfo->curr == pInfo->end) { \
1463 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Premature end of file after <integer> on line %d"), lineNumber(pInfo)); \
1468 static CFTypeRef
parseIntegerTag(_CFXMLPlistParseInfo
*pInfo
) {
1469 bool isHex
= false, isNeg
= false, hadLeadingZero
= false;
1470 int64_t value
= (int64_t)0;
1473 // decimal_constant S*(-|+)?S*[0-9]+ (S == space)
1474 // hex_constant S*(-|+)?S*0[xX][0-9a-fA-F]+ (S == space)
1476 while (pInfo
->curr
< pInfo
->end
&& __CFIsWhitespace(*(pInfo
->curr
))) pInfo
->curr
++;
1479 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered empty <integer> on line %d"), lineNumber(pInfo
));
1482 if ('-' == ch
|| '+' == ch
) {
1483 isNeg
= ('-' == ch
);
1485 while (pInfo
->curr
< pInfo
->end
&& __CFIsWhitespace(*(pInfo
->curr
))) pInfo
->curr
++;
1489 if (pInfo
->curr
+ 1 < pInfo
->end
&& ('x' == *(pInfo
->curr
+ 1) || 'X' == *(pInfo
->curr
+ 1))) {
1493 hadLeadingZero
= true;
1499 hadLeadingZero
= true;
1503 if ('<' == ch
&& hadLeadingZero
) { // nothing but zeros
1505 if (!checkForCloseTag(pInfo
, CFXMLPlistTags
[INTEGER_IX
], INTEGER_TAG_LENGTH
)) {
1506 // checkForCloseTag() sets error string
1509 return CFNumberCreate(pInfo
->allocator
, kCFNumberSInt32Type
, &val
);
1512 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Incomplete <integer> on line %d"), lineNumber(pInfo
));
1516 int64_t old_value
= value
;
1518 case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
1519 value
= (isHex
? 16 : 10) * value
+ (ch
- '0');
1521 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
1523 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Hex digit in non-hex <integer> on line %d"), lineNumber(pInfo
));
1526 value
= 16 * value
+ (ch
- 'a' + 10);
1528 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
1530 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Hex digit in non-hex <integer> on line %d"), lineNumber(pInfo
));
1533 value
= 16 * value
+ (ch
- 'A' + 10);
1535 default: // other character
1536 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Unknown character '%c' (0x%x) in <integer> on line %d"), ch
, ch
, lineNumber(pInfo
));
1539 if (isNeg
&& LLONG_MIN
== value
) {
1540 // overflow by one when isNeg gives the proper value, if we're done with the number
1541 if (pInfo
->curr
+ 1 < pInfo
->end
&& '<' == *(pInfo
->curr
+ 1)) {
1547 if (value
< old_value
) {
1548 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered <integer> too large to represent on line %d"), lineNumber(pInfo
));
1554 if (!checkForCloseTag(pInfo
, CFXMLPlistTags
[INTEGER_IX
], INTEGER_TAG_LENGTH
)) {
1555 // checkForCloseTag() sets error string
1558 if (isNeg
) value
= -value
;
1559 return CFNumberCreate(pInfo
->allocator
, kCFNumberSInt64Type
, &value
);
1564 // Returned object is retained; caller must free. pInfo->curr expected to point to the first character after the '<'
1565 static CFTypeRef
parseXMLElement(_CFXMLPlistParseInfo
*pInfo
, Boolean
*isKey
) {
1566 const UniChar
*marker
= pInfo
->curr
;
1567 int markerLength
= -1;
1571 if (isKey
) *isKey
= false;
1572 while (pInfo
->curr
< pInfo
->end
) {
1573 UniChar ch
= *(pInfo
->curr
);
1574 if (ch
== ' ' || ch
== '\t' || ch
== '\n' || ch
=='\r') {
1575 if (markerLength
== -1) markerLength
= pInfo
->curr
- marker
;
1576 } else if (ch
== '>') {
1581 if (pInfo
->curr
>= pInfo
->end
) return NULL
;
1582 isEmpty
= (*(pInfo
->curr
-1) == '/');
1583 if (markerLength
== -1)
1584 markerLength
= pInfo
->curr
- (isEmpty
? 1 : 0) - marker
;
1585 pInfo
->curr
++; // Advance past '>'
1586 if (markerLength
== 0) {
1587 // Back up to the beginning of the marker
1588 pInfo
->curr
= marker
;
1589 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Malformed tag on line %d"), lineNumber(pInfo
));
1594 if (markerLength
== ARRAY_TAG_LENGTH
&& matchString(marker
, CFXMLPlistTags
[ARRAY_IX
], ARRAY_TAG_LENGTH
))
1595 markerIx
= ARRAY_IX
;
1597 case 'd': // Dictionary, data, or date; Fortunately, they all have the same marker length....
1598 if (markerLength
!= DICT_TAG_LENGTH
)
1600 if (matchString(marker
, CFXMLPlistTags
[DICT_IX
], DICT_TAG_LENGTH
))
1602 else if (matchString(marker
, CFXMLPlistTags
[DATA_IX
], DATA_TAG_LENGTH
))
1604 else if (matchString(marker
, CFXMLPlistTags
[DATE_IX
], DATE_TAG_LENGTH
))
1607 case 'f': // false (boolean)
1608 if (markerLength
== FALSE_TAG_LENGTH
&& matchString(marker
, CFXMLPlistTags
[FALSE_IX
], FALSE_TAG_LENGTH
)) {
1609 markerIx
= FALSE_IX
;
1612 case 'i': // integer
1613 if (markerLength
== INTEGER_TAG_LENGTH
&& matchString(marker
, CFXMLPlistTags
[INTEGER_IX
], INTEGER_TAG_LENGTH
))
1614 markerIx
= INTEGER_IX
;
1616 case 'k': // Key of a dictionary
1617 if (markerLength
== KEY_TAG_LENGTH
&& matchString(marker
, CFXMLPlistTags
[KEY_IX
], KEY_TAG_LENGTH
)) {
1619 if (isKey
) *isKey
= true;
1623 if (markerLength
== PLIST_TAG_LENGTH
&& matchString(marker
, CFXMLPlistTags
[PLIST_IX
], PLIST_TAG_LENGTH
))
1624 markerIx
= PLIST_IX
;
1627 if (markerLength
== REAL_TAG_LENGTH
&& matchString(marker
, CFXMLPlistTags
[REAL_IX
], REAL_TAG_LENGTH
))
1631 if (markerLength
== STRING_TAG_LENGTH
&& matchString(marker
, CFXMLPlistTags
[STRING_IX
], STRING_TAG_LENGTH
))
1632 markerIx
= STRING_IX
;
1634 case 't': // true (boolean)
1635 if (markerLength
== TRUE_TAG_LENGTH
&& matchString(marker
, CFXMLPlistTags
[TRUE_IX
], TRUE_TAG_LENGTH
))
1640 if (!pInfo
->allowNewTypes
&& markerIx
!= PLIST_IX
&& markerIx
!= ARRAY_IX
&& markerIx
!= DICT_IX
&& markerIx
!= STRING_IX
&& markerIx
!= KEY_IX
&& markerIx
!= DATA_IX
) {
1641 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered new tag when expecting only old-style property list objects", CFStringGetSystemEncoding());
1648 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered empty plist tag", CFStringGetSystemEncoding());
1651 return parsePListTag(pInfo
);
1654 return pInfo
->mutabilityOption
== kCFPropertyListImmutable
? CFArrayCreate(pInfo
->allocator
, NULL
, 0, &kCFTypeArrayCallBacks
) : CFArrayCreateMutable(pInfo
->allocator
, 0, &kCFTypeArrayCallBacks
);
1656 return parseArrayTag(pInfo
);
1660 if (pInfo
->mutabilityOption
== kCFPropertyListImmutable
) {
1661 return CFDictionaryCreate(pInfo
->allocator
, NULL
, NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1663 return CFDictionaryCreateMutable(pInfo
->allocator
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
1666 return parseDictTag(pInfo
);
1672 int tagLen
= (markerIx
== KEY_IX
) ? KEY_TAG_LENGTH
: STRING_TAG_LENGTH
;
1674 return pInfo
->mutabilityOption
== kCFPropertyListMutableContainersAndLeaves
? CFStringCreateMutable(pInfo
->allocator
, 0) : CFStringCreateWithCharacters(pInfo
->allocator
, NULL
, 0);
1676 str
= getString(pInfo
);
1677 if (!str
) return NULL
; // getString will already have set the error string
1678 if (!checkForCloseTag(pInfo
, CFXMLPlistTags
[markerIx
], tagLen
)) {
1686 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered empty <data> on line %d"), lineNumber(pInfo
));
1689 return parseDataTag(pInfo
);
1693 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered empty <date> on line %d"), lineNumber(pInfo
));
1696 return parseDateTag(pInfo
);
1700 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered non-empty <true> tag on line %d"), lineNumber(pInfo
));
1703 return CFRetain(kCFBooleanTrue
);
1707 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered non-empty <false> tag on line %d"), lineNumber(pInfo
));
1710 return CFRetain(kCFBooleanFalse
);
1714 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered empty <real> on line %d"), lineNumber(pInfo
));
1717 return parseRealTag(pInfo
);
1721 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered empty <integer> on line %d"), lineNumber(pInfo
));
1724 return parseIntegerTag(pInfo
);
1727 CFStringRef markerStr
= CFStringCreateWithCharacters(pInfo
->allocator
, marker
, markerLength
);
1728 pInfo
->curr
= marker
;
1729 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Encountered unknown tag %@ on line %d"), markerStr
, lineNumber(pInfo
));
1730 CFRelease(markerStr
);
1736 static CFTypeRef
parseXMLPropertyList(_CFXMLPlistParseInfo
*pInfo
) {
1737 while (!pInfo
->errorString
&& pInfo
->curr
< pInfo
->end
) {
1739 skipWhitespace(pInfo
);
1740 if (pInfo
->curr
+1 >= pInfo
->end
) {
1741 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "No XML content found", CFStringGetSystemEncoding());
1744 if (*(pInfo
->curr
) != '<') {
1745 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Unexpected character %c at line %d"), *(pInfo
->curr
), lineNumber(pInfo
));
1748 ch
= *(++ pInfo
->curr
);
1752 if (pInfo
->curr
+1 < pInfo
->end
&& *pInfo
->curr
== '-' && *(pInfo
->curr
+1) == '-') {
1755 skipXMLComment(pInfo
);
1759 } else if (ch
== '?') {
1760 // Processing instruction
1762 skipXMLProcessingInstruction(pInfo
);
1765 return parseXMLElement(pInfo
, NULL
);
1766 // 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
1769 // Should never get here
1770 if (!(pInfo
->errorString
))
1771 pInfo
->errorString
= CFStringCreateWithCString(pInfo
->allocator
, "Encountered unexpected EOF", CFStringGetSystemEncoding());
1775 static CFStringEncoding
encodingForXMLData(CFDataRef data
, CFStringRef
*error
) {
1776 const uint8_t *bytes
= (uint8_t *)CFDataGetBytePtr(data
);
1777 UInt32 length
= CFDataGetLength(data
);
1778 const uint8_t *idx
, *end
;
1781 // Check for the byte order mark first
1783 ((*bytes
== 0xFF && *(bytes
+1) == 0xFE) ||
1784 (*bytes
== 0xFE && *(bytes
+1) == 0xFF) ||
1785 *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
1786 return kCFStringEncodingUnicode
;
1788 // Scan for the <?xml.... ?> opening
1789 if (length
< 5 || strncmp((char const *) bytes
, "<?xml", 5) != 0) return kCFStringEncodingUTF8
;
1791 end
= bytes
+ length
;
1792 // Found "<?xml"; now we scan for "encoding"
1795 const uint8_t *scan
;
1796 if ( ch
== '?' || ch
== '>') return kCFStringEncodingUTF8
;
1799 if (ch
== 'e' && *scan
++ == 'n' && *scan
++ == 'c' && *scan
++ == 'o' && *scan
++ == 'd' && *scan
++ == 'i'
1800 && *scan
++ == 'n' && *scan
++ == 'g' && *scan
++ == '=') {
1805 if (idx
>= end
) return kCFStringEncodingUTF8
;
1807 if (quote
!= '\'' && quote
!= '\"') return kCFStringEncodingUTF8
;
1809 CFStringRef encodingName
;
1810 const uint8_t *base
= idx
+1; // Move past the quote character
1811 CFStringEncoding enc
;
1814 while (idx
< end
&& *idx
!= quote
) idx
++;
1815 if (idx
>= end
) return kCFStringEncodingUTF8
;
1817 if (len
== 5 && (*base
== 'u' || *base
== 'U') && (base
[1] == 't' || base
[1] == 'T') && (base
[2] == 'f' || base
[2] == 'F') && (base
[3] == '-') && (base
[4] == '8'))
1818 return kCFStringEncodingUTF8
;
1819 encodingName
= CFStringCreateWithBytes(NULL
, base
, len
, kCFStringEncodingISOLatin1
, false);
1820 enc
= CFStringConvertIANACharSetNameToEncoding(encodingName
);
1821 if (enc
!= kCFStringEncodingInvalidId
) {
1822 CFRelease(encodingName
);
1827 *error
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("Encountered unknown encoding (%@)"), encodingName
);
1828 CFRelease(encodingName
);
1834 CFTypeRef __CFNastyFile__
= NULL
;
1835 static CFSpinLock_t __CFNastyFileLock__
= 0;
1837 void __CFSetNastyFile(CFTypeRef cf
) {
1838 __CFSpinLock(&__CFNastyFileLock__
);
1839 if (__CFNastyFile__
) CFRelease(__CFNastyFile__
);
1840 __CFNastyFile__
= cf
? CFRetain(cf
) : cf
;
1841 __CFSpinUnlock(&__CFNastyFileLock__
);
1844 extern bool __CFTryParseBinaryPlist(CFAllocatorRef allocator
, CFDataRef data
, CFOptionFlags option
, CFPropertyListRef
*plist
, CFStringRef
*errorString
);
1845 int32_t _CFPropertyListAllowNonUTF8
= 0;
1847 CFTypeRef
_CFPropertyListCreateFromXMLData(CFAllocatorRef allocator
, CFDataRef xmlData
, CFOptionFlags option
, CFStringRef
*errorString
, Boolean allowNewTypes
, CFPropertyListFormat
*format
) {
1848 CFStringEncoding encoding
;
1849 CFStringRef xmlString
;
1851 CFPropertyListRef plist
;
1853 if (errorString
) *errorString
= NULL
;
1854 if (!xmlData
|| CFDataGetLength(xmlData
) == 0) {
1856 *errorString
= CFSTR("Cannot parse a NULL or zero-length data");
1857 CFRetain(*errorString
); // Caller expects to release
1862 if (__CFTryParseBinaryPlist(allocator
, xmlData
, option
, &plist
, errorString
)) {
1863 if (format
) *format
= kCFPropertyListBinaryFormat_v1_0
;
1867 allocator
= allocator
? allocator
: __CFGetDefaultAllocator();
1868 CFRetain(allocator
);
1870 encoding
= encodingForXMLData(xmlData
, errorString
); // 0 is an error return, NOT MacRoman.
1872 if (encoding
== 0) {
1873 // Couldn't find an encoding; encodingForXMLData already set *errorString if necessary
1874 // Note that encodingForXMLData() will give us the right values for a standard plist, too.
1875 if (errorString
) *errorString
= CFStringCreateWithFormat(allocator
, NULL
, CFSTR("Could not determine the encoding of the XML data"));
1879 xmlString
= CFStringCreateWithBytes(allocator
, CFDataGetBytePtr(xmlData
), CFDataGetLength(xmlData
), encoding
, true);
1880 if (NULL
== xmlString
&& (!_CFExecutableLinkedOnOrAfter(CFSystemVersionChablis
) || _CFPropertyListAllowNonUTF8
)) { // conversion failed, probably because not in proper encoding
1881 CFTypeRef f
= (__CFNastyFile__
) ? (__CFNastyFile__
) : CFSTR("(UNKNOWN)");
1882 if (encoding
== kCFStringEncodingUTF8
) {
1883 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
);
1886 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
);
1889 // Call __CFStringCreateImmutableFunnel3() the same way CFStringCreateWithBytes() does, except with the addt'l flag
1890 if (encoding
== kCFStringEncodingUTF8
) xmlString
= __CFStringCreateImmutableFunnel3(allocator
, CFDataGetBytePtr(xmlData
), CFDataGetLength(xmlData
), kCFStringEncodingUTF8
, true, true, false, false, false, (void *)-1 /* ALLOCATORSFREEFUNC */, kCFStringEncodingLenientUTF8Conversion
);
1892 length
= xmlString
? CFStringGetLength(xmlString
) : 0;
1895 _CFXMLPlistParseInfo pInfoBuf
;
1896 _CFXMLPlistParseInfo
*pInfo
= &pInfoBuf
;
1898 UniChar
*buf
= (UniChar
*)CFStringGetCharactersPtr(xmlString
);
1900 if (errorString
) *errorString
= NULL
;
1902 buf
= (UniChar
*)CFAllocatorAllocate(allocator
, length
* sizeof(UniChar
), 0);
1903 CFStringGetCharacters(xmlString
, CFRangeMake(0, length
), buf
);
1904 CFRelease(xmlString
);
1908 pInfo
->end
= buf
+length
;
1910 pInfo
->allocator
= allocator
;
1911 pInfo
->errorString
= NULL
;
1912 pInfo
->stringSet
= NULL
;
1913 pInfo
->tmpString
= NULL
;
1914 pInfo
->mutabilityOption
= option
;
1915 pInfo
->allowNewTypes
= allowNewTypes
;
1917 // 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.....
1918 result
= parseXMLPropertyList(pInfo
);
1919 if (result
&& format
) *format
= kCFPropertyListXMLFormat_v1_0
;
1921 CFStringRef err
= pInfo
->errorString
;
1922 // Reset pInfo so we can try again
1923 pInfo
->curr
= pInfo
->begin
;
1924 pInfo
->errorString
= NULL
;
1926 result
= parseOldStylePropertyListOrStringsFile(pInfo
);
1927 if (result
&& format
) *format
= kCFPropertyListOpenStepFormat
;
1929 if (errorString
) *errorString
= CFStringCreateWithFormat(NULL
, NULL
, CFSTR("XML parser error:\n\t%@\nOld-style plist parser error:\n\t%@\n"), err
, pInfo
->errorString
);
1931 if (err
) CFRelease(err
);
1932 if (pInfo
->errorString
) CFRelease(pInfo
->errorString
);
1935 CFRelease(xmlString
);
1937 CFAllocatorDeallocate(allocator
, (void *)pInfo
->begin
);
1939 if (pInfo
->stringSet
) CFRelease(pInfo
->stringSet
);
1940 if (pInfo
->tmpString
) CFRelease(pInfo
->tmpString
);
1941 CFRelease(allocator
);
1945 *errorString
= CFRetain(CFSTR("Conversion of data failed. The file is not UTF-8, or in the encoding specified in XML header if XML."));
1950 CFTypeRef
CFPropertyListCreateFromXMLData(CFAllocatorRef allocator
, CFDataRef xmlData
, CFOptionFlags option
, CFStringRef
*errorString
) {
1951 CFAssert1(xmlData
!= NULL
, __kCFLogAssertion
, "%s(): NULL data not allowed", __PRETTY_FUNCTION__
);
1952 CFAssert2(option
== kCFPropertyListImmutable
|| option
== kCFPropertyListMutableContainers
|| option
== kCFPropertyListMutableContainersAndLeaves
, __kCFLogAssertion
, "%s(): Unrecognized option %d", __PRETTY_FUNCTION__
, option
);
1953 return _CFPropertyListCreateFromXMLData(allocator
, xmlData
, option
, errorString
, true, NULL
);
1956 CFIndex
CFPropertyListWriteToStream(CFPropertyListRef propertyList
, CFWriteStreamRef stream
, CFPropertyListFormat format
, CFStringRef
*errorString
) {
1957 CFAssert1(stream
!= NULL
, __kCFLogAssertion
, "%s(): NULL stream not allowed", __PRETTY_FUNCTION__
);
1958 CFAssert2(format
== kCFPropertyListOpenStepFormat
|| format
== kCFPropertyListXMLFormat_v1_0
|| format
== kCFPropertyListBinaryFormat_v1_0
, __kCFLogAssertion
, "%s(): Unrecognized option %d", __PRETTY_FUNCTION__
, format
);
1959 CFAssert1(propertyList
!= NULL
, __kCFLogAssertion
, "%s(): Cannot be called with a NULL property list", __PRETTY_FUNCTION__
);
1960 __CFAssertIsPList(propertyList
);
1961 CFAssert1(CFWriteStreamGetTypeID() == CFGetTypeID(stream
), __kCFLogAssertion
, "%s(): stream argument is not a write stream", __PRETTY_FUNCTION__
);
1962 CFAssert1(kCFStreamStatusOpen
== CFWriteStreamGetStatus(stream
) || kCFStreamStatusWriting
== CFWriteStreamGetStatus(stream
), __kCFLogAssertion
, "%s(): stream is not open", __PRETTY_FUNCTION__
);
1964 if (errorString
) *errorString
= NULL
;
1965 if (!CFPropertyListIsValid(propertyList
, format
)) {
1966 if (errorString
) *errorString
= CFRetain(CFSTR("Property list invalid for format"));
1969 if (format
== kCFPropertyListOpenStepFormat
) {
1970 if (errorString
) *errorString
= CFRetain(CFSTR("Property list format kCFPropertyListOpenStepFormat not supported for writing"));
1973 if (format
== kCFPropertyListXMLFormat_v1_0
) {
1974 CFDataRef data
= CFPropertyListCreateXMLData(kCFAllocatorSystemDefault
, propertyList
);
1975 CFIndex len
= data
? CFDataGetLength(data
) : 0;
1976 CFIndex ret
= CFWriteStreamWrite(stream
, CFDataGetBytePtr(data
), len
);
1982 if (format
== kCFPropertyListBinaryFormat_v1_0
) {
1983 CFIndex len
= __CFBinaryPlistWriteToStream(propertyList
, stream
);
1986 if (errorString
) *errorString
= CFRetain(CFSTR("Unknown format option"));
1990 static void __CFConvertReadStreamToBytes(CFReadStreamRef stream
, CFIndex max
, uint8_t **buffer
, CFIndex
*length
) {
1991 int32_t buflen
= 0, bufsize
= 0, retlen
;
1992 uint8_t *buf
= NULL
, sbuf
[8192];
1994 retlen
= CFReadStreamRead(stream
, sbuf
, __CFMin(8192, max
));
1996 if (retlen
<= 0 || max
<= 0) {
2001 if (bufsize
< buflen
+ retlen
) {
2002 bufsize
= 2 * bufsize
;
2003 if (bufsize
< buflen
+ retlen
) bufsize
= buflen
+ retlen
;
2004 buf
= CFAllocatorReallocate(kCFAllocatorSystemDefault
, buf
, bufsize
, 0);
2006 memmove(buf
+ buflen
, sbuf
, retlen
);
2011 CFPropertyListRef
CFPropertyListCreateFromStream(CFAllocatorRef allocator
, CFReadStreamRef stream
, CFIndex length
, CFOptionFlags mutabilityOption
, CFPropertyListFormat
*format
, CFStringRef
*errorString
) {
2012 CFPropertyListRef pl
;
2015 uint8_t *buffer
= NULL
;
2016 CFAssert1(stream
!= NULL
, __kCFLogAssertion
, "%s(): NULL stream not allowed", __PRETTY_FUNCTION__
);
2017 CFAssert1(CFReadStreamGetTypeID() == CFGetTypeID(stream
), __kCFLogAssertion
, "%s(): stream argument is not a read stream", __PRETTY_FUNCTION__
);
2018 CFAssert1(kCFStreamStatusOpen
== CFReadStreamGetStatus(stream
) || kCFStreamStatusReading
== CFReadStreamGetStatus(stream
), __kCFLogAssertion
, "%s(): stream is not open", __PRETTY_FUNCTION__
);
2019 CFAssert2(mutabilityOption
== kCFPropertyListImmutable
|| mutabilityOption
== kCFPropertyListMutableContainers
|| mutabilityOption
== kCFPropertyListMutableContainersAndLeaves
, __kCFLogAssertion
, "%s(): Unrecognized option %d", __PRETTY_FUNCTION__
, mutabilityOption
);
2021 if (errorString
) *errorString
= NULL
;
2022 if (0 == length
) length
= INT_MAX
;
2023 __CFConvertReadStreamToBytes(stream
, length
, &buffer
, &buflen
);
2024 if (!buffer
|| buflen
< 6) {
2025 if (buffer
) CFAllocatorDeallocate(kCFAllocatorSystemDefault
, buffer
);
2026 if (errorString
) *errorString
= CFRetain(CFSTR("stream had too few bytes"));
2029 data
= CFDataCreateWithBytesNoCopy(kCFAllocatorSystemDefault
, buffer
, buflen
, kCFAllocatorSystemDefault
);
2030 pl
= _CFPropertyListCreateFromXMLData(allocator
, data
, mutabilityOption
, errorString
, true, format
);
2035 // ========================================================================
2038 // Old NeXT-style property lists
2041 static CFTypeRef
parsePlistObject(_CFXMLPlistParseInfo
*pInfo
, bool requireObject
);
2043 #define isValidUnquotedStringCharacter(x) (((x) >= 'a' && (x) <= 'z') || ((x) >= 'A' && (x) <= 'Z') || ((x) >= '0' && (x) <= '9') || (x) == '_' || (x) == '$' || (x) == '/' || (x) == ':' || (x) == '.' || (x) == '-')
2045 static void advanceToNonSpace(_CFXMLPlistParseInfo
*pInfo
) {
2047 while (pInfo
->curr
< pInfo
->end
) {
2048 ch2
= *(pInfo
->curr
);
2050 if (ch2
>= 9 && ch2
<= 0x0d) continue; // tab, newline, vt, form feed, carriage return
2051 if (ch2
== ' ' || ch2
== 0x2028 || ch2
== 0x2029) continue; // space and Unicode line sep, para sep
2053 if (pInfo
->curr
>= pInfo
->end
) {
2054 // whoops; back up and return
2057 } else if (*(pInfo
->curr
) == '/') {
2059 while (pInfo
->curr
< pInfo
->end
) { // go to end of comment line
2060 UniChar ch3
= *(pInfo
->curr
);
2061 if (ch3
== '\n' || ch3
== '\r' || ch3
== 0x2028 || ch3
== 0x2029) break;
2064 } else if (*(pInfo
->curr
) == '*') { // handle /* ... */
2066 while (pInfo
->curr
< pInfo
->end
) {
2067 ch2
= *(pInfo
->curr
);
2069 if (ch2
== '*' && pInfo
->curr
< pInfo
->end
&& *(pInfo
->curr
) == '/') {
2070 pInfo
->curr
++; // advance past the '/'
2085 static UniChar
getSlashedChar(_CFXMLPlistParseInfo
*pInfo
) {
2086 UniChar ch
= *(pInfo
->curr
);
2097 uint8_t num
= ch
- '0';
2100 /* three digits maximum to avoid reading \000 followed by 5 as \5 ! */
2101 if ((ch
= *(pInfo
->curr
)) >= '0' && ch
<= '7') { // we use in this test the fact that the buffer is zero-terminated
2103 num
= (num
<< 3) + ch
- '0';
2104 if ((pInfo
->curr
< pInfo
->end
) && (ch
= *(pInfo
->curr
)) >= '0' && ch
<= '7') {
2106 num
= (num
<< 3) + ch
- '0';
2109 CFStringEncodingBytesToUnicode(kCFStringEncodingNextStepLatin
, 0, &num
, sizeof(uint8_t), NULL
, &result
, 1, &usedCharLen
);
2110 return (usedCharLen
== 1) ? result
: 0;
2113 unsigned num
= 0, numDigits
= 4; /* Parse four digits */
2114 while (pInfo
->curr
< pInfo
->end
&& numDigits
--) {
2115 if (((ch
= *(pInfo
->curr
)) < 128) && isxdigit(ch
)) {
2117 num
= (num
<< 4) + ((ch
<= '9') ? (ch
- '0') : ((ch
<= 'F') ? (ch
- 'A' + 10) : (ch
- 'a' + 10)));
2122 case 'a': return '\a'; // Note: the meaning of '\a' varies with -traditional to gcc
2123 case 'b': return '\b';
2124 case 'f': return '\f';
2125 case 'n': return '\n';
2126 case 'r': return '\r';
2127 case 't': return '\t';
2128 case 'v': return '\v';
2129 case '"': return '\"';
2130 case '\n': return '\n';
2135 static CFStringRef
parseQuotedPlistString(_CFXMLPlistParseInfo
*pInfo
, UniChar quote
) {
2136 CFMutableStringRef str
= NULL
;
2137 const UniChar
*startMark
= pInfo
->curr
;
2138 const UniChar
*mark
= pInfo
->curr
;
2139 while (pInfo
->curr
< pInfo
->end
) {
2140 UniChar ch
= *(pInfo
->curr
);
2141 if (ch
== quote
) break;
2143 _catFromMarkToBuf(mark
, pInfo
->curr
, &str
, pInfo
->allocator
);
2145 ch
= getSlashedChar(pInfo
);
2146 CFStringAppendCharacters(str
, &ch
, 1);
2149 // 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.
2153 if (pInfo
->end
<= pInfo
->curr
) {
2154 if (str
) CFRelease(str
);
2155 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2156 pInfo
->curr
= startMark
;
2157 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Unterminated quoted string starting on line %d"), lineNumber(pInfo
));
2162 if (pInfo
->mutabilityOption
== kCFPropertyListMutableContainersAndLeaves
) {
2163 _catFromMarkToBuf(mark
, pInfo
->curr
, &str
, pInfo
->allocator
);
2165 str
= (CFMutableStringRef
)_uniqueStringForCharacters(pInfo
, mark
, pInfo
->curr
-mark
);
2169 if (mark
!= pInfo
->curr
) {
2170 _catFromMarkToBuf(mark
, pInfo
->curr
, &str
, pInfo
->allocator
);
2172 if (pInfo
->mutabilityOption
!= kCFPropertyListMutableContainersAndLeaves
) {
2173 CFStringRef uniqueString
= _uniqueStringForString(pInfo
, str
);
2175 CFRetain(uniqueString
);
2176 str
= (CFMutableStringRef
)uniqueString
;
2179 pInfo
->curr
++; // Advance past the quote character before returning.
2180 if (pInfo
->errorString
) {
2181 CFRelease(pInfo
->errorString
);
2182 pInfo
->errorString
= NULL
;
2187 static CFStringRef
parseUnquotedPlistString(_CFXMLPlistParseInfo
*pInfo
) {
2188 const UniChar
*mark
= pInfo
->curr
;
2189 while (pInfo
->curr
< pInfo
->end
) {
2190 UniChar ch
= *pInfo
->curr
;
2191 if (isValidUnquotedStringCharacter(ch
))
2195 if (pInfo
->curr
!= mark
) {
2196 if (pInfo
->mutabilityOption
!= kCFPropertyListMutableContainersAndLeaves
) {
2197 CFStringRef str
= _uniqueStringForCharacters(pInfo
, mark
, pInfo
->curr
-mark
);
2201 CFMutableStringRef str
= CFStringCreateMutable(pInfo
->allocator
, 0);
2202 CFStringAppendCharacters(str
, mark
, pInfo
->curr
- mark
);
2206 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2207 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Unexpected EOF"));
2212 static CFStringRef
parsePlistString(_CFXMLPlistParseInfo
*pInfo
, bool requireObject
) {
2214 advanceToNonSpace(pInfo
);
2215 if (pInfo
->curr
>= pInfo
->end
) {
2216 if (requireObject
&& _CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2217 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Unexpected EOF while parsing string"));
2221 ch
= *(pInfo
->curr
);
2222 if (ch
== '\'' || ch
== '\"') {
2224 return parseQuotedPlistString(pInfo
, ch
);
2225 } else if (isValidUnquotedStringCharacter(ch
)) {
2226 return parseUnquotedPlistString(pInfo
);
2228 if (requireObject
&& _CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2229 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Invalid string character at line %d"), lineNumber(pInfo
));
2235 static CFTypeRef
parsePlistArray(_CFXMLPlistParseInfo
*pInfo
) {
2236 CFMutableArrayRef array
= CFArrayCreateMutable(pInfo
->allocator
, 0, &kCFTypeArrayCallBacks
);
2237 CFTypeRef tmp
= parsePlistObject(pInfo
, false);
2239 CFArrayAppendValue(array
, tmp
);
2241 advanceToNonSpace(pInfo
);
2242 if (*pInfo
->curr
!= ',') {
2246 tmp
= parsePlistObject(pInfo
, false);
2249 advanceToNonSpace(pInfo
);
2250 if (*pInfo
->curr
!= ')') {
2252 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2253 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Expected terminating ')' for array at line %d"), lineNumber(pInfo
));
2257 if (pInfo
->errorString
) {
2258 CFRelease(pInfo
->errorString
);
2259 pInfo
->errorString
= NULL
;
2265 static CFDictionaryRef
parsePlistDictContent(_CFXMLPlistParseInfo
*pInfo
) {
2266 CFMutableDictionaryRef dict
= CFDictionaryCreateMutable(pInfo
->allocator
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2267 CFStringRef key
= NULL
;
2268 Boolean failedParse
= false;
2269 key
= parsePlistString(pInfo
, false);
2272 advanceToNonSpace(pInfo
);
2273 if (*pInfo
->curr
== ';') {
2274 /* This is a strings file using the shortcut format */
2275 /* although this check here really applies to all plists. */
2276 value
= CFRetain(key
);
2277 } else if (*pInfo
->curr
== '=') {
2279 value
= parsePlistObject(pInfo
, true);
2285 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2286 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Unexpected ';' or '=' after key at line %d"), lineNumber(pInfo
));
2291 CFDictionarySetValue(dict
, key
, value
);
2296 advanceToNonSpace(pInfo
);
2297 if (*pInfo
->curr
== ';') {
2299 key
= parsePlistString(pInfo
, false);
2300 } else if (!allowMissingSemi
&& _CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2301 static int yanmode
= -1;
2302 if (-1 == yanmode
) yanmode
= (getenv("YanMode") != NULL
);
2304 CFLog(0, CFSTR("CFPropertyListCreateFromXMLData(): Old-style plist parser: missing semicolon in dictionary."));
2305 if (__CFNastyFile__
) {
2306 CFLog(0, CFSTR("CFPropertyListCreateFromXMLData(): The file name for this data might be (or it might not): %@"), __CFNastyFile__
);
2310 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Missing ';' on line %d"), lineNumber(pInfo
));
2312 // on pre-Jaguar systems, do nothing except silently ignore the rest
2313 // of the dictionary, which is what happened on those systems.
2318 if (key
) CFRelease(key
);
2322 if (pInfo
->errorString
) {
2323 CFRelease(pInfo
->errorString
);
2324 pInfo
->errorString
= NULL
;
2329 static CFTypeRef
parsePlistDict(_CFXMLPlistParseInfo
*pInfo
) {
2330 CFDictionaryRef dict
= parsePlistDictContent(pInfo
);
2331 if (!dict
) return NULL
;
2332 advanceToNonSpace(pInfo
);
2333 if (*pInfo
->curr
!= '}') {
2335 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2336 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Expected terminating '}' for dictionary at line %d"), lineNumber(pInfo
));
2344 CF_INLINE
unsigned char fromHexDigit(unsigned char ch
) {
2345 if (isdigit(ch
)) return ch
- '0';
2346 if ((ch
>= 'a') && (ch
<= 'f')) return ch
- 'a' + 10;
2347 if ((ch
>= 'A') && (ch
<= 'F')) return ch
- 'A' + 10;
2348 return 0xff; // Just choose a large number for the error code
2351 /* Gets up to bytesSize bytes from a plist data. Returns number of bytes actually read. Leaves cursor at first non-space, non-hex character.
2352 -1 is returned for unexpected char, -2 for uneven number of hex digits
2354 static int getDataBytes(_CFXMLPlistParseInfo
*pInfo
, unsigned char *bytes
, int bytesSize
) {
2355 int numBytesRead
= 0;
2356 while ((pInfo
->curr
< pInfo
->end
) && (numBytesRead
< bytesSize
)) {
2358 UniChar ch1
= *pInfo
->curr
;
2359 if (ch1
== '>') return numBytesRead
; // Meaning we're done
2360 first
= fromHexDigit(ch1
);
2361 if (first
!= 0xff) { // If the first char is a hex, then try to read a second hex
2363 if (pInfo
->curr
>= pInfo
->end
) return -2; // Error: uneven number of hex digits
2364 UniChar ch2
= *pInfo
->curr
;
2365 second
= fromHexDigit(ch2
);
2366 if (second
== 0xff) return -2; // Error: uneven number of hex digits
2367 bytes
[numBytesRead
++] = (first
<< 4) + second
;
2369 } else if (ch1
== ' ' || ch1
== '\n' || ch1
== '\t' || ch1
== '\r' || ch1
== 0x2028 || ch1
== 0x2029) {
2372 return -1; // Error: unexpected character
2375 return numBytesRead
; // This does likely mean we didn't encounter a '>', but we'll let the caller deal with that
2378 static CFTypeRef
parsePlistData(_CFXMLPlistParseInfo
*pInfo
) {
2379 CFMutableDataRef result
= CFDataCreateMutable(pInfo
->allocator
, 0);
2381 // Read hex bytes and append them to result
2383 #define numBytes 400
2384 unsigned char bytes
[numBytes
];
2385 int numBytesRead
= getDataBytes(pInfo
, bytes
, numBytes
);
2386 if (numBytesRead
< 0) {
2388 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2389 switch (numBytesRead
) {
2390 case -2: pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Malformed data byte group at line %d; uneven length"), lineNumber(pInfo
)); break;
2391 default: pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Malformed data byte group at line %d; invalid hex"), lineNumber(pInfo
)); break;
2396 if (numBytesRead
== 0) break;
2397 CFDataAppendBytes(result
, bytes
, numBytesRead
);
2400 if (pInfo
->errorString
) {
2401 CFRelease(pInfo
->errorString
);
2402 pInfo
->errorString
= NULL
;
2405 if (*(pInfo
->curr
) == '>') {
2406 pInfo
->curr
++; // Move past '>'
2410 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2411 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Expected terminating '>' for data at line %d"), lineNumber(pInfo
));
2417 // Returned object is retained; caller must free.
2418 static CFTypeRef
parsePlistObject(_CFXMLPlistParseInfo
*pInfo
, bool requireObject
) {
2420 advanceToNonSpace(pInfo
);
2421 if (pInfo
->curr
+ 1 >= pInfo
->end
) {
2422 if (requireObject
&& _CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2423 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Unexpected EOF while parsing plist"));
2427 ch
= *(pInfo
->curr
);
2430 return parsePlistDict(pInfo
);
2431 } else if (ch
== '(') {
2432 return parsePlistArray(pInfo
);
2433 } else if (ch
== '<') {
2434 return parsePlistData(pInfo
);
2435 } else if (ch
== '\'' || ch
== '\"') {
2436 return parseQuotedPlistString(pInfo
, ch
);
2437 } else if (isValidUnquotedStringCharacter(ch
)) {
2439 return parseUnquotedPlistString(pInfo
);
2441 pInfo
->curr
--; // Must back off the charcter we just read
2442 if (requireObject
&& _CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2443 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Unexpected character '0x%x' at line %d"), ch
, lineNumber(pInfo
));
2449 static CFTypeRef
parseOldStylePropertyListOrStringsFile(_CFXMLPlistParseInfo
*pInfo
) {
2450 const UniChar
*begin
= pInfo
->curr
;
2452 advanceToNonSpace(pInfo
);
2453 // A file consisting only of whitespace (or empty) is now defined to be an empty dictionary
2454 if (pInfo
->curr
>= pInfo
->end
) return CFDictionaryCreateMutable(pInfo
->allocator
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2455 result
= parsePlistObject(pInfo
, true);
2456 advanceToNonSpace(pInfo
);
2457 if (pInfo
->curr
>= pInfo
->end
) return result
;
2458 if (!result
) return NULL
;
2459 if (CFGetTypeID(result
) != CFStringGetTypeID()) {
2461 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2462 pInfo
->errorString
= CFStringCreateWithFormat(pInfo
->allocator
, NULL
, CFSTR("Junk after plist at line %d"), lineNumber(pInfo
));
2467 // Check for a strings file (looks like a dictionary without the opening/closing curly braces)
2468 pInfo
->curr
= begin
;
2469 return parsePlistDictContent(pInfo
);
2472 #undef isValidUnquotedStringCharacter
2474 static CFArrayRef
_arrayDeepImmutableCopy(CFAllocatorRef allocator
, CFArrayRef array
, CFOptionFlags mutabilityOption
) {
2475 CFArrayRef result
= NULL
;
2476 CFIndex i
, c
= CFArrayGetCount(array
);
2479 result
= CFArrayCreate(allocator
, NULL
, 0, &kCFTypeArrayCallBacks
);
2480 } else if ((values
= CFAllocatorAllocate(allocator
, c
*sizeof(CFTypeRef
), 0)) != NULL
) {
2481 CFArrayGetValues(array
, CFRangeMake(0, c
), values
);
2482 for (i
= 0; i
< c
; i
++) {
2483 values
[i
] = CFPropertyListCreateDeepCopy(allocator
, values
[i
], mutabilityOption
);
2484 if (values
[i
] == NULL
) {
2488 result
= (i
== c
) ? CFArrayCreate(allocator
, values
, c
, &kCFTypeArrayCallBacks
) : NULL
;
2490 for (i
= 0; i
< c
; i
++) {
2491 CFRelease(values
[i
]);
2493 CFAllocatorDeallocate(allocator
, values
);
2498 static CFMutableArrayRef
_arrayDeepMutableCopy(CFAllocatorRef allocator
, CFArrayRef array
, CFOptionFlags mutabilityOption
) {
2499 CFIndex i
, c
= CFArrayGetCount(array
);
2500 CFMutableArrayRef result
= CFArrayCreateMutable(allocator
, 0, &kCFTypeArrayCallBacks
);
2502 for (i
= 0; i
< c
; i
++) {
2503 CFTypeRef newValue
= CFPropertyListCreateDeepCopy(allocator
, CFArrayGetValueAtIndex(array
, i
), mutabilityOption
);
2504 if (!newValue
) break;
2505 CFArrayAppendValue(result
, newValue
);
2506 CFRelease(newValue
);
2516 CFPropertyListRef
CFPropertyListCreateDeepCopy(CFAllocatorRef allocator
, CFPropertyListRef propertyList
, CFOptionFlags mutabilityOption
) {
2518 CFPropertyListRef result
= NULL
;
2519 CFAssert1(propertyList
!= NULL
, __kCFLogAssertion
, "%s(): cannot copy a NULL property list", __PRETTY_FUNCTION__
);
2520 __CFAssertIsPList(propertyList
);
2521 CFAssert2(mutabilityOption
== kCFPropertyListImmutable
|| mutabilityOption
== kCFPropertyListMutableContainers
|| mutabilityOption
== kCFPropertyListMutableContainersAndLeaves
, __kCFLogAssertion
, "%s(): Unrecognized option %d", __PRETTY_FUNCTION__
, mutabilityOption
);
2522 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar
)) {
2523 if (!CFPropertyListIsValid(propertyList
, kCFPropertyListBinaryFormat_v1_0
)) return NULL
;
2526 if (allocator
== NULL
) {
2527 allocator
= CFRetain(__CFGetDefaultAllocator());
2529 CFRetain(allocator
);
2532 typeID
= CFGetTypeID(propertyList
);
2533 if (typeID
== CFDictionaryGetTypeID()) {
2534 CFDictionaryRef dict
= (CFDictionaryRef
)propertyList
;
2535 Boolean
mutable = (mutabilityOption
!= kCFPropertyListImmutable
);
2536 CFIndex count
= CFDictionaryGetCount(dict
);
2537 CFTypeRef
*keys
, *values
;
2539 result
= mutable ? CFDictionaryCreateMutable(allocator
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
): CFDictionaryCreate(allocator
, NULL
, NULL
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2540 } else if ((keys
= CFAllocatorAllocate(allocator
, 2 * count
* sizeof(CFTypeRef
), 0)) != NULL
) {
2542 values
= keys
+count
;
2543 CFDictionaryGetKeysAndValues(dict
, keys
, values
);
2544 for (i
= 0; i
< count
; i
++) {
2545 keys
[i
] = CFStringCreateCopy(allocator
, keys
[i
]);
2546 if (keys
[i
] == NULL
) {
2549 values
[i
] = CFPropertyListCreateDeepCopy(allocator
, values
[i
], mutabilityOption
);
2550 if (values
[i
] == NULL
) {
2556 result
= mutable ? CFDictionaryCreateMutable(allocator
, 0, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
) : CFDictionaryCreate(allocator
, keys
, values
, count
, &kCFTypeDictionaryKeyCallBacks
, &kCFTypeDictionaryValueCallBacks
);
2557 for (i
= 0; i
< count
; i
++) {
2559 CFDictionarySetValue((CFMutableDictionaryRef
)result
, keys
[i
], values
[i
]);
2562 CFRelease(values
[i
]);
2567 for (i
= 0; i
< count
; i
++) {
2569 CFRelease(values
[i
]);
2572 CFAllocatorDeallocate(allocator
, keys
);
2576 } else if (typeID
== CFArrayGetTypeID()) {
2577 if (mutabilityOption
== kCFPropertyListImmutable
) {
2578 result
= _arrayDeepImmutableCopy(allocator
, (CFArrayRef
)propertyList
, mutabilityOption
);
2580 result
= _arrayDeepMutableCopy(allocator
, (CFArrayRef
)propertyList
, mutabilityOption
);
2582 } else if (typeID
== CFDataGetTypeID()) {
2583 if (mutabilityOption
== kCFPropertyListMutableContainersAndLeaves
) {
2584 result
= CFDataCreateMutableCopy(allocator
, 0, (CFDataRef
)propertyList
);
2586 result
= CFDataCreateCopy(allocator
, (CFDataRef
)propertyList
);
2588 } else if (typeID
== CFNumberGetTypeID()) {
2589 // Warning - this will break if byteSize is ever greater than 16
2591 CFNumberType numType
= CFNumberGetType((CFNumberRef
)propertyList
);
2592 CFNumberGetValue((CFNumberRef
)propertyList
, numType
, (void *)bytes
);
2593 result
= CFNumberCreate(allocator
, numType
, (void *)bytes
);
2594 } else if (typeID
== CFBooleanGetTypeID()) {
2595 // Booleans are immutable & shared instances
2596 CFRetain(propertyList
);
2597 result
= propertyList
;
2598 } else if (typeID
== CFDateGetTypeID()) {
2599 // Dates are immutable
2600 result
= CFDateCreate(allocator
, CFDateGetAbsoluteTime((CFDateRef
)propertyList
));
2601 } else if (typeID
== CFStringGetTypeID()) {
2602 if (mutabilityOption
== kCFPropertyListMutableContainersAndLeaves
) {
2603 result
= CFStringCreateMutableCopy(allocator
, 0, (CFStringRef
)propertyList
);
2605 result
= CFStringCreateCopy(allocator
, (CFStringRef
)propertyList
);
2608 CFAssert2(false, __kCFLogAssertion
, "%s(): 0x%x is not a property list type", __PRETTY_FUNCTION__
, propertyList
);
2611 CFRelease(allocator
);