-/*
- * 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