Line data Source code
1 0 : /*
2 :
3 : OOPListSchemaVerifier.m
4 :
5 :
6 : Copyright (C) 2007-2013 Jens Ayton
7 :
8 : Permission is hereby granted, free of charge, to any person obtaining a copy
9 : of this software and associated documentation files (the "Software"), to deal
10 : in the Software without restriction, including without limitation the rights
11 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 : copies of the Software, and to permit persons to whom the Software is
13 : furnished to do so, subject to the following conditions:
14 :
15 : The above copyright notice and this permission notice shall be included in all
16 : copies or substantial portions of the Software.
17 :
18 : THE SOFTWARE IS PROVIDED ìAS ISî, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 : SOFTWARE.
25 :
26 : */
27 :
28 : #import "OOPListSchemaVerifier.h"
29 :
30 : #if OO_OXP_VERIFIER_ENABLED
31 :
32 : #import "OOLoggingExtended.h"
33 : #import "OOCollectionExtractors.h"
34 : #import "OOMaths.h"
35 : #include <limits.h>
36 :
37 :
38 0 : #define PLIST_VERIFIER_DEBUG_DUMP_ENABLED 1
39 :
40 :
41 0 : enum
42 : {
43 : // Largest allowable number of characters for string included in error message.
44 : kMaximumLengthForStringInErrorMessage = 100
45 : };
46 :
47 :
48 : // Internal error codes.
49 0 : enum
50 : {
51 : kStartOfPrivateErrorCodes = kPListErrorLastErrorCode,
52 :
53 : kPListErrorFailedAndErrorHasBeenReported
54 : };
55 :
56 :
57 : #if PLIST_VERIFIER_DEBUG_DUMP_ENABLED
58 0 : static BOOL sDebugDump = NO;
59 :
60 0 : #define DebugDumpIndent() do { if (sDebugDump) OOLogIndent(); } while (0)
61 0 : #define DebugDumpOutdent() do { if (sDebugDump) OOLogOutdent(); } while (0)
62 0 : #define DebugDumpPushIndent() do { if (sDebugDump) OOLogPushIndent(); } while (0)
63 0 : #define DebugDumpPopIndent() do { if (sDebugDump) OOLogPopIndent(); } while (0)
64 0 : #define DebugDump(...) do { if (sDebugDump) OOLog(@"verifyOXP.verbose.plistDebugDump", __VA_ARGS__); } while (0)
65 : #else
66 : #define DebugDumpIndent() do { } while (0)
67 : #define DebugDumpOutdent() do { } while (0)
68 : #define DebugDumpPushIndent() do { } while (0)
69 : #define DebugDumpPopIndent() do { } while (0)
70 : #define DebugDump(...) do { } while (0)
71 : #endif
72 :
73 :
74 0 : NSString * const kOOPListSchemaVerifierErrorDomain = @"org.aegidian.oolite.OOPListSchemaVerifier.ErrorDomain";
75 :
76 0 : NSString * const kPListKeyPathErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier plist key path";
77 0 : NSString * const kSchemaKeyPathErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier schema key path";
78 :
79 0 : NSString * const kExpectedClassErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier expected class";
80 0 : NSString * const kExpectedClassNameErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier expected class name";
81 0 : NSString * const kUnknownKeyErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier unknown key";
82 0 : NSString * const kMissingRequiredKeysErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier missing required keys";
83 0 : NSString * const kMissingSubStringErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier missing substring";
84 0 : NSString * const kUnnownFilterErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier unknown filter";
85 0 : NSString * const kErrorsByOptionErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier errors by option";
86 :
87 0 : NSString * const kUnknownTypeErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier unknown type";
88 0 : NSString * const kUndefinedMacroErrorKey = @"org.aegidian.oolite.OOPListSchemaVerifier undefined macro";
89 :
90 :
91 0 : typedef enum
92 : {
93 : kTypeUnknown,
94 : kTypeString,
95 : kTypeArray,
96 : kTypeDictionary,
97 : kTypeInteger,
98 : kTypePositiveInteger,
99 : kTypeFloat,
100 : kTypePositiveFloat,
101 : kTypeOneOf,
102 : kTypeEnumeration,
103 : kTypeBoolean,
104 : kTypeFuzzyBoolean,
105 : kTypeVector,
106 : kTypeQuaternion,
107 : kTypeDelegatedType
108 : } SchemaType;
109 :
110 :
111 0 : typedef struct BackLinkChain BackLinkChain;
112 0 : struct BackLinkChain
113 : {
114 0 : BackLinkChain *link;
115 0 : id element;
116 : };
117 :
118 0 : OOINLINE BackLinkChain BackLink(BackLinkChain *link, id element)
119 : {
120 : BackLinkChain result = { link, element };
121 : return result;
122 : }
123 :
124 0 : OOINLINE BackLinkChain BackLinkIndex(BackLinkChain *link, NSUInteger index)
125 : {
126 : BackLinkChain result = { link, [NSNumber numberWithInteger:index] };
127 : return result;
128 : }
129 :
130 0 : OOINLINE BackLinkChain BackLinkRoot(void)
131 : {
132 : BackLinkChain result = { NULL, NULL };
133 : return result;
134 : }
135 :
136 :
137 : static SchemaType StringToSchemaType(NSString *string, NSError **outError);
138 : static NSString *ApplyStringFilter(NSString *string, id filterSpec, BackLinkChain keyPath, NSError **outError);
139 : static BOOL ApplyStringTest(NSString *string, id test, SEL testSelector, NSString *testDescription, BackLinkChain keyPath, NSError **outError);
140 : static NSArray *KeyPathToArray(BackLinkChain keyPath);
141 : static NSString *KeyPathToString(BackLinkChain keyPath);
142 : static NSString *StringForErrorReport(NSString *string);
143 : static NSString *ArrayForErrorReport(NSArray *array);
144 : static NSString *SetForErrorReport(NSSet *set);
145 : static NSString *StringOrArrayForErrorReport(id value, NSString *arrayPrefix);
146 :
147 : static NSError *Error(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSString *format, ...);
148 : static NSError *ErrorWithProperty(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSString *propKey, id propValue, NSString *format, ...);
149 : static NSError *ErrorWithDictionary(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSDictionary *dict, NSString *format, ...);
150 : static NSError *ErrorWithDictionaryAndArguments(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSDictionary *dict, NSString *format, va_list arguments);
151 :
152 : static NSError *ErrorTypeMismatch(Class expectedClass, NSString *expectedClassName, id actualObject, BackLinkChain keyPath);
153 : static NSError *ErrorFailureAlreadyReported(void);
154 : static BOOL IsFailureAlreadyReportedError(NSError *error);
155 :
156 :
157 : @interface OOPListSchemaVerifier (OOPrivate)
158 :
159 : // Call delegate methods.
160 0 : - (BOOL)delegateVerifierWithPropertyList:(id)rootPList
161 : named:(NSString *)name
162 : testProperty:(id)subPList
163 : atPath:(BackLinkChain)keyPath
164 : againstType:(NSString *)typeKey
165 : error:(NSError **)outError;
166 :
167 0 : - (BOOL)delegateVerifierWithPropertyList:(id)rootPList
168 : named:(NSString *)name
169 : failedForProperty:(id)subPList
170 : withError:(NSError *)error
171 : expectedType:(NSDictionary *)localSchema;
172 :
173 0 : - (BOOL)verifyPList:(id)rootPList
174 : named:(NSString *)name
175 : subProperty:(id)subProperty
176 : againstSchemaType:(id)subSchema
177 : atPath:(BackLinkChain)keyPath
178 : tentative:(BOOL)tentative
179 : error:(NSError **)outError
180 : stop:(BOOL *)outStop;
181 :
182 0 : - (NSDictionary *)resolveSchemaType:(id)specifier
183 : atPath:(BackLinkChain)keyPath
184 : error:(NSError **)outError;
185 :
186 : @end
187 :
188 :
189 : @interface NSString (OOPListSchemaVerifierHelpers)
190 :
191 0 : - (BOOL)ooPListVerifierHasSubString:(NSString *)string;
192 :
193 : @end
194 :
195 :
196 0 : #define VERIFY_PROTO(T) static NSError *Verify_##T(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
197 0 : VERIFY_PROTO(String);
198 0 : VERIFY_PROTO(Array);
199 0 : VERIFY_PROTO(Dictionary);
200 0 : VERIFY_PROTO(Integer);
201 0 : VERIFY_PROTO(PositiveInteger);
202 0 : VERIFY_PROTO(Float);
203 0 : VERIFY_PROTO(PositiveFloat);
204 0 : VERIFY_PROTO(OneOf);
205 0 : VERIFY_PROTO(Enumeration);
206 0 : VERIFY_PROTO(Boolean);
207 0 : VERIFY_PROTO(FuzzyBoolean);
208 0 : VERIFY_PROTO(Vector);
209 0 : VERIFY_PROTO(Quaternion);
210 0 : VERIFY_PROTO(DelegatedType);
211 :
212 :
213 : @implementation OOPListSchemaVerifier
214 :
215 : + (id)verifierWithSchema:(NSDictionary *)schema
216 : {
217 : return [[[self alloc] initWithSchema:schema] autorelease];
218 : }
219 :
220 :
221 : - (id)initWithSchema:(NSDictionary *)schema
222 : {
223 : self = [super init];
224 : if (self != nil)
225 : {
226 : _schema = [schema retain];
227 : _definitions = [[_schema oo_dictionaryForKey:@"$definitions"] retain];
228 : sDebugDump = [[NSUserDefaults standardUserDefaults] boolForKey:@"plist-schema-verifier-dump-structure"];
229 : if (sDebugDump) OOLogSetDisplayMessagesInClass(@"verifyOXP.verbose.plistDebugDump", YES);
230 :
231 : if (_schema == nil)
232 : {
233 : [self release];
234 : self = nil;
235 : }
236 : }
237 :
238 : return self;
239 : }
240 :
241 :
242 0 : - (void)dealloc
243 : {
244 : [_schema release];
245 : [_definitions release];
246 :
247 : [super dealloc];
248 : }
249 :
250 :
251 : - (void)setDelegate:(id)delegate
252 : {
253 : if (_delegate != delegate)
254 : {
255 : _delegate = delegate;
256 : _badDelegateWarning = NO;
257 : }
258 : }
259 :
260 :
261 : - (id)delegate
262 : {
263 : return _delegate;
264 : }
265 :
266 :
267 : - (BOOL)verifyPropertyList:(id)plist named:(NSString *)name
268 : {
269 : BOOL OK;
270 : BOOL stop = NO;
271 :
272 : OK = [self verifyPList:plist
273 : named:name
274 : subProperty:plist
275 : againstSchemaType:_schema
276 : atPath:BackLinkRoot()
277 : tentative:NO
278 : error:NULL
279 : stop:&stop];
280 :
281 : return OK;
282 : }
283 :
284 :
285 : + (NSString *)descriptionForKeyPath:(NSArray *)keyPath
286 : {
287 : NSMutableString *result = nil;
288 : NSEnumerator *componentEnum = nil;
289 : id component = nil;
290 : BOOL first = YES;
291 :
292 : result = [NSMutableString string];
293 :
294 : for (componentEnum = [keyPath objectEnumerator]; (component = [componentEnum nextObject]); )
295 : {
296 : if ([component isKindOfClass:[NSNumber class]])
297 : {
298 : [result appendFormat:@"[%@]", component];
299 : }
300 : else if ([component isKindOfClass:[NSString class]])
301 : {
302 : if (!first) [result appendString:@"."];
303 : [result appendString:component];
304 : }
305 : else return nil;
306 : first = NO;
307 : }
308 :
309 : if (first)
310 : {
311 : // Empty path
312 : return @"root";
313 : }
314 :
315 : return result;
316 : }
317 :
318 : @end
319 :
320 :
321 : @implementation OOPListSchemaVerifier (OOPrivate)
322 :
323 : - (BOOL)delegateVerifierWithPropertyList:(id)rootPList
324 : named:(NSString *)name
325 : testProperty:(id)subPList
326 : atPath:(BackLinkChain)keyPath
327 : againstType:(NSString *)typeKey
328 : error:(NSError **)outError
329 : {
330 : BOOL result;
331 : NSError *error = nil;
332 :
333 : if ([_delegate respondsToSelector:@selector(verifier:withPropertyList:named:testProperty:atPath:againstType:error:)])
334 : {
335 : @try
336 : {
337 : result = [_delegate verifier:self
338 : withPropertyList:rootPList
339 : named:name
340 : testProperty:subPList
341 : atPath:KeyPathToArray(keyPath)
342 : againstType:typeKey
343 : error:&error];
344 : }
345 : @catch (NSException *exception)
346 : {
347 : OOLog(@"plistVerifier.delegateException", @"Property list schema verifier: delegate threw exception (%@) in -verifier:withPropertyList:named:testProperty:atPath:againstType: for type \"%@\" at %@ in %@ -- treating as failure.", [exception name], typeKey,KeyPathToString(keyPath), name);
348 : result = NO;
349 : error = nil;
350 : }
351 :
352 : if (outError != NULL)
353 : {
354 : if (!result || error != nil)
355 : {
356 : // Note: Generates an error if delegate returned NO (meaning stop) or if delegate produced an error but did not request a stop.
357 : *outError = ErrorWithProperty(kPListDelegatedTypeError, &keyPath, NSUnderlyingErrorKey, error, @"Value at %@ does not match delegated type \"%@\".", KeyPathToString(keyPath), typeKey);
358 : }
359 : else *outError = nil;
360 : }
361 : }
362 : else
363 : {
364 : if (!_badDelegateWarning)
365 : {
366 : OOLog(@"plistVerifier.badDelegate", @"%@", @"Property list schema verifier: delegate does not handle delegated types.");
367 : _badDelegateWarning = YES;
368 : }
369 : result = YES;
370 : }
371 :
372 : return result;
373 : }
374 :
375 :
376 : - (BOOL)delegateVerifierWithPropertyList:(id)rootPList
377 : named:(NSString *)name
378 : failedForProperty:(id)subPList
379 : withError:(NSError *)error
380 : expectedType:(NSDictionary *)localSchema
381 : {
382 : BOOL result;
383 :
384 : if ([_delegate respondsToSelector:@selector(verifier:withPropertyList:named:failedForProperty:withError:expectedType:)])
385 : {
386 : @try
387 : {
388 : result = [_delegate verifier:self
389 : withPropertyList:rootPList
390 : named:name
391 : failedForProperty:subPList
392 : withError:error
393 : expectedType:localSchema];
394 : }
395 : @catch (NSException *exception)
396 : {
397 : OOLog(@"plistVerifier.delegateException", @"Property list schema verifier: delegate threw exception (%@) in -verifier:withPropertyList:named:failedForProperty:atPath:expectedType: at %@ in %@ -- stopping.", [exception name], [error plistKeyPathDescription], name);
398 : result = NO;
399 : }
400 : }
401 : else
402 : {
403 : OOLog(@"plistVerifier.failed", @"Verification of property list \"%@\" failed at %@: %@", name, [error plistKeyPathDescription], [error localizedFailureReason]);
404 : result = NO;
405 : }
406 : return result;
407 : }
408 :
409 :
410 : - (BOOL)verifyPList:(id)rootPList
411 : named:(NSString *)name
412 : subProperty:(id)subProperty
413 : againstSchemaType:(id)subSchema
414 : atPath:(BackLinkChain)keyPath
415 : tentative:(BOOL)tentative
416 : error:(NSError **)outError
417 : stop:(BOOL *)outStop
418 : {
419 : SchemaType type = kTypeUnknown;
420 : NSError *error = nil;
421 : NSDictionary *resolvedSpecifier = nil;
422 : NSAutoreleasePool *pool = nil;
423 :
424 : assert(outStop != NULL);
425 :
426 : pool = [[NSAutoreleasePool alloc] init];
427 :
428 : DebugDumpPushIndent();
429 :
430 : @try
431 : {
432 : DebugDumpIndent();
433 :
434 : resolvedSpecifier = [self resolveSchemaType:subSchema atPath:keyPath error:&error];
435 : if (resolvedSpecifier != nil) type = StringToSchemaType([resolvedSpecifier objectForKey:@"type"], &error);
436 :
437 0 : #define VERIFY_CASE(T) case kType##T: error = Verify_##T(self, subProperty, resolvedSpecifier, rootPList, name, keyPath, tentative, outStop); break;
438 :
439 : switch (type)
440 : {
441 : VERIFY_CASE(String);
442 : VERIFY_CASE(Array);
443 : VERIFY_CASE(Dictionary);
444 : VERIFY_CASE(Integer);
445 : VERIFY_CASE(PositiveInteger);
446 : VERIFY_CASE(Float);
447 : VERIFY_CASE(PositiveFloat);
448 : VERIFY_CASE(OneOf);
449 : VERIFY_CASE(Enumeration);
450 : VERIFY_CASE(Boolean);
451 : VERIFY_CASE(FuzzyBoolean);
452 : VERIFY_CASE(Vector);
453 : VERIFY_CASE(Quaternion);
454 : VERIFY_CASE(DelegatedType);
455 :
456 : case kTypeUnknown:
457 : // resolveSchemaType:... or StringToSchemaType() should have provided an error.
458 : *outStop = YES;
459 : }
460 : }
461 : @catch (NSException *exception)
462 : {
463 : error = Error(kPListErrorInternal, (BackLinkChain *)&keyPath, @"Uncaught exception %@: %@ in plist verifier for \"%@\" at %@.", [exception name], [exception reason], name, KeyPathToString(keyPath));
464 : }
465 :
466 : DebugDumpPopIndent();
467 :
468 : if (error != nil)
469 : {
470 : if (!tentative && !IsFailureAlreadyReportedError(error))
471 : {
472 : *outStop = ![self delegateVerifierWithPropertyList:rootPList
473 : named:name
474 : failedForProperty:subProperty
475 : withError:error
476 : expectedType:subSchema];
477 : }
478 : else if (tentative) *outStop = YES;
479 : }
480 :
481 : if (outError != NULL && error != nil)
482 : {
483 : *outError = [error retain];
484 : [pool release];
485 : [error autorelease];
486 : }
487 : else
488 : {
489 : [pool release];
490 : }
491 :
492 : return error == nil;
493 : }
494 :
495 :
496 : - (NSDictionary *)resolveSchemaType:(id)specifier
497 : atPath:(BackLinkChain)keyPath
498 : error:(NSError **)outError
499 : {
500 : id typeVal = nil;
501 : NSString *complaint = nil;
502 :
503 : assert(outError != NULL);
504 :
505 : if (![specifier isKindOfClass:[NSString class]] && ![specifier isKindOfClass:[NSDictionary class]]) goto BAD_TYPE;
506 :
507 : for (;;)
508 : {
509 : if ([specifier isKindOfClass:[NSString class]]) specifier = [NSDictionary dictionaryWithObject:specifier forKey:@"type"];
510 : typeVal = [(NSDictionary *)specifier objectForKey:@"type"];
511 :
512 : if ([typeVal isKindOfClass:[NSString class]])
513 : {
514 : if ([typeVal hasPrefix:@"$"])
515 : {
516 : // Macro reference; look it up in $definitions
517 : specifier = [_definitions objectForKey:typeVal];
518 : if (specifier == nil)
519 : {
520 : *outError = ErrorWithProperty(kPListErrorSchemaUndefiniedMacroReference, &keyPath, kUndefinedMacroErrorKey, typeVal, @"Bad schema: reference to undefined macro \"%@\".", StringForErrorReport(typeVal));
521 : return nil;
522 : }
523 : }
524 : else
525 : {
526 : // Non-macro string
527 : return specifier;
528 : }
529 : }
530 : else if ([typeVal isKindOfClass:[NSDictionary class]])
531 : {
532 : specifier = typeVal;
533 : }
534 : else
535 : {
536 : goto BAD_TYPE;
537 : }
538 : }
539 :
540 : BAD_TYPE:
541 : // Error: bad type
542 : if (typeVal == nil) complaint = @"no type specified";
543 : else complaint = @"not string or dictionary";
544 :
545 : *outError = Error(kPListErrorSchemaBadTypeSpecifier, &keyPath, @"Bad schema: invalid type specifier for path %@ (%@).", KeyPathToString(keyPath), complaint);
546 : return nil;
547 : }
548 :
549 : @end
550 :
551 :
552 0 : static SchemaType StringToSchemaType(NSString *string, NSError **outError)
553 : {
554 : static NSDictionary *typeMap = nil;
555 : SchemaType result;
556 :
557 : if (typeMap == nil)
558 : {
559 : typeMap =
560 : [[NSDictionary dictionaryWithObjectsAndKeys:
561 : [NSNumber numberWithUnsignedInt:kTypeString], @"string",
562 : [NSNumber numberWithUnsignedInt:kTypeArray], @"array",
563 : [NSNumber numberWithUnsignedInt:kTypeDictionary], @"dictionary",
564 : [NSNumber numberWithUnsignedInt:kTypeInteger], @"integer",
565 : [NSNumber numberWithUnsignedInt:kTypePositiveInteger], @"positiveInteger",
566 : [NSNumber numberWithUnsignedInt:kTypeFloat], @"float",
567 : [NSNumber numberWithUnsignedInt:kTypePositiveFloat], @"positiveFloat",
568 : [NSNumber numberWithUnsignedInt:kTypeOneOf], @"oneOf",
569 : [NSNumber numberWithUnsignedInt:kTypeEnumeration], @"enumeration",
570 : [NSNumber numberWithUnsignedInt:kTypeBoolean], @"boolean",
571 : [NSNumber numberWithUnsignedInt:kTypeFuzzyBoolean], @"fuzzyBoolean",
572 : [NSNumber numberWithUnsignedInt:kTypeVector], @"vector",
573 : [NSNumber numberWithUnsignedInt:kTypeQuaternion], @"quaternion",
574 : [NSNumber numberWithUnsignedInt:kTypeDelegatedType], @"delegatedType",
575 : nil
576 : ] retain];
577 : }
578 :
579 : result = [[typeMap objectForKey:string] unsignedIntValue];
580 : if (result == kTypeUnknown && outError != NULL)
581 : {
582 : if ([string hasPrefix:@"$"])
583 : {
584 : *outError = ErrorWithProperty(kPListErrorSchemaUnknownType, NULL, kUnknownTypeErrorKey, string, @"Bad schema: unresolved macro reference \"%@\".", string);
585 : }
586 : else
587 : {
588 : *outError = ErrorWithProperty(kPListErrorSchemaUnknownType, NULL, kUnknownTypeErrorKey, string, @"Bad schema: unknown type \"%@\".", string);
589 : }
590 : }
591 :
592 : return result;
593 : }
594 :
595 :
596 0 : static NSString *ApplyStringFilter(NSString *string, id filterSpec, BackLinkChain keyPath, NSError **outError)
597 : {
598 : NSEnumerator *filterEnum = nil;
599 : id filter = nil;
600 : NSRange range;
601 :
602 : assert(outError != NULL);
603 :
604 : if (filterSpec == nil) return string;
605 :
606 : if ([filterSpec isKindOfClass:[NSString class]])
607 : {
608 : filterSpec = [NSArray arrayWithObject:filterSpec];
609 : }
610 : if ([filterSpec isKindOfClass:[NSArray class]])
611 : {
612 : for (filterEnum = [filterSpec objectEnumerator]; (filter = [filterEnum nextObject]); )
613 : {
614 : if ([filter isKindOfClass:[NSString class]])
615 : {
616 : if ([filter isEqual:@"lowerCase"]) string = [string lowercaseString];
617 : else if ([filter isEqual:@"upperCase"]) string = [string uppercaseString];
618 : else if ([filter isEqual:@"capitalized"]) string = [string capitalizedString];
619 : else if ([filter hasPrefix:@"truncFront:"])
620 : {
621 : string = [string substringToIndex:[[filter substringFromIndex:11] intValue]];
622 : }
623 : else if ([filter hasPrefix:@"truncBack:"])
624 : {
625 : string = [string substringToIndex:[[filter substringFromIndex:10] intValue]];
626 : }
627 : else if ([filter hasPrefix:@"subStringTo:"])
628 : {
629 : range = [string rangeOfString:[filter substringFromIndex:12]];
630 : if (range.location != NSNotFound)
631 : {
632 : string = [string substringToIndex:range.location];
633 : }
634 : }
635 : else if ([filter hasPrefix:@"subStringFrom:"])
636 : {
637 : range = [string rangeOfString:[filter substringFromIndex:14]];
638 : if (range.location != NSNotFound)
639 : {
640 : string = [string substringFromIndex:range.location + range.length];
641 : }
642 : }
643 : else if ([filter hasPrefix:@"subStringToInclusive:"])
644 : {
645 : range = [string rangeOfString:[filter substringFromIndex:21]];
646 : if (range.location != NSNotFound)
647 : {
648 : string = [string substringToIndex:range.location + range.length];
649 : }
650 : }
651 : else if ([filter hasPrefix:@"subStringFromInclusive:"])
652 : {
653 : range = [string rangeOfString:[filter substringFromIndex:23]];
654 : if (range.location != NSNotFound)
655 : {
656 : string = [string substringFromIndex:range.location];
657 : }
658 : }
659 : else
660 : {
661 : *outError = ErrorWithProperty(kPListErrorSchemaUnknownFilter, &keyPath, kUnnownFilterErrorKey, filter, @"Bad schema: unknown string filter specifier \"%@\".", filter);
662 : }
663 : }
664 : else
665 : {
666 : *outError = Error(kPListErrorSchemaUnknownFilter, &keyPath, @"Bad schema: filter specifier is not a string.");
667 : }
668 : }
669 : }
670 : else
671 : {
672 : *outError = Error(kPListErrorSchemaUnknownFilter, &keyPath, @"Bad schema: \"filter\" must be a string or an array.");
673 : }
674 :
675 : return string;
676 : }
677 :
678 :
679 0 : static BOOL ApplyStringTest(NSString *string, id test, SEL testSelector, NSString *testDescription, BackLinkChain keyPath, NSError **outError)
680 : {
681 : BOOL (*testIMP)(id, SEL, NSString *);
682 : NSEnumerator *testEnum = nil;
683 : id subTest = nil;
684 :
685 : assert(outError != NULL);
686 :
687 : if (test == nil) return YES;
688 :
689 : testIMP = (BOOL(*)(id, SEL, NSString *))[string methodForSelector:testSelector];
690 : if (testIMP == NULL)
691 : {
692 : *outError = Error(kPListErrorInternal, &keyPath, @"OOPListSchemaVerifier internal error: NSString does not respond to test selector %@.", NSStringFromSelector(testSelector));
693 : return NO;
694 : }
695 :
696 : if ([test isKindOfClass:[NSString class]])
697 : {
698 : test = [NSArray arrayWithObject:test];
699 : }
700 :
701 : if ([test isKindOfClass:[NSArray class]])
702 : {
703 : for (testEnum = [test objectEnumerator]; (subTest = [testEnum nextObject]); )
704 : {
705 : if ([subTest isKindOfClass:[NSString class]])
706 : {
707 : if (testIMP(string, testSelector, subTest)) return YES;
708 : }
709 : else
710 : {
711 : *outError = Error(kPListErrorSchemaBadComparator, &keyPath, @"Bad schema: required %@ is not a string.", testDescription);
712 : return NO;
713 : }
714 : }
715 : }
716 : else
717 : {
718 : *outError = Error(kPListErrorSchemaBadComparator, &keyPath, @"Bad schema: %@ requirement specification is not a string or array.", testDescription);
719 : }
720 : return NO;
721 : }
722 :
723 :
724 0 : static NSArray *KeyPathToArray(BackLinkChain keyPath)
725 : {
726 : NSMutableArray *result = nil;
727 : BackLinkChain *curr = NULL;
728 :
729 : result = [NSMutableArray array];
730 : for (curr = &keyPath; curr != NULL; curr = curr->link)
731 : {
732 : if (curr->element != nil) [result insertObject:curr->element atIndex:0];
733 : }
734 :
735 : return result;
736 : }
737 :
738 :
739 0 : static NSString *KeyPathToString(BackLinkChain keyPath)
740 : {
741 : return [OOPListSchemaVerifier descriptionForKeyPath:KeyPathToArray(keyPath)];
742 : }
743 :
744 :
745 0 : static NSString *StringForErrorReport(NSString *string)
746 : {
747 : id result = nil;
748 :
749 : if (kMaximumLengthForStringInErrorMessage < [string length])
750 : {
751 : string = [string substringToIndex:kMaximumLengthForStringInErrorMessage];
752 : }
753 : result = [NSMutableString stringWithString:string];
754 : [result replaceOccurrencesOfString:@"\t" withString:@" " options:0 range:NSMakeRange(0, [string length])];
755 : [result replaceOccurrencesOfString:@"\r\n" withString:@" \\ " options:0 range:NSMakeRange(0, [string length])];
756 : [result replaceOccurrencesOfString:@"\n" withString:@" \\ " options:0 range:NSMakeRange(0, [string length])];
757 : [result replaceOccurrencesOfString:@"\r" withString:@" \\ " options:0 range:NSMakeRange(0, [string length])];
758 :
759 : if (kMaximumLengthForStringInErrorMessage < [result length])
760 : {
761 : result = [result substringToIndex:kMaximumLengthForStringInErrorMessage - 3];
762 : result = [result stringByAppendingString:@"..."];
763 : }
764 :
765 : return result;
766 : }
767 :
768 :
769 0 : static NSString *ArrayForErrorReport(NSArray *array)
770 : {
771 : NSString *result = nil;
772 : NSString *string = nil;
773 : NSUInteger i, count;
774 : NSAutoreleasePool *pool = nil;
775 :
776 : count = [array count];
777 : if (count == 0) return @"( )";
778 :
779 : pool = [[NSAutoreleasePool alloc] init];
780 :
781 : result = [NSString stringWithFormat:@"(%@", [array objectAtIndex:0]];
782 :
783 : for (i = 1; i != count; ++i)
784 : {
785 : string = [result stringByAppendingFormat:@", %@", [array objectAtIndex:i]];
786 : if (kMaximumLengthForStringInErrorMessage < [string length])
787 : {
788 : result = [result stringByAppendingString:@", ..."];
789 : break;
790 : }
791 : result = string;
792 : }
793 :
794 : result = [result stringByAppendingString:@")"];
795 :
796 : [result retain];
797 : [pool release];
798 : return [result autorelease];
799 : }
800 :
801 :
802 0 : static NSString *SetForErrorReport(NSSet *set)
803 : {
804 : return ArrayForErrorReport([[set allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)]);
805 : }
806 :
807 :
808 0 : static NSString *StringOrArrayForErrorReport(id value, NSString *arrayPrefix)
809 : {
810 : if ([value isKindOfClass:[NSString class]])
811 : {
812 : return [NSString stringWithFormat:@"\"%@\"", StringForErrorReport(value)];
813 : }
814 :
815 : if (arrayPrefix == nil) arrayPrefix = @"";
816 : if ([value isKindOfClass:[NSArray class]])
817 : {
818 : return [arrayPrefix stringByAppendingString:ArrayForErrorReport(value)];
819 : }
820 : if ([value isKindOfClass:[NSSet class]])
821 : {
822 : return [arrayPrefix stringByAppendingString:SetForErrorReport(value)];
823 : }
824 : if (value == nil) return @"(null)";
825 : return @"<?>";
826 : }
827 :
828 :
829 : // Specific type verifiers
830 :
831 0 : #define REQUIRE_TYPE(CLASSNAME, NAMESTRING) do { \
832 : if (![value isKindOfClass:[CLASSNAME class]]) \
833 : { \
834 : return ErrorTypeMismatch([CLASSNAME class], NAMESTRING, value, keyPath); \
835 : } \
836 : } while (0)
837 :
838 0 : static NSError *Verify_String(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
839 : {
840 : NSString *filteredString = nil;
841 : id testValue = nil;
842 : NSUInteger length;
843 : NSUInteger lengthConstraint;
844 : NSError *error = nil;
845 :
846 : REQUIRE_TYPE(NSString, @"string");
847 :
848 : DebugDump(@"* string: \"%@\"", StringForErrorReport(value));
849 :
850 : // Apply filters
851 : filteredString = ApplyStringFilter(value, [params objectForKey:@"filter"], keyPath, &error);
852 : if (filteredString == nil) return error;
853 :
854 : // Apply substring requirements
855 : testValue = [params objectForKey:@"requiredPrefix"];
856 : if (testValue != nil)
857 : {
858 : if (!ApplyStringTest(filteredString, testValue, @selector(hasPrefix:), @"prefix", keyPath, &error))
859 : {
860 : if (error == nil) error = ErrorWithProperty(kPListErrorStringPrefixMissing, &keyPath, kMissingSubStringErrorKey, testValue, @"String \"%@\" does not have required %@ %@.", StringForErrorReport(value), @"prefix", StringOrArrayForErrorReport(testValue, @"in "));
861 : return error;
862 : }
863 : }
864 :
865 : testValue = [params objectForKey:@"requiredSuffix"];
866 : if (testValue != nil)
867 : {
868 : if (!ApplyStringTest(filteredString, testValue, @selector(hasSuffix:), @"suffix", keyPath, &error))
869 : {
870 : if (error == nil) error = ErrorWithProperty(kPListErrorStringSuffixMissing, &keyPath, kMissingSubStringErrorKey, testValue, @"String \"%@\" does not have required %@ %@.", StringForErrorReport(value), @"suffix", StringOrArrayForErrorReport(testValue, @"in "));
871 : return error;
872 : }
873 : }
874 :
875 : testValue = [params objectForKey:@"requiredSubString"];
876 : if (testValue != nil)
877 : {
878 : if (!ApplyStringTest(filteredString, testValue, @selector(ooPListVerifierHasSubString:), @"substring", keyPath, &error))
879 : {
880 : if (error == nil) error = ErrorWithProperty(kPListErrorStringSubstringMissing, &keyPath, kMissingSubStringErrorKey, testValue, @"String \"%@\" does not have required %@ %@.", StringForErrorReport(value), @"substring", StringOrArrayForErrorReport(testValue, @"in "));
881 : return error;
882 : }
883 : }
884 :
885 : // Apply length bounds.
886 : length = [filteredString length];
887 : lengthConstraint = [params oo_unsignedIntegerForKey:@"minLength"];
888 : if (length < lengthConstraint)
889 : {
890 : return Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"String \"%@\" is too short (%u bytes, minimum is %u).", StringForErrorReport(filteredString), length, lengthConstraint);
891 : }
892 :
893 : lengthConstraint = [params oo_unsignedIntegerForKey:@"maxLength" defaultValue:NSUIntegerMax];
894 : if (lengthConstraint < length)
895 : {
896 : return Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"String \"%@\" is too long (%u bytes, maximum is %u).", StringForErrorReport(filteredString), length, lengthConstraint);
897 : }
898 :
899 : // All tests passed.
900 : return nil;
901 : }
902 :
903 :
904 0 : static NSError *Verify_Array(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
905 : {
906 : id valueType = nil;
907 : BOOL OK = YES, stop = NO;
908 : NSUInteger i, count;
909 : id subProperty = nil;
910 : NSUInteger constraint;
911 :
912 : REQUIRE_TYPE(NSArray, @"array");
913 :
914 : DebugDump(@"%@", @"* array");
915 :
916 : // Apply count bounds.
917 : count = [value count];
918 : constraint = [params oo_unsignedIntegerForKey:@"minCount" defaultValue:0];
919 : if (count < constraint)
920 : {
921 : return Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"Array has too few members (%u, minimum is %u).", count, constraint);
922 : }
923 :
924 : constraint = [params oo_unsignedIntegerForKey:@"maxCount" defaultValue:NSUIntegerMax];
925 : if (constraint < count)
926 : {
927 : return Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"Array has too many members (%u, maximum is %u).", count, constraint);
928 : }
929 :
930 : // Test member objects.
931 : valueType = [params objectForKey:@"valueType"];
932 : if (valueType != nil)
933 : {
934 : for (i = 0; i != count; ++i)
935 : {
936 : subProperty = [value objectAtIndex:i];
937 :
938 : if (![verifier verifyPList:rootPList
939 : named:name
940 : subProperty:subProperty
941 : againstSchemaType:valueType
942 : atPath:BackLinkIndex(&keyPath, i)
943 : tentative:tentative
944 : error:NULL
945 : stop:&stop])
946 : {
947 : OK = NO;
948 : }
949 :
950 : if ((stop && !tentative) || (tentative && !OK)) break;
951 : }
952 : }
953 :
954 : *outStop = stop && !tentative;
955 :
956 : if (!OK) return ErrorFailureAlreadyReported();
957 : else return nil;
958 : }
959 :
960 :
961 0 : static NSError *Verify_Dictionary(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
962 : {
963 : NSDictionary *schema = nil;
964 : id valueType = nil,
965 : typeSpec = nil;
966 : NSEnumerator *keyEnum = nil;
967 : NSString *key = nil;
968 : id subProperty = nil;
969 : BOOL OK = YES, stop = NO, prematureExit = NO;
970 : BOOL allowOthers;
971 : NSMutableSet *requiredKeys = nil;
972 : NSArray *requiredKeyList = nil;
973 : NSUInteger count, constraint;
974 :
975 : REQUIRE_TYPE(NSDictionary, @"dictionary");
976 :
977 : DebugDump(@"%@", @"* dictionary");
978 :
979 : // Apply count bounds.
980 : count = [value count];
981 : constraint = [params oo_unsignedIntegerForKey:@"minCount" defaultValue:0];
982 : if (count < constraint)
983 : {
984 : return Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"Dictionary has too few pairs (%u, minimum is %u).", count, constraint);
985 : }
986 : constraint = [params oo_unsignedIntegerForKey:@"maxCount" defaultValue:NSUIntegerMax];
987 : if (constraint < count)
988 : {
989 : return Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"Dictionary has too manu pairs (%u, maximum is %u).", count, constraint);
990 : }
991 :
992 : // Get schema.
993 : schema = [params oo_dictionaryForKey:@"schema"];
994 : valueType = [params objectForKey:@"valueType"];
995 : allowOthers = [params oo_boolForKey:@"allowOthers" defaultValue:YES];
996 : requiredKeyList = [params oo_arrayForKey:@"requiredKeys"];
997 :
998 : // If these conditions are met, all members must pass:
999 : if (schema == nil && valueType == nil && requiredKeyList == nil && allowOthers) return nil;
1000 :
1001 : if (requiredKeyList != nil)
1002 : {
1003 : requiredKeys = [NSMutableSet setWithArray:requiredKeyList];
1004 : }
1005 :
1006 : DebugDumpIndent();
1007 :
1008 : // Test member objects.
1009 : for (keyEnum = [value keyEnumerator]; (key = [keyEnum nextObject]) && !stop; )
1010 : {
1011 : subProperty = [(NSDictionary *)value objectForKey:key];
1012 : typeSpec = [schema objectForKey:key];
1013 : if (typeSpec == nil) typeSpec = valueType;
1014 :
1015 : DebugDump(@"- \"%@\"", key);
1016 : DebugDumpIndent();
1017 :
1018 : if (typeSpec != nil)
1019 : {
1020 : if (![verifier verifyPList:rootPList
1021 : named:name
1022 : subProperty:subProperty
1023 : againstSchemaType:typeSpec
1024 : atPath:BackLink(&keyPath, key)
1025 : tentative:tentative
1026 : error:NULL
1027 : stop:&stop])
1028 : {
1029 : OK = NO;
1030 : }
1031 : }
1032 : else if (!allowOthers && ![requiredKeys containsObject:key] && [schema objectForKey:key] == nil)
1033 : {
1034 : // Report error now rather than returning it, since there may be several unknown keys.
1035 : if (!tentative)
1036 : {
1037 : NSError *error = ErrorWithProperty(kPListErrorDictionaryUnknownKey, &keyPath, kUnknownKeyErrorKey, key, @"Unpermitted key \"%@\" in dictionary.", StringForErrorReport(key));
1038 : stop = ![verifier delegateVerifierWithPropertyList:rootPList
1039 : named:name
1040 : failedForProperty:value
1041 : withError:error
1042 : expectedType:params];
1043 : }
1044 : OK = NO;
1045 : }
1046 :
1047 : DebugDumpOutdent();
1048 :
1049 : [requiredKeys removeObject:key];
1050 :
1051 : if ((stop && !tentative) || (tentative && !OK))
1052 : {
1053 : prematureExit = YES;
1054 : break;
1055 : }
1056 : }
1057 :
1058 : DebugDumpOutdent();
1059 :
1060 : // Check that all required keys were present.
1061 : if (!prematureExit && [requiredKeys count] != 0)
1062 : {
1063 : return ErrorWithProperty(kPListErrorDictionaryMissingRequiredKeys, &keyPath, kMissingRequiredKeysErrorKey, requiredKeys, @"Required keys %@ missing from dictionary.", SetForErrorReport(requiredKeys));
1064 : }
1065 :
1066 : *outStop = stop && !tentative;
1067 :
1068 : if (!OK) return ErrorFailureAlreadyReported();
1069 : else return nil;
1070 : }
1071 :
1072 :
1073 0 : static NSError *Verify_Integer(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
1074 : {
1075 : long long numericValue;
1076 : long long constraint;
1077 :
1078 : numericValue = OOLongLongFromObject(value, 0);
1079 :
1080 : DebugDump(@"* integer: %lli", numericValue);
1081 :
1082 : // Check basic parseability. If there's inequality here, the default value is being returned.
1083 : if (numericValue != OOLongLongFromObject(value, 1))
1084 : {
1085 : return ErrorTypeMismatch([NSNumber class], @"integer", value, keyPath);
1086 : }
1087 :
1088 : // Check constraints.
1089 : constraint = [params oo_longLongForKey:@"minimum" defaultValue:LLONG_MIN];
1090 : if (numericValue < constraint)
1091 : {
1092 : return Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"Number is too small (%lli, minimum is %lli).", numericValue, constraint);
1093 : }
1094 :
1095 : constraint = [params oo_longLongForKey:@"maximum" defaultValue:LLONG_MAX];
1096 : if (constraint < numericValue)
1097 : {
1098 : return Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"Number is too large (%lli, maximum is %lli).", numericValue, constraint);
1099 : }
1100 :
1101 : return nil;
1102 : }
1103 :
1104 :
1105 0 : static NSError *Verify_PositiveInteger(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
1106 : {
1107 : unsigned long long numericValue;
1108 : unsigned long long constraint;
1109 :
1110 : numericValue = OOUnsignedLongLongFromObject(value, 0);
1111 :
1112 : DebugDump(@"* positive integer: %llu", numericValue);
1113 :
1114 : // Check basic parseability. If there's inequality here, the default value is being returned.
1115 : if (numericValue != OOUnsignedLongLongFromObject(value, 1))
1116 : {
1117 : return ErrorTypeMismatch([NSNumber class], @"positive integer", value, keyPath);
1118 : }
1119 :
1120 : // Check constraints.
1121 : constraint = [params oo_unsignedLongLongForKey:@"minimum" defaultValue:0];
1122 : if (numericValue < constraint)
1123 : {
1124 : return Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"Number is too small (%llu, minimum is %llu).", numericValue, constraint);
1125 : }
1126 :
1127 : constraint = [params oo_unsignedLongLongForKey:@"maximum" defaultValue:ULLONG_MAX];
1128 : if (constraint < numericValue)
1129 : {
1130 : return Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"Number is too large (%llu, maximum is %llu).", numericValue, constraint);
1131 : }
1132 :
1133 : return nil;
1134 : }
1135 :
1136 :
1137 0 : static NSError *Verify_Float(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
1138 : {
1139 : double numericValue;
1140 : double constraint;
1141 :
1142 : numericValue = OODoubleFromObject(value, 0);
1143 :
1144 : DebugDump(@"* float: %g", numericValue);
1145 :
1146 : // Check basic parseability. If there's inequality here, the default value is being returned.
1147 : if (numericValue != OODoubleFromObject(value, 1))
1148 : {
1149 : return ErrorTypeMismatch([NSNumber class], @"number", value, keyPath);
1150 : }
1151 :
1152 : // Check constraints.
1153 : constraint = [params oo_doubleForKey:@"minimum" defaultValue:-INFINITY];
1154 : if (numericValue < constraint)
1155 : {
1156 : return Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"Number is too small (%g, minimum is %g).", numericValue, constraint);
1157 : }
1158 :
1159 : constraint = [params oo_doubleForKey:@"maximum" defaultValue:INFINITY];
1160 : if (constraint < numericValue)
1161 : {
1162 : return Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"Number is too large (%g, maximum is %g).", numericValue, constraint);
1163 : }
1164 :
1165 : return nil;
1166 : }
1167 :
1168 :
1169 0 : static NSError *Verify_PositiveFloat(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
1170 : {
1171 : double numericValue;
1172 : double constraint;
1173 :
1174 : numericValue = OODoubleFromObject(value, 0);
1175 :
1176 : DebugDump(@"* positive float: %g", numericValue);
1177 :
1178 : // Check basic parseability. If there's inequality here, the default value is being returned.
1179 : if (numericValue != OODoubleFromObject(value, 1))
1180 : {
1181 : return ErrorTypeMismatch([NSNumber class], @"positive number", value, keyPath);
1182 : }
1183 :
1184 : if (numericValue < 0)
1185 : {
1186 : return Error(kPListErrorNumberIsNegative, &keyPath, @"Expected non-negative number, found %g.", numericValue);
1187 : }
1188 :
1189 : // Check constraints.
1190 : constraint = [params oo_doubleForKey:@"minimum" defaultValue:0];
1191 : if (numericValue < constraint)
1192 : {
1193 : return Error(kPListErrorMinimumConstraintNotMet, &keyPath, @"Number is too small (%g, minimum is %g).", numericValue, constraint);
1194 : }
1195 :
1196 : constraint = [params oo_doubleForKey:@"maximum" defaultValue:INFINITY];
1197 : if (constraint < numericValue)
1198 : {
1199 : return Error(kPListErrorMaximumConstraintNotMet, &keyPath, @"Number is too large (%g, maximum is %g).", numericValue, constraint);
1200 : }
1201 :
1202 : return nil;
1203 : }
1204 :
1205 :
1206 0 : static NSError *Verify_OneOf(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
1207 : {
1208 : NSArray *options = nil;
1209 : BOOL OK = NO, stop = NO;
1210 : NSEnumerator *optionEnum = nil;
1211 : id option = nil;
1212 : NSError *error;
1213 : NSMutableDictionary *errors = nil;
1214 :
1215 : DebugDump(@"%@", @"* oneOf");
1216 :
1217 : options = [params oo_arrayForKey:@"options"];
1218 : if (options == nil)
1219 : {
1220 : *outStop = YES;
1221 : return Error(kPListErrorSchemaNoOneOfOptions, &keyPath, @"Bad schema: no options specified for oneOf type.");
1222 : }
1223 :
1224 : errors = [[NSMutableDictionary alloc] initWithCapacity:[options count]];
1225 :
1226 : for (optionEnum = [options objectEnumerator]; (option = [optionEnum nextObject]) ;)
1227 : {
1228 : if ([verifier verifyPList:rootPList
1229 : named:name
1230 : subProperty:value
1231 : againstSchemaType:option
1232 : atPath:keyPath
1233 : tentative:YES
1234 : error:&error
1235 : stop:&stop])
1236 : {
1237 : DebugDump(@"%@", @"> Match.");
1238 : OK = YES;
1239 : break;
1240 : }
1241 : [errors setObject:error forKey:option];
1242 : }
1243 :
1244 : if (!OK)
1245 : {
1246 : DebugDump(@"%@", @"! No match.");
1247 : return ErrorWithProperty(kPListErrorOneOfNoMatch, &keyPath, kErrorsByOptionErrorKey, [errors autorelease], @"No matching type rule could be found.");
1248 : }
1249 :
1250 : // Ignore stop in tentatives.
1251 : [errors release];
1252 : return nil;
1253 : }
1254 :
1255 :
1256 0 : static NSError *Verify_Enumeration(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
1257 : {
1258 : NSArray *values = nil;
1259 : NSString *filteredString = nil;
1260 : NSError *error = nil;
1261 :
1262 : DebugDump(@"%@", @"* enumeration");
1263 :
1264 : REQUIRE_TYPE(NSString, @"string");
1265 :
1266 : values = [params oo_arrayForKey:@"values"];
1267 : DebugDump(@" - \"%@\" in %@", StringForErrorReport(value), ArrayForErrorReport(values));
1268 :
1269 : if (values == nil)
1270 : {
1271 : *outStop = YES;
1272 : return Error(kPListErrorSchemaNoEnumerationValues, &keyPath, @"Bad schema: no options specified for oneOf type.");
1273 : }
1274 :
1275 : filteredString = ApplyStringFilter(value, [params objectForKey:@"filter"], keyPath, &error);
1276 : if (filteredString == nil) return error;
1277 :
1278 : if ([values containsObject:filteredString]) return nil;
1279 :
1280 : return Error(kPListErrorEnumerationBadValue, &keyPath, @"Value \"%@\" not recognized, should be one of %@.", StringForErrorReport(value), ArrayForErrorReport(values));
1281 : }
1282 :
1283 :
1284 0 : static NSError *Verify_Boolean(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
1285 : {
1286 : DebugDump(@"* boolean: %@", value);
1287 :
1288 : // Check basic parseability. If there's inequality here, the default value is being returned.
1289 : if (OOBooleanFromObject(value, 0) == OOBooleanFromObject(value, 1)) return nil;
1290 : else return ErrorTypeMismatch([NSNumber class], @"boolean", value, keyPath);
1291 : }
1292 :
1293 :
1294 0 : static NSError *Verify_FuzzyBoolean(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
1295 : {
1296 : DebugDump(@"* fuzzy boolean: %@", value);
1297 :
1298 : // Check basic parseability. If there's inequality here, the default value is being returned.
1299 : if (OODoubleFromObject(value, 0) == OODoubleFromObject(value, 1)) return nil;
1300 : else if (OOBooleanFromObject(value, 0) == OOBooleanFromObject(value, 1)) return nil;
1301 : else return ErrorTypeMismatch([NSNumber class], @"fuzzy boolean", value, keyPath);
1302 : }
1303 :
1304 :
1305 0 : static NSError *Verify_Vector(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
1306 : {
1307 : DebugDump(@"* vector: %@", value);
1308 :
1309 : // Check basic parseability. If there's inequality here, the default value is being returned.
1310 : if (vector_equal(OOVectorFromObject(value, kZeroVector), OOVectorFromObject(value, kBasisXVector))) return nil;
1311 : else return ErrorTypeMismatch(Nil, @"vector", value, keyPath);
1312 : }
1313 :
1314 :
1315 0 : static NSError *Verify_Quaternion(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
1316 : {
1317 : DebugDump(@"* quaternion: %@", value);
1318 :
1319 : // Check basic parseability. If there's inequality here, the default value is being returned.
1320 : if (quaternion_equal(OOQuaternionFromObject(value, kZeroQuaternion), OOQuaternionFromObject(value, kIdentityQuaternion))) return nil;
1321 : else return ErrorTypeMismatch(Nil, @"quaternion", value, keyPath);
1322 : }
1323 :
1324 :
1325 0 : static NSError *Verify_DelegatedType(OOPListSchemaVerifier *verifier, id value, NSDictionary *params, id rootPList, NSString *name, BackLinkChain keyPath, BOOL tentative, BOOL *outStop)
1326 : {
1327 : id baseType = nil;
1328 : NSString *key = nil;
1329 : BOOL stop = NO;
1330 : NSError *error = nil;
1331 :
1332 : DebugDump(@"* delegated type: %@", [params objectForKey:@"key"]);
1333 :
1334 : baseType = [params objectForKey:@"baseType"];
1335 : if (baseType != nil)
1336 : {
1337 : if (![verifier verifyPList:rootPList
1338 : named:name
1339 : subProperty:value
1340 : againstSchemaType:baseType
1341 : atPath:keyPath
1342 : tentative:tentative
1343 : error:NULL
1344 : stop:&stop])
1345 : {
1346 : *outStop = stop;
1347 : return nil;
1348 : }
1349 : }
1350 :
1351 : key = [params objectForKey:@"key"];
1352 : *outStop = ![verifier delegateVerifierWithPropertyList:rootPList
1353 : named:name
1354 : testProperty:value
1355 : atPath:keyPath
1356 : againstType:key
1357 : error:&error];
1358 : return error;
1359 : }
1360 :
1361 :
1362 : @implementation NSString (OOPListSchemaVerifierHelpers)
1363 :
1364 : - (BOOL)ooPListVerifierHasSubString:(NSString *)string
1365 : {
1366 : return [self rangeOfString:string].location != NSNotFound;
1367 : }
1368 :
1369 : @end
1370 :
1371 :
1372 : @implementation NSError (OOPListSchemaVerifierConveniences)
1373 :
1374 : - (NSArray *)plistKeyPath
1375 : {
1376 : return [[self userInfo] oo_arrayForKey:kPListKeyPathErrorKey];
1377 : }
1378 :
1379 :
1380 : - (NSString *)plistKeyPathDescription
1381 : {
1382 : return [OOPListSchemaVerifier descriptionForKeyPath:[self plistKeyPath]];
1383 : }
1384 :
1385 :
1386 : - (NSSet *)missingRequiredKeys
1387 : {
1388 : return [[self userInfo] oo_setForKey:kMissingRequiredKeysErrorKey];
1389 : }
1390 :
1391 :
1392 : - (Class)expectedClass
1393 : {
1394 : return [[self userInfo] objectForKey:kExpectedClassErrorKey];
1395 : }
1396 :
1397 :
1398 : - (NSString *)expectedClassName
1399 : {
1400 : NSString *result = [[self userInfo] objectForKey:kExpectedClassNameErrorKey];
1401 : if (result == nil) result = [[self expectedClass] description];
1402 : return result;
1403 : }
1404 :
1405 : @end
1406 :
1407 :
1408 0 : static NSError *Error(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSString *format, ...)
1409 : {
1410 : NSError *result = nil;
1411 : va_list args;
1412 :
1413 : va_start(args, format);
1414 : result = ErrorWithDictionaryAndArguments(errorCode, keyPath, nil, format, args);
1415 : va_end(args);
1416 :
1417 : return result;
1418 : }
1419 :
1420 :
1421 0 : static NSError *ErrorWithProperty(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSString *propKey, id propValue, NSString *format, ...)
1422 : {
1423 : NSError *result = nil;
1424 : va_list args;
1425 : NSDictionary *dict = nil;
1426 :
1427 : if (propKey != nil && propValue != nil)
1428 : {
1429 : dict = [NSDictionary dictionaryWithObject:propValue forKey:propKey];
1430 : }
1431 : va_start(args, format);
1432 : result = ErrorWithDictionaryAndArguments(errorCode, keyPath, dict, format, args);
1433 : va_end(args);
1434 :
1435 : return result;
1436 : }
1437 :
1438 :
1439 0 : static NSError *ErrorWithDictionary(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSDictionary *dict, NSString *format, ...)
1440 : {
1441 : NSError *result = nil;
1442 : va_list args;
1443 :
1444 : va_start(args, format);
1445 : result = ErrorWithDictionaryAndArguments(errorCode, keyPath, dict, format, args);
1446 : va_end(args);
1447 :
1448 : return result;
1449 : }
1450 :
1451 :
1452 0 : static NSError *ErrorWithDictionaryAndArguments(OOPListSchemaVerifierErrorCode errorCode, BackLinkChain *keyPath, NSDictionary *dict, NSString *format, va_list arguments)
1453 : {
1454 : NSString *message = nil;
1455 : NSMutableDictionary *userInfo = nil;
1456 :
1457 : message = [[NSString alloc] initWithFormat:format arguments:arguments];
1458 :
1459 : userInfo = [NSMutableDictionary dictionaryWithDictionary:dict];
1460 : [userInfo setObject:message forKey:NSLocalizedFailureReasonErrorKey];
1461 : if (keyPath != NULL)
1462 : {
1463 : [userInfo setObject:KeyPathToArray(*keyPath) forKey:kPListKeyPathErrorKey];
1464 : }
1465 :
1466 : [message release];
1467 :
1468 : return [NSError errorWithDomain:kOOPListSchemaVerifierErrorDomain code:errorCode userInfo:userInfo];
1469 : }
1470 :
1471 :
1472 0 : static NSError *ErrorTypeMismatch(Class expectedClass, NSString *expectedClassName, id actualObject, BackLinkChain keyPath)
1473 : {
1474 : NSDictionary *dict = nil;
1475 : NSString *className = nil;
1476 :
1477 : if (expectedClassName == nil) expectedClassName = [expectedClass description];
1478 :
1479 : dict = [NSDictionary dictionaryWithObjectsAndKeys:
1480 : expectedClassName, kExpectedClassNameErrorKey,
1481 : expectedClass, kExpectedClassErrorKey,
1482 : nil];
1483 :
1484 : if (actualObject == nil) className = @"nothing";
1485 : else if ([actualObject isKindOfClass:[NSString class]]) className = @"string";
1486 : else if ([actualObject isKindOfClass:[NSNumber class]]) className = @"number";
1487 : else if ([actualObject isKindOfClass:[NSArray class]]) className = @"array";
1488 : else if ([actualObject isKindOfClass:[NSDictionary class]]) className = @"dictionary";
1489 : else if ([actualObject isKindOfClass:[NSData class]]) className = @"data";
1490 : else if ([actualObject isKindOfClass:[NSDate class]]) className = @"date";
1491 : else className = [[actualObject class] description];
1492 :
1493 : return ErrorWithDictionary(kPListErrorTypeMismatch, &keyPath, dict, @"Expected %@, found %@.", expectedClassName, className);
1494 : }
1495 :
1496 :
1497 0 : static NSError *ErrorFailureAlreadyReported(void)
1498 : {
1499 : return [NSError errorWithDomain:kOOPListSchemaVerifierErrorDomain code:kPListErrorFailedAndErrorHasBeenReported userInfo:nil];
1500 : }
1501 :
1502 :
1503 0 : static BOOL IsFailureAlreadyReportedError(NSError *error)
1504 : {
1505 : return [[error domain] isEqualToString:kOOPListSchemaVerifierErrorDomain] && [error code] == kPListErrorFailedAndErrorHasBeenReported;
1506 : }
1507 :
1508 : #endif // OO_OXP_VERIFIER_ENABLED
|