]> git.saurik.com Git - apple/cf.git/blob - Parsing.subproj/CFPropertyList.c
CF-299.3.tar.gz
[apple/cf.git] / Parsing.subproj / CFPropertyList.c
1 /*
2 * Copyright (c) 2003 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
7 *
8 * This file contains Original Code and/or Modifications of Original Code
9 * as defined in and that are subject to the Apple Public Source License
10 * Version 2.0 (the 'License'). You may not use this file except in
11 * compliance with the License. Please obtain a copy of the License at
12 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * file.
14 *
15 * The Original Code and all software distributed under the License are
16 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
17 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
18 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
20 * Please see the License for the specific language governing rights and
21 * limitations under the License.
22 *
23 * @APPLE_LICENSE_HEADER_END@
24 */
25 /* CFPropertyList.c
26 Copyright 1999-2002, Apple, Inc. All rights reserved.
27 Responsibility: Christopher Kane
28 */
29
30 #include <CoreFoundation/CFPropertyList.h>
31 #include <CoreFoundation/CFDate.h>
32 #include <CoreFoundation/CFNumber.h>
33 #include <CoreFoundation/CFSet.h>
34 #include "CFUtilities.h"
35 #include "CFStringEncodingConverter.h"
36 #include "CFInternal.h"
37 #include <limits.h>
38 #include <float.h>
39 #include <string.h>
40 #include <stdlib.h>
41 #include <math.h>
42 #if defined(__MACH__) || defined(__WIN32__)
43 #include <ctype.h>
44 #elif !defined(__WIN32__)
45 #define isspace(x) ((x)==' ' || (x)=='\n' || (x)=='\f' || (x)=='\r' || (x)=='\t' || (x)=='\v')
46 #define isdigit(x) ((x) <= '9' && (x) >= '0')
47 #define isxdigit(x) (((x) <= '9' && (x) >= '0') || ((x) >= 'a' && (x) <= 'f') || ((x) >= 'A' && (x) <= 'F'))
48 #endif
49
50 __private_extern__ bool allowMissingSemi = false;
51
52 // Should move this somewhere else
53 intptr_t _CFDoOperation(intptr_t code, intptr_t subcode1, intptr_t subcode2) {
54 switch (code) {
55 case 15317: allowMissingSemi = subcode1 ? true : false; break;
56 }
57 return code;
58 }
59
60 #define PLIST_IX 0
61 #define ARRAY_IX 1
62 #define DICT_IX 2
63 #define KEY_IX 3
64 #define STRING_IX 4
65 #define DATA_IX 5
66 #define DATE_IX 6
67 #define REAL_IX 7
68 #define INTEGER_IX 8
69 #define TRUE_IX 9
70 #define FALSE_IX 10
71 #define DOCTYPE_IX 11
72 #define CDSECT_IX 12
73
74 #define PLIST_TAG_LENGTH 5
75 #define ARRAY_TAG_LENGTH 5
76 #define DICT_TAG_LENGTH 4
77 #define KEY_TAG_LENGTH 3
78 #define STRING_TAG_LENGTH 6
79 #define DATA_TAG_LENGTH 4
80 #define DATE_TAG_LENGTH 4
81 #define REAL_TAG_LENGTH 4
82 #define INTEGER_TAG_LENGTH 7
83 #define TRUE_TAG_LENGTH 4
84 #define FALSE_TAG_LENGTH 5
85 #define DOCTYPE_TAG_LENGTH 7
86 #define CDSECT_TAG_LENGTH 9
87
88 // don't allow _CFKeyedArchiverUID here
89 #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);
90
91 static bool __CFPropertyListIsValidAux(CFPropertyListRef plist, bool recursive, CFMutableSetRef set, CFPropertyListFormat format);
92
93 struct context {
94 bool answer;
95 CFMutableSetRef set;
96 CFPropertyListFormat format;
97 };
98
99 static void __CFPropertyListIsArrayPlistAux(const void *value, void *context) {
100 struct context *ctx = (struct context *)context;
101 if (!ctx->answer) return;
102 #if defined(DEBUG)
103 if (!value) CFLog(0, CFSTR("CFPropertyListIsValid(): property list arrays cannot contain NULL"));
104 #endif
105 ctx->answer = value && __CFPropertyListIsValidAux(value, true, ctx->set, ctx->format);
106 }
107
108 static void __CFPropertyListIsDictPlistAux(const void *key, const void *value, void *context) {
109 struct context *ctx = (struct context *)context;
110 if (!ctx->answer) return;
111 #if defined(DEBUG)
112 if (!key) CFLog(0, CFSTR("CFPropertyListIsValid(): property list dictionaries cannot contain NULL keys"));
113 if (!value) CFLog(0, CFSTR("CFPropertyListIsValid(): property list dictionaries cannot contain NULL values"));
114 if (CFStringGetTypeID() != CFGetTypeID(key)) {
115 CFStringRef desc = CFCopyTypeIDDescription(CFGetTypeID(key));
116 CFLog(0, CFSTR("CFPropertyListIsValid(): property list dictionaries may only have keys which are CFStrings, not '%@'"), desc);
117 CFRelease(desc);
118 }
119 #endif
120 ctx->answer = key && value && (CFStringGetTypeID() == CFGetTypeID(key)) && __CFPropertyListIsValidAux(value, true, ctx->set, ctx->format);
121 }
122
123 static bool __CFPropertyListIsValidAux(CFPropertyListRef plist, bool recursive, CFMutableSetRef set, CFPropertyListFormat format) {
124 CFTypeID type;
125 #if defined(DEBUG)
126 if (!plist) CFLog(0, CFSTR("CFPropertyListIsValid(): property lists cannot contain NULL"));
127 #endif
128 if (!plist) return false;
129 type = CFGetTypeID(plist);
130 if (CFStringGetTypeID() == type) return true;
131 if (CFDataGetTypeID() == type) return true;
132 if (kCFPropertyListOpenStepFormat != format) {
133 if (CFBooleanGetTypeID() == type) return true;
134 if (CFNumberGetTypeID() == type) return true;
135 if (CFDateGetTypeID() == type) return true;
136 if (_CFKeyedArchiverUIDGetTypeID() == type) return true;
137 }
138 if (!recursive && CFArrayGetTypeID() == type) return true;
139 if (!recursive && CFDictionaryGetTypeID() == type) return true;
140 // at any one invocation of this function, set should contain the objects in the "path" down to this object
141 #if defined(DEBUG)
142 if (CFSetContainsValue(set, plist)) CFLog(0, CFSTR("CFPropertyListIsValid(): property lists cannot contain recursive container references"));
143 #endif
144 if (CFSetContainsValue(set, plist)) return false;
145 if (CFArrayGetTypeID() == type) {
146 struct context ctx = {true, set, format};
147 CFSetAddValue(set, plist);
148 CFArrayApplyFunction(plist, CFRangeMake(0, CFArrayGetCount(plist)), __CFPropertyListIsArrayPlistAux, &ctx);
149 CFSetRemoveValue(set, plist);
150 return ctx.answer;
151 }
152 if (CFDictionaryGetTypeID() == type) {
153 struct context ctx = {true, set, format};
154 CFSetAddValue(set, plist);
155 CFDictionaryApplyFunction(plist, __CFPropertyListIsDictPlistAux, &ctx);
156 CFSetRemoveValue(set, plist);
157 return ctx.answer;
158 }
159 #if defined(DEBUG)
160 {
161 CFStringRef desc = CFCopyTypeIDDescription(type);
162 CFLog(0, CFSTR("CFPropertyListIsValid(): property lists cannot contain objects of type '%@'"), desc);
163 CFRelease(desc);
164 }
165 #endif
166 return false;
167 }
168
169 Boolean CFPropertyListIsValid(CFPropertyListRef plist, CFPropertyListFormat format) {
170 CFMutableSetRef set;
171 bool result;
172 CFAssert1(plist != NULL, __kCFLogAssertion, "%s(): NULL is not a property list", __PRETTY_FUNCTION__);
173 set = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, NULL);
174 result = __CFPropertyListIsValidAux(plist, true, set, format);
175 CFRelease(set);
176 return result;
177 }
178
179 static const UniChar CFXMLPlistTags[13][10]= {
180 {'p', 'l', 'i', 's', 't', '\0', '\0', '\0', '\0', '\0'},
181 {'a', 'r', 'r', 'a', 'y', '\0', '\0', '\0', '\0', '\0'},
182 {'d', 'i', 'c', 't', '\0', '\0', '\0', '\0', '\0', '\0'},
183 {'k', 'e', 'y', '\0', '\0', '\0', '\0', '\0', '\0', '\0'},
184 {'s', 't', 'r', 'i', 'n', 'g', '\0', '\0', '\0', '\0'},
185 {'d', 'a', 't', 'a', '\0', '\0', '\0', '\0', '\0', '\0'},
186 {'d', 'a', 't', 'e', '\0', '\0', '\0', '\0', '\0', '\0'},
187 {'r', 'e', 'a', 'l', '\0', '\0', '\0', '\0', '\0', '\0'},
188 {'i', 'n', 't', 'e', 'g', 'e', 'r', '\0', '\0', '\0'},
189 {'t', 'r', 'u', 'e', '\0', '\0', '\0', '\0', '\0', '\0'},
190 {'f', 'a', 'l', 's', 'e', '\0', '\0', '\0', '\0', '\0'},
191 {'D', 'O', 'C', 'T', 'Y', 'P', 'E', '\0', '\0', '\0'},
192 {'<', '!', '[', 'C', 'D', 'A', 'T', 'A', '[', '\0'}
193 };
194
195 typedef struct {
196 const UniChar *begin; // first character of the XML to be parsed
197 const UniChar *curr; // current parse location
198 const UniChar *end; // the first character _after_ the end of the XML
199 CFStringRef errorString;
200 CFAllocatorRef allocator;
201 UInt32 mutabilityOption;
202 CFMutableSetRef stringSet; // set of all strings involved in this parse; allows us to share non-mutable strings in the returned plist
203 CFMutableStringRef tmpString; // Mutable string with external characters that functions can feel free to use as temporary storage as the parse progresses
204 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)
205 char _padding[3];
206 } _CFXMLPlistParseInfo;
207
208 static CFTypeRef parseOldStylePropertyListOrStringsFile(_CFXMLPlistParseInfo *pInfo);
209
210
211
212 // 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.
213
214 // Null-terminated, ASCII or UTF8 string
215 //
216 static void _plistAppendUTF8CString(CFMutableDataRef mData, const char *cString) {
217 CFDataAppendBytes (mData, (const UInt8 *)cString, strlen(cString));
218 }
219
220 // UniChars
221 //
222 static void _plistAppendCharacters(CFMutableDataRef mData, const UniChar *chars, CFIndex length) {
223 CFIndex curLoc = 0;
224
225 do { // Flush out ASCII chars, BUFLEN at a time
226 #define BUFLEN 400
227 UInt8 buf[BUFLEN], *bufPtr = buf;
228 CFIndex cnt = 0;
229 while (cnt < length && (cnt - curLoc < BUFLEN) && (chars[cnt] < 128)) *bufPtr++ = (UInt8)(chars[cnt++]);
230 if (cnt > curLoc) { // Flush any ASCII bytes
231 CFDataAppendBytes(mData, buf, cnt - curLoc);
232 curLoc = cnt;
233 }
234 } while (curLoc < length && (chars[curLoc] < 128)); // We will exit out of here when we run out of chars or hit a non-ASCII char
235
236 if (curLoc < length) { // Now deal with non-ASCII chars
237 CFDataRef data = NULL;
238 CFStringRef str = NULL;
239 if ((str = CFStringCreateWithCharactersNoCopy(NULL, chars + curLoc, length - curLoc, kCFAllocatorNull))) {
240 if ((data = CFStringCreateExternalRepresentation(NULL, str, kCFStringEncodingUTF8, 0))) {
241 CFDataAppendBytes (mData, CFDataGetBytePtr(data), CFDataGetLength(data));
242 CFRelease(data);
243 }
244 CFRelease(str);
245 }
246 CFAssert1(str && data, __kCFLogAssertion, "%s(): Error writing plist", __PRETTY_FUNCTION__);
247 }
248 }
249
250 // Append CFString
251 //
252 static void _plistAppendString(CFMutableDataRef mData, CFStringRef str) {
253 const UniChar *chars;
254 const char *cStr;
255 CFDataRef data;
256 if ((chars = CFStringGetCharactersPtr(str))) {
257 _plistAppendCharacters(mData, chars, CFStringGetLength(str));
258 } else if ((cStr = CFStringGetCStringPtr(str, kCFStringEncodingASCII)) || (cStr = CFStringGetCStringPtr(str, kCFStringEncodingUTF8))) {
259 _plistAppendUTF8CString(mData, cStr);
260 } else if ((data = CFStringCreateExternalRepresentation(NULL, str, kCFStringEncodingUTF8, 0))) {
261 CFDataAppendBytes (mData, CFDataGetBytePtr(data), CFDataGetLength(data));
262 CFRelease(data);
263 } else {
264 CFAssert1(TRUE, __kCFLogAssertion, "%s(): Error in plist writing", __PRETTY_FUNCTION__);
265 }
266 }
267
268
269 // Append CFString-style format + arguments
270 //
271 static void _plistAppendFormat(CFMutableDataRef mData, CFStringRef format, ...) {
272 CFStringRef fStr;
273 va_list argList;
274
275 va_start(argList, format);
276 fStr = CFStringCreateWithFormatAndArguments(NULL, NULL, format, argList);
277 va_end(argList);
278
279 CFAssert1(fStr, __kCFLogAssertion, "%s(): Error writing plist", __PRETTY_FUNCTION__);
280 _plistAppendString(mData, fStr);
281 CFRelease(fStr);
282 }
283
284
285
286 static void _appendIndents(CFIndex numIndents, CFMutableDataRef str) {
287 #define NUMTABS 4
288 static const UniChar tabs[NUMTABS] = {'\t','\t','\t','\t'};
289 for (; numIndents > 0; numIndents -= NUMTABS) _plistAppendCharacters(str, tabs, (numIndents >= NUMTABS) ? NUMTABS : numIndents);
290 }
291
292 /* Append the escaped version of origStr to mStr.
293 */
294 static void _appendEscapedString(CFStringRef origStr, CFMutableDataRef mStr) {
295 #define BUFSIZE 64
296 CFIndex i, length = CFStringGetLength(origStr);
297 CFIndex bufCnt = 0;
298 UniChar buf[BUFSIZE];
299 CFStringInlineBuffer inlineBuffer;
300
301 CFStringInitInlineBuffer(origStr, &inlineBuffer, CFRangeMake(0, length));
302
303 for (i = 0; i < length; i ++) {
304 UniChar ch = __CFStringGetCharacterFromInlineBufferQuick(&inlineBuffer, i);
305 switch(ch) {
306 case '<':
307 if (bufCnt) _plistAppendCharacters(mStr, buf, bufCnt);
308 bufCnt = 0;
309 _plistAppendUTF8CString(mStr, "&lt;");
310 break;
311 case '>':
312 if (bufCnt) _plistAppendCharacters(mStr, buf, bufCnt);
313 bufCnt = 0;
314 _plistAppendUTF8CString(mStr, "&gt;");
315 break;
316 case '&':
317 if (bufCnt) _plistAppendCharacters(mStr, buf, bufCnt);
318 bufCnt = 0;
319 _plistAppendUTF8CString(mStr, "&amp;");
320 break;
321 default:
322 buf[bufCnt++] = ch;
323 if (bufCnt == BUFSIZE) {
324 _plistAppendCharacters(mStr, buf, bufCnt);
325 bufCnt = 0;
326 }
327 break;
328 }
329 }
330 if (bufCnt) _plistAppendCharacters(mStr, buf, bufCnt);
331 }
332
333
334
335 /* Base-64 encoding/decoding */
336
337 /* The base-64 encoding packs three 8-bit bytes into four 7-bit ASCII
338 * characters. If the number of bytes in the original data isn't divisable
339 * by three, "=" characters are used to pad the encoded data. The complete
340 * set of characters used in base-64 are:
341 *
342 * 'A'..'Z' => 00..25
343 * 'a'..'z' => 26..51
344 * '0'..'9' => 52..61
345 * '+' => 62
346 * '/' => 63
347 * '=' => pad
348 */
349
350 // Write the inputData to the mData using Base 64 encoding
351
352 static void _XMLPlistAppendDataUsingBase64(CFMutableDataRef mData, CFDataRef inputData, CFIndex indent) {
353 static const char __CFPLDataEncodeTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
354 #define MAXLINELEN 76
355 char buf[MAXLINELEN + 4 + 2]; // For the slop and carriage return and terminating NULL
356
357 const uint8_t *bytes = CFDataGetBytePtr(inputData);
358 CFIndex length = CFDataGetLength(inputData);
359 CFIndex i, pos;
360 const uint8_t *p;
361
362 if (indent > 8) indent = 8; // refuse to indent more than 64 characters
363
364 pos = 0; // position within buf
365
366 for (i = 0, p = bytes; i < length; i++, p++) {
367 /* 3 bytes are encoded as 4 */
368 switch (i % 3) {
369 case 0:
370 buf[pos++] = __CFPLDataEncodeTable [ ((p[0] >> 2) & 0x3f)];
371 break;
372 case 1:
373 buf[pos++] = __CFPLDataEncodeTable [ ((((p[-1] << 8) | p[0]) >> 4) & 0x3f)];
374 break;
375 case 2:
376 buf[pos++] = __CFPLDataEncodeTable [ ((((p[-1] << 8) | p[0]) >> 6) & 0x3f)];
377 buf[pos++] = __CFPLDataEncodeTable [ (p[0] & 0x3f)];
378 break;
379 }
380 /* Flush the line out every 76 (or fewer) chars --- indents count against the line length*/
381 if (pos >= MAXLINELEN - 8 * indent) {
382 buf[pos++] = '\n';
383 buf[pos++] = 0;
384 _appendIndents(indent, mData);
385 _plistAppendUTF8CString(mData, buf);
386 pos = 0;
387 }
388 }
389
390 switch (i % 3) {
391 case 0:
392 break;
393 case 1:
394 buf[pos++] = __CFPLDataEncodeTable [ ((p[-1] << 4) & 0x30)];
395 buf[pos++] = '=';
396 buf[pos++] = '=';
397 break;
398 case 2:
399 buf[pos++] = __CFPLDataEncodeTable [ ((p[-1] << 2) & 0x3c)];
400 buf[pos++] = '=';
401 break;
402 }
403
404 if (pos > 0) {
405 buf[pos++] = '\n';
406 buf[pos++] = 0;
407 _appendIndents(indent, mData);
408 _plistAppendUTF8CString(mData, buf);
409 }
410 }
411
412 extern CFStringRef __CFNumberCopyFormattingDescriptionAsFloat64(CFTypeRef cf);
413
414 static void _CFAppendXML0(CFTypeRef object, UInt32 indentation, CFMutableDataRef xmlString) {
415 UInt32 typeID = CFGetTypeID(object);
416 _appendIndents(indentation, xmlString);
417 if (typeID == CFStringGetTypeID()) {
418 _plistAppendUTF8CString(xmlString, "<");
419 _plistAppendCharacters(xmlString, CFXMLPlistTags[STRING_IX], STRING_TAG_LENGTH);
420 _plistAppendUTF8CString(xmlString, ">");
421 _appendEscapedString(object, xmlString);
422 _plistAppendUTF8CString(xmlString, "</");
423 _plistAppendCharacters(xmlString, CFXMLPlistTags[STRING_IX], STRING_TAG_LENGTH);
424 _plistAppendUTF8CString(xmlString, ">\n");
425 } else if (typeID == _CFKeyedArchiverUIDGetTypeID()) {
426 uint64_t v = _CFKeyedArchiverUIDGetValue(object);
427 CFNumberRef num = CFNumberCreate(kCFAllocatorSystemDefault, kCFNumberSInt64Type, &v);
428 _plistAppendUTF8CString(xmlString, "<");
429 _plistAppendCharacters(xmlString, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH);
430 _plistAppendUTF8CString(xmlString, ">\n");
431 _appendIndents(indentation+1, xmlString);
432 _plistAppendUTF8CString(xmlString, "<");
433 _plistAppendCharacters(xmlString, CFXMLPlistTags[KEY_IX], KEY_TAG_LENGTH);
434 _plistAppendUTF8CString(xmlString, ">");
435 _appendEscapedString(CFSTR("CF$UID"), xmlString);
436 _plistAppendUTF8CString(xmlString, "</");
437 _plistAppendCharacters(xmlString, CFXMLPlistTags[KEY_IX], KEY_TAG_LENGTH);
438 _plistAppendUTF8CString(xmlString, ">\n");
439 _CFAppendXML0(num, indentation+1, xmlString);
440 _appendIndents(indentation, xmlString);
441 _plistAppendUTF8CString(xmlString, "</");
442 _plistAppendCharacters(xmlString, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH);
443 _plistAppendUTF8CString(xmlString, ">\n");
444 } else if (typeID == CFArrayGetTypeID()) {
445 UInt32 i, count = CFArrayGetCount(object);
446 if (count == 0) {
447 _plistAppendUTF8CString(xmlString, "<");
448 _plistAppendCharacters(xmlString, CFXMLPlistTags[ARRAY_IX], ARRAY_TAG_LENGTH);
449 _plistAppendUTF8CString(xmlString, "/>\n");
450 return;
451 }
452 _plistAppendUTF8CString(xmlString, "<");
453 _plistAppendCharacters(xmlString, CFXMLPlistTags[ARRAY_IX], ARRAY_TAG_LENGTH);
454 _plistAppendUTF8CString(xmlString, ">\n");
455 for (i = 0; i < count; i ++) {
456 _CFAppendXML0(CFArrayGetValueAtIndex(object, i), indentation+1, xmlString);
457 }
458 _appendIndents(indentation, xmlString);
459 _plistAppendUTF8CString(xmlString, "</");
460 _plistAppendCharacters(xmlString, CFXMLPlistTags[ARRAY_IX], ARRAY_TAG_LENGTH);
461 _plistAppendUTF8CString(xmlString, ">\n");
462 } else if (typeID == CFDictionaryGetTypeID()) {
463 UInt32 i, count = CFDictionaryGetCount(object);
464 CFAllocatorRef allocator = CFGetAllocator(xmlString);
465 CFMutableArrayRef keyArray;
466 CFTypeRef *keys;
467 if (count == 0) {
468 _plistAppendUTF8CString(xmlString, "<");
469 _plistAppendCharacters(xmlString, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH);
470 _plistAppendUTF8CString(xmlString, "/>\n");
471 return;
472 }
473 _plistAppendUTF8CString(xmlString, "<");
474 _plistAppendCharacters(xmlString, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH);
475 _plistAppendUTF8CString(xmlString, ">\n");
476 keys = (CFTypeRef *)CFAllocatorAllocate(allocator, count * sizeof(CFTypeRef), 0);
477 CFDictionaryGetKeysAndValues(object, keys, NULL);
478 keyArray = CFArrayCreateMutable(allocator, count, &kCFTypeArrayCallBacks);
479 CFArrayReplaceValues(keyArray, CFRangeMake(0, 0), keys, count);
480 CFArraySortValues(keyArray, CFRangeMake(0, count), (CFComparatorFunction)CFStringCompare, NULL);
481 CFArrayGetValues(keyArray, CFRangeMake(0, count), keys);
482 CFRelease(keyArray);
483 for (i = 0; i < count; i ++) {
484 CFTypeRef key = keys[i];
485 _appendIndents(indentation+1, xmlString);
486 _plistAppendUTF8CString(xmlString, "<");
487 _plistAppendCharacters(xmlString, CFXMLPlistTags[KEY_IX], KEY_TAG_LENGTH);
488 _plistAppendUTF8CString(xmlString, ">");
489 _appendEscapedString(key, xmlString);
490 _plistAppendUTF8CString(xmlString, "</");
491 _plistAppendCharacters(xmlString, CFXMLPlistTags[KEY_IX], KEY_TAG_LENGTH);
492 _plistAppendUTF8CString(xmlString, ">\n");
493 _CFAppendXML0(CFDictionaryGetValue(object, key), indentation+1, xmlString);
494 }
495 CFAllocatorDeallocate(allocator, keys);
496 _appendIndents(indentation, xmlString);
497 _plistAppendUTF8CString(xmlString, "</");
498 _plistAppendCharacters(xmlString, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH);
499 _plistAppendUTF8CString(xmlString, ">\n");
500 } else if (typeID == CFDataGetTypeID()) {
501 _plistAppendUTF8CString(xmlString, "<");
502 _plistAppendCharacters(xmlString, CFXMLPlistTags[DATA_IX], DATA_TAG_LENGTH);
503 _plistAppendUTF8CString(xmlString, ">\n");
504 _XMLPlistAppendDataUsingBase64(xmlString, object, indentation);
505 _appendIndents(indentation, xmlString);
506 _plistAppendUTF8CString(xmlString, "</");
507 _plistAppendCharacters(xmlString, CFXMLPlistTags[DATA_IX], DATA_TAG_LENGTH);
508 _plistAppendUTF8CString(xmlString, ">\n");
509 } else if (typeID == CFDateGetTypeID()) {
510 // YYYY '-' MM '-' DD 'T' hh ':' mm ':' ss 'Z'
511 CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(CFDateGetAbsoluteTime(object), NULL);
512
513 _plistAppendUTF8CString(xmlString, "<");
514 _plistAppendCharacters(xmlString, CFXMLPlistTags[DATE_IX], DATE_TAG_LENGTH);
515 _plistAppendUTF8CString(xmlString, ">");
516
517 _plistAppendFormat(xmlString, CFSTR("%04d-%02d-%02dT%02d:%02d:%02dZ"), date.year, date.month, date.day, date.hour, date.minute, (int)date.second);
518
519 _plistAppendUTF8CString(xmlString, "</");
520 _plistAppendCharacters(xmlString, CFXMLPlistTags[DATE_IX], DATE_TAG_LENGTH);
521 _plistAppendUTF8CString(xmlString, ">\n");
522 } else if (typeID == CFNumberGetTypeID()) {
523 if (CFNumberIsFloatType(object)) {
524 _plistAppendUTF8CString(xmlString, "<");
525 _plistAppendCharacters(xmlString, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH);
526 _plistAppendUTF8CString(xmlString, ">");
527
528 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
529 CFStringRef s = __CFNumberCopyFormattingDescriptionAsFloat64(object);
530 _plistAppendString(xmlString, s);
531 CFRelease(s);
532 } else if (CFNumberGetType(object) == kCFNumberFloat64Type || CFNumberGetType(object) == kCFNumberDoubleType) {
533 double doubleVal;
534 static CFStringRef doubleFormatString = NULL;
535 CFNumberGetValue(object, kCFNumberDoubleType, &doubleVal);
536 if (!doubleFormatString) {
537 doubleFormatString = CFStringCreateWithFormat(NULL, NULL, CFSTR("%%.%de"), DBL_DIG);
538 }
539 _plistAppendFormat(xmlString, doubleFormatString, doubleVal);
540 } else {
541 float floatVal;
542 static CFStringRef floatFormatString = NULL;
543 CFNumberGetValue(object, kCFNumberFloatType, &floatVal);
544 if (!floatFormatString) {
545 floatFormatString = CFStringCreateWithFormat(NULL, NULL, CFSTR("%%.%de"), FLT_DIG);
546 }
547 _plistAppendFormat(xmlString, floatFormatString, floatVal);
548 }
549
550 _plistAppendUTF8CString(xmlString, "</");
551 _plistAppendCharacters(xmlString, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH);
552 _plistAppendUTF8CString(xmlString, ">\n");
553 } else {
554 _plistAppendUTF8CString(xmlString, "<");
555 _plistAppendCharacters(xmlString, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH);
556 _plistAppendUTF8CString(xmlString, ">");
557
558 _plistAppendFormat(xmlString, CFSTR("%@"), object);
559
560 _plistAppendUTF8CString(xmlString, "</");
561 _plistAppendCharacters(xmlString, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH);
562 _plistAppendUTF8CString(xmlString, ">\n");
563 }
564 } else if (typeID == CFBooleanGetTypeID()) {
565 if (CFBooleanGetValue(object)) {
566 _plistAppendUTF8CString(xmlString, "<");
567 _plistAppendCharacters(xmlString, CFXMLPlistTags[TRUE_IX], TRUE_TAG_LENGTH);
568 _plistAppendUTF8CString(xmlString, "/>\n");
569 } else {
570 _plistAppendUTF8CString(xmlString, "<");
571 _plistAppendCharacters(xmlString, CFXMLPlistTags[FALSE_IX], FALSE_TAG_LENGTH);
572 _plistAppendUTF8CString(xmlString, "/>\n");
573 }
574 }
575 }
576
577 static void _CFGenerateXMLPropertyListToData(CFMutableDataRef xml, CFTypeRef propertyList) {
578 _plistAppendUTF8CString(xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE ");
579 _plistAppendCharacters(xml, CFXMLPlistTags[PLIST_IX], PLIST_TAG_LENGTH);
580 _plistAppendUTF8CString(xml, " PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<");
581 _plistAppendCharacters(xml, CFXMLPlistTags[PLIST_IX], PLIST_TAG_LENGTH);
582 _plistAppendUTF8CString(xml, " version=\"1.0\">\n");
583
584 _CFAppendXML0(propertyList, 0, xml);
585
586 _plistAppendUTF8CString(xml, "</");
587 _plistAppendCharacters(xml, CFXMLPlistTags[PLIST_IX], PLIST_TAG_LENGTH);
588 _plistAppendUTF8CString(xml, ">\n");
589 }
590
591 CFDataRef CFPropertyListCreateXMLData(CFAllocatorRef allocator, CFPropertyListRef propertyList) {
592 CFMutableDataRef xml;
593 CFAssert1(propertyList != NULL, __kCFLogAssertion, "%s(): Cannot be called with a NULL property list", __PRETTY_FUNCTION__);
594 __CFAssertIsPList(propertyList);
595 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
596 if (!CFPropertyListIsValid(propertyList, kCFPropertyListXMLFormat_v1_0)) return NULL;
597 }
598 xml = CFDataCreateMutable(allocator, 0);
599 _CFGenerateXMLPropertyListToData(xml, propertyList);
600 return xml;
601 }
602
603 CFDataRef _CFPropertyListCreateXMLDataWithExtras(CFAllocatorRef allocator, CFPropertyListRef propertyList) {
604 CFMutableDataRef xml;
605 CFAssert1(propertyList != NULL, __kCFLogAssertion, "%s(): Cannot be called with a NULL property list", __PRETTY_FUNCTION__);
606 xml = CFDataCreateMutable(allocator, 0);
607 _CFGenerateXMLPropertyListToData(xml, propertyList);
608 return xml;
609 }
610
611 // ========================================================================
612
613 //
614 // ------------------------- Reading plists ------------------
615 //
616
617 static void skipInlineDTD(_CFXMLPlistParseInfo *pInfo);
618 static CFTypeRef parseXMLElement(_CFXMLPlistParseInfo *pInfo, Boolean *isKey);
619
620 // warning: doesn't have a good idea of Unicode line separators
621 static UInt32 lineNumber(_CFXMLPlistParseInfo *pInfo) {
622 const UniChar *p = pInfo->begin;
623 UInt32 count = 1;
624 while (p < pInfo->curr) {
625 if (*p == '\r') {
626 count ++;
627 if (*(p + 1) == '\n')
628 p ++;
629 } else if (*p == '\n') {
630 count ++;
631 }
632 p ++;
633 }
634 return count;
635 }
636
637 // warning: doesn't have a good idea of Unicode white space
638 CF_INLINE void skipWhitespace(_CFXMLPlistParseInfo *pInfo) {
639 while (pInfo->curr < pInfo->end) {
640 switch (*(pInfo->curr)) {
641 case ' ':
642 case '\t':
643 case '\n':
644 case '\r':
645 pInfo->curr ++;
646 continue;
647 default:
648 return;
649 }
650 }
651 }
652
653 /* 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. */
654
655 // pInfo should be just past "<!--"
656 static void skipXMLComment(_CFXMLPlistParseInfo *pInfo) {
657 const UniChar *p = pInfo->curr;
658 const UniChar *end = pInfo->end - 3; // Need at least 3 characters to compare against
659 while (p < end) {
660 if (*p == '-' && *(p+1) == '-' && *(p+2) == '>') {
661 pInfo->curr = p+3;
662 return;
663 }
664 p ++;
665 }
666 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unterminated comment started on line %d"), lineNumber(pInfo));
667 }
668
669 // stringToMatch and buf must both be of at least len
670 static Boolean matchString(const UniChar *buf, const UniChar *stringToMatch, UInt32 len) {
671 switch (len) {
672 case 10: if (buf[9] != stringToMatch[9]) return false;
673 case 9: if (buf[8] != stringToMatch[8]) return false;
674 case 8: if (buf[7] != stringToMatch[7]) return false;
675 case 7: if (buf[6] != stringToMatch[6]) return false;
676 case 6: if (buf[5] != stringToMatch[5]) return false;
677 case 5: if (buf[4] != stringToMatch[4]) return false;
678 case 4: if (buf[3] != stringToMatch[3]) return false;
679 case 3: if (buf[2] != stringToMatch[2]) return false;
680 case 2: if (buf[1] != stringToMatch[1]) return false;
681 case 1: if (buf[0] != stringToMatch[0]) return false;
682 case 0: return true;
683 }
684 return false; // internal error
685 }
686
687 // pInfo should be set to the first character after "<?"
688 static void skipXMLProcessingInstruction(_CFXMLPlistParseInfo *pInfo) {
689 const UniChar *begin = pInfo->curr, *end = pInfo->end - 2; // Looking for "?>" so we need at least 2 characters
690 while (pInfo->curr < end) {
691 if (*(pInfo->curr) == '?' && *(pInfo->curr+1) == '>') {
692 pInfo->curr += 2;
693 return;
694 }
695 pInfo->curr ++;
696 }
697 pInfo->curr = begin;
698 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected EOF while parsing the processing instruction begun on line %d"), lineNumber(pInfo));
699 }
700
701 // first character should be immediately after the "<!"
702 static void skipDTD(_CFXMLPlistParseInfo *pInfo) {
703 // First pass "DOCTYPE"
704 if (pInfo->end - pInfo->curr < DOCTYPE_TAG_LENGTH || !matchString(pInfo->curr, CFXMLPlistTags[DOCTYPE_IX], DOCTYPE_TAG_LENGTH)) {
705 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Malformed DTD on line %d"), lineNumber(pInfo));
706 return;
707 }
708 pInfo->curr += DOCTYPE_TAG_LENGTH;
709 skipWhitespace(pInfo);
710
711 // Look for either the beginning of a complex DTD or the end of the DOCTYPE structure
712 while (pInfo->curr < pInfo->end) {
713 UniChar ch = *(pInfo->curr);
714 if (ch == '[') break; // inline DTD
715 if (ch == '>') { // End of the DTD
716 pInfo->curr ++;
717 return;
718 }
719 pInfo->curr ++;
720 }
721 if (pInfo->curr == pInfo->end) {
722 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF while parsing DTD", CFStringGetSystemEncoding());
723 return;
724 }
725
726 // *Sigh* Must parse in-line DTD
727 skipInlineDTD(pInfo);
728 if (pInfo->errorString) return;
729 skipWhitespace(pInfo);
730 if (pInfo->errorString) return;
731 if (pInfo->curr < pInfo->end) {
732 if (*(pInfo->curr) == '>') {
733 pInfo->curr ++;
734 } else {
735 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c on line %d while parsing DTD"), *(pInfo->curr), lineNumber(pInfo));
736 }
737 } else {
738 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF while parsing DTD", CFStringGetSystemEncoding());
739 }
740 }
741
742 static void skipPERef(_CFXMLPlistParseInfo *pInfo) {
743 const UniChar *p = pInfo->curr;
744 while (p < pInfo->end) {
745 if (*p == ';') {
746 pInfo->curr = p+1;
747 return;
748 }
749 p ++;
750 }
751 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected EOF while parsing percent-escape sequence begun on line %d"), lineNumber(pInfo));
752 }
753
754 // First character should be just past '['
755 static void skipInlineDTD(_CFXMLPlistParseInfo *pInfo) {
756 while (!pInfo->errorString && pInfo->curr < pInfo->end) {
757 UniChar ch;
758 skipWhitespace(pInfo);
759 ch = *pInfo->curr;
760 if (ch == '%') {
761 pInfo->curr ++;
762 skipPERef(pInfo);
763 } else if (ch == '<') {
764 pInfo->curr ++;
765 if (pInfo->curr >= pInfo->end) {
766 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF while parsing inline DTD", CFStringGetSystemEncoding());
767 return;
768 }
769 ch = *(pInfo->curr);
770 if (ch == '?') {
771 pInfo->curr ++;
772 skipXMLProcessingInstruction(pInfo);
773 } else if (ch == '!') {
774 if (pInfo->curr + 2 < pInfo->end && (*(pInfo->curr+1) == '-' && *(pInfo->curr+2) == '-')) {
775 pInfo->curr += 3;
776 skipXMLComment(pInfo);
777 } else {
778 // Skip the myriad of DTD declarations of the form "<!string" ... ">"
779 pInfo->curr ++; // Past both '<' and '!'
780 while (pInfo->curr < pInfo->end) {
781 if (*(pInfo->curr) == '>') break;
782 pInfo->curr ++;
783 }
784 if (*(pInfo->curr) != '>') {
785 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF while parsing inline DTD", CFStringGetSystemEncoding());
786 return;
787
788 }
789 pInfo->curr ++;
790 }
791 } else {
792 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c on line %d while parsing inline DTD"), ch, lineNumber(pInfo));
793 return;
794 }
795 } else if (ch == ']') {
796 pInfo->curr ++;
797 return;
798 } else {
799 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c on line %d while parsing inline DTD"), ch, lineNumber(pInfo));
800 return;
801 }
802 }
803 if (!pInfo->errorString)
804 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF while parsing inline DTD", CFStringGetSystemEncoding());
805 }
806
807 /* 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. */
808
809 static const signed char __CFPLDataDecodeTable[128] = {
810 /* 000 */ -1, -1, -1, -1, -1, -1, -1, -1,
811 /* 010 */ -1, -1, -1, -1, -1, -1, -1, -1,
812 /* 020 */ -1, -1, -1, -1, -1, -1, -1, -1,
813 /* 030 */ -1, -1, -1, -1, -1, -1, -1, -1,
814 /* ' ' */ -1, -1, -1, -1, -1, -1, -1, -1,
815 /* '(' */ -1, -1, -1, 62, -1, -1, -1, 63,
816 /* '0' */ 52, 53, 54, 55, 56, 57, 58, 59,
817 /* '8' */ 60, 61, -1, -1, -1, 0, -1, -1,
818 /* '@' */ -1, 0, 1, 2, 3, 4, 5, 6,
819 /* 'H' */ 7, 8, 9, 10, 11, 12, 13, 14,
820 /* 'P' */ 15, 16, 17, 18, 19, 20, 21, 22,
821 /* 'X' */ 23, 24, 25, -1, -1, -1, -1, -1,
822 /* '`' */ -1, 26, 27, 28, 29, 30, 31, 32,
823 /* 'h' */ 33, 34, 35, 36, 37, 38, 39, 40,
824 /* 'p' */ 41, 42, 43, 44, 45, 46, 47, 48,
825 /* 'x' */ 49, 50, 51, -1, -1, -1, -1, -1
826 };
827
828 static CFDataRef __CFPLDataDecode(_CFXMLPlistParseInfo *pInfo, Boolean mutable) {
829 int tmpbufpos = 0;
830 int tmpbuflen = 64;
831 uint8_t *tmpbuf;
832 int numeq = 0;
833 int acc = 0;
834 int cntr = 0;
835
836 tmpbuf = CFAllocatorAllocate(pInfo->allocator, tmpbuflen, 0);
837 for (; pInfo->curr < pInfo->end; pInfo->curr++) {
838 UniChar c = *(pInfo->curr);
839 if (c == '<') {
840 break;
841 }
842 if ('=' == c) {
843 numeq++;
844 } else if (!isspace(c)) {
845 numeq = 0;
846 }
847 if (__CFPLDataDecodeTable[c] < 0)
848 continue;
849 cntr++;
850 acc <<= 6;
851 acc += __CFPLDataDecodeTable[c];
852 if (0 == (cntr & 0x3)) {
853 if (tmpbuflen <= tmpbufpos + 2) {
854 tmpbuflen <<= 2;
855 tmpbuf = CFAllocatorReallocate(pInfo->allocator, tmpbuf, tmpbuflen, 0);
856 }
857 tmpbuf[tmpbufpos++] = (acc >> 16) & 0xff;
858 if (numeq < 2)
859 tmpbuf[tmpbufpos++] = (acc >> 8) & 0xff;
860 if (numeq < 1)
861 tmpbuf[tmpbufpos++] = acc & 0xff;
862 }
863 }
864 if (mutable) {
865 CFMutableDataRef result = CFDataCreateMutable(pInfo->allocator, 0);
866 CFDataAppendBytes(result, tmpbuf, tmpbufpos);
867 CFAllocatorDeallocate(pInfo->allocator, tmpbuf);
868 return result;
869 } else {
870 return CFDataCreateWithBytesNoCopy(pInfo->allocator, (char const *) tmpbuf, tmpbufpos, pInfo->allocator);
871 }
872 }
873
874 // content ::== (element | CharData | Reference | CDSect | PI | Comment)*
875 // 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).
876 static CFTypeRef getContentObject(_CFXMLPlistParseInfo *pInfo, Boolean *isKey) {
877 if (isKey) *isKey = false;
878 while (!pInfo->errorString && pInfo->curr < pInfo->end) {
879 skipWhitespace(pInfo);
880 if (pInfo->curr >= pInfo->end) {
881 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
882 return NULL;
883 }
884 if (*(pInfo->curr) != '<') {
885 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c on line %d"), *(pInfo->curr), lineNumber(pInfo));
886 return NULL;
887 }
888 pInfo->curr ++;
889 if (pInfo->curr >= pInfo->end) {
890 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
891 return NULL;
892 }
893 switch (*(pInfo->curr)) {
894 case '?':
895 // Processing instruction
896 skipXMLProcessingInstruction(pInfo);
897 break;
898 case '!':
899 // Could be a comment
900 if (pInfo->curr+2 >= pInfo->end) {
901 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
902 return NULL;
903 }
904 if (*(pInfo->curr+1) == '-' && *(pInfo->curr+2) == '-') {
905 pInfo->curr += 2;
906 skipXMLComment(pInfo);
907 } else {
908 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
909 return NULL;
910 }
911 break;
912 case '/':
913 // Whoops! Looks like we got to the end tag for the element whose content we're parsing
914 pInfo->curr --; // Back off to the '<'
915 return NULL;
916 default:
917 // Should be an element
918 return parseXMLElement(pInfo, isKey);
919 }
920 }
921 // 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"
922 return NULL;
923 }
924
925 static void _catFromMarkToBuf(const UniChar *mark, const UniChar *buf, CFMutableStringRef *string, CFAllocatorRef allocator ) {
926 if (!(*string)) {
927 *string = CFStringCreateMutable(allocator, 0);
928 }
929 CFStringAppendCharacters(*string, mark, buf-mark);
930 }
931
932 static void parseCDSect_pl(_CFXMLPlistParseInfo *pInfo, CFMutableStringRef string) {
933 const UniChar *end, *begin;
934 if (pInfo->end - pInfo->curr < CDSECT_TAG_LENGTH) {
935 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
936 return;
937 }
938 if (!matchString(pInfo->curr, CFXMLPlistTags[CDSECT_IX], CDSECT_TAG_LENGTH)) {
939 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered improper CDATA opening at line %d"), lineNumber(pInfo));
940 return;
941 }
942 pInfo->curr += CDSECT_TAG_LENGTH;
943 begin = pInfo->curr; // Marks the first character of the CDATA content
944 end = pInfo->end-2; // So we can safely look 2 characters beyond p
945 while (pInfo->curr < end) {
946 if (*(pInfo->curr) == ']' && *(pInfo->curr+1) == ']' && *(pInfo->curr+2) == '>') {
947 // Found the end!
948 CFStringAppendCharacters(string, begin, pInfo->curr-begin);
949 pInfo->curr += 3;
950 return;
951 }
952 pInfo->curr ++;
953 }
954 // Never found the end mark
955 pInfo->curr = begin;
956 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Could not find end of CDATA started on line %d"), lineNumber(pInfo));
957 }
958
959 // Only legal references are {lt, gt, amp, apos, quote, #ddd, #xAAA}
960 static void parseEntityReference_pl(_CFXMLPlistParseInfo *pInfo, CFMutableStringRef string) {
961 int len;
962 UniChar ch;
963 pInfo->curr ++; // move past the '&';
964 len = pInfo->end - pInfo->curr; // how many characters we can safely scan
965 if (len < 1) {
966 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
967 return;
968 }
969 switch (*(pInfo->curr)) {
970 case 'l': // "lt"
971 if (len >= 3 && *(pInfo->curr+1) == 't' && *(pInfo->curr+2) == ';') {
972 ch = '<';
973 pInfo->curr += 3;
974 break;
975 }
976 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo));
977 return;
978 case 'g': // "gt"
979 if (len >= 3 && *(pInfo->curr+1) == 't' && *(pInfo->curr+2) == ';') {
980 ch = '>';
981 pInfo->curr += 3;
982 break;
983 }
984 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo));
985 return;
986 case 'a': // "apos" or "amp"
987 if (len < 4) { // Not enough characters for either conversion
988 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
989 return;
990 }
991 if (*(pInfo->curr+1) == 'm') {
992 // "amp"
993 if (*(pInfo->curr+2) == 'p' && *(pInfo->curr+3) == ';') {
994 ch = '&';
995 pInfo->curr += 4;
996 break;
997 }
998 } else if (*(pInfo->curr+1) == 'p') {
999 // "apos"
1000 if (len > 4 && *(pInfo->curr+2) == 'o' && *(pInfo->curr+3) == 's' && *(pInfo->curr+4) == ';') {
1001 ch = '\'';
1002 pInfo->curr += 5;
1003 break;
1004 }
1005 }
1006 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo));
1007 return;
1008 case 'q': // "quote"
1009 if (len >= 5 && *(pInfo->curr+1) == 'u' && *(pInfo->curr+2) == 'o' && *(pInfo->curr+3) == 't' && *(pInfo->curr+4) == ';') {
1010 ch = '\"';
1011 pInfo->curr += 5;
1012 break;
1013 }
1014 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo));
1015 return;
1016 case '#':
1017 {
1018 uint16_t num = 0;
1019 Boolean isHex = false;
1020 if ( len < 4) { // Not enough characters to make it all fit! Need at least "&#d;"
1021 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
1022 return;
1023 }
1024 pInfo->curr ++;
1025 if (*(pInfo->curr) == 'x') {
1026 isHex = true;
1027 pInfo->curr ++;
1028 }
1029 while (pInfo->curr < pInfo->end) {
1030 ch = *(pInfo->curr);
1031 pInfo->curr ++;
1032 if (ch == ';') {
1033 CFStringAppendCharacters(string, &num, 1);
1034 return;
1035 }
1036 if (!isHex) num = num*10;
1037 else num = num << 4;
1038 if (ch <= '9' && ch >= '0') {
1039 num += (ch - '0');
1040 } else if (!isHex) {
1041 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c at line %d"), ch, lineNumber(pInfo));
1042 return;
1043 } else if (ch >= 'a' && ch <= 'f') {
1044 num += 10 + (ch - 'a');
1045 } else if (ch >= 'A' && ch <= 'F') {
1046 num += 10 + (ch - 'A');
1047 } else {
1048 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c at line %d"), ch, lineNumber(pInfo));
1049 return;
1050 }
1051 }
1052 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
1053 return;
1054 }
1055 default:
1056 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unknown ampersand-escape sequence at line %d"), lineNumber(pInfo));
1057 return;
1058 }
1059 CFStringAppendCharacters(string, &ch, 1);
1060 }
1061
1062 extern const void *__CFSetAddValueAndReturn(CFMutableSetRef set, const void *value);
1063
1064 static CFStringRef _uniqueStringForString(_CFXMLPlistParseInfo *pInfo, CFStringRef stringToUnique) {
1065 if (!pInfo->stringSet) {
1066 pInfo->stringSet = CFSetCreateMutable(pInfo->allocator, 0, &kCFCopyStringSetCallBacks);
1067 _CFSetSetCapacity(pInfo->stringSet, 160); // set capacity high to avoid lots of rehashes, though waste some memory
1068 }
1069 return __CFSetAddValueAndReturn(pInfo->stringSet, stringToUnique);
1070 }
1071
1072 extern void _CFStrSetDesiredCapacity(CFMutableStringRef str, CFIndex len);
1073
1074 static CFStringRef _uniqueStringForCharacters(_CFXMLPlistParseInfo *pInfo, const UniChar *base, CFIndex length) {
1075 CFIndex idx;
1076 uint8_t *ascii, buffer[1024];
1077 bool isASCII;
1078 if (!pInfo->stringSet) {
1079 pInfo->stringSet = CFSetCreateMutable(pInfo->allocator, 0, &kCFCopyStringSetCallBacks);
1080 _CFSetSetCapacity(pInfo->stringSet, 160); // set capacity high to avoid lots of rehashes, though waste some memory
1081 }
1082 if (pInfo->tmpString) {
1083 CFStringDelete(pInfo->tmpString, CFRangeMake(0, CFStringGetLength(pInfo->tmpString)));
1084 } else {
1085 pInfo->tmpString = CFStringCreateMutable(pInfo->allocator, 0);
1086 _CFStrSetDesiredCapacity(pInfo->tmpString, 512);
1087 }
1088 // This is to avoid having to promote the buffers of all the strings compared against
1089 // during the set probe; if a Unicode string is passed in, that's what happens.
1090 isASCII = true;
1091 for (idx = 0; isASCII && idx < length; idx++) isASCII = isASCII && (base[idx] < 0x80);
1092 if (isASCII) {
1093 ascii = (length < (CFIndex)sizeof(buffer)) ? buffer : CFAllocatorAllocate(kCFAllocatorSystemDefault, length + 1, 0);
1094 for (idx = 0; idx < length; idx++) ascii[idx] = (uint8_t)base[idx];
1095 ascii[length] = '\0';
1096 CFStringAppendCString(pInfo->tmpString, ascii, kCFStringEncodingASCII);
1097 if (ascii != buffer) CFAllocatorDeallocate(kCFAllocatorSystemDefault, ascii);
1098 } else {
1099 CFStringAppendCharacters(pInfo->tmpString, base, length);
1100 }
1101 return __CFSetAddValueAndReturn(pInfo->stringSet, pInfo->tmpString);
1102 }
1103
1104
1105 // String could be comprised of characters, CDSects, or references to one of the "well-known" entities ('<', '>', '&', ''', '"')
1106 // returns a retained object in *string.
1107 static CFStringRef getString(_CFXMLPlistParseInfo *pInfo) {
1108 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
1109 CFMutableStringRef string = NULL;
1110 while (!pInfo->errorString && pInfo->curr < pInfo->end) {
1111 UniChar ch = *(pInfo->curr);
1112 if (ch == '<') {
1113 // Could be a CDSect; could be the end of the string
1114 if (*(pInfo->curr+1) != '!') break; // End of the string
1115 _catFromMarkToBuf(mark, pInfo->curr, &string, pInfo->allocator);
1116 parseCDSect_pl(pInfo, string);
1117 mark = pInfo->curr;
1118 } else if (ch == '&') {
1119 _catFromMarkToBuf(mark, pInfo->curr, &string, pInfo->allocator);
1120 parseEntityReference_pl(pInfo, string);
1121 mark = pInfo->curr;
1122 } else {
1123 pInfo->curr ++;
1124 }
1125 }
1126
1127 if (pInfo->errorString) {
1128 if (string) CFRelease(string);
1129 return NULL;
1130 }
1131 if (!string) {
1132 if (pInfo->mutabilityOption != kCFPropertyListMutableContainersAndLeaves) {
1133 CFStringRef uniqueString = _uniqueStringForCharacters(pInfo, mark, pInfo->curr-mark);
1134 CFRetain(uniqueString);
1135 return uniqueString;
1136 } else {
1137 string = CFStringCreateMutable(pInfo->allocator, 0);
1138 CFStringAppendCharacters(string, mark, pInfo->curr - mark);
1139 return string;
1140 }
1141 }
1142 _catFromMarkToBuf(mark, pInfo->curr, &string, pInfo->allocator);
1143 if (pInfo->mutabilityOption != kCFPropertyListMutableContainersAndLeaves) {
1144 CFStringRef uniqueString = _uniqueStringForString(pInfo, string);
1145 CFRetain(uniqueString);
1146 CFRelease(string);
1147 return uniqueString;
1148 }
1149 return string;
1150 }
1151
1152 static Boolean checkForCloseTag(_CFXMLPlistParseInfo *pInfo, const UniChar *tag, CFIndex tagLen) {
1153 if (pInfo->end - pInfo->curr < tagLen + 3) {
1154 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
1155 return false;
1156 }
1157 if (*(pInfo->curr) != '<' || *(++pInfo->curr) != '/') {
1158 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c on line %d"), *(pInfo->curr), lineNumber(pInfo));
1159 return false;
1160 }
1161 pInfo->curr ++;
1162 if (!matchString(pInfo->curr, tag, tagLen)) {
1163 CFStringRef str = CFStringCreateWithCharactersNoCopy(pInfo->allocator, tag, tagLen, kCFAllocatorNull);
1164 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Close tag on line %d does not match open tag %@"), lineNumber(pInfo), str);
1165 CFRelease(str);
1166 return false;
1167 }
1168 pInfo->curr += tagLen;
1169 skipWhitespace(pInfo);
1170 if (pInfo->curr == pInfo->end) {
1171 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
1172 return false;
1173 }
1174 if (*(pInfo->curr) != '>') {
1175 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected character %c on line %d"), *(pInfo->curr), lineNumber(pInfo));
1176 return false;
1177 }
1178 pInfo->curr ++;
1179 return true;
1180 }
1181
1182 // pInfo should be set to the first content character of the <plist>
1183 static CFTypeRef parsePListTag(_CFXMLPlistParseInfo *pInfo) {
1184 CFTypeRef result, tmp = NULL;
1185 const UniChar *save;
1186 result = getContentObject(pInfo, NULL);
1187 if (!result) {
1188 if (!pInfo->errorString) pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered empty plist tag", CFStringGetSystemEncoding());
1189 return NULL;
1190 }
1191 save = pInfo->curr; // Save this in case the next step fails
1192 tmp = getContentObject(pInfo, NULL);
1193 if (tmp) {
1194 // Got an extra object
1195 CFRelease(tmp);
1196 CFRelease(result);
1197 pInfo->curr = save;
1198 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unexpected element at line %d (plist can only include one object)"), lineNumber(pInfo));
1199 return NULL;
1200 }
1201 if (pInfo->errorString) {
1202 // Parse failed catastrophically
1203 CFRelease(result);
1204 return NULL;
1205 }
1206 if (checkForCloseTag(pInfo, CFXMLPlistTags[PLIST_IX], PLIST_TAG_LENGTH)) {
1207 return result;
1208 }
1209 CFRelease(result);
1210 return NULL;
1211 }
1212
1213 static int allowImmutableCollections = -1;
1214
1215 static void checkImmutableCollections(void) {
1216 allowImmutableCollections = (NULL == getenv("CFPropertyListAllowImmutableCollections")) ? 0 : 1;
1217 }
1218
1219 static CFTypeRef parseArrayTag(_CFXMLPlistParseInfo *pInfo) {
1220 CFMutableArrayRef array = CFArrayCreateMutable(pInfo->allocator, 0, &kCFTypeArrayCallBacks);
1221 CFTypeRef tmp = getContentObject(pInfo, NULL);
1222 while (tmp) {
1223 CFArrayAppendValue(array, tmp);
1224 CFRelease(tmp);
1225 tmp = getContentObject(pInfo, NULL);
1226 }
1227 if (pInfo->errorString) { // getContentObject encountered a parse error
1228 CFRelease(array);
1229 return NULL;
1230 }
1231 if (checkForCloseTag(pInfo, CFXMLPlistTags[ARRAY_IX], ARRAY_TAG_LENGTH)) {
1232 if (-1 == allowImmutableCollections) checkImmutableCollections();
1233 if (1 == allowImmutableCollections) {
1234 if (pInfo->mutabilityOption == kCFPropertyListImmutable) {
1235 CFArrayRef newArray = CFArrayCreateCopy(pInfo->allocator, array);
1236 CFRelease(array);
1237 array = (CFMutableArrayRef)newArray;
1238 }
1239 }
1240 return array;
1241 }
1242 CFRelease(array);
1243 return NULL;
1244 }
1245
1246 static CFTypeRef parseDictTag(_CFXMLPlistParseInfo *pInfo) {
1247 CFMutableDictionaryRef dict = NULL;
1248 CFTypeRef key=NULL, value=NULL;
1249 Boolean gotKey;
1250 const UniChar *base = pInfo->curr;
1251 key = getContentObject(pInfo, &gotKey);
1252 while (key) {
1253 if (!gotKey) {
1254 if (key) CFRelease(key);
1255 if (dict) CFRelease(dict);
1256 pInfo->curr = base;
1257 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Found non-key inside <dict> at line %d"), lineNumber(pInfo));
1258 return NULL;
1259 }
1260 value = getContentObject(pInfo, NULL);
1261 if (!value) {
1262 if (key) CFRelease(key);
1263 if (dict) CFRelease(dict);
1264 if (!pInfo->errorString)
1265 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Value missing for key inside <dict> at line %d"), lineNumber(pInfo));
1266 return NULL;
1267 }
1268 if (NULL == dict) {
1269 dict = CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1270 _CFDictionarySetCapacity(dict, 10);
1271 }
1272 CFDictionarySetValue(dict, key, value);
1273 CFRelease(key);
1274 key = NULL;
1275 CFRelease(value);
1276 value = NULL;
1277 base = pInfo->curr;
1278 key = getContentObject(pInfo, &gotKey);
1279 }
1280 if (checkForCloseTag(pInfo, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH)) {
1281 if (NULL == dict) {
1282 if (pInfo->mutabilityOption == kCFPropertyListImmutable) {
1283 dict = (CFMutableDictionaryRef)CFDictionaryCreate(pInfo->allocator, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1284 } else {
1285 dict = CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1286 }
1287 } else {
1288 CFIndex cnt = CFDictionaryGetCount(dict);
1289 if (1 == cnt) {
1290 CFTypeRef val = CFDictionaryGetValue(dict, CFSTR("CF$UID"));
1291 if (val && CFGetTypeID(val) == CFNumberGetTypeID()) {
1292 CFTypeRef uid;
1293 uint32_t v;
1294 CFNumberGetValue(val, kCFNumberSInt32Type, &v);
1295 uid = (CFTypeRef)_CFKeyedArchiverUIDCreate(pInfo->allocator, v);
1296 CFRelease(dict);
1297 return uid;
1298 }
1299 }
1300 if (-1 == allowImmutableCollections) checkImmutableCollections();
1301 if (1 == allowImmutableCollections) {
1302 if (pInfo->mutabilityOption == kCFPropertyListImmutable) {
1303 CFDictionaryRef newDict = CFDictionaryCreateCopy(pInfo->allocator, dict);
1304 CFRelease(dict);
1305 dict = (CFMutableDictionaryRef)newDict;
1306 }
1307 }
1308 }
1309 return dict;
1310 }
1311 if (dict) CFRelease(dict);
1312 return NULL;
1313 }
1314
1315 static CFTypeRef parseDataTag(_CFXMLPlistParseInfo *pInfo) {
1316 CFDataRef result;
1317 const UniChar *base = pInfo->curr;
1318 result = __CFPLDataDecode(pInfo, pInfo->mutabilityOption == kCFPropertyListMutableContainersAndLeaves);
1319 if (!result) {
1320 pInfo->curr = base;
1321 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Could not interpret <data> at line %d (should be base64-encoded)"), lineNumber(pInfo));
1322 return NULL;
1323 }
1324 if (checkForCloseTag(pInfo, CFXMLPlistTags[DATA_IX], DATA_TAG_LENGTH)) return result;
1325 CFRelease(result);
1326 return NULL;
1327 }
1328
1329 CF_INLINE Boolean read2DigitNumber(_CFXMLPlistParseInfo *pInfo, int8_t *result) {
1330 UniChar ch1, ch2;
1331 if (pInfo->curr + 2 >= pInfo->end) return false;
1332 ch1 = *pInfo->curr;
1333 ch2 = *(pInfo->curr + 1);
1334 pInfo->curr += 2;
1335 if (!isdigit(ch1) || !isdigit(ch2)) return false;
1336 *result = (ch1 - '0')*10 + (ch2 - '0');
1337 return true;
1338 }
1339
1340 // YYYY '-' MM '-' DD 'T' hh ':' mm ':' ss 'Z'
1341 static CFTypeRef parseDateTag(_CFXMLPlistParseInfo *pInfo) {
1342 CFGregorianDate date;
1343 int8_t num;
1344 Boolean badForm = false;
1345
1346 date.year = 0;
1347 while (pInfo->curr < pInfo->end && isdigit(*pInfo->curr)) {
1348 date.year = 10*date.year + (*pInfo->curr) - '0';
1349 pInfo->curr ++;
1350 }
1351 if (pInfo->curr >= pInfo->end || *pInfo->curr != '-') {
1352 badForm = true;
1353 } else {
1354 pInfo->curr ++;
1355 }
1356
1357 if (!badForm && read2DigitNumber(pInfo, &date.month) && pInfo->curr < pInfo->end && *pInfo->curr == '-') {
1358 pInfo->curr ++;
1359 } else {
1360 badForm = true;
1361 }
1362
1363 if (!badForm && read2DigitNumber(pInfo, &date.day) && pInfo->curr < pInfo->end && *pInfo->curr == 'T') {
1364 pInfo->curr ++;
1365 } else {
1366 badForm = true;
1367 }
1368
1369 if (!badForm && read2DigitNumber(pInfo, &date.hour) && pInfo->curr < pInfo->end && *pInfo->curr == ':') {
1370 pInfo->curr ++;
1371 } else {
1372 badForm = true;
1373 }
1374
1375 if (!badForm && read2DigitNumber(pInfo, &date.minute) && pInfo->curr < pInfo->end && *pInfo->curr == ':') {
1376 pInfo->curr ++;
1377 } else {
1378 badForm = true;
1379 }
1380
1381 if (!badForm && read2DigitNumber(pInfo, &num) && pInfo->curr < pInfo->end && *pInfo->curr == 'Z') {
1382 date.second = num;
1383 pInfo->curr ++;
1384 } else {
1385 badForm = true;
1386 }
1387
1388 if (badForm) {
1389 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Could not interpret <date> at line %d"), lineNumber(pInfo));
1390 return NULL;
1391 }
1392 if (!checkForCloseTag(pInfo, CFXMLPlistTags[DATE_IX], DATE_TAG_LENGTH)) return NULL;
1393 return CFDateCreate(pInfo->allocator, CFGregorianDateGetAbsoluteTime(date, NULL));
1394 }
1395
1396 static CFTypeRef parseRealTag(_CFXMLPlistParseInfo *pInfo) {
1397 CFStringRef str = getString(pInfo);
1398 SInt32 idx, len;
1399 double val;
1400 CFNumberRef result;
1401 CFStringInlineBuffer buf;
1402 if (!str) {
1403 if (!pInfo->errorString)
1404 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered empty <real> on line %d"), lineNumber(pInfo));
1405 return NULL;
1406 }
1407
1408 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
1409 if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("nan"), kCFCompareCaseInsensitive)) {
1410 CFRelease(str);
1411 return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberNaN) : NULL;
1412 }
1413 if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("+infinity"), kCFCompareCaseInsensitive)) {
1414 CFRelease(str);
1415 return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberPositiveInfinity) : NULL;
1416 }
1417 if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("-infinity"), kCFCompareCaseInsensitive)) {
1418 CFRelease(str);
1419 return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberNegativeInfinity) : NULL;
1420 }
1421 if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("infinity"), kCFCompareCaseInsensitive)) {
1422 CFRelease(str);
1423 return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberPositiveInfinity) : NULL;
1424 }
1425 if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("-inf"), kCFCompareCaseInsensitive)) {
1426 CFRelease(str);
1427 return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberNegativeInfinity) : NULL;
1428 }
1429 if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("inf"), kCFCompareCaseInsensitive)) {
1430 CFRelease(str);
1431 return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberPositiveInfinity) : NULL;
1432 }
1433 if (kCFCompareEqualTo == CFStringCompare(str, CFSTR("+inf"), kCFCompareCaseInsensitive)) {
1434 CFRelease(str);
1435 return (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) ? CFRetain(kCFNumberPositiveInfinity) : NULL;
1436 }
1437 }
1438
1439 len = CFStringGetLength(str);
1440 CFStringInitInlineBuffer(str, &buf, CFRangeMake(0, len));
1441 idx = 0;
1442 if (!__CFStringScanDouble(&buf, NULL, &idx, &val) || idx != len) {
1443 CFRelease(str);
1444 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered misformatted real on line %d"), lineNumber(pInfo));
1445 return NULL;
1446 }
1447 CFRelease(str);
1448 result = CFNumberCreate(pInfo->allocator, kCFNumberDoubleType, &val);
1449 if (checkForCloseTag(pInfo, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH)) return result;
1450 CFRelease(result);
1451 return NULL;
1452 }
1453
1454 #define GET_CH if (pInfo->curr == pInfo->end) { \
1455 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Premature end of file after <integer> on line %d"), lineNumber(pInfo)); \
1456 return NULL; \
1457 } \
1458 ch = *(pInfo->curr)
1459
1460 static CFTypeRef parseIntegerTag(_CFXMLPlistParseInfo *pInfo) {
1461 bool isHex = false, isNeg = false, hadLeadingZero = false;
1462 int64_t value = (int64_t)0;
1463 UniChar ch = 0;
1464
1465 // decimal_constant S*(-|+)?S*[0-9]+ (S == space)
1466 // hex_constant S*(-|+)?S*0[xX][0-9a-fA-F]+ (S == space)
1467
1468 while (pInfo->curr < pInfo->end && __CFIsWhitespace(*(pInfo->curr))) pInfo->curr++;
1469 GET_CH;
1470 if ('<' == ch) {
1471 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered empty <integer> on line %d"), lineNumber(pInfo));
1472 return NULL;
1473 }
1474 if ('-' == ch || '+' == ch) {
1475 isNeg = ('-' == ch);
1476 pInfo->curr++;
1477 while (pInfo->curr < pInfo->end && __CFIsWhitespace(*(pInfo->curr))) pInfo->curr++;
1478 }
1479 GET_CH;
1480 if ('0' == ch) {
1481 if (pInfo->curr + 1 < pInfo->end && ('x' == *(pInfo->curr + 1) || 'X' == *(pInfo->curr + 1))) {
1482 pInfo->curr++;
1483 isHex = true;
1484 } else {
1485 hadLeadingZero = true;
1486 }
1487 pInfo->curr++;
1488 }
1489 GET_CH;
1490 while ('0' == ch) {
1491 hadLeadingZero = true;
1492 pInfo->curr++;
1493 GET_CH;
1494 }
1495 if ('<' == ch && hadLeadingZero) { // nothing but zeros
1496 int32_t val = 0;
1497 if (!checkForCloseTag(pInfo, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH)) {
1498 // checkForCloseTag() sets error string
1499 return NULL;
1500 }
1501 return CFNumberCreate(pInfo->allocator, kCFNumberSInt32Type, &val);
1502 }
1503 if ('<' == ch) {
1504 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Incomplete <integer> on line %d"), lineNumber(pInfo));
1505 return NULL;
1506 }
1507 while ('<' != ch) {
1508 int64_t old_value = value;
1509 switch (ch) {
1510 case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9':
1511 value = (isHex ? 16 : 10) * value + (ch - '0');
1512 break;
1513 case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
1514 if (!isHex) {
1515 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Hex digit in non-hex <integer> on line %d"), lineNumber(pInfo));
1516 return NULL;
1517 }
1518 value = 16 * value + (ch - 'a' + 10);
1519 break;
1520 case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
1521 if (!isHex) {
1522 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Hex digit in non-hex <integer> on line %d"), lineNumber(pInfo));
1523 return NULL;
1524 }
1525 value = 16 * value + (ch - 'A' + 10);
1526 break;
1527 default: // other character
1528 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unknown character '%c' (0x%x) in <integer> on line %d"), ch, ch, lineNumber(pInfo));
1529 return NULL;
1530 }
1531 if (isNeg && LLONG_MIN == value) {
1532 // overflow by one when isNeg gives the proper value, if we're done with the number
1533 if (pInfo->curr + 1 < pInfo->end && '<' == *(pInfo->curr + 1)) {
1534 pInfo->curr++;
1535 isNeg = false;
1536 break;
1537 }
1538 }
1539 if (value < old_value) {
1540 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered <integer> too large to represent on line %d"), lineNumber(pInfo));
1541 return NULL;
1542 }
1543 pInfo->curr++;
1544 GET_CH;
1545 }
1546 if (!checkForCloseTag(pInfo, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH)) {
1547 // checkForCloseTag() sets error string
1548 return NULL;
1549 }
1550 if (isNeg) value = -value;
1551 return CFNumberCreate(pInfo->allocator, kCFNumberSInt64Type, &value);
1552 }
1553
1554 #undef GET_CH
1555
1556 // Returned object is retained; caller must free. pInfo->curr expected to point to the first character after the '<'
1557 static CFTypeRef parseXMLElement(_CFXMLPlistParseInfo *pInfo, Boolean *isKey) {
1558 const UniChar *marker = pInfo->curr;
1559 int markerLength = -1;
1560 Boolean isEmpty;
1561 int markerIx = -1;
1562
1563 if (isKey) *isKey = false;
1564 while (pInfo->curr < pInfo->end) {
1565 UniChar ch = *(pInfo->curr);
1566 if (ch == ' ' || ch == '\t' || ch == '\n' || ch =='\r') {
1567 if (markerLength == -1) markerLength = pInfo->curr - marker;
1568 } else if (ch == '>') {
1569 break;
1570 }
1571 pInfo->curr ++;
1572 }
1573 if (pInfo->curr >= pInfo->end) return NULL;
1574 isEmpty = (*(pInfo->curr-1) == '/');
1575 if (markerLength == -1)
1576 markerLength = pInfo->curr - (isEmpty ? 1 : 0) - marker;
1577 pInfo->curr ++; // Advance past '>'
1578 if (markerLength == 0) {
1579 // Back up to the beginning of the marker
1580 pInfo->curr = marker;
1581 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Malformed tag on line %d"), lineNumber(pInfo));
1582 return NULL;
1583 }
1584 switch (*marker) {
1585 case 'a': // Array
1586 if (markerLength == ARRAY_TAG_LENGTH && matchString(marker, CFXMLPlistTags[ARRAY_IX], ARRAY_TAG_LENGTH))
1587 markerIx = ARRAY_IX;
1588 break;
1589 case 'd': // Dictionary, data, or date; Fortunately, they all have the same marker length....
1590 if (markerLength != DICT_TAG_LENGTH)
1591 break;
1592 if (matchString(marker, CFXMLPlistTags[DICT_IX], DICT_TAG_LENGTH))
1593 markerIx = DICT_IX;
1594 else if (matchString(marker, CFXMLPlistTags[DATA_IX], DATA_TAG_LENGTH))
1595 markerIx = DATA_IX;
1596 else if (matchString(marker, CFXMLPlistTags[DATE_IX], DATE_TAG_LENGTH))
1597 markerIx = DATE_IX;
1598 break;
1599 case 'f': // false (boolean)
1600 if (markerLength == FALSE_TAG_LENGTH && matchString(marker, CFXMLPlistTags[FALSE_IX], FALSE_TAG_LENGTH)) {
1601 markerIx = FALSE_IX;
1602 }
1603 break;
1604 case 'i': // integer
1605 if (markerLength == INTEGER_TAG_LENGTH && matchString(marker, CFXMLPlistTags[INTEGER_IX], INTEGER_TAG_LENGTH))
1606 markerIx = INTEGER_IX;
1607 break;
1608 case 'k': // Key of a dictionary
1609 if (markerLength == KEY_TAG_LENGTH && matchString(marker, CFXMLPlistTags[KEY_IX], KEY_TAG_LENGTH)) {
1610 markerIx = KEY_IX;
1611 if (isKey) *isKey = true;
1612 }
1613 break;
1614 case 'p': // Plist
1615 if (markerLength == PLIST_TAG_LENGTH && matchString(marker, CFXMLPlistTags[PLIST_IX], PLIST_TAG_LENGTH))
1616 markerIx = PLIST_IX;
1617 break;
1618 case 'r': // real
1619 if (markerLength == REAL_TAG_LENGTH && matchString(marker, CFXMLPlistTags[REAL_IX], REAL_TAG_LENGTH))
1620 markerIx = REAL_IX;
1621 break;
1622 case 's': // String
1623 if (markerLength == STRING_TAG_LENGTH && matchString(marker, CFXMLPlistTags[STRING_IX], STRING_TAG_LENGTH))
1624 markerIx = STRING_IX;
1625 break;
1626 case 't': // true (boolean)
1627 if (markerLength == TRUE_TAG_LENGTH && matchString(marker, CFXMLPlistTags[TRUE_IX], TRUE_TAG_LENGTH))
1628 markerIx = TRUE_IX;
1629 break;
1630 }
1631
1632 if (!pInfo->allowNewTypes && markerIx != PLIST_IX && markerIx != ARRAY_IX && markerIx != DICT_IX && markerIx != STRING_IX && markerIx != KEY_IX && markerIx != DATA_IX) {
1633 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered new tag when expecting only old-style property list objects", CFStringGetSystemEncoding());
1634 return NULL;
1635 }
1636
1637 switch (markerIx) {
1638 case PLIST_IX:
1639 if (isEmpty) {
1640 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered empty plist tag", CFStringGetSystemEncoding());
1641 return NULL;
1642 }
1643 return parsePListTag(pInfo);
1644 case ARRAY_IX:
1645 if (isEmpty) {
1646 return pInfo->mutabilityOption == kCFPropertyListImmutable ? CFArrayCreate(pInfo->allocator, NULL, 0, &kCFTypeArrayCallBacks) : CFArrayCreateMutable(pInfo->allocator, 0, &kCFTypeArrayCallBacks);
1647 } else {
1648 return parseArrayTag(pInfo);
1649 }
1650 case DICT_IX:
1651 if (isEmpty) {
1652 if (pInfo->mutabilityOption == kCFPropertyListImmutable) {
1653 return CFDictionaryCreate(pInfo->allocator, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1654 } else {
1655 return CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
1656 }
1657 } else {
1658 return parseDictTag(pInfo);
1659 }
1660 case KEY_IX:
1661 case STRING_IX:
1662 {
1663 CFStringRef str;
1664 int tagLen = (markerIx == KEY_IX) ? KEY_TAG_LENGTH : STRING_TAG_LENGTH;
1665 if (isEmpty) {
1666 return pInfo->mutabilityOption == kCFPropertyListMutableContainersAndLeaves ? CFStringCreateMutable(pInfo->allocator, 0) : CFStringCreateWithCharacters(pInfo->allocator, NULL, 0);
1667 }
1668 str = getString(pInfo);
1669 if (!str) return NULL; // getString will already have set the error string
1670 if (!checkForCloseTag(pInfo, CFXMLPlistTags[markerIx], tagLen)) {
1671 CFRelease(str);
1672 return NULL;
1673 }
1674 return str;
1675 }
1676 case DATA_IX:
1677 if (isEmpty) {
1678 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered empty <data> on line %d"), lineNumber(pInfo));
1679 return NULL;
1680 } else {
1681 return parseDataTag(pInfo);
1682 }
1683 case DATE_IX:
1684 if (isEmpty) {
1685 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered empty <date> on line %d"), lineNumber(pInfo));
1686 return NULL;
1687 } else {
1688 return parseDateTag(pInfo);
1689 }
1690 case TRUE_IX:
1691 if (!isEmpty) {
1692 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered non-empty <true> tag on line %d"), lineNumber(pInfo));
1693 return NULL;
1694 } else {
1695 return CFRetain(kCFBooleanTrue);
1696 }
1697 case FALSE_IX:
1698 if (!isEmpty) {
1699 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered non-empty <false> tag on line %d"), lineNumber(pInfo));
1700 return NULL;
1701 } else {
1702 return CFRetain(kCFBooleanFalse);
1703 }
1704 case REAL_IX:
1705 if (isEmpty) {
1706 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered empty <real> on line %d"), lineNumber(pInfo));
1707 return NULL;
1708 } else {
1709 return parseRealTag(pInfo);
1710 }
1711 case INTEGER_IX:
1712 if (isEmpty) {
1713 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered empty <integer> on line %d"), lineNumber(pInfo));
1714 return NULL;
1715 } else {
1716 return parseIntegerTag(pInfo);
1717 }
1718 default: {
1719 CFStringRef markerStr = CFStringCreateWithCharacters(pInfo->allocator, marker, markerLength);
1720 pInfo->curr = marker;
1721 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Encountered unknown tag %@ on line %d"), markerStr, lineNumber(pInfo));
1722 CFRelease(markerStr);
1723 return NULL;
1724 }
1725 }
1726 }
1727
1728 static CFTypeRef parseXMLPropertyList(_CFXMLPlistParseInfo *pInfo) {
1729 while (!pInfo->errorString && pInfo->curr < pInfo->end) {
1730 UniChar ch;
1731 skipWhitespace(pInfo);
1732 if (pInfo->curr+1 >= pInfo->end) {
1733 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "No XML content found", CFStringGetSystemEncoding());
1734 return NULL;
1735 }
1736 if (*(pInfo->curr) != '<') {
1737 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unexpected character %c at line %d"), *(pInfo->curr), lineNumber(pInfo));
1738 return NULL;
1739 }
1740 ch = *(++ pInfo->curr);
1741 if (ch == '!') {
1742 // Comment or DTD
1743 ++ pInfo->curr;
1744 if (pInfo->curr+1 < pInfo->end && *pInfo->curr == '-' && *(pInfo->curr+1) == '-') {
1745 // Comment
1746 pInfo->curr += 2;
1747 skipXMLComment(pInfo);
1748 } else {
1749 skipDTD(pInfo);
1750 }
1751 } else if (ch == '?') {
1752 // Processing instruction
1753 pInfo->curr++;
1754 skipXMLProcessingInstruction(pInfo);
1755 } else {
1756 // Tag or malformed
1757 return parseXMLElement(pInfo, NULL);
1758 // 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
1759 }
1760 }
1761 // Should never get here
1762 if (!(pInfo->errorString))
1763 pInfo->errorString = CFStringCreateWithCString(pInfo->allocator, "Encountered unexpected EOF", CFStringGetSystemEncoding());
1764 return NULL;
1765 }
1766
1767 static CFStringEncoding encodingForXMLData(CFDataRef data, CFStringRef *error) {
1768 const uint8_t *bytes = (uint8_t *)CFDataGetBytePtr(data);
1769 UInt32 length = CFDataGetLength(data);
1770 const uint8_t *idx, *end;
1771 char quote;
1772
1773 // Check for the byte order mark first
1774 if (length > 2 &&
1775 ((*bytes == 0xFF && *(bytes+1) == 0xFE) ||
1776 (*bytes == 0xFE && *(bytes+1) == 0xFF) ||
1777 *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
1778 return kCFStringEncodingUnicode;
1779
1780 // Scan for the <?xml.... ?> opening
1781 if (length < 5 || strncmp((char const *) bytes, "<?xml", 5) != 0) return kCFStringEncodingUTF8;
1782 idx = bytes + 5;
1783 end = bytes + length;
1784 // Found "<?xml"; now we scan for "encoding"
1785 while (idx < end) {
1786 uint8_t ch = *idx;
1787 const uint8_t *scan;
1788 if ( ch == '?' || ch == '>') return kCFStringEncodingUTF8;
1789 idx ++;
1790 scan = idx;
1791 if (ch == 'e' && *scan++ == 'n' && *scan++ == 'c' && *scan++ == 'o' && *scan++ == 'd' && *scan++ == 'i'
1792 && *scan++ == 'n' && *scan++ == 'g' && *scan++ == '=') {
1793 idx = scan;
1794 break;
1795 }
1796 }
1797 if (idx >= end) return kCFStringEncodingUTF8;
1798 quote = *idx;
1799 if (quote != '\'' && quote != '\"') return kCFStringEncodingUTF8;
1800 else {
1801 CFStringRef encodingName;
1802 const uint8_t *base = idx+1; // Move past the quote character
1803 CFStringEncoding enc;
1804 UInt32 len;
1805 idx ++;
1806 while (idx < end && *idx != quote) idx ++;
1807 if (idx >= end) return kCFStringEncodingUTF8;
1808 len = idx - base;
1809 if (len == 5 && (*base == 'u' || *base == 'U') && (base[1] == 't' || base[1] == 'T') && (base[2] == 'f' || base[2] == 'F') && (base[3] == '-') && (base[4] == '8'))
1810 return kCFStringEncodingUTF8;
1811 encodingName = CFStringCreateWithBytes(NULL, base, len, kCFStringEncodingISOLatin1, false);
1812 enc = CFStringConvertIANACharSetNameToEncoding(encodingName);
1813 if (enc != kCFStringEncodingInvalidId) {
1814 CFRelease(encodingName);
1815 return enc;
1816 }
1817
1818 if (error) {
1819 *error = CFStringCreateWithFormat(NULL, NULL, CFSTR("Encountered unknown encoding (%@)"), encodingName);
1820 CFRelease(encodingName);
1821 }
1822 return 0;
1823 }
1824 }
1825
1826 CFTypeRef __CFNastyFile__ = NULL;
1827 static CFSpinLock_t __CFNastyFileLock__ = 0;
1828
1829 void __CFSetNastyFile(CFTypeRef cf) {
1830 __CFSpinLock(&__CFNastyFileLock__);
1831 if (__CFNastyFile__) CFRelease(__CFNastyFile__);
1832 __CFNastyFile__ = cf ? CFRetain(cf) : cf;
1833 __CFSpinUnlock(&__CFNastyFileLock__);
1834 }
1835
1836 extern bool __CFTryParseBinaryPlist(CFAllocatorRef allocator, CFDataRef data, CFOptionFlags option, CFPropertyListRef *plist, CFStringRef *errorString);
1837 int _CFPropertyListAllowNonUTF8 = 1;
1838
1839 static CFTypeRef _CFPropertyListCreateFromXMLData(CFAllocatorRef allocator, CFDataRef xmlData, CFOptionFlags option, CFStringRef *errorString, Boolean allowNewTypes, CFPropertyListFormat *format) {
1840 CFStringEncoding encoding;
1841 CFStringRef xmlString;
1842 UInt32 length;
1843 CFPropertyListRef plist;
1844
1845 if (!xmlData || CFDataGetLength(xmlData) == 0) {
1846 if (errorString) {
1847 *errorString = CFSTR("Cannot parse a NULL or zero-length data");
1848 CFRetain(*errorString); // Caller expects to release
1849 }
1850 return NULL;
1851 }
1852
1853 if (__CFTryParseBinaryPlist(allocator, xmlData, option, &plist, errorString)) {
1854 if (format) *format = kCFPropertyListBinaryFormat_v1_0;
1855 return plist;
1856 }
1857
1858 allocator = allocator ? allocator : __CFGetDefaultAllocator();
1859 CFRetain(allocator);
1860
1861 if (errorString) *errorString = NULL;
1862 encoding = encodingForXMLData(xmlData, errorString); // 0 is an error return, NOT MacRoman.
1863
1864 if (encoding == 0) {
1865 // Couldn't find an encoding; encodingForXMLData already set *errorString if necessary
1866 // Note that encodingForXMLData() will give us the right values for a standard plist, too.
1867 if (errorString) *errorString = CFStringCreateWithFormat(allocator, NULL, CFSTR("Could not determine the encoding of the XML data"));
1868 return NULL;
1869 }
1870
1871 xmlString = CFStringCreateWithBytes(allocator, CFDataGetBytePtr(xmlData), CFDataGetLength(xmlData), encoding, true);
1872 if (NULL == xmlString && !_CFExecutableLinkedOnOrAfter(CFSystemVersionMerlot) && _CFPropertyListAllowNonUTF8) { // conversion failed, probably because not in proper encoding
1873 static int yanmode = -1;
1874 if (-1 == yanmode) yanmode = (getenv("YanMode") != NULL);
1875 if (1 != yanmode && _CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
1876 CFTypeRef f = (__CFNastyFile__) ? (__CFNastyFile__) : CFSTR("(UNKNOWN)");
1877 if (encoding == kCFStringEncodingUTF8) {
1878 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);
1879 #if defined(DEBUG)
1880 } else {
1881 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);
1882 #endif
1883 }
1884 }
1885 // Call __CFStringCreateImmutableFunnel3() the same way CFStringCreateWithBytes() does, except with the addt'l flag
1886 if (encoding == kCFStringEncodingUTF8) xmlString = __CFStringCreateImmutableFunnel3(allocator, CFDataGetBytePtr(xmlData), CFDataGetLength(xmlData), kCFStringEncodingUTF8, true, true, false, false, false, (void *)-1 /* ALLOCATORSFREEFUNC */, kCFStringEncodingLenientUTF8Conversion);
1887 }
1888 length = xmlString ? CFStringGetLength(xmlString) : 0;
1889
1890 if (length) {
1891 _CFXMLPlistParseInfo pInfoBuf;
1892 _CFXMLPlistParseInfo *pInfo = &pInfoBuf;
1893 CFTypeRef result;
1894 UniChar *buf = (UniChar *)CFStringGetCharactersPtr(xmlString);
1895
1896 if (errorString) *errorString = NULL;
1897 if (!buf) {
1898 buf = (UniChar *)CFAllocatorAllocate(allocator, length * sizeof(UniChar), 0);
1899 CFStringGetCharacters(xmlString, CFRangeMake(0, length), buf);
1900 CFRelease(xmlString);
1901 xmlString = NULL;
1902 }
1903 pInfo->begin = buf;
1904 pInfo->end = buf+length;
1905 pInfo->curr = buf;
1906 pInfo->allocator = allocator;
1907 pInfo->errorString = NULL;
1908 pInfo->stringSet = NULL;
1909 pInfo->tmpString = NULL;
1910 pInfo->mutabilityOption = option;
1911 pInfo->allowNewTypes = allowNewTypes;
1912
1913 // 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.....
1914 result = parseXMLPropertyList(pInfo);
1915 if (result && format) *format = kCFPropertyListXMLFormat_v1_0;
1916 if (!result) {
1917 CFStringRef err = pInfo->errorString;
1918 // Reset pInfo so we can try again
1919 pInfo->curr = pInfo->begin;
1920 pInfo->errorString = NULL;
1921 // Try pList
1922 result = parseOldStylePropertyListOrStringsFile(pInfo);
1923 if (result && format) *format = kCFPropertyListOpenStepFormat;
1924 if (!result) {
1925 if (errorString) *errorString = CFStringCreateWithFormat(NULL, NULL, CFSTR("XML parser error:\n\t%@\nOld-style plist parser error:\n\t%@\n"), err, pInfo->errorString);
1926 }
1927 if (err) CFRelease(err);
1928 if (pInfo->errorString) CFRelease(pInfo->errorString);
1929 }
1930 if (xmlString) {
1931 CFRelease(xmlString);
1932 } else {
1933 CFAllocatorDeallocate(allocator, (void *)pInfo->begin);
1934 }
1935 if (pInfo->stringSet) CFRelease(pInfo->stringSet);
1936 if (pInfo->tmpString) CFRelease(pInfo->tmpString);
1937 CFRelease(allocator);
1938 return result;
1939 } else {
1940 if (errorString)
1941 *errorString = CFRetain(CFSTR("Conversion of data failed. The file is not UTF-8, or in the encoding specified in XML header if XML."));
1942 return NULL;
1943 }
1944 }
1945
1946 CFTypeRef CFPropertyListCreateFromXMLData(CFAllocatorRef allocator, CFDataRef xmlData, CFOptionFlags option, CFStringRef *errorString) {
1947 CFAssert1(xmlData != NULL, __kCFLogAssertion, "%s(): NULL data not allowed", __PRETTY_FUNCTION__);
1948 CFAssert2(option == kCFPropertyListImmutable || option == kCFPropertyListMutableContainers || option == kCFPropertyListMutableContainersAndLeaves, __kCFLogAssertion, "%s(): Unrecognized option %d", __PRETTY_FUNCTION__, option);
1949 return _CFPropertyListCreateFromXMLData(allocator, xmlData, option, errorString, true, NULL);
1950 }
1951
1952
1953 // ========================================================================
1954
1955 //
1956 // Old NeXT-style property lists
1957 //
1958
1959 static CFTypeRef parsePlistObject(_CFXMLPlistParseInfo *pInfo, bool requireObject);
1960
1961 #define isValidUnquotedStringCharacter(x) (((x) >= 'a' && (x) <= 'z') || ((x) >= 'A' && (x) <= 'Z') || ((x) >= '0' && (x) <= '9') || (x) == '_' || (x) == '$' || (x) == '/' || (x) == ':' || (x) == '.' || (x) == '-')
1962
1963 static void advanceToNonSpace(_CFXMLPlistParseInfo *pInfo) {
1964 UniChar ch2;
1965 while (pInfo->curr < pInfo->end) {
1966 ch2 = *(pInfo->curr);
1967 pInfo->curr ++;
1968 if (ch2 >= 9 && ch2 <= 0x0d) continue; // tab, newline, vt, form feed, carriage return
1969 if (ch2 == ' ' || ch2 == 0x2028 || ch2 == 0x2029) continue; // space and Unicode line sep, para sep
1970 if (ch2 == '/') {
1971 if (pInfo->curr >= pInfo->end) {
1972 // whoops; back up and return
1973 pInfo->curr --;
1974 return;
1975 } else if (*(pInfo->curr) == '/') {
1976 pInfo->curr ++;
1977 while (pInfo->curr < pInfo->end) { // go to end of comment line
1978 UniChar ch3 = *(pInfo->curr);
1979 if (ch3 == '\n' || ch3 == '\r' || ch3 == 0x2028 || ch3 == 0x2029) break;
1980 pInfo->curr ++;
1981 }
1982 } else if (*(pInfo->curr) == '*') { // handle /* ... */
1983 pInfo->curr ++;
1984 while (pInfo->curr < pInfo->end) {
1985 ch2 = *(pInfo->curr);
1986 pInfo->curr ++;
1987 if (ch2 == '*' && pInfo->curr < pInfo->end && *(pInfo->curr) == '/') {
1988 pInfo->curr ++; // advance past the '/'
1989 break;
1990 }
1991 }
1992 } else {
1993 pInfo->curr --;
1994 return;
1995 }
1996 } else {
1997 pInfo->curr --;
1998 return;
1999 }
2000 }
2001 }
2002
2003 static UniChar getSlashedChar(_CFXMLPlistParseInfo *pInfo) {
2004 UniChar ch = *(pInfo->curr);
2005 pInfo->curr ++;
2006 switch (ch) {
2007 case '0':
2008 case '1':
2009 case '2':
2010 case '3':
2011 case '4':
2012 case '5':
2013 case '6':
2014 case '7': {
2015 uint8_t num = ch - '0';
2016 UniChar result;
2017 UInt32 usedCharLen;
2018 /* three digits maximum to avoid reading \000 followed by 5 as \5 ! */
2019 if ((ch = *(pInfo->curr)) >= '0' && ch <= '7') { // we use in this test the fact that the buffer is zero-terminated
2020 pInfo->curr ++;
2021 num = (num << 3) + ch - '0';
2022 if ((pInfo->curr < pInfo->end) && (ch = *(pInfo->curr)) >= '0' && ch <= '7') {
2023 pInfo->curr ++;
2024 num = (num << 3) + ch - '0';
2025 }
2026 }
2027 CFStringEncodingBytesToUnicode(kCFStringEncodingNextStepLatin, 0, &num, sizeof(uint8_t), NULL, &result, 1, &usedCharLen);
2028 return (usedCharLen == 1) ? result : 0;
2029 }
2030 case 'U': {
2031 unsigned num = 0, numDigits = 4; /* Parse four digits */
2032 while (pInfo->curr < pInfo->end && numDigits--) {
2033 if (((ch = *(pInfo->curr)) < 128) && isxdigit(ch)) {
2034 pInfo->curr ++;
2035 num = (num << 4) + ((ch <= '9') ? (ch - '0') : ((ch <= 'F') ? (ch - 'A' + 10) : (ch - 'a' + 10)));
2036 }
2037 }
2038 return num;
2039 }
2040 case 'a': return '\a'; // Note: the meaning of '\a' varies with -traditional to gcc
2041 case 'b': return '\b';
2042 case 'f': return '\f';
2043 case 'n': return '\n';
2044 case 'r': return '\r';
2045 case 't': return '\t';
2046 case 'v': return '\v';
2047 case '"': return '\"';
2048 case '\n': return '\n';
2049 }
2050 return ch;
2051 }
2052
2053 static CFStringRef parseQuotedPlistString(_CFXMLPlistParseInfo *pInfo, UniChar quote) {
2054 CFMutableStringRef str = NULL;
2055 const UniChar *startMark = pInfo->curr;
2056 const UniChar *mark = pInfo->curr;
2057 while (pInfo->curr < pInfo->end) {
2058 UniChar ch = *(pInfo->curr);
2059 if (ch == quote) break;
2060 if (ch == '\\') {
2061 _catFromMarkToBuf(mark, pInfo->curr, &str, pInfo->allocator);
2062 pInfo->curr ++;
2063 ch = getSlashedChar(pInfo);
2064 CFStringAppendCharacters(str, &ch, 1);
2065 mark = pInfo->curr;
2066 } else {
2067 // 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.
2068 pInfo->curr ++;
2069 }
2070 }
2071 if (pInfo->end <= pInfo->curr) {
2072 if (str) CFRelease(str);
2073 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
2074 pInfo->curr = startMark;
2075 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unterminated quoted string starting on line %d"), lineNumber(pInfo));
2076 }
2077 return NULL;
2078 }
2079 if (!str) {
2080 if (pInfo->mutabilityOption == kCFPropertyListMutableContainersAndLeaves) {
2081 _catFromMarkToBuf(mark, pInfo->curr, &str, pInfo->allocator);
2082 } else {
2083 str = (CFMutableStringRef)_uniqueStringForCharacters(pInfo, mark, pInfo->curr-mark);
2084 CFRetain(str);
2085 }
2086 } else {
2087 if (mark != pInfo->curr) {
2088 _catFromMarkToBuf(mark, pInfo->curr, &str, pInfo->allocator);
2089 }
2090 if (pInfo->mutabilityOption != kCFPropertyListMutableContainersAndLeaves) {
2091 CFStringRef uniqueString = _uniqueStringForString(pInfo, str);
2092 CFRelease(str);
2093 CFRetain(uniqueString);
2094 str = (CFMutableStringRef)uniqueString;
2095 }
2096 }
2097 pInfo->curr ++; // Advance past the quote character before returning.
2098 if (pInfo->errorString) {
2099 CFRelease(pInfo->errorString);
2100 pInfo->errorString = NULL;
2101 }
2102 return str;
2103 }
2104
2105 static CFStringRef parseUnquotedPlistString(_CFXMLPlistParseInfo *pInfo) {
2106 const UniChar *mark = pInfo->curr;
2107 while (pInfo->curr < pInfo->end) {
2108 UniChar ch = *pInfo->curr;
2109 if (isValidUnquotedStringCharacter(ch))
2110 pInfo->curr ++;
2111 else break;
2112 }
2113 if (pInfo->curr != mark) {
2114 if (pInfo->mutabilityOption != kCFPropertyListMutableContainersAndLeaves) {
2115 CFStringRef str = _uniqueStringForCharacters(pInfo, mark, pInfo->curr-mark);
2116 CFRetain(str);
2117 return str;
2118 } else {
2119 CFMutableStringRef str = CFStringCreateMutable(pInfo->allocator, 0);
2120 CFStringAppendCharacters(str, mark, pInfo->curr - mark);
2121 return str;
2122 }
2123 }
2124 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
2125 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unexpected EOF"));
2126 }
2127 return NULL;
2128 }
2129
2130 static CFStringRef parsePlistString(_CFXMLPlistParseInfo *pInfo, bool requireObject) {
2131 UniChar ch;
2132 advanceToNonSpace(pInfo);
2133 if (pInfo->curr >= pInfo->end) {
2134 if (requireObject && _CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
2135 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unexpected EOF while parsing string"));
2136 }
2137 return NULL;
2138 }
2139 ch = *(pInfo->curr);
2140 if (ch == '\'' || ch == '\"') {
2141 pInfo->curr ++;
2142 return parseQuotedPlistString(pInfo, ch);
2143 } else if (isValidUnquotedStringCharacter(ch)) {
2144 return parseUnquotedPlistString(pInfo);
2145 } else {
2146 if (requireObject && _CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
2147 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Invalid string character at line %d"), lineNumber(pInfo));
2148 }
2149 return NULL;
2150 }
2151 }
2152
2153 static CFTypeRef parsePlistArray(_CFXMLPlistParseInfo *pInfo) {
2154 CFMutableArrayRef array = CFArrayCreateMutable(pInfo->allocator, 0, &kCFTypeArrayCallBacks);
2155 CFTypeRef tmp = parsePlistObject(pInfo, false);
2156 while (tmp) {
2157 CFArrayAppendValue(array, tmp);
2158 CFRelease(tmp);
2159 advanceToNonSpace(pInfo);
2160 if (*pInfo->curr != ',') {
2161 tmp = NULL;
2162 } else {
2163 pInfo->curr ++;
2164 tmp = parsePlistObject(pInfo, false);
2165 }
2166 }
2167 advanceToNonSpace(pInfo);
2168 if (*pInfo->curr != ')') {
2169 CFRelease(array);
2170 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
2171 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Expected terminating ')' for array at line %d"), lineNumber(pInfo));
2172 }
2173 return NULL;
2174 }
2175 if (pInfo->errorString) {
2176 CFRelease(pInfo->errorString);
2177 pInfo->errorString = NULL;
2178 }
2179 pInfo->curr ++;
2180 return array;
2181 }
2182
2183 static CFDictionaryRef parsePlistDictContent(_CFXMLPlistParseInfo *pInfo) {
2184 CFMutableDictionaryRef dict = CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2185 CFStringRef key = NULL;
2186 Boolean failedParse = false;
2187 key = parsePlistString(pInfo, false);
2188 while (key) {
2189 CFTypeRef value;
2190 advanceToNonSpace(pInfo);
2191 if (*pInfo->curr == ';') {
2192 /* This is a strings file using the shortcut format */
2193 /* although this check here really applies to all plists. */
2194 value = CFRetain(key);
2195 } else if (*pInfo->curr == '=') {
2196 pInfo->curr ++;
2197 value = parsePlistObject(pInfo, true);
2198 if (!value) {
2199 failedParse = true;
2200 break;
2201 }
2202 } else {
2203 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
2204 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unexpected ';' or '=' after key at line %d"), lineNumber(pInfo));
2205 }
2206 failedParse = true;
2207 break;
2208 }
2209 CFDictionarySetValue(dict, key, value);
2210 CFRelease(key);
2211 key = NULL;
2212 CFRelease(value);
2213 value = NULL;
2214 advanceToNonSpace(pInfo);
2215 if (*pInfo->curr == ';') {
2216 pInfo->curr ++;
2217 key = parsePlistString(pInfo, false);
2218 } else if (!allowMissingSemi && _CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
2219 static int yanmode = -1;
2220 if (-1 == yanmode) yanmode = (getenv("YanMode") != NULL);
2221 if (1 != yanmode) {
2222 CFLog(0, CFSTR("CFPropertyListCreateFromXMLData(): Old-style plist parser: missing semicolon in dictionary."));
2223 if (__CFNastyFile__) {
2224 CFLog(0, CFSTR("CFPropertyListCreateFromXMLData(): The file name for this data might be (or it might not): %@"), __CFNastyFile__);
2225 }
2226 }
2227 failedParse = true;
2228 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Missing ';' on line %d"), lineNumber(pInfo));
2229 } else {
2230 // on pre-Jaguar systems, do nothing except silently ignore the rest
2231 // of the dictionary, which is what happened on those systems.
2232 }
2233 }
2234
2235 if (failedParse) {
2236 if (key) CFRelease(key);
2237 CFRelease(dict);
2238 return NULL;
2239 }
2240 if (pInfo->errorString) {
2241 CFRelease(pInfo->errorString);
2242 pInfo->errorString = NULL;
2243 }
2244 return dict;
2245 }
2246
2247 static CFTypeRef parsePlistDict(_CFXMLPlistParseInfo *pInfo) {
2248 CFDictionaryRef dict = parsePlistDictContent(pInfo);
2249 if (!dict) return NULL;
2250 advanceToNonSpace(pInfo);
2251 if (*pInfo->curr != '}') {
2252 CFRelease(dict);
2253 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
2254 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Expected terminating '}' for dictionary at line %d"), lineNumber(pInfo));
2255 }
2256 return NULL;
2257 }
2258 pInfo->curr ++;
2259 return dict;
2260 }
2261
2262 static unsigned char fromHexDigit(unsigned char ch) {
2263 if (isdigit(ch)) return ch - '0';
2264 if ((ch >= 'a') && (ch <= 'f')) return ch - 'a' + 10;
2265 if ((ch >= 'A') && (ch <= 'F')) return ch - 'A' + 10;
2266 return 0xff; // Just choose a large number for the error code
2267 }
2268
2269 static CFTypeRef parsePlistData(_CFXMLPlistParseInfo *pInfo) {
2270 CFStringRef token;
2271 unsigned length = 0;
2272 CFMutableDataRef result = CFDataCreateMutable(pInfo->allocator, 0);
2273
2274 advanceToNonSpace(pInfo);
2275 while ( (token = parseUnquotedPlistString(pInfo)) ) {
2276 unsigned tlength = CFStringGetLength(token);
2277 unsigned char *bytes;
2278 unsigned idx;
2279 if (tlength & 1) { // Token must have an even number of characters
2280 CFRelease(token);
2281 CFRelease(result);
2282 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
2283 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Malformed data byte group at line %d; uneven length"), lineNumber(pInfo));
2284 }
2285 return NULL;
2286 }
2287 CFDataSetLength(result, length + tlength/2);
2288 bytes = (unsigned char *) CFDataGetMutableBytePtr(result) + length;
2289 length += tlength / 2;
2290 for (idx = 0; idx < tlength; idx += 2) {
2291 unsigned char hi = fromHexDigit(CFStringGetCharacterAtIndex(token, idx)), lo = fromHexDigit(CFStringGetCharacterAtIndex(token, idx+1));
2292 if (hi == 0xff || lo == 0xff) {
2293 CFRelease(token);
2294 CFRelease(result);
2295 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
2296 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Malformed data byte group at line %d; invalid hex"), lineNumber(pInfo));
2297 }
2298 return NULL;
2299 }
2300 *bytes = (hi << 4) + lo;
2301 bytes++;
2302 }
2303 CFRelease(token);
2304 token = NULL;
2305 advanceToNonSpace(pInfo);
2306 }
2307 if (pInfo->errorString) {
2308 CFRelease(pInfo->errorString);
2309 pInfo->errorString = NULL;
2310 }
2311
2312 if (*(pInfo->curr) == '>') {
2313 pInfo->curr ++; // Move past '>'
2314 return result;
2315 } else {
2316 CFRelease(result);
2317 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
2318 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Expected terminating '>' for data at line %d"), lineNumber(pInfo));
2319 }
2320 return NULL;
2321 }
2322 }
2323
2324 // Returned object is retained; caller must free.
2325 static CFTypeRef parsePlistObject(_CFXMLPlistParseInfo *pInfo, bool requireObject) {
2326 UniChar ch;
2327 advanceToNonSpace(pInfo);
2328 if (pInfo->curr + 1 >= pInfo->end) {
2329 if (requireObject && _CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
2330 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unexpected EOF while parsing plist"));
2331 }
2332 return NULL;
2333 }
2334 ch = *(pInfo->curr);
2335 pInfo->curr ++;
2336 if (ch == '{') {
2337 return parsePlistDict(pInfo);
2338 } else if (ch == '(') {
2339 return parsePlistArray(pInfo);
2340 } else if (ch == '<') {
2341 return parsePlistData(pInfo);
2342 } else if (ch == '\'' || ch == '\"') {
2343 return parseQuotedPlistString(pInfo, ch);
2344 } else if (isValidUnquotedStringCharacter(ch)) {
2345 pInfo->curr --;
2346 return parseUnquotedPlistString(pInfo);
2347 } else {
2348 pInfo->curr --; // Must back off the charcter we just read
2349 if (requireObject && _CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
2350 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Unexpected character '0x%x' at line %d"), ch, lineNumber(pInfo));
2351 }
2352 return NULL;
2353 }
2354 }
2355
2356 static CFTypeRef parseOldStylePropertyListOrStringsFile(_CFXMLPlistParseInfo *pInfo) {
2357 const UniChar *begin = pInfo->curr;
2358 CFTypeRef result;
2359 advanceToNonSpace(pInfo);
2360 // A file consisting only of whitespace (or empty) is now defined to be an empty dictionary
2361 if (pInfo->curr >= pInfo->end) return CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2362 result = parsePlistObject(pInfo, true);
2363 advanceToNonSpace(pInfo);
2364 if (pInfo->curr >= pInfo->end) return result;
2365 if (!result) return NULL;
2366 if (CFGetTypeID(result) != CFStringGetTypeID()) {
2367 CFRelease(result);
2368 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
2369 pInfo->errorString = CFStringCreateWithFormat(pInfo->allocator, NULL, CFSTR("Junk after plist at line %d"), lineNumber(pInfo));
2370 }
2371 return NULL;
2372 }
2373 CFRelease(result);
2374 // Check for a strings file (looks like a dictionary without the opening/closing curly braces)
2375 pInfo->curr = begin;
2376 return parsePlistDictContent(pInfo);
2377 }
2378
2379 #undef isValidUnquotedStringCharacter
2380
2381 static CFArrayRef _arrayDeepImmutableCopy(CFAllocatorRef allocator, CFArrayRef array, CFOptionFlags mutabilityOption) {
2382 CFArrayRef result = NULL;
2383 CFIndex i, c = CFArrayGetCount(array);
2384 CFTypeRef *values;
2385 if (c == 0) {
2386 result = CFArrayCreate(allocator, NULL, 0, &kCFTypeArrayCallBacks);
2387 } else if ((values = CFAllocatorAllocate(allocator, c*sizeof(CFTypeRef), 0)) != NULL) {
2388 CFArrayGetValues(array, CFRangeMake(0, c), values);
2389 for (i = 0; i < c; i ++) {
2390 values[i] = CFPropertyListCreateDeepCopy(allocator, values[i], mutabilityOption);
2391 if (values[i] == NULL) {
2392 break;
2393 }
2394 }
2395 result = (i == c) ? CFArrayCreate(allocator, values, c, &kCFTypeArrayCallBacks) : NULL;
2396 c = i;
2397 for (i = 0; i < c; i ++) {
2398 CFRelease(values[i]);
2399 }
2400 CFAllocatorDeallocate(allocator, values);
2401 }
2402 return result;
2403 }
2404
2405 static CFMutableArrayRef _arrayDeepMutableCopy(CFAllocatorRef allocator, CFArrayRef array, CFOptionFlags mutabilityOption) {
2406 CFIndex i, c = CFArrayGetCount(array);
2407 CFMutableArrayRef result = CFArrayCreateMutable(allocator, 0, &kCFTypeArrayCallBacks);
2408 if (result) {
2409 for (i = 0; i < c; i ++) {
2410 CFTypeRef newValue = CFPropertyListCreateDeepCopy(allocator, CFArrayGetValueAtIndex(array, i), mutabilityOption);
2411 if (!newValue) break;
2412 CFArrayAppendValue(result, newValue);
2413 CFRelease(newValue);
2414 }
2415 if (i != c) {
2416 CFRelease(result);
2417 result = NULL;
2418 }
2419 }
2420 return result;
2421 }
2422
2423 CFPropertyListRef CFPropertyListCreateDeepCopy(CFAllocatorRef allocator, CFPropertyListRef propertyList, CFOptionFlags mutabilityOption) {
2424 CFTypeID typeID;
2425 CFPropertyListRef result = NULL;
2426 CFAssert1(propertyList != NULL, __kCFLogAssertion, "%s(): cannot copy a NULL property list", __PRETTY_FUNCTION__);
2427 __CFAssertIsPList(propertyList);
2428 CFAssert2(mutabilityOption == kCFPropertyListImmutable || mutabilityOption == kCFPropertyListMutableContainers || mutabilityOption == kCFPropertyListMutableContainersAndLeaves, __kCFLogAssertion, "%s(): Unrecognized option %d", __PRETTY_FUNCTION__, mutabilityOption);
2429 if (_CFExecutableLinkedOnOrAfter(CFSystemVersionJaguar)) {
2430 if (!CFPropertyListIsValid(propertyList, kCFPropertyListBinaryFormat_v1_0)) return NULL;
2431 }
2432
2433 if (allocator == NULL) {
2434 allocator = CFRetain(__CFGetDefaultAllocator());
2435 } else {
2436 CFRetain(allocator);
2437 }
2438
2439 typeID = CFGetTypeID(propertyList);
2440 if (typeID == CFDictionaryGetTypeID()) {
2441 CFDictionaryRef dict = (CFDictionaryRef)propertyList;
2442 Boolean mutable = (mutabilityOption != kCFPropertyListImmutable);
2443 CFIndex count = CFDictionaryGetCount(dict);
2444 CFTypeRef *keys, *values;
2445 if (count == 0) {
2446 result = mutable ? CFDictionaryCreateMutable(allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks): CFDictionaryCreate(allocator, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2447 } else if ((keys = CFAllocatorAllocate(allocator, 2 * count * sizeof(CFTypeRef), 0)) != NULL) {
2448 CFIndex i;
2449 values = keys+count;
2450 CFDictionaryGetKeysAndValues(dict, keys, values);
2451 for (i = 0; i < count; i ++) {
2452 keys[i] = CFStringCreateCopy(allocator, keys[i]);
2453 if (keys[i] == NULL) {
2454 break;
2455 }
2456 values[i] = CFPropertyListCreateDeepCopy(allocator, values[i], mutabilityOption);
2457 if (values[i] == NULL) {
2458 CFRelease(keys[i]);
2459 break;
2460 }
2461 }
2462 if (i == count) {
2463 result = mutable ? CFDictionaryCreateMutable(allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks) : CFDictionaryCreate(allocator, keys, values, count, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
2464 for (i = 0; i < count; i ++) {
2465 if (mutable) {
2466 CFDictionarySetValue((CFMutableDictionaryRef)result, keys[i], values[i]);
2467 }
2468 CFRelease(keys[i]);
2469 CFRelease(values[i]);
2470 }
2471 } else {
2472 result = NULL;
2473 count = i;
2474 for (i = 0; i < count; i ++) {
2475 CFRelease(keys[i]);
2476 CFRelease(values[i]);
2477 }
2478 }
2479 CFAllocatorDeallocate(allocator, keys);
2480 } else {
2481 result = NULL;
2482 }
2483 } else if (typeID == CFArrayGetTypeID()) {
2484 if (mutabilityOption == kCFPropertyListImmutable) {
2485 result = _arrayDeepImmutableCopy(allocator, (CFArrayRef)propertyList, mutabilityOption);
2486 } else {
2487 result = _arrayDeepMutableCopy(allocator, (CFArrayRef)propertyList, mutabilityOption);
2488 }
2489 } else if (typeID == CFDataGetTypeID()) {
2490 if (mutabilityOption == kCFPropertyListMutableContainersAndLeaves) {
2491 result = CFDataCreateMutableCopy(allocator, 0, (CFDataRef)propertyList);
2492 } else {
2493 result = CFDataCreateCopy(allocator, (CFDataRef)propertyList);
2494 }
2495 } else if (typeID == CFNumberGetTypeID()) {
2496 // Warning - this will break if byteSize is ever greater than 16
2497 uint8_t bytes[16];
2498 CFNumberType numType = CFNumberGetType((CFNumberRef)propertyList);
2499 CFNumberGetValue((CFNumberRef)propertyList, numType, (void *)bytes);
2500 result = CFNumberCreate(allocator, numType, (void *)bytes);
2501 } else if (typeID == CFBooleanGetTypeID()) {
2502 // Booleans are immutable & shared instances
2503 CFRetain(propertyList);
2504 result = propertyList;
2505 } else if (typeID == CFDateGetTypeID()) {
2506 // Dates are immutable
2507 result = CFDateCreate(allocator, CFDateGetAbsoluteTime((CFDateRef)propertyList));
2508 } else if (typeID == CFStringGetTypeID()) {
2509 if (mutabilityOption == kCFPropertyListMutableContainersAndLeaves) {
2510 result = CFStringCreateMutableCopy(allocator, 0, (CFStringRef)propertyList);
2511 } else {
2512 result = CFStringCreateCopy(allocator, (CFStringRef)propertyList);
2513 }
2514 } else {
2515 CFAssert2(false, __kCFLogAssertion, "%s(): 0x%x is not a property list type", __PRETTY_FUNCTION__, propertyList);
2516 result = NULL;
2517 }
2518 CFRelease(allocator);
2519 return result;
2520 }