Oolite 1.91.0.7644-241112-7f5034b
Loading...
Searching...
No Matches
OOLegacyScriptWhitelist.m
Go to the documentation of this file.
1/*
2
3OOLegacyScriptWhitelist.m
4
5
6Oolite
7Copyright (C) 2004-2013 Giles C Williams and contributors
8
9This program is free software; you can redistribute it and/or
10modify it under the terms of the GNU General Public License
11as published by the Free Software Foundation; either version 2
12of the License, or (at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17GNU General Public License for more details.
18
19You should have received a copy of the GNU General Public License
20along with this program; if not, write to the Free Software
21Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22MA 02110-1301, USA.
23
24*/
25
26#import "OOCocoa.h"
28#import "OOStringParsing.h"
29#import "ResourceManager.h"
33#import "OODeepCopy.h"
34
35
36#define INCLUDE_RAW_STRING OOLITE_DEBUG // If nonzero, raw condition strings are included; if zero, a placeholder is used.
37
38
41{
43 NSString *key; // Dictionary key; nil for arrays.
44 NSUInteger index; // Array index if key is nil.
45};
46
47
48static NSArray *OOSanitizeLegacyScriptInternal(NSArray *script, SanStackElement *stack, BOOL allowAIMethods);
49static NSArray *OOSanitizeLegacyScriptConditionsInternal(NSArray *conditions, SanStackElement *stack);
50
51static NSArray *SanitizeCondition(NSString *condition, SanStackElement *stack);
52static NSArray *SanitizeConditionalStatement(NSDictionary *statement, SanStackElement *stack, BOOL allowAIMethods);
53static NSArray *SanitizeActionStatement(NSString *statement, SanStackElement *stack, BOOL allowAIMethods);
54static OOOperationType ClassifyLHSConditionSelector(NSString *selectorString, NSString **outSanitizedMethod, SanStackElement *stack);
55static NSString *SanitizeQueryMethod(NSString *selectorString); // Checks aliases and whitelist, returns nil if whitelist fails.
56static NSString *SanitizeActionMethod(NSString *selectorString, BOOL allowAIMethods); // Checks aliases and whitelist, returns nil if whitelist fails.
57static NSArray *AlwaysFalseConditions(void);
58static BOOL IsAlwaysFalseConditions(NSArray *conditions);
59
60static NSString *StringFromStack(SanStackElement *topOfStack);
61
62
63NSArray *OOSanitizeLegacyScript(NSArray *script, NSString *context, BOOL allowAIMethods)
64{
65 SanStackElement stackRoot = { NULL, context, 0 };
66 NSArray *result = OOSanitizeLegacyScriptInternal(script, &stackRoot, allowAIMethods);
67 return [OODeepCopy(result) autorelease];
68}
69
70
71static NSArray *OOSanitizeLegacyScriptInternal(NSArray *script, SanStackElement *stack, BOOL allowAIMethods)
72{
73 NSAutoreleasePool *pool = nil;
74 NSMutableArray *result = nil;
75 NSEnumerator *statementEnum = nil;
76 id statement = nil;
77 NSUInteger index = 0;
78
79 pool = [[NSAutoreleasePool alloc] init];
80
81 result = [NSMutableArray arrayWithCapacity:[script count]];
82
83 for (statementEnum = [script objectEnumerator]; (statement = [statementEnum nextObject]); )
84 {
85 SanStackElement subStack =
86 {
87 stack, nil, index++
88 };
89
90 if ([statement isKindOfClass:[NSDictionary class]])
91 {
92 statement = SanitizeConditionalStatement(statement, &subStack, allowAIMethods);
93 }
94 else if ([statement isKindOfClass:[NSString class]])
95 {
96 statement = SanitizeActionStatement(statement, &subStack, allowAIMethods);
97 }
98 else
99 {
100 OOLog(@"script.syntax.statement.invalidType", @"***** SCRIPT ERROR: in %@, statement is of invalid type - expected string or dictionary, got %@.", StringFromStack(stack), [statement class]);
101 statement = nil;
102 }
103
104 if (statement != nil)
105 {
106 [result addObject:statement];
107 }
108 }
109
110 [result retain];
111 [pool release];
112
113 return [result autorelease];
114}
115
116
117NSArray *OOSanitizeLegacyScriptConditions(NSArray *conditions, NSString *context)
118{
119 if (context == nil) context = @"<anonymous conditions>";
120 SanStackElement stackRoot = { NULL, context, 0 };
121 NSArray *result = OOSanitizeLegacyScriptConditionsInternal(conditions, &stackRoot);
122 return [OODeepCopy(result) autorelease];
123}
124
125
126static NSArray *OOSanitizeLegacyScriptConditionsInternal(NSArray *conditions, SanStackElement *stack)
127{
128 NSEnumerator *conditionEnum = nil;
129 NSString *condition = nil;
130 NSMutableArray *result = nil;
131 NSArray *tokens = nil;
132 BOOL OK = YES;
133 NSUInteger index = 0;
134
135 if (OOLegacyConditionsAreSanitized(conditions) || conditions == nil) return conditions;
136
137 result = [NSMutableArray arrayWithCapacity:[conditions count]];
138
139 for (conditionEnum = [conditions objectEnumerator]; (condition = [conditionEnum nextObject]); )
140 {
141 SanStackElement subStack =
142 {
143 stack, nil, index++
144 };
145
146 if (![condition isKindOfClass:[NSString class]])
147 {
148 OOLog(@"script.syntax.condition.notString", @"***** SCRIPT ERROR: in %@, bad condition - expected string, got %@; ignoring.", StringFromStack(stack), [condition class]);
149 OK = NO;
150 break;
151 }
152
153 tokens = SanitizeCondition(condition, &subStack);
154 if (tokens != nil)
155 {
156 [result addObject:tokens];
157 }
158 else
159 {
160 OK = NO;
161 break;
162 }
163 }
164
165 if (OK) return result;
166 else return AlwaysFalseConditions();
167}
168
169
170BOOL OOLegacyConditionsAreSanitized(NSArray *conditions)
171{
172 if ([conditions count] == 0) return YES; // Empty array is safe.
173 return [[conditions objectAtIndex:0] isKindOfClass:[NSArray class]];
174}
175
176
177static NSArray *SanitizeCondition(NSString *condition, SanStackElement *stack)
178{
179 NSArray *tokens = nil;
180 NSUInteger i, tokenCount;
181 OOOperationType opType;
182 NSString *selectorString = nil;
183 NSString *sanitizedSelectorString = nil;
184 NSString *comparatorString = nil;
185 OOComparisonType comparatorValue;
186 NSMutableArray *rhs = nil;
187 NSString *rhsItem = nil;
188 NSString *rhsSelector = nil;
189 NSArray *sanitizedRHSItem = nil;
190 NSString *stringSegment = nil;
191
192 tokens = ScanTokensFromString(condition);
193 tokenCount = [tokens count];
194
195 if (tokenCount < 1)
196 {
197 OOLog(@"script.debug.syntax.scriptCondition.noneSpecified", @"***** SCRIPT ERROR: in %@, empty script condition.", StringFromStack(stack));
198 return nil;
199 }
200
201 // Parse left-hand side.
202 selectorString = [tokens oo_stringAtIndex:0];
203 opType = ClassifyLHSConditionSelector(selectorString, &sanitizedSelectorString, stack);
204 if (opType >= OP_INVALID)
205 {
206 OOLog(@"script.unpermittedMethod", @"***** SCRIPT ERROR: in %@ (\"%@\"), method \"%@\" not allowed.", StringFromStack(stack), condition, selectorString);
207 return nil;
208 }
209
210 // Parse operator.
211 if (tokenCount > 1)
212 {
213 comparatorString = [tokens oo_stringAtIndex:1];
214 if ([comparatorString isEqualToString:@"equal"]) comparatorValue = COMPARISON_EQUAL;
215 else if ([comparatorString isEqualToString:@"notequal"]) comparatorValue = COMPARISON_NOTEQUAL;
216 else if ([comparatorString isEqualToString:@"lessthan"]) comparatorValue = COMPARISON_LESSTHAN;
217 else if ([comparatorString isEqualToString:@"greaterthan"]) comparatorValue = COMPARISON_GREATERTHAN;
218 else if ([comparatorString isEqualToString:@"morethan"]) comparatorValue = COMPARISON_GREATERTHAN;
219 else if ([comparatorString isEqualToString:@"oneof"]) comparatorValue = COMPARISON_ONEOF;
220 else if ([comparatorString isEqualToString:@"undefined"]) comparatorValue = COMPARISON_UNDEFINED;
221 else
222 {
223 OOLog(@"script.debug.syntax.badComparison", @"***** SCRIPT ERROR: in %@ (\"%@\"), unknown comparison operator \"%@\", will return NO.", StringFromStack(stack), condition, comparatorString);
224 return nil;
225 }
226 }
227 else
228 {
229 /* In the direct interpreter, having no operator resulted in an
230 implicit COMPARISON_NO operator, which always evaluated to false.
231 Returning NO here causes AlwaysFalseConditions() to be used, which
232 has the same effect.
233 */
234 OOLog(@"script.debug.syntax.noOperator", @"----- WARNING: SCRIPT in %@ -- No operator in expression \"%@\", will always evaluate as false.", StringFromStack(stack), condition);
235 return nil;
236 }
237
238 // Check for invalid opType/comparator combinations.
239 if (opType == OP_NUMBER && comparatorValue == COMPARISON_UNDEFINED)
240 {
241 OOLog(@"script.debug.syntax.invalidOperator", @"***** SCRIPT ERROR: in %@ (\"%@\"), comparison operator \"%@\" is not valid for %@.", StringFromStack(stack), condition, @"undefined", @"numbers");
242 return nil;
243 }
244 else if (opType == OP_BOOL)
245 {
246 switch (comparatorValue)
247 {
248 // Valid comparators
249 case COMPARISON_EQUAL:
251 break;
252
253 default:
254 OOLog(@"script.debug.syntax.invalidOperator", @"***** SCRIPT ERROR: in %@ (\"%@\"), comparison operator \"%@\" is not valid for %@.", StringFromStack(stack), condition, OOComparisonTypeToString(comparatorValue), @"booleans");
255 return nil;
256
257 }
258 }
259
260 /* Parse right-hand side. Each token is converted to an array of the
261 token and a boolean indicating whether it's a selector.
262
263 This also coalesces non-selector tokens, i.e. whitespace-separated
264 string segments.
265 */
266 if (tokenCount > 2)
267 {
268 rhs = [NSMutableArray arrayWithCapacity:tokenCount - 2];
269 for (i = 2; i < tokenCount; i++)
270 {
271 rhsItem = [tokens oo_stringAtIndex:i];
272 rhsSelector = SanitizeQueryMethod(rhsItem);
273 if (rhsSelector != nil)
274 {
275 // Method
276 if (stringSegment != nil)
277 {
278 // Add stringSegment as a literal token.
279 sanitizedRHSItem = [NSArray arrayWithObjects:[NSNumber numberWithBool:NO], stringSegment, nil];
280 [rhs addObject:sanitizedRHSItem];
281 stringSegment = nil;
282 }
283
284 sanitizedRHSItem = [NSArray arrayWithObjects:[NSNumber numberWithBool:YES], rhsSelector, nil];
285 [rhs addObject:sanitizedRHSItem];
286 }
287 else
288 {
289 // String; append to stringSegment
290 if (stringSegment == nil) stringSegment = rhsItem;
291 else stringSegment = [NSString stringWithFormat:@"%@ %@", stringSegment, rhsItem];
292 }
293 }
294
295 if (stringSegment != nil)
296 {
297 sanitizedRHSItem = [NSArray arrayWithObjects:[NSNumber numberWithBool:NO], stringSegment, nil];
298 [rhs addObject:sanitizedRHSItem];
299 }
300 }
301 else
302 {
303 rhs = [NSMutableArray array];
304 }
305
306 NSString *rawString = nil;
307#if INCLUDE_RAW_STRING
308 rawString = condition;
309#else
310 rawString = @"<condition>";
311#endif
312
313 return [NSArray arrayWithObjects:
314 [NSNumber numberWithUnsignedInt:opType],
315 rawString,
316 sanitizedSelectorString,
317 [NSNumber numberWithUnsignedInt:comparatorValue],
318 rhs,
319 nil];
320}
321
322
323static NSArray *SanitizeConditionalStatement(NSDictionary *statement, SanStackElement *stack, BOOL allowAIMethods)
324{
325 NSArray *conditions = nil;
326 NSArray *doActions = nil;
327 NSArray *elseActions = nil;
328
329 conditions = [statement oo_arrayForKey:@"conditions"];
330 if (conditions == nil)
331 {
332 OOLog(@"script.syntax.noConditions", @"***** SCRIPT ERROR: in %@, conditions array contains no \"conditions\" entry, ignoring.", StringFromStack(stack));
333 return nil;
334 }
335
336 // Sanitize conditions.
337 SanStackElement subStack = { stack, @"conditions", 0 };
338 conditions = OOSanitizeLegacyScriptConditionsInternal(conditions, &subStack);
339 if (conditions == nil)
340 {
341 return nil;
342 }
343
344 // Sanitize do and else.
345 if (!IsAlwaysFalseConditions(conditions)) doActions = [statement oo_arrayForKey:@"do"];
346 if (doActions != nil)
347 {
348 subStack.key = @"do";
349 doActions = OOSanitizeLegacyScriptInternal(doActions, &subStack, allowAIMethods);
350 }
351
352 elseActions = [statement oo_arrayForKey:@"else"];
353 if (elseActions != nil)
354 {
355 subStack.key = @"else";
356 elseActions = OOSanitizeLegacyScriptInternal(elseActions, &subStack, allowAIMethods);
357 }
358
359 // If neither does anything, the statment has no effect.
360 if ([doActions count] == 0 && [elseActions count] == 0)
361 {
362 return nil;
363 }
364
365 if (doActions == nil) doActions = [NSArray array];
366 if (elseActions == nil) elseActions = [NSArray array];
367
368 return [NSArray arrayWithObjects:[NSNumber numberWithBool:YES], conditions, doActions, elseActions, nil];
369}
370
371
372static NSArray *SanitizeActionStatement(NSString *statement, SanStackElement *stack, BOOL allowAIMethods)
373{
374 NSMutableArray *tokens = nil;
375 NSUInteger tokenCount;
376 NSString *rawSelectorString = nil;
377 NSString *selectorString = nil;
378 NSString *argument = nil;
379
380 tokens = ScanTokensFromString(statement);
381 tokenCount = [tokens count];
382 if (tokenCount == 0) return nil;
383
384 rawSelectorString = [tokens objectAtIndex:0];
385 selectorString = SanitizeActionMethod(rawSelectorString, allowAIMethods);
386 if (selectorString == nil)
387 {
388 OOLog(@"script.unpermittedMethod", @"***** SCRIPT ERROR: in %@ (\"%@\"), method \"%@\" not allowed.", StringFromStack(stack), statement, rawSelectorString);
389 return nil;
390 }
391
392 if ([selectorString isEqualToString:@"doNothing"])
393 {
394 return nil;
395 }
396
397 if ([selectorString hasSuffix:@":"])
398 {
399 // Expects an argument
400 if (tokenCount == 2)
401 {
402 argument = [tokens objectAtIndex:1];
403 }
404 else
405 {
406 [tokens removeObjectAtIndex:0];
407 argument = [tokens componentsJoinedByString:@" "];
408 }
409
410 argument = [argument stringByReplacingOccurrencesOfString:@"[credits_number]" withString:@"[_oo_legacy_credits_number]"];
411 }
412
413 return [NSArray arrayWithObjects:[NSNumber numberWithBool:NO], selectorString, argument, nil];
414}
415
416
417static OOOperationType ClassifyLHSConditionSelector(NSString *selectorString, NSString **outSanitizedSelector, SanStackElement *stack)
418{
419 assert(outSanitizedSelector != NULL);
420
421 *outSanitizedSelector = selectorString;
422
423 // Allow arbitrary mission_foo or local_foo pseudo-selectors.
424 if ([selectorString hasPrefix:@"mission_"]) return OP_MISSION_VAR;
425 if ([selectorString hasPrefix:@"local_"]) return OP_LOCAL_VAR;
426
427 // If it's a real method, check against whitelist.
428 *outSanitizedSelector = SanitizeQueryMethod(selectorString);
429 if (*outSanitizedSelector == nil)
430 {
431 return OP_INVALID;
432 }
433
434 // If it's a real method, and in the whitelist, classify by suffix.
435 if ([selectorString hasSuffix:@"_string"]) return OP_STRING;
436 if ([selectorString hasSuffix:@"_number"]) return OP_NUMBER;
437 if ([selectorString hasSuffix:@"_bool"]) return OP_BOOL;
438
439 // If we got here, something's wrong.
440 OOLog(@"script.sanitize.unclassifiedSelector", @"***** ERROR: Whitelisted query method \"%@\" has no type suffix, treating as invalid.", selectorString);
441 return OP_INVALID;
442}
443
444
445static NSString *SanitizeQueryMethod(NSString *selectorString)
446{
447 static NSSet *whitelist = nil;
448 static NSDictionary *aliases = nil;
449 NSString *aliasedSelector = nil;
450
451 if (whitelist == nil)
452 {
453 whitelist = [[NSSet alloc] initWithArray:[[ResourceManager whitelistDictionary] oo_arrayForKey:@"query_methods"]];
454 aliases = [[[ResourceManager whitelistDictionary] oo_dictionaryForKey:@"query_method_aliases"] retain];
455 }
456
457 aliasedSelector = [aliases oo_stringForKey:selectorString];
458 if (aliasedSelector != nil) selectorString = aliasedSelector;
459
460 if (![whitelist containsObject:selectorString]) selectorString = nil;
461
462 return selectorString;
463}
464
465
466static NSString *SanitizeActionMethod(NSString *selectorString, BOOL allowAIMethods)
467{
468 static NSSet *whitelist = nil;
469 static NSSet *whitelistWithAI = nil;
470 static NSDictionary *aliases = nil;
471 static NSDictionary *aliasesWithAI = nil;
472 NSString *aliasedSelector = nil;
473
474 if (whitelist == nil)
475 {
476 NSArray *actionMethods = nil;
477 NSArray *aiMethods = nil;
478 NSArray *aiAndActionMethods = nil;
479
480 actionMethods = [[ResourceManager whitelistDictionary] oo_arrayForKey:@"action_methods"];
481 aiMethods = [[ResourceManager whitelistDictionary] oo_arrayForKey:@"ai_methods"];
482 aiAndActionMethods = [[ResourceManager whitelistDictionary] oo_arrayForKey:@"ai_and_action_methods"];
483
484 if (actionMethods == nil) actionMethods = [NSArray array];
485 if (aiMethods == nil) aiMethods = [NSArray array];
486
487 if (aiAndActionMethods != nil) actionMethods = [actionMethods arrayByAddingObjectsFromArray:aiAndActionMethods];
488
489 whitelist = [[NSSet alloc] initWithArray:actionMethods];
490 whitelistWithAI = [[NSSet alloc] initWithArray:[aiMethods arrayByAddingObjectsFromArray:actionMethods]];
491
492 aliases = [[[ResourceManager whitelistDictionary] oo_dictionaryForKey:@"action_method_aliases"] retain];
493
494 aliasesWithAI = [[ResourceManager whitelistDictionary] oo_dictionaryForKey:@"ai_method_aliases"];
495 if (aliasesWithAI != nil)
496 {
497 aliasesWithAI = [[aliasesWithAI dictionaryByAddingEntriesFromDictionary:aliases] copy];
498 }
499 else
500 {
501 aliasesWithAI = [aliases copy];
502 }
503 }
504
505 aliasedSelector = [(allowAIMethods ? aliasesWithAI : aliases) oo_stringForKey:selectorString];
506 if (aliasedSelector != nil) selectorString = aliasedSelector;
507
508 if (![(allowAIMethods ? whitelistWithAI : whitelist) containsObject:selectorString]) selectorString = nil;
509
510 return selectorString;
511}
512
513
514// Return a conditions array that always evaluates as false.
515static NSArray *AlwaysFalseConditions(void)
516{
517 static NSArray *alwaysFalse = nil;
518 if (alwaysFalse != nil)
519 {
520 alwaysFalse = [NSArray arrayWithObject:[NSArray arrayWithObject:[NSNumber numberWithUnsignedInt:OP_FALSE]]];
521 [alwaysFalse retain];
522 }
523
524 return alwaysFalse;
525}
526
527
528static BOOL IsAlwaysFalseConditions(NSArray *conditions)
529{
530 return [[conditions oo_arrayAtIndex:0] oo_unsignedIntAtIndex:0] == OP_FALSE;
531}
532
533
534static NSMutableString *StringFromStackInternal(SanStackElement *topOfStack)
535{
536 if (topOfStack == NULL) return nil;
537
538 NSMutableString *base = StringFromStackInternal(topOfStack->back);
539 if (base == nil) base = [NSMutableString string];
540
541 NSString *string = topOfStack->key;
542 if (string == nil) string = [NSString stringWithFormat:@"%lu", (unsigned long)topOfStack->index];
543 if ([base length] > 0) [base appendString:@"."];
544
545 [base appendString:string];
546
547 return base;
548}
549
550
551static NSString *StringFromStack(SanStackElement *topOfStack)
552{
553 return StringFromStackInternal(topOfStack);
554}
static NSString * SanitizeActionMethod(NSString *selectorString, BOOL allowAIMethods)
static NSString * SanitizeQueryMethod(NSString *selectorString)
static NSString * StringFromStack(SanStackElement *topOfStack)
static NSArray * SanitizeConditionalStatement(NSDictionary *statement, SanStackElement *stack, BOOL allowAIMethods)
static OOOperationType ClassifyLHSConditionSelector(NSString *selectorString, NSString **outSanitizedMethod, SanStackElement *stack)
static NSMutableString * StringFromStackInternal(SanStackElement *topOfStack)
static NSArray * OOSanitizeLegacyScriptInternal(NSArray *script, SanStackElement *stack, BOOL allowAIMethods)
NSArray * OOSanitizeLegacyScriptConditions(NSArray *conditions, NSString *context)
static NSArray * OOSanitizeLegacyScriptConditionsInternal(NSArray *conditions, SanStackElement *stack)
static NSArray * SanitizeCondition(NSString *condition, SanStackElement *stack)
static NSArray * SanitizeActionStatement(NSString *statement, SanStackElement *stack, BOOL allowAIMethods)
static BOOL IsAlwaysFalseConditions(NSArray *conditions)
NSArray * OOSanitizeLegacyScript(NSArray *script, NSString *context, BOOL allowAIMethods)
BOOL OOLegacyConditionsAreSanitized(NSArray *conditions)
static NSArray * AlwaysFalseConditions(void)
#define OOLog(class, format,...)
Definition OOLogging.h:88
unsigned count
return nil
NSMutableArray * ScanTokensFromString(NSString *values)
NSString * OOComparisonTypeToString(OOComparisonType type) CONST_FUNC
NSDictionary * whitelistDictionary()
SanStackElement * back