]> err.no Git - sope/blob - sope-appserver/NGObjWeb/Templates/WOxElemBuilder.m
improved SOPE security exceptions
[sope] / sope-appserver / NGObjWeb / Templates / WOxElemBuilder.m
1 /*
2   Copyright (C) 2000-2004 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
6   OGo is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with OGo; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include <NGObjWeb/WOxElemBuilder.h>
23 #include <DOM/EDOM.h>
24 #include <SaxObjC/XMLNamespaces.h>
25 #include <NGObjWeb/WOApplication.h>
26 #include <NGObjWeb/WOElement.h>
27 #include <NGObjWeb/WOAssociation.h>
28 #include <NGObjWeb/WOComponentScript.h>
29 #include "WOComponentFault.h"
30 #include "common.h"
31
32 @interface WOElement(UsedPrivates)
33 - (id)initWithValue:(id)_value escapeHTML:(BOOL)_flag;
34 @end
35
36 @interface WOAssociation(misc)
37 - (id)initWithScript:(NSString *)_script language:(NSString *)_lang;
38 @end
39
40 @implementation WOxElemBuilderComponentInfo
41
42 - (id)initWithComponentId:(NSString *)_cid
43   componentName:(NSString *)_name
44   bindings:(NSMutableDictionary *)_bindings
45 {
46   self->cid      = [_cid copy];
47   self->pageName = [_name copy];
48   self->bindings = [_bindings retain];
49   return self;
50 }
51 - (void)dealloc {
52   [self->cid      release];
53   [self->pageName release];
54   [self->bindings release];
55   [super dealloc];
56 }
57
58 /* accessors */
59
60 - (NSString *)componentId {
61   return self->cid;
62 }
63
64 - (NSString *)pageName {
65   return self->pageName;
66 }
67
68 - (NSMutableDictionary *)bindings {
69   return self->bindings;
70 }
71
72 /* operations */
73
74 - (WOComponent *)instantiateWithResourceManager:(WOResourceManager *)_rm
75   languages:(NSArray *)_languages
76 {
77   static Class FaultClass = Nil;
78   WOComponentFault *fault;
79   
80   if (FaultClass == Nil)
81     FaultClass = [WOComponentFault class];
82   
83   fault = [FaultClass alloc];
84   NSAssert1(fault, @"couldn't allocated object of class '%@' ..", FaultClass);
85   
86   fault = [fault initWithResourceManager:_rm
87                  pageName:self->pageName
88                  languages:_languages
89                  bindings:self->bindings];
90   return (id)fault;
91 }
92
93 @end /* SxElementBuilderComponentInfo */
94
95 @implementation WOxElemBuilder
96
97 static Class        StrClass  = Nil;
98 static Class        AStrClass = Nil;
99 static NSDictionary *defaultAssocMap = nil;
100 static Class        ValAssoc = Nil;
101 static BOOL         logAssocMap      = NO;
102 static BOOL         logAssocCreation = NO;
103 static BOOL         debugOn          = NO;
104
105 + (int)version {
106   return 1;
107 }
108 + (void)initialize {
109   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
110   static BOOL didInit = NO;
111   if (didInit) return;
112   didInit = YES;
113   
114   StrClass = NSClassFromString(@"_WOSimpleStaticString");
115   if (StrClass == Nil)
116     NSLog(@"ERROR: missing class _WOSimpleStaticString !");
117   AStrClass = NSClassFromString(@"_WOSimpleStaticASCIIString");
118   if (AStrClass == Nil)
119     NSLog(@"ERROR: missing class _WOSimpleStaticASCIIString !");
120
121   logAssocMap = [ud boolForKey:@"WOxElemBuilder_LogAssociationMapping"];
122   logAssocCreation = 
123     [ud boolForKey:@"WOxElemBuilder_LogAssociationCreation"];
124   if (logAssocMap)      NSLog(@"Note: association mapping is logged!");
125   if (logAssocCreation) NSLog(@"Note: association creation is logged!");
126   
127   defaultAssocMap = [[ud dictionaryForKey:@"WOxAssociationClassMapping"] copy];
128   if (defaultAssocMap == nil)
129     NSLog(@"WARNING: WOxAssociationClassMapping default is not set!");
130   
131   if (ValAssoc == Nil)
132     ValAssoc = NSClassFromString(@"WOValueAssociation");
133 }
134
135 + (WOxElemBuilder *)createBuilderQueue:(NSArray *)_classNames {
136   unsigned       i, count;
137   WOxElemBuilder *first, *current = nil;
138   NSMutableArray *missingBuilders = nil;
139   
140   if ((count = [_classNames count]) == 0)
141     return nil;
142   
143   for (first = nil, i = 0; i < count; i++) {
144     WOxElemBuilder *nx;
145     NSString *cn;
146     Class    clazz;
147     
148     cn = [_classNames objectAtIndex:i];
149     
150     if ((clazz = NSClassFromString(cn)) == Nil) {
151       if (missingBuilders == nil) 
152         missingBuilders = [NSMutableArray arrayWithCapacity:16];
153       [missingBuilders addObject:cn];
154       continue;
155     }
156     
157     if ((nx = [[clazz alloc] init])) {
158       if (first == nil) {
159         first = current = nx;
160         [nx autorelease];
161       }
162       else {
163         [current setNextBuilder:nx];
164         current = [nx autorelease];
165       }
166     }
167     else {
168       NSLog(@"%s: couldn't allocate builder (class=%@)", cn);
169       continue;
170     }
171   }
172   
173   if (missingBuilders) {
174     NSLog(@"WOxElemBuilder: could not locate builders: %@", 
175           [missingBuilders componentsJoinedByString:@","]);
176   }
177   return first;
178 }
179
180 + (WOxElemBuilder *)createBuilderQueueV:(NSString *)_className, ... {
181   // TODO: reimplement using createBuilderQueue:
182   va_list       ap;
183   NSString      *cn;
184   WOxElemBuilder *first, *current;
185   
186   if (_className == nil)
187     return [[[self alloc] init] autorelease];
188     
189   first = [[[NSClassFromString(_className) alloc] init] autorelease];
190     
191   va_start(ap, _className);
192   for (current = first; (cn = va_arg(ap, id)); ) {
193     WOxElemBuilder *nx;
194
195     nx = [[NSClassFromString(cn) alloc] init];
196     [current setNextBuilder:nx];
197     current = [nx autorelease];
198   }
199   va_end(ap);
200     
201   return first;
202 }
203
204 - (void)dealloc {
205   [self->script            release];
206   [self->subcomponentInfos release];
207   [self->nsToAssoc         release];
208   [self->nextBuilder       release];
209   [super dealloc];
210 }
211
212 /* building an element (returns a retained object !!!) */
213
214 - (WOElement *)buildNode:(id<DOMNode>)_node templateBuilder:(id)_builder {
215   if (_node == nil)
216     return nil;
217
218   switch ([_node nodeType]) {
219     case DOM_ELEMENT_NODE:
220       return [self buildElement:(id<DOMElement>)_node
221                    templateBuilder:_builder];
222     case DOM_TEXT_NODE:
223       return [self buildText:(id<DOMText>)_node
224                    templateBuilder:_builder];
225     case DOM_CDATA_SECTION_NODE:
226       return [self buildCDATASection:(id<DOMCDATASection>)_node
227                    templateBuilder:_builder];
228     case DOM_COMMENT_NODE:
229       return [self buildComment:(id<DOMComment>)_node
230                    templateBuilder:_builder];
231     case DOM_DOCUMENT_NODE:
232       return [self buildDocument:(id<DOMDocument>)_node
233                    templateBuilder:_builder];
234       
235     default:
236       if (self->nextBuilder)
237         return [self->nextBuilder buildNode:_node templateBuilder:_builder];
238       else {
239         NSLog(@"unknown node type %i, node %@", [_node nodeType], _node);
240         return nil;
241       }
242   }
243 }
244
245 - (NSArray *)buildNodes:(id<DOMNodeList>)_nodes templateBuilder:(id)_bld {
246   // Note: returns a regular autoreleased array
247   NSMutableArray *children;
248   unsigned       i, count;
249   
250   if ((count = [_nodes length]) == 0)
251     return nil;
252   
253   children = [NSMutableArray arrayWithCapacity:(count + 1)];
254   
255   for (i = 0; i < count; i++) {
256     WOElement *e;
257
258     e = [_bld buildNode:[_nodes objectAtIndex:i] templateBuilder:_bld];
259     if (e) {
260       [children addObject:e];
261       [e release];
262     }
263   }
264   return children;
265 }
266
267 /* building methods specialized on type (return retained objects !!!) */
268
269 - (WOElement *)buildDocument:(id<DOMDocument>)_node templateBuilder:(id)_bld {
270   return [self buildElement:[_node documentElement] templateBuilder:_bld];
271 }
272
273 - (WOElement *)buildElement:(id<DOMElement>)_node templateBuilder:(id)_bld {
274   if (self->nextBuilder)
275     return [self->nextBuilder buildElement:_node templateBuilder:_bld];
276
277   [self logWithFormat:@"cannot build node %@ (template builder %@)",
278           _node, _bld];
279   return nil;
280 }
281
282 - (WOElement *)buildCharacterData:(id<DOMCharacterData>)_text
283   templateBuilder:(id)_builder
284 {
285   static Class ValClass = Nil;
286   WOElement *textElement;
287   unsigned len;
288   BOOL     isASCII = NO;
289   id       str;
290   
291   str = [_text data];
292   if ((len = [str length]) == 0) return nil;
293   
294   /* 
295      we use WOValueAssociation directly, because WOAssociation caches all
296      values
297   */
298   if (ValClass == Nil)
299     ValClass = NSClassFromString(@"WOValueAssociation");
300
301 #if 0
302 #  warning not using ASCII string !
303   isASCII = NO;
304 #else
305   if (len > 1) {
306     // TODO(perf): improve on that
307     /* not very efficient, but only used during template parsing ... */
308     if ([str dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:NO])
309       isASCII = YES;
310     else
311       isASCII = NO;
312   }
313   else {
314     isASCII = ([str characterAtIndex:0] < 128) ? YES : NO;
315   }
316 #endif
317   
318   str = [[ValClass alloc] initWithString:str];
319   textElement = 
320     [[(isASCII?AStrClass:StrClass) alloc] initWithValue:str escapeHTML:YES];
321   [str release];
322   return textElement;
323 }
324 - (WOElement *)buildText:(id<DOMText>)_node
325   templateBuilder:(id)_builder
326 {
327   return [self buildCharacterData:_node templateBuilder:_builder];
328 }
329 - (WOElement *)buildCDATASection:(id<DOMCDATASection>)_node
330   templateBuilder:(id)_builder
331 {
332   return [self buildCharacterData:_node templateBuilder:_builder];
333 }
334
335 - (WOElement *)buildComment:(id<DOMComment>)_node
336   templateBuilder:(id)_builder
337 {
338   /* comments aren't delivered ... */
339   return nil;
340 }
341
342 /* building the whole template */
343
344 - (WOElement *)buildTemplateFromDocument:(id<DOMDocument>)_document {
345   NSAutoreleasePool *pool;
346   WOElement *result;
347   
348   pool   = [[NSAutoreleasePool alloc] init];
349   result = [[self buildNode:_document templateBuilder:self] retain];
350   [pool release];
351   return [result autorelease];
352 }
353
354 /* association callbacks */
355
356 - (WOAssociation *)associationForValue:(id)_value {
357   return [WOAssociation associationWithValue:_value];
358 }
359
360 - (WOAssociation *)associationForKeyPath:(NSString *)_path {
361   return [WOAssociation associationWithKeyPath:_path];
362 }
363
364 - (WOAssociation *)associationForJavaScript:(NSString *)_js {
365   WOAssociation *assoc;
366   
367   assoc = [NSClassFromString(@"WOScriptAssociation") alloc];
368   assoc = [(id)assoc initWithScript:_js language:@"javascript"];
369   return [assoc autorelease];
370 }
371
372 - (WOAssociation *)associationForAttribute:(id<DOMAttr>)_attribute {
373   NSString      *nsuri;
374   NSString      *value;
375   WOAssociation *assoc;
376   Class c;
377   
378   nsuri = [_attribute namespaceURI];
379   value = [_attribute nodeValue];
380   
381   c = [self associationClassForNamespaceURI:[_attribute namespaceURI]];
382   if (c == Nil) {
383     [self logWithFormat:
384             @"WARNING, found no association class for "
385             @"attribute %@ (namespace=%@)",
386             _attribute, [_attribute namespaceURI]];
387     return nil;
388   }
389   if (logAssocMap) {
390     [self logWithFormat:@"use class %@ for namespaceURI %@ (attribute %@)",
391             c, [_attribute namespaceURI], [_attribute name]];
392   }
393   
394   assoc = [[c alloc] initWithString:value];
395   if (logAssocCreation) {
396     [self logWithFormat:@"created assoc %@ for attribute %@", 
397             assoc, [_attribute name]];
398   }
399   
400   return [assoc autorelease];
401 }
402
403 - (NSMutableDictionary *)associationsForAttributes:(id<DOMNamedNodeMap>)_attrs{
404   NSMutableDictionary *assocs;
405   int i, count;
406   
407   if ((count = [_attrs length]) == 0)
408     return nil;
409   
410   assocs = [NSMutableDictionary dictionaryWithCapacity:(count + 1)];
411
412   for (i = 0; i < count; i++) {
413     id<DOMAttr>   attr;
414     WOAssociation *assoc;
415
416     attr = [_attrs objectAtIndex:i];
417     
418     if ((assoc = [self associationForAttribute:attr])) {
419       NSString *key;
420       
421       key = [attr name];
422       if ([key characterAtIndex:0] == '_')
423         key = [@"?" stringByAppendingString:[key substringFromIndex:1]];
424       
425       [assocs setObject:assoc forKey:key];
426     }
427   }
428   return assocs;
429 }
430
431 - (void)_ensureDefaultAssocMappings {
432   NSEnumerator *e;
433   NSString     *ns;
434   
435   if (self->nsToAssoc) 
436     return;
437   
438   self->nsToAssoc = [[NSMutableDictionary alloc] initWithCapacity:8];
439   e = [defaultAssocMap keyEnumerator];
440   while ((ns = [e nextObject])) {
441     NSString *className;
442     Class    clazz;
443     
444     className = [defaultAssocMap objectForKey:ns];
445     clazz = NSClassFromString(className);
446     
447     if (clazz == Nil) {
448       [self logWithFormat:@"WARNING: did not find association class: '%@'",
449               className];
450       continue;
451     }
452     
453     /* register */
454     [self->nsToAssoc setObject:clazz forKey:ns];
455   }
456 }
457 - (void)registerAssociationClass:(Class)_class forNamespaceURI:(NSString *)_ns{
458   if (_ns    == nil) return;
459   if (_class == Nil) return;
460   
461   [self _ensureDefaultAssocMappings];
462   [self->nsToAssoc setObject:_class forKey:_ns];
463 }
464 - (Class)associationClassForNamespaceURI:(NSString *)_ns {
465   Class c;
466   
467   [self _ensureDefaultAssocMappings];
468   
469   if ((c = [self->nsToAssoc objectForKey:_ns]) == nil)
470     /* if we have no class mapped for a namespace, we treat it as a value */
471     c = ValAssoc;
472   
473   if (debugOn)
474     [self debugWithFormat:@"using class %@ for namespace %@", c, _ns];
475   return c;
476 }
477
478 /* creating unique IDs */
479
480 - (NSString *)uniqueIDForNode:(id)_node {
481   NSMutableArray  *nodePath;
482   NSMutableString *uid;
483   NSEnumerator    *topDown;
484   id   node, parent;
485   BOOL isFirst;
486
487   if (_node == nil) return nil;
488
489   nodePath = [NSMutableArray arrayWithCapacity:16];
490
491   /* collect all parent nodes in bottom-up form */
492
493   for (node = _node; node; node = [node parentNode])
494     [nodePath addObject:node];
495
496   /* generate ID */
497
498   uid     = [NSMutableString stringWithCapacity:64];
499   topDown = [nodePath reverseObjectEnumerator];
500   isFirst = YES;
501   parent  = nil;
502
503   for (isFirst = YES; (node = [topDown nextObject]); parent = node) {
504     if (!isFirst) {
505       NSArray  *children;
506       unsigned i, count;
507
508       [uid appendString:@"."];
509
510       /* determine index of _node */
511
512       children = (NSArray *)[parent childNodes];
513       for (i = 0, count = [children count]; i < count; i++) {
514         if ([children objectAtIndex:i] == node)
515           break;
516       }
517       [uid appendFormat:@"%d", i];
518     }
519     else {
520       [uid appendString:@"R"];
521       isFirst = NO;
522     }
523   }
524
525   return [[uid copy] autorelease];
526 }
527
528 /* logging */
529
530 - (void)logWithFormat:(NSString *)_format, ... {
531   NSString *value = nil;
532   va_list  ap;
533
534   va_start(ap, _format);
535   value = [[NSString alloc] initWithFormat:_format arguments:ap];
536   va_end(ap);
537
538   NSLog(@"|%@| %@", self, value);
539   [value release];
540 }
541 - (void)debugWithFormat:(NSString *)_format, ... {
542   static char showDebug = 2;
543   NSString *value = nil;
544   va_list  ap;
545   
546   if (showDebug == 2) {
547     showDebug = [WOApplication isDebuggingEnabled] ? 1 : 0;
548   }
549   
550   if (showDebug) {
551     va_start(ap, _format);
552     value = [[NSString alloc] initWithFormat:_format arguments:ap];
553     va_end(ap);
554     
555     NSLog(@"|%@|D %@", self, value);
556     [value release];
557   }
558 }
559
560 /* managing builder queues */
561
562 - (void)setNextBuilder:(WOxElemBuilder *)_builder {
563   ASSIGN(self->nextBuilder, _builder);
564 }
565 - (WOxElemBuilder *)nextBuilder {
566   return self->nextBuilder;
567 }
568
569 /* component script parts */
570
571 - (void)addComponentScriptPart:(WOComponentScriptPart *)_part {
572   if (self->script == nil)
573     self->script = [[WOComponentScript alloc] init];
574   
575   [self->script addScriptPart:_part];
576 }
577 - (void)addComponentScript:(NSString *)_script line:(unsigned)_line {
578   WOComponentScriptPart *part;
579   
580   part = [[WOComponentScriptPart alloc] initWithURL:nil startLine:_line
581                                         script:_script];
582   [self addComponentScriptPart:part];
583   RELEASE(part);
584 }
585
586 - (WOComponentScript *)componentScript {
587   return self->script;
588 }
589
590 /* subcomponent registry, created during parsing ... */
591
592 - (void)registerSubComponentWithId:(NSString *)_cid
593   componentName:(NSString *)_name
594   bindings:(NSMutableDictionary *)_bindings
595 {
596   WOxElemBuilderComponentInfo *info;
597   
598   info = [[WOxElemBuilderComponentInfo alloc] initWithComponentId:_cid
599     componentName:_name
600     bindings:_bindings];
601     
602   if (self->subcomponentInfos == nil)
603     self->subcomponentInfos = [[NSMutableArray alloc] initWithCapacity:16];
604   [self->subcomponentInfos addObject:info];
605   RELEASE(info);
606 }
607
608 - (NSArray *)subcomponentInfos {
609   return self->subcomponentInfos;
610 }
611
612 - (void)reset {
613   [self->subcomponentInfos removeAllObjects];
614   ASSIGN(self->script, (id)nil);
615 }
616
617 @end /* WOxElemBuilder */
618
619 @implementation WOxTagClassElemBuilder
620
621 - (Class)classForElement:(id<DOMElement>)_element {
622   return Nil;
623 }
624
625 - (WOElement *)buildElement:(id<DOMElement>)_element templateBuilder:(id)_b {
626   Class clazz;
627   
628   if ((clazz = [self classForElement:_element]) == Nil) {
629     if (self->nextBuilder)
630       return [self->nextBuilder buildElement:_element templateBuilder:_b];
631     else {
632       [self logWithFormat:
633               @"did not find dynamic element class for DOM element %@",
634               _element];
635       return nil;
636     }
637   }
638   return [[clazz alloc] initWithElement:_element templateBuilder:_b];
639 }
640
641 @end /* SxTagClassElemBuilder */