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