/ CFOldStylePList.c
CFOldStylePList.c
  1  /*
  2   * Copyright (c) 2015 Apple Inc. All rights reserved.
  3   *
  4   * @APPLE_LICENSE_HEADER_START@
  5   *
  6   * This file contains Original Code and/or Modifications of Original Code
  7   * as defined in and that are subject to the Apple Public Source License
  8   * Version 2.0 (the 'License'). You may not use this file except in
  9   * compliance with the License. Please obtain a copy of the License at
 10   * http://www.opensource.apple.com/apsl/ and read it before using this
 11   * file.
 12   *
 13   * The Original Code and all software distributed under the License are
 14   * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 15   * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 16   * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 17   * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 18   * Please see the License for the specific language governing rights and
 19   * limitations under the License.
 20   *
 21   * @APPLE_LICENSE_HEADER_END@
 22   */
 23  
 24  /*	CFOldStylePList.c
 25  	Copyright (c) 1999-2014, Apple Inc. All rights reserved.
 26  	Responsibility: Tony Parker
 27  */
 28  
 29  #include <CoreFoundation/CFPropertyList.h>
 30  #include <CoreFoundation/CFDate.h>
 31  #include <CoreFoundation/CFNumber.h>
 32  #include <CoreFoundation/CFError.h>
 33  #include <CoreFoundation/CFStringEncodingConverter.h>
 34  #include "CFInternal.h"
 35  #include <CoreFoundation/CFCalendar.h>
 36  #include <CoreFoundation/CFSet.h>
 37  
 38  #include <ctype.h>
 39  
 40  //
 41  // Old NeXT-style property lists
 42  //
 43  
 44  CF_INLINE void __CFPListRelease(CFTypeRef cf, CFAllocatorRef allocator) {
 45      if (cf && !(0)) CFRelease(cf);
 46  }
 47  
 48  CF_PRIVATE CFErrorRef __CFPropertyListCreateError(CFIndex code, CFStringRef debugString, ...);
 49  
 50  typedef struct {
 51      const UniChar *begin;
 52      const UniChar *curr;
 53      const UniChar *end;
 54      CFErrorRef error;
 55      CFAllocatorRef allocator;
 56      UInt32 mutabilityOption;
 57      CFMutableSetRef stringSet;  // set of all strings involved in this parse; allows us to share non-mutable strings in the returned plist
 58  } _CFStringsFileParseInfo;
 59  
 60  // warning: doesn't have a good idea of Unicode line separators
 61  static UInt32 lineNumberStrings(_CFStringsFileParseInfo *pInfo) {
 62      const UniChar *p = pInfo->begin;
 63      UInt32 count = 1;
 64      while (p < pInfo->curr) {
 65          if (*p == '\r') {
 66              count ++;
 67              if (*(p + 1) == '\n')
 68                  p ++;
 69          } else if (*p == '\n') {
 70              count ++;
 71          }
 72          p ++;
 73      }
 74      return count;
 75  }
 76  
 77  static CFTypeRef parsePlistObject(_CFStringsFileParseInfo *pInfo, bool requireObject);
 78  
 79  #define isValidUnquotedStringCharacter(x) (((x) >= 'a' && (x) <= 'z') || ((x) >= 'A' && (x) <= 'Z') || ((x) >= '0' && (x) <= '9') || (x) == '_' || (x) == '$' || (x) == '/' || (x) == ':' || (x) == '.' || (x) == '-')
 80  
 81  // Returns true if the advance found something before the end of the buffer, false otherwise
 82  static Boolean advanceToNonSpace(_CFStringsFileParseInfo *pInfo) {
 83      UniChar ch2;
 84      while (pInfo->curr < pInfo->end) {
 85  	ch2 = *(pInfo->curr);
 86          pInfo->curr ++;
 87          if (ch2 >= 9 && ch2 <= 0x0d) continue;	// tab, newline, vt, form feed, carriage return
 88          if (ch2 == ' ' || ch2 == 0x2028 || ch2 == 0x2029) continue;	// space and Unicode line sep, para sep
 89  	if (ch2 == '/') {
 90              if (pInfo->curr >= pInfo->end) {
 91                  // whoops; back up and return
 92                  pInfo->curr --;
 93                  return true;
 94              } else if (*(pInfo->curr) == '/') {
 95                  pInfo->curr ++;
 96                  while (pInfo->curr < pInfo->end) {	// go to end of comment line
 97                      UniChar ch3 = *(pInfo->curr);
 98                      if (ch3 == '\n' || ch3 == '\r' || ch3 == 0x2028 || ch3 == 0x2029) break;
 99                      pInfo->curr ++;
100  		}
101  	    } else if (*(pInfo->curr) == '*') {		// handle /* ... */
102                  pInfo->curr ++;
103  		while (pInfo->curr < pInfo->end) {
104  		    ch2 = *(pInfo->curr);
105                      pInfo->curr ++;
106  		    if (ch2 == '*' && pInfo->curr < pInfo->end && *(pInfo->curr) == '/') {
107                          pInfo->curr ++; // advance past the '/'
108                          break;
109                      }
110                  }
111              } else {
112                  pInfo->curr --;
113                  return true;
114  	    }
115          } else {
116              pInfo->curr --;
117              return true;
118          }
119      }
120      return false;
121  }
122  
123  static UniChar getSlashedChar(_CFStringsFileParseInfo *pInfo) {
124      UniChar ch = *(pInfo->curr);
125      pInfo->curr ++;
126      switch (ch) {
127  	case '0':
128  	case '1':	
129  	case '2':	
130  	case '3':	
131  	case '4':	
132  	case '5':	
133  	case '6':	
134  	case '7':  {
135              uint8_t num = ch - '0';
136              UniChar result;
137              CFIndex usedCharLen;
138  	    /* three digits maximum to avoid reading \000 followed by 5 as \5 ! */
139  	    if ((ch = *(pInfo->curr)) >= '0' && ch <= '7') { // we use in this test the fact that the buffer is zero-terminated
140                  pInfo->curr ++;
141  		num = (num << 3) + ch - '0';
142  		if ((pInfo->curr < pInfo->end) && (ch = *(pInfo->curr)) >= '0' && ch <= '7') {
143                      pInfo->curr ++;
144  		    num = (num << 3) + ch - '0';
145  		}
146  	    }
147              CFStringEncodingBytesToUnicode(kCFStringEncodingNextStepLatin, 0, &num, sizeof(uint8_t), NULL,  &result, 1, &usedCharLen);
148              return (usedCharLen == 1) ? result : 0;
149  	}
150  	case 'U': {
151  	    unsigned num = 0, numDigits = 4;	/* Parse four digits */
152  	    while (pInfo->curr < pInfo->end && numDigits--) {
153                  if (((ch = *(pInfo->curr)) < 128) && isxdigit(ch)) { 
154                      pInfo->curr ++;
155  		    num = (num << 4) + ((ch <= '9') ? (ch - '0') : ((ch <= 'F') ? (ch - 'A' + 10) : (ch - 'a' + 10)));
156  		}
157  	    }
158  	    return num;
159  	}
160  	case 'a':	return '\a';	// Note: the meaning of '\a' varies with -traditional to gcc
161  	case 'b':	return '\b';
162  	case 'f':	return '\f';
163  	case 'n':	return '\n';
164  	case 'r':	return '\r';
165  	case 't':	return '\t';
166  	case 'v':	return '\v';
167  	case '"':	return '\"';
168  	case '\n':	return '\n';
169      }
170      return ch;
171  }
172  
173  static CFStringRef _uniqueStringForCharacters(_CFStringsFileParseInfo *pInfo, const UniChar *base, CFIndex length) {
174      if (0 == length) return !(0) ? (CFStringRef)CFRetain(CFSTR("")) : CFSTR("");
175      // This is to avoid having to promote the buffers of all the strings compared against
176      // during the set probe; if a Unicode string is passed in, that's what happens.
177      CFStringRef stringToUnique = NULL;
178      Boolean use_stack = (length < 2048);
179      STACK_BUFFER_DECL(uint8_t, buffer, use_stack ? length + 1 : 1);
180      uint8_t *ascii = use_stack ? buffer : (uint8_t *)CFAllocatorAllocate(kCFAllocatorSystemDefault, length + 1, 0);
181      for (CFIndex idx = 0; idx < length; idx++) {
182          UniChar ch = base[idx];
183  	if (ch < 0x80) {
184  	    ascii[idx] = (uint8_t)ch;
185          } else {
186  	    stringToUnique = CFStringCreateWithCharacters(pInfo->allocator, base, length);
187  	    break;
188  	}
189      }
190      if (!stringToUnique) {
191          ascii[length] = '\0';
192          stringToUnique = CFStringCreateWithBytes(pInfo->allocator, ascii, length, kCFStringEncodingASCII, false);
193      }
194      if (ascii != buffer) CFAllocatorDeallocate(kCFAllocatorSystemDefault, ascii);
195      CFStringRef uniqued = (CFStringRef)CFSetGetValue(pInfo->stringSet, stringToUnique);
196      if (!uniqued) {
197          CFSetAddValue(pInfo->stringSet, stringToUnique);
198  	uniqued = stringToUnique;
199      }
200      __CFPListRelease(stringToUnique, pInfo->allocator);
201      if (uniqued && !(0)) CFRetain(uniqued);
202      return uniqued;
203  }
204  
205  static CFStringRef _uniqueStringForString(_CFStringsFileParseInfo *pInfo, CFStringRef stringToUnique) {
206      CFStringRef uniqued = (CFStringRef)CFSetGetValue(pInfo->stringSet, stringToUnique);
207      if (!uniqued) {
208          uniqued = (CFStringRef)__CFStringCollectionCopy(pInfo->allocator, stringToUnique);
209          CFSetAddValue(pInfo->stringSet, uniqued);
210          __CFTypeCollectionRelease(pInfo->allocator, uniqued);
211      }
212      if (uniqued && !(0)) CFRetain(uniqued);
213      return uniqued;
214  }
215  
216  static CFStringRef parseQuotedPlistString(_CFStringsFileParseInfo *pInfo, UniChar quote) {
217      CFMutableStringRef str = NULL;
218      const UniChar *startMark = pInfo->curr;
219      const UniChar *mark = pInfo->curr;
220      while (pInfo->curr < pInfo->end) {
221  	UniChar ch = *(pInfo->curr);
222          if (ch == quote) break;
223          if (ch == '\\') {
224              if (!str) str = CFStringCreateMutable(pInfo->allocator, 0);
225              CFStringAppendCharacters(str, mark, pInfo->curr - mark);
226              pInfo->curr ++;
227              ch = getSlashedChar(pInfo);
228              CFStringAppendCharacters(str, &ch, 1);
229              mark = pInfo->curr;
230  	} else {
231              // 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.
232              pInfo->curr ++;
233          }
234      }
235      if (pInfo->end <= pInfo->curr) {
236          __CFPListRelease(str, pInfo->allocator);
237          pInfo->curr = startMark;
238          pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unterminated quoted string starting on line %d"), lineNumberStrings(pInfo));
239          return NULL;
240      }
241      if (!str) {
242          if (pInfo->mutabilityOption == kCFPropertyListMutableContainersAndLeaves) {
243              str = CFStringCreateMutable(pInfo->allocator, 0);
244              CFStringAppendCharacters(str, mark, pInfo->curr - mark);
245          } else {
246              str = (CFMutableStringRef)_uniqueStringForCharacters(pInfo, mark, pInfo->curr-mark);
247          }
248      } else {
249          if (mark != pInfo->curr) {
250              CFStringAppendCharacters(str, mark, pInfo->curr - mark);
251          }
252          if (pInfo->mutabilityOption != kCFPropertyListMutableContainersAndLeaves) {
253              CFStringRef uniqueString = _uniqueStringForString(pInfo, str);
254              __CFPListRelease(str, pInfo->allocator);
255              str = (CFMutableStringRef)uniqueString;
256          }
257      }
258      pInfo->curr ++;  // Advance past the quote character before returning.
259      if (pInfo->error) {
260          CFRelease(pInfo->error);
261          pInfo->error = NULL;
262      }
263      return str;
264  }
265  
266  static CFStringRef parseUnquotedPlistString(_CFStringsFileParseInfo *pInfo) {
267      const UniChar *mark = pInfo->curr;
268      while (pInfo->curr < pInfo->end) {
269          UniChar ch = *pInfo->curr;
270          if (isValidUnquotedStringCharacter(ch))
271              pInfo->curr ++;
272          else break;
273      }
274      if (pInfo->curr != mark) {
275          if (pInfo->mutabilityOption != kCFPropertyListMutableContainersAndLeaves) {
276              CFStringRef str = _uniqueStringForCharacters(pInfo, mark, pInfo->curr-mark);
277              return str;
278          } else {
279              CFMutableStringRef str = CFStringCreateMutable(pInfo->allocator, 0);
280              CFStringAppendCharacters(str, mark, pInfo->curr - mark);
281              return str;
282          }
283      }
284      pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unexpected EOF"));
285      return NULL;
286  }
287  
288  static CFStringRef parsePlistString(_CFStringsFileParseInfo *pInfo, bool requireObject) {
289      UniChar ch;
290      Boolean foundChar = advanceToNonSpace(pInfo);
291      if (!foundChar) {
292          if (requireObject) {
293              pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unexpected EOF while parsing string"));
294          }
295          return NULL;
296      }
297      ch = *(pInfo->curr);
298      if (ch == '\'' || ch == '\"') {
299          pInfo->curr ++;
300          return parseQuotedPlistString(pInfo, ch);
301      } else if (isValidUnquotedStringCharacter(ch)) {
302          return parseUnquotedPlistString(pInfo);
303      } else {
304          if (requireObject) {
305              pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Invalid string character at line %d"), lineNumberStrings(pInfo));
306  	}
307          return NULL;
308      }
309  }
310  
311  static CFTypeRef parsePlistArray(_CFStringsFileParseInfo *pInfo) {
312      CFMutableArrayRef array = CFArrayCreateMutable(pInfo->allocator, 0, &kCFTypeArrayCallBacks);
313      CFTypeRef tmp = parsePlistObject(pInfo, false);
314      Boolean foundChar;
315      while (tmp) {
316          CFArrayAppendValue(array, tmp);
317          __CFPListRelease(tmp, pInfo->allocator);
318          foundChar = advanceToNonSpace(pInfo);
319  	if (!foundChar) {
320  	    __CFPListRelease(array, pInfo->allocator);
321  	    pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Expected ',' for array at line %d"), lineNumberStrings(pInfo));
322  	    return NULL;
323  	}
324          if (*pInfo->curr != ',') {
325              tmp = NULL;
326          } else {
327              pInfo->curr ++;
328              tmp = parsePlistObject(pInfo, false);
329          }
330      }
331      foundChar = advanceToNonSpace(pInfo);
332      if (!foundChar || *pInfo->curr != ')') {
333          __CFPListRelease(array, pInfo->allocator);
334          pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Expected terminating ')' for array at line %d"), lineNumberStrings(pInfo));
335          return NULL;
336      }
337      if (pInfo->error) {
338          CFRelease(pInfo->error);
339          pInfo->error = NULL;
340      }
341      pInfo->curr ++;
342      return array;
343  }
344  
345  __attribute__((noinline)) void _CFPropertyListMissingSemicolon(UInt32 line) {
346      CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListCreateFromXMLData(): Old-style plist parser: missing semicolon in dictionary on line %d. Parsing will be abandoned. Break on _CFPropertyListMissingSemicolon to debug."), line);
347  }
348  
349  __attribute__((noinline)) void _CFPropertyListMissingSemicolonOrValue(UInt32 line) {
350      CFLog(kCFLogLevelWarning, CFSTR("CFPropertyListCreateFromXMLData(): Old-style plist parser: missing semicolon or value in dictionary on line %d. Parsing will be abandoned. Break on _CFPropertyListMissingSemicolonOrValue to debug."), line);
351  }
352  
353  static CFDictionaryRef parsePlistDictContent(_CFStringsFileParseInfo *pInfo) {
354      CFMutableDictionaryRef dict = CFDictionaryCreateMutable(pInfo->allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
355      CFStringRef key = NULL;
356      Boolean failedParse = false;
357      key = parsePlistString(pInfo, false);
358      while (key) {
359          CFTypeRef value;
360          Boolean foundChar = advanceToNonSpace(pInfo);
361          if (!foundChar) {
362              UInt32 line = lineNumberStrings(pInfo);
363              _CFPropertyListMissingSemicolonOrValue(line);
364              failedParse = true;
365              pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Missing ';' on line %d"), line);
366              break;
367          }
368  	
369  	if (*pInfo->curr == ';') {
370  	    /* This is a strings file using the shortcut format */
371  	    /* although this check here really applies to all plists. */
372  	    value = CFRetain(key);
373  	} else if (*pInfo->curr == '=') {
374  	    pInfo->curr ++;
375  	    value = parsePlistObject(pInfo, true);
376  	    if (!value) {
377  		failedParse = true;
378  		break;
379  	    }
380  	} else {
381              pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unexpected ';' or '=' after key at line %d"), lineNumberStrings(pInfo));
382  	    failedParse = true;
383  	    break;
384  	}
385  	CFDictionarySetValue(dict, key, value);
386  	__CFPListRelease(key, pInfo->allocator);
387  	key = NULL;
388  	__CFPListRelease(value, pInfo->allocator);
389  	value = NULL;
390  	foundChar = advanceToNonSpace(pInfo);
391  	if (foundChar && *pInfo->curr == ';') {
392  	    pInfo->curr ++;
393  	    key = parsePlistString(pInfo, false);
394  	} else if (true || !foundChar) {
395              UInt32 line = lineNumberStrings(pInfo);
396              _CFPropertyListMissingSemicolon(line);
397  	    failedParse = true;
398  	    pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Missing ';' on line %d"), line);
399  	}
400      }
401      
402      if (failedParse) {
403          __CFPListRelease(key, pInfo->allocator);
404          __CFPListRelease(dict, pInfo->allocator);
405          return NULL;
406      }
407      if (pInfo->error) {
408          CFRelease(pInfo->error);
409          pInfo->error = NULL;
410      }
411      return dict;
412  }
413  
414  static CFTypeRef parsePlistDict(_CFStringsFileParseInfo *pInfo) {
415      CFDictionaryRef dict = parsePlistDictContent(pInfo);
416      if (!dict) return NULL;
417      Boolean foundChar = advanceToNonSpace(pInfo);
418      if (!foundChar || *pInfo->curr != '}') {
419          __CFPListRelease(dict, pInfo->allocator);
420          pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Expected terminating '}' for dictionary at line %d"), lineNumberStrings(pInfo));
421          return NULL;
422      }
423      pInfo->curr ++;
424      return dict;
425  }
426  
427  CF_INLINE unsigned char fromHexDigit(unsigned char ch) {
428      if (isdigit(ch)) return ch - '0';
429      if ((ch >= 'a') && (ch <= 'f')) return ch - 'a' + 10;
430      if ((ch >= 'A') && (ch <= 'F')) return ch - 'A' + 10;
431      return 0xff; // Just choose a large number for the error code
432  }
433  
434  /* Gets up to bytesSize bytes from a plist data. Returns number of bytes actually read. Leaves cursor at first non-space, non-hex character.
435   -1 is returned for unexpected char, -2 for uneven number of hex digits
436   */
437  static int getDataBytes(_CFStringsFileParseInfo *pInfo, unsigned char *bytes, int bytesSize) {
438      int numBytesRead = 0;
439      while ((pInfo->curr < pInfo->end) && (numBytesRead < bytesSize)) {
440  	int first, second;
441  	UniChar ch1 = *pInfo->curr;
442  	if (ch1 == '>') return numBytesRead;  // Meaning we're done
443  	first = fromHexDigit((unsigned char)ch1);
444  	if (first != 0xff) {	// If the first char is a hex, then try to read a second hex
445  	    pInfo->curr++;
446  	    if (pInfo->curr >= pInfo->end) return -2;   // Error: uneven number of hex digits
447  	    UniChar ch2 = *pInfo->curr;
448  	    second = fromHexDigit((unsigned char)ch2);
449  	    if (second == 0xff) return -2;  // Error: uneven number of hex digits
450  	    bytes[numBytesRead++] = (first << 4) + second;
451  	    pInfo->curr++;
452  	} else if (ch1 == ' ' || ch1 == '\n' || ch1 == '\t' || ch1 == '\r' || ch1 == 0x2028 || ch1 == 0x2029) {
453  	    pInfo->curr++;
454  	} else {
455  	    return -1;  // Error: unexpected character
456  	}
457      }
458      return numBytesRead;    // This does likely mean we didn't encounter a '>', but we'll let the caller deal with that
459  }
460  
461  #define numBytes 400
462  static CFTypeRef parsePlistData(_CFStringsFileParseInfo *pInfo) {
463      CFMutableDataRef result = CFDataCreateMutable(pInfo->allocator, 0);
464      
465      // Read hex bytes and append them to result
466      while (1) {
467  	unsigned char bytes[numBytes];
468  	int numBytesRead = getDataBytes(pInfo, bytes, numBytes);
469  	if (numBytesRead < 0) {
470  	    __CFPListRelease(result, pInfo->allocator);
471              switch (numBytesRead) {
472                  case -2: 
473                      pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Malformed data byte group at line %d; uneven length"), lineNumberStrings(pInfo));
474                      break;
475                  default: 
476                      pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Malformed data byte group at line %d; invalid hex"), lineNumberStrings(pInfo));
477                      break;
478              }
479  	    return NULL;
480  	}
481  	if (numBytesRead == 0) break;
482  	CFDataAppendBytes(result, bytes, numBytesRead);
483      }
484      
485      if (pInfo->error) {
486          CFRelease(pInfo->error);
487          pInfo->error = NULL;
488      }
489      
490      if (*(pInfo->curr) == '>') {
491          pInfo->curr ++; // Move past '>'
492          return result;
493      } else {
494          __CFPListRelease(result, pInfo->allocator);
495          pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Expected terminating '>' for data at line %d"), lineNumberStrings(pInfo));
496          return NULL;
497      }
498  }
499  #undef numBytes
500  
501  // Returned object is retained; caller must free.
502  static CFTypeRef parsePlistObject(_CFStringsFileParseInfo *pInfo, bool requireObject) {
503      UniChar ch;
504      Boolean foundChar = advanceToNonSpace(pInfo);
505      if (!foundChar) {
506          if (requireObject) {
507              pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unexpected EOF while parsing plist"));
508          }
509          return NULL;
510      }
511      ch = *(pInfo->curr);
512      pInfo->curr ++;
513      if (ch == '{') {
514          return parsePlistDict(pInfo);
515      } else if (ch == '(') {
516          return parsePlistArray(pInfo);
517      } else if (ch == '<') {
518          return parsePlistData(pInfo);
519      } else if (ch == '\'' || ch == '\"') {
520          return parseQuotedPlistString(pInfo, ch);
521      } else if (isValidUnquotedStringCharacter(ch)) {
522          pInfo->curr --;
523          return parseUnquotedPlistString(pInfo);
524      } else {
525          pInfo->curr --;  // Must back off the charcter we just read
526          if (requireObject) {
527              pInfo->error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unexpected character '0x%x' at line %d"), ch, lineNumberStrings(pInfo));
528          }
529          return NULL;
530      }
531  }
532  
533  // CFAllocatorRef allocator, CFDataRef xmlData, CFStringRef originalString, CFStringEncoding guessedEncoding, CFOptionFlags option, CFErrorRef *outError, Boolean allowNewTypes, CFPropertyListFormat *format, CFSetRef keyPaths
534  
535  CF_PRIVATE CFTypeRef __CFCreateOldStylePropertyListOrStringsFile(CFAllocatorRef allocator, CFDataRef xmlData, CFStringRef originalString, CFStringEncoding guessedEncoding, CFOptionFlags option, CFErrorRef *outError,CFPropertyListFormat *format) {
536      
537      // Convert the string to UTF16 for parsing old-style
538      if (originalString) {
539          // Ensure that originalString is not collected while we are using it
540          CFRetain(originalString);
541      } else {
542          originalString = CFStringCreateWithBytes(kCFAllocatorSystemDefault, CFDataGetBytePtr(xmlData), CFDataGetLength(xmlData), guessedEncoding, NO);
543          if (!originalString) {
544              // Couldn't convert
545              if (outError) *outError = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Conversion of string failed."));
546              return NULL;
547          }
548      }
549          
550      UInt32 length;
551      Boolean createdBuffer = false;
552      length = CFStringGetLength(originalString);
553      if (!length) {
554          if (outError) *outError = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Conversion of string failed. The string is empty."));
555          return NULL;
556      }
557      
558      UniChar *buf = (UniChar *)CFStringGetCharactersPtr(originalString);
559      if (!buf) {
560          buf = (UniChar *)CFAllocatorAllocate(allocator, length * sizeof(UniChar), 0);
561          if (!buf) {
562              CRSetCrashLogMessage("CFPropertyList ran out of memory while attempting to allocate temporary storage.");
563              return NULL;
564          }
565          CFStringGetCharacters(originalString, CFRangeMake(0, length), buf);
566          createdBuffer = true;
567      }
568      
569      _CFStringsFileParseInfo stringsPInfo;
570      stringsPInfo.begin = buf;
571      stringsPInfo.end = buf+length;
572      stringsPInfo.curr = buf;
573      stringsPInfo.allocator = allocator;
574      stringsPInfo.mutabilityOption = option;
575      stringsPInfo.stringSet = CFSetCreateMutable(allocator, 0, &kCFTypeSetCallBacks);
576      stringsPInfo.error = NULL;
577      
578      const UniChar *begin = stringsPInfo.curr;
579      CFTypeRef result = NULL;
580      Boolean foundChar = advanceToNonSpace(&stringsPInfo);
581      if (!foundChar) {
582          // A file consisting only of whitespace (or empty) is now defined to be an empty dictionary
583          result = CFDictionaryCreateMutable(allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
584      } else {
585          result = parsePlistObject(&stringsPInfo, true);
586          if (result) {
587              foundChar = advanceToNonSpace(&stringsPInfo);
588              if (foundChar) {
589                  if (CFGetTypeID(result) != CFStringGetTypeID()) {
590                      __CFPListRelease(result, allocator);
591                      result = NULL;
592                      if (stringsPInfo.error) CFRelease(stringsPInfo.error);
593                      stringsPInfo.error = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Junk after plist at line %d"), lineNumberStrings(&stringsPInfo));
594                  } else {
595                      // Reset info and keep parsing
596                      __CFPListRelease(result, allocator);
597                      if (stringsPInfo.error) CFRelease(stringsPInfo.error);
598                      stringsPInfo.error = NULL;
599                      
600                      // Check for a strings file (looks like a dictionary without the opening/closing curly braces)
601                      stringsPInfo.curr = begin;
602                      result = parsePlistDictContent(&stringsPInfo);
603                  }
604              }
605          }
606      }
607      
608      if (!result) {
609          // Must return some kind of error if requested
610          if (outError) {
611              if (stringsPInfo.error) {
612                  // Transfer ownership
613                  *outError = stringsPInfo.error;
614              } else {
615                  *outError = __CFPropertyListCreateError(kCFPropertyListReadCorruptError, CFSTR("Unknown error parsing property list around line %d"), lineNumberStrings(&stringsPInfo));
616              }
617          } else if (stringsPInfo.error) {
618              // Caller doesn't want it, so we need to free it
619              CFRelease(stringsPInfo.error);
620          }
621      }
622      
623      if (result && format) *format = kCFPropertyListOpenStepFormat;
624      
625      if (createdBuffer && !(0)) CFAllocatorDeallocate(allocator, buf);
626      CFRelease(stringsPInfo.stringSet);
627      CFRelease(originalString);
628      return result;
629  }
630  
631  #undef isValidUnquotedStringCharacter