+/*
+ * Copyright (c) 2006-2007 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 <security_utilities/cfutilities.h>
+#include <security_utilities/errors.h>
+
+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(noErr)
+{
+ 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 '}':
+ assert(false); // unexpected
+ return NULL; // error
+ default:
+ if (isdigit(*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 <class Value>
+ bool CFScan::scannumber(CFTypeRef obj);
+
+ template <class Type>
+ 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<CFTypeRef>(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 <class Type>
+void CFScan::store(Type value)
+{
+ if (!suppress)
+ *va_arg(args, Type *) = value;
+}
+
+
+//
+// Convert a CFNumber to an external numeric form
+//
+template <class Value>
+bool CFScan::scannumber(CFTypeRef obj)
+{
+ ++format; // consume format code
+ if (!obj)
+ return true; // suppressed, okay
+ if (CFGetTypeID(obj) != CFNumberGetTypeID())
+ return false;
+ store<Value>(cfNumber<Value>(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<CFTypeRef>(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<int>(obj == kCFBooleanTrue);
+ return true;
+ }
+ case F_DICTIONARY:
+ return typescan(obj, CFDictionaryGetTypeID()) == done;
+ case 'd': // %d - int
+ return scannumber<int>(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<std::string>(cfString(CFStringRef(obj)));
+ return true;
+ case 'u':
+ return scannumber<unsigned int>(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