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