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