/* * Copyright (c) 2006-2007,2011,2013-2014 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ // // CoreFoundation building and parsing functions. // // These classes provide a printf/scanf-like interface to nested data structures // of the Property List Subset of CoreFoundation. // #include "cfmunge.h" #include #include namespace Security { // // Format codes for consistency // #define F_ARRAY 'A' #define F_BOOLEAN 'B' #define F_DATA 'X' #define F_DICTIONARY 'D' #define F_OBJECT 'O' #define F_STRING 'S' #define F_NUMBER 'N' // // Initialize a CFMunge. We start out with the default CFAllocator, and // we do not throw errors. // CFMunge::CFMunge(const char *fmt, va_list arg) : format(fmt), allocator(NULL), error(errSecSuccess) { va_copy(args, arg); } CFMunge::~CFMunge() { va_end(args); } // // Skip whitespace and other fluff and deliver the next significant character. // char CFMunge::next() { while (*format && (isspace(*format) || *format == ',')) ++format; return *format; } // // Locate and consume an optional character // bool CFMunge::next(char c) { if (next() == c) { ++format; return true; } else return false; } // // Process @? parameter specifications. // The @ operator is used for side effects, and does not return a value. // bool CFMunge::parameter() { switch (*++format) { case 'A': ++format; allocator = va_arg(args, CFAllocatorRef); return true; case 'E': ++format; error = va_arg(args, OSStatus); return true; default: return false; } } // // The top constructor. // CFTypeRef CFMake::make() { while (next() == '@') parameter(); switch (next()) { case '\0': return NULL; case '{': return makedictionary(); case '[': return makearray(); case '\'': return makestring(); case '%': return makeformat(); case '#': return makespecial(); case ']': case '}': return NULL; // error default: if (isdigit(*format) || *format == '-') return makenumber(); else if (isalpha(*format)) return makestring(); else { assert(false); return NULL; } } } CFTypeRef CFMake::makeformat() { ++format; switch (*format++) { case 'b': // blob (pointer, length) { const void *data = va_arg(args, const void *); size_t length = va_arg(args, size_t); return CFDataCreate(allocator, (const UInt8 *)data, length); } case F_BOOLEAN: // boolean (with int promotion) return va_arg(args, int) ? kCFBooleanTrue : kCFBooleanFalse; case 'd': return makeCFNumber(va_arg(args, int)); case 's': return CFStringCreateWithCString(allocator, va_arg(args, const char *), kCFStringEncodingUTF8); case F_OBJECT: return CFRetain(va_arg(args, CFTypeRef)); case 'u': return makeCFNumber(va_arg(args, unsigned int)); default: assert(false); return NULL; } } CFTypeRef CFMake::makespecial() { ++format; switch (*format++) { case 'N': return kCFNull; case 't': case 'T': return kCFBooleanTrue; case 'f': case 'F': return kCFBooleanFalse; default: assert(false); return NULL; } } CFTypeRef CFMake::makenumber() { double value = strtod(format, (char **)&format); return CFNumberCreate(allocator, kCFNumberDoubleType, &value); } // // Embedded strings can either be alphanumeric (only), or delimited with single quotes ''. // No escapes are processed within such quotes. If you want arbitrary string values, use %s. // CFTypeRef CFMake::makestring() { const char *start, *end; if (*format == '\'') { start = ++format; // next quote if (!(end = strchr(format, '\''))) { assert(false); return NULL; } format = end + 1; } else { start = format; for (end = start + 1; isalnum(*end); ++end) ; format = end; } return CFStringCreateWithBytes(allocator, (const UInt8 *)start, end - start, kCFStringEncodingUTF8, false); } // // Construct a CFDictionary // CFTypeRef CFMake::makedictionary() { ++format; // next '{' next('!'); // indicates mutable (currently always true) CFMutableDictionaryRef dict; if (next('+')) { // {+%O, => copy dictionary argument, then proceed if (next('%') && next('O')) { CFDictionaryRef source = va_arg(args, CFDictionaryRef); dict = CFDictionaryCreateMutableCopy(allocator, NULL, source); if (next('}')) return dict; } else return NULL; // bad syntax } else dict = CFDictionaryCreateMutable(allocator, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); if (add(dict)) return dict; else { CFRelease(dict); return NULL; } } CFDictionaryRef CFMake::add(CFMutableDictionaryRef dict) { while (next() != '}') { CFTypeRef key = make(); if (key == NULL) return NULL; if (!next('=')) { CFRelease(key); return NULL; } if (CFTypeRef value = make()) { CFDictionaryAddValue(dict, key, value); CFRelease(key); CFRelease(value); } else { CFRelease(key); return NULL; } } ++format; return dict; } CFDictionaryRef CFMake::addto(CFMutableDictionaryRef dict) { if (next('{')) return add(dict); else { assert(false); return NULL; } } // // Construct a CFArray // CFTypeRef CFMake::makearray() { ++format; // next '[' next('!'); // indicates mutable (currently always) CFMutableArrayRef array = makeCFMutableArray(0); while (next() != ']') { CFTypeRef value = make(); if (value == NULL) { CFRelease(array); return NULL; } CFArrayAppendValue(array, value); CFRelease(value); } ++format; return array; } // // A CFScan processes its format by parsing through an existing CF object // structure, matching and extracting values as directed. Note that CFScan // is a structure (tree) scanner rather than a linear parser, and will happily // parse out a subset of the input object graph. // class CFScan : public CFMake { public: CFScan(const char *format, va_list args) : CFMake(format, args), suppress(false) { } bool scan(CFTypeRef obj); CFTypeRef dictpath(CFTypeRef obj); protected: bool scandictionary(CFDictionaryRef obj); bool scanarray(CFArrayRef obj); bool scanformat(CFTypeRef obj); enum Typescan { fail = -1, more = 0, done = 1 }; Typescan typescan(CFTypeRef obj, CFTypeID type); template bool scannumber(CFTypeRef obj); template void store(Type value); bool suppress; // output suppression }; // // Master scan function // bool CFScan::scan(CFTypeRef obj) { while (next() == '@') parameter(); switch (next()) { case '\0': return true; // done, okay case '{': if (obj && CFGetTypeID(obj) != CFDictionaryGetTypeID()) return false; return scandictionary(CFDictionaryRef(obj)); case '[': if (obj && CFGetTypeID(obj) != CFArrayGetTypeID()) return false; return scanarray(CFArrayRef(obj)); case '%': // return this value in some form return scanformat(obj); case '=': // match value { ++format; CFTypeRef match = make(); bool rc = CFEqual(obj, match); CFRelease(match); return rc; } case ']': case '}': assert(false); // unexpected return false; default: assert(false); return false; } } // // Primitive type-match helper. // Ensures the object has the CF runtime type required, and processes // the %?o format (return CFTypeRef) and %?n format (ignore value). // CFScan::Typescan CFScan::typescan(CFTypeRef obj, CFTypeID type) { if (obj && CFGetTypeID(obj) != type) return fail; switch (*++format) { case F_OBJECT: // return CFTypeRef ++format; store(obj); return done; case 'n': // suppress assignment ++format; return done; default: return more; } } // // Store a value into the next varargs slot, unless output suppression is on. // template void CFScan::store(Type value) { if (!suppress) *va_arg(args, Type *) = value; } // // Convert a CFNumber to an external numeric form // template bool CFScan::scannumber(CFTypeRef obj) { ++format; // consume format code if (!obj) return true; // suppressed, okay if (CFGetTypeID(obj) != CFNumberGetTypeID()) return false; store(cfNumber(CFNumberRef(obj))); return true; } // // Process % scan forms. // This delivers the object value, scanf-style, somehow. // bool CFScan::scanformat(CFTypeRef obj) { switch (*++format) { case F_OBJECT: store(obj); return true; case F_ARRAY: // %a* return typescan(obj, CFArrayGetTypeID()) == done; case F_BOOLEAN: if (Typescan rc = typescan(obj, CFBooleanGetTypeID())) return rc == done; switch (*format) { case 'f': // %Bf - two arguments (value, &variable) { unsigned flag = va_arg(args, unsigned); unsigned *value = va_arg(args, unsigned *); if (obj == kCFBooleanTrue && !suppress) *value |= flag; return true; } default: // %b - CFBoolean as int boolean store(obj == kCFBooleanTrue); return true; } case F_DICTIONARY: return typescan(obj, CFDictionaryGetTypeID()) == done; case 'd': // %d - int return scannumber(obj); case F_NUMBER: return typescan(obj, CFNumberGetTypeID()) == done; case F_STRING: case 's': if (Typescan rc = typescan(obj, CFStringGetTypeID())) return rc == done; // %s store(cfString(CFStringRef(obj))); return true; case 'u': return scannumber(obj); case F_DATA: return typescan(obj, CFDataGetTypeID()) == done; default: assert(false); return false; } } bool CFScan::scandictionary(CFDictionaryRef obj) { ++format; // skip '{' while (next() != '}') { bool optional = next('?'); if (CFTypeRef key = make()) { bool oldSuppress = suppress; CFTypeRef elem = obj ? CFDictionaryGetValue(obj, key) : NULL; if (elem || optional) { suppress |= (elem == NULL); if (next('=')) { if (scan(elem)) { suppress = oldSuppress; // restore CFRelease(key); continue; } } } CFRelease(key); return false; } else { assert(false); // bad format return false; } } return true; } bool CFScan::scanarray(CFArrayRef obj) { ++format; // skip '[' CFIndex length = CFArrayGetCount(obj); for (int pos = 0; pos < length; ++pos) { if (next() == ']') return true; if (!scan(CFArrayGetValueAtIndex(obj, pos))) return false; } return false; // array length exceeded } // // Run down a "dictionary path", validating heavily. // CFTypeRef CFScan::dictpath(CFTypeRef obj) { while (next()) { // while we've got more text next('.'); // optional if (obj == NULL || CFGetTypeID(obj) != CFDictionaryGetTypeID()) return NULL; CFTypeRef key = make(); obj = CFDictionaryGetValue(CFDictionaryRef(obj), key); CFRelease(key); } return obj; } // // The public functions // CFTypeRef cfmake(const char *format, ...) { va_list args; va_start(args, format); CFTypeRef result = CFMake(format, args).make(); va_end(args); return result; } CFTypeRef vcfmake(const char *format, va_list args) { return CFMake(format, args).make(); } CFDictionaryRef cfadd(CFMutableDictionaryRef dict, const char *format, ...) { va_list args; va_start(args, format); CFDictionaryRef result = CFMake(format, args).addto(dict); va_end(args); return result; } bool cfscan(CFTypeRef obj, const char *format, ...) { va_list args; va_start(args, format); bool result = vcfscan(obj, format, args); va_end(args); return result; } bool vcfscan(CFTypeRef obj, const char *format, va_list args) { return CFScan(format, args).scan(obj); } CFTypeRef cfget(CFTypeRef obj, const char *format, ...) { va_list args; va_start(args, format); CFTypeRef result = vcfget(obj, format, args); va_end(args); return result; } CFTypeRef vcfget(CFTypeRef obj, const char *format, va_list args) { return CFScan(format, args).dictpath(obj); } } // end namespace Security