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