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