1/*
2 * Copyright (c) 2006-2007,2011,2013-2014 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// CoreFoundation building and parsing functions.
25//
26// These classes provide a printf/scanf-like interface to nested data structures
27// of the Property List Subset of CoreFoundation.
28//
29#include "cfmunge.h"
30#include <security_utilities/cfutilities.h>
31#include <security_utilities/errors.h>
32
33namespace Security {
34
35
36//
37// Format codes for consistency
38//
39#define F_ARRAY			'A'
40#define F_BOOLEAN		'B'
41#define F_DATA			'X'
42#define F_DICTIONARY	'D'
43#define F_OBJECT		'O'
44#define F_STRING		'S'
45#define F_NUMBER		'N'
46
47
48//
49// Initialize a CFMunge. We start out with the default CFAllocator, and
50// we do not throw errors.
51//
52CFMunge::CFMunge(const char *fmt, va_list arg)
53	: format(fmt), allocator(NULL), error(errSecSuccess)
54{
55	va_copy(args, arg);
56}
57
58CFMunge::~CFMunge()
59{
60	va_end(args);
61}
62
63
64//
65// Skip whitespace and other fluff and deliver the next significant character.
66//
67char CFMunge::next()
68{
69	while (*format && (isspace(*format) || *format == ',')) ++format;
70	return *format;
71}
72
73
74//
75// Locate and consume an optional character
76//
77bool CFMunge::next(char c)
78{
79	if (next() == c) {
80		++format;
81		return true;
82	} else
83		return false;
84}
85
86
87//
88// Process @? parameter specifications.
89// The @ operator is used for side effects, and does not return a value.
90//
91bool CFMunge::parameter()
92{
93	switch (*++format) {
94	case 'A':
95		++format;
96		allocator = va_arg(args, CFAllocatorRef);
97		return true;
98	case 'E':
99		++format;
100		error = va_arg(args, OSStatus);
101		return true;
102	default:
103		return false;
104	}
105}
106
107
108//
109// The top constructor.
110//
111CFTypeRef CFMake::make()
112{
113	while (next() == '@')
114		parameter();
115	switch (next()) {
116	case '\0':
117		return NULL;
118	case '{':
119		return makedictionary();
120	case '[':
121		return makearray();
122	case '\'':
123		return makestring();
124	case '%':
125		return makeformat();
126	case '#':
127		return makespecial();
128	case ']':
129	case '}':
130		return NULL;	// error
131	default:
132		if (isdigit(*format) || *format == '-')
133			return makenumber();
134		else if (isalpha(*format))
135			return makestring();
136		else {
137			assert(false);
138			return NULL;
139		}
140	}
141}
142
143
144CFTypeRef CFMake::makeformat()
145{
146	++format;
147	switch (*format++) {
148	case 'b':	// blob (pointer, length)
149		{
150			const void *data = va_arg(args, const void *);
151			size_t length = va_arg(args, size_t);
152			return CFDataCreate(allocator, (const UInt8 *)data, length);
153		}
154	case F_BOOLEAN:	// boolean (with int promotion)
155		return va_arg(args, int) ? kCFBooleanTrue : kCFBooleanFalse;
156	case 'd':
157		return makeCFNumber(va_arg(args, int));
158	case 's':
159		return CFStringCreateWithCString(allocator, va_arg(args, const char *),
160			kCFStringEncodingUTF8);
161	case F_OBJECT:
162		return CFRetain(va_arg(args, CFTypeRef));
163	case 'u':
164		return makeCFNumber(va_arg(args, unsigned int));
165	default:
166		assert(false);
167		return NULL;
168	}
169}
170
171
172CFTypeRef CFMake::makespecial()
173{
174	++format;
175	switch (*format++) {
176	case 'N':
177		return kCFNull;
178	case 't':
179	case 'T':
180		return kCFBooleanTrue;
181	case 'f':
182	case 'F':
183		return kCFBooleanFalse;
184	default:
185		assert(false);
186		return NULL;
187	}
188}
189
190
191CFTypeRef CFMake::makenumber()
192{
193	double value = strtod(format, (char **)&format);
194	return CFNumberCreate(allocator, kCFNumberDoubleType, &value);
195}
196
197
198//
199// Embedded strings can either be alphanumeric (only), or delimited with single quotes ''.
200// No escapes are processed within such quotes. If you want arbitrary string values, use %s.
201//
202CFTypeRef CFMake::makestring()
203{
204	const char *start, *end;
205	if (*format == '\'') {
206		start = ++format;	// next quote
207		if (!(end = strchr(format, '\''))) {
208			assert(false);
209			return NULL;
210		}
211		format = end + 1;
212	} else {
213		start = format;
214		for (end = start + 1; isalnum(*end); ++end) ;
215		format = end;
216	}
217	return CFStringCreateWithBytes(allocator,
218		(const UInt8 *)start, end - start,
219		kCFStringEncodingUTF8, false);
220}
221
222
223//
224// Construct a CFDictionary
225//
226CFTypeRef CFMake::makedictionary()
227{
228	++format;	// next '{'
229	next('!');	// indicates mutable (currently always true)
230	CFMutableDictionaryRef dict;
231	if (next('+')) { // {+%O, => copy dictionary argument, then proceed
232		if (next('%') && next('O')) {
233			CFDictionaryRef source = va_arg(args, CFDictionaryRef);
234			dict = CFDictionaryCreateMutableCopy(allocator, NULL, source);
235			if (next('}'))
236				return dict;
237		} else
238			return NULL;	// bad syntax
239	} else
240		dict = CFDictionaryCreateMutable(allocator, 0,
241			&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
242	if (add(dict))
243		return dict;
244	else {
245		CFRelease(dict);
246		return NULL;
247	}
248}
249
250CFDictionaryRef CFMake::add(CFMutableDictionaryRef dict)
251{
252	while (next() != '}') {
253		CFTypeRef key = make();
254		if (key == NULL)
255			return NULL;
256		if (!next('=')) {
257			CFRelease(key);
258			return NULL;
259		}
260		if (CFTypeRef value = make()) {
261			CFDictionaryAddValue(dict, key, value);
262			CFRelease(key);
263			CFRelease(value);
264		} else {
265			CFRelease(key);
266			return NULL;
267		}
268	}
269	++format;
270	return dict;
271}
272
273
274CFDictionaryRef CFMake::addto(CFMutableDictionaryRef dict)
275{
276	if (next('{'))
277		return add(dict);
278	else {
279		assert(false);
280		return NULL;
281	}
282}
283
284
285//
286// Construct a CFArray
287//
288CFTypeRef CFMake::makearray()
289{
290	++format;	// next '['
291	next('!');	// indicates mutable (currently always)
292	CFMutableArrayRef array = makeCFMutableArray(0);
293	while (next() != ']') {
294		CFTypeRef value = make();
295		if (value == NULL) {
296			CFRelease(array);
297			return NULL;
298		}
299		CFArrayAppendValue(array, value);
300		CFRelease(value);
301	}
302	++format;
303	return array;
304}
305
306
307//
308// A CFScan processes its format by parsing through an existing CF object
309// structure, matching and extracting values as directed. Note that CFScan
310// is a structure (tree) scanner rather than a linear parser, and will happily
311// parse out a subset of the input object graph.
312//
313class CFScan : public CFMake {
314public:
315	CFScan(const char *format, va_list args)
316		: CFMake(format, args), suppress(false) { }
317
318	bool scan(CFTypeRef obj);
319	CFTypeRef dictpath(CFTypeRef obj);
320
321protected:
322	bool scandictionary(CFDictionaryRef obj);
323	bool scanarray(CFArrayRef obj);
324	bool scanformat(CFTypeRef obj);
325
326	enum Typescan { fail = -1, more = 0, done = 1 };
327	Typescan typescan(CFTypeRef obj, CFTypeID type);
328
329	template <class Value>
330	bool scannumber(CFTypeRef obj);
331
332	template <class Type>
333	void store(Type value);
334
335	bool suppress;				// output suppression
336};
337
338
339//
340// Master scan function
341//
342bool CFScan::scan(CFTypeRef obj)
343{
344	while (next() == '@')
345		parameter();
346	switch (next()) {
347	case '\0':
348		return true;	// done, okay
349	case '{':
350		if (obj && CFGetTypeID(obj) != CFDictionaryGetTypeID())
351			return false;
352		return scandictionary(CFDictionaryRef(obj));
353	case '[':
354		if (obj && CFGetTypeID(obj) != CFArrayGetTypeID())
355			return false;
356		return scanarray(CFArrayRef(obj));
357	case '%':	// return this value in some form
358		return scanformat(obj);
359	case '=':	// match value
360		{
361			++format;
362			CFTypeRef match = make();
363			bool rc = CFEqual(obj, match);
364			CFRelease(match);
365			return rc;
366		}
367	case ']':
368	case '}':
369		assert(false);	// unexpected
370		return false;
371	default:
372		assert(false);
373		return false;
374	}
375}
376
377
378//
379// Primitive type-match helper.
380// Ensures the object has the CF runtime type required, and processes
381// the %?o format (return CFTypeRef) and %?n format (ignore value).
382//
383CFScan::Typescan CFScan::typescan(CFTypeRef obj, CFTypeID type)
384{
385	if (obj && CFGetTypeID(obj) != type)
386		return fail;
387	switch (*++format) {
388	case F_OBJECT:	// return CFTypeRef
389		++format;
390		store<CFTypeRef>(obj);
391		return done;
392	case 'n':	// suppress assignment
393		++format;
394		return done;
395	default:
396		return more;
397	}
398}
399
400
401//
402// Store a value into the next varargs slot, unless output suppression is on.
403//
404template <class Type>
405void CFScan::store(Type value)
406{
407	if (!suppress)
408		*va_arg(args, Type *) = value;
409}
410
411
412//
413// Convert a CFNumber to an external numeric form
414//
415template <class Value>
416bool CFScan::scannumber(CFTypeRef obj)
417{
418	++format;	// consume format code
419	if (!obj)
420		return true; // suppressed, okay
421	if (CFGetTypeID(obj) != CFNumberGetTypeID())
422		return false;
423	store<Value>(cfNumber<Value>(CFNumberRef(obj)));
424	return true;
425}
426
427
428//
429// Process % scan forms.
430// This delivers the object value, scanf-style, somehow.
431//
432bool CFScan::scanformat(CFTypeRef obj)
433{
434	switch (*++format) {
435	case F_OBJECT:
436		store<CFTypeRef>(obj);
437		return true;
438	case F_ARRAY:	// %a*
439		return typescan(obj, CFArrayGetTypeID()) == done;
440	case F_BOOLEAN:
441		if (Typescan rc = typescan(obj, CFBooleanGetTypeID()))
442			return rc == done;
443		switch (*format) {
444		case 'f':	// %Bf - two arguments (value, &variable)
445			{
446				unsigned flag = va_arg(args, unsigned);
447				unsigned *value = va_arg(args, unsigned *);
448				if (obj == kCFBooleanTrue && !suppress)
449					*value |= flag;
450				return true;
451			}
452		default:	// %b - CFBoolean as int boolean
453			store<int>(obj == kCFBooleanTrue);
454			return true;
455		}
456	case F_DICTIONARY:
457		return typescan(obj, CFDictionaryGetTypeID()) == done;
458	case 'd':	// %d - int
459		return scannumber<int>(obj);
460	case F_NUMBER:
461		return typescan(obj, CFNumberGetTypeID()) == done;
462	case F_STRING:
463	case 's':
464		if (Typescan rc = typescan(obj, CFStringGetTypeID()))
465			return rc == done;
466		// %s
467		store<std::string>(cfString(CFStringRef(obj)));
468		return true;
469	case 'u':
470		return scannumber<unsigned int>(obj);
471	case F_DATA:
472		return typescan(obj, CFDataGetTypeID()) == done;
473	default:
474		assert(false);
475		return false;
476	}
477}
478
479
480bool CFScan::scandictionary(CFDictionaryRef obj)
481{
482	++format;	// skip '{'
483	while (next() != '}') {
484		bool optional = next('?');
485		if (CFTypeRef key = make()) {
486			bool oldSuppress = suppress;
487			CFTypeRef elem = obj ? CFDictionaryGetValue(obj, key) : NULL;
488			if (elem || optional) {
489				suppress |= (elem == NULL);
490				if (next('=')) {
491					if (scan(elem)) {
492						suppress = oldSuppress;	// restore
493						CFRelease(key);
494						continue;
495					}
496				}
497			}
498			CFRelease(key);
499			return false;
500		} else {
501			assert(false);	// bad format
502			return false;
503		}
504	}
505	return true;
506}
507
508
509bool CFScan::scanarray(CFArrayRef obj)
510{
511	++format;	// skip '['
512	CFIndex length = CFArrayGetCount(obj);
513	for (int pos = 0; pos < length; ++pos) {
514		if (next() == ']')
515			return true;
516		if (!scan(CFArrayGetValueAtIndex(obj, pos)))
517			return false;
518	}
519	return false;	// array length exceeded
520}
521
522
523//
524// Run down a "dictionary path", validating heavily.
525//
526CFTypeRef CFScan::dictpath(CFTypeRef obj)
527{
528	while (next()) {	// while we've got more text
529		next('.');		// optional
530		if (obj == NULL || CFGetTypeID(obj) != CFDictionaryGetTypeID())
531			return NULL;
532		CFTypeRef key = make();
533		obj = CFDictionaryGetValue(CFDictionaryRef(obj), key);
534		CFRelease(key);
535	}
536	return obj;
537}
538
539
540//
541// The public functions
542//
543CFTypeRef cfmake(const char *format, ...)
544{
545	va_list args;
546	va_start(args, format);
547	CFTypeRef result = CFMake(format, args).make();
548	va_end(args);
549	return result;
550}
551
552CFTypeRef vcfmake(const char *format, va_list args)
553{
554	return CFMake(format, args).make();
555}
556
557CFDictionaryRef cfadd(CFMutableDictionaryRef dict, const char *format, ...)
558{
559	va_list args;
560	va_start(args, format);
561	CFDictionaryRef result = CFMake(format, args).addto(dict);
562	va_end(args);
563	return result;
564}
565
566
567bool cfscan(CFTypeRef obj, const char *format, ...)
568{
569	va_list args;
570	va_start(args, format);
571	bool result = vcfscan(obj, format, args);
572	va_end(args);
573	return result;
574}
575
576bool vcfscan(CFTypeRef obj, const char *format, va_list args)
577{
578	return CFScan(format, args).scan(obj);
579}
580
581
582CFTypeRef cfget(CFTypeRef obj, const char *format, ...)
583{
584	va_list args;
585	va_start(args, format);
586	CFTypeRef result = vcfget(obj, format, args);
587	va_end(args);
588	return result;
589}
590
591CFTypeRef vcfget(CFTypeRef obj, const char *format, va_list args)
592{
593	return CFScan(format, args).dictpath(obj);
594}
595
596}	// end namespace Security
597