]> err.no Git - sope/blob - sope-appserver/NGObjWeb/Templates/WODParser.m
improved SOPE security exceptions
[sope] / sope-appserver / NGObjWeb / Templates / WODParser.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 // $Id$
22
23 #include "WODParser.h"
24 #include "common.h"
25
26 @implementation WODParser
27
28 static Class    StrClass    = Nil;
29 static Class    DictClass   = Nil;
30 static Class    NumberClass = Nil;
31 static NSNumber *yesNum     = nil;
32 static NSNumber *noNum      = nil;
33 static BOOL     useUTF8     = NO;
34
35 + (void)initialize {
36   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
37
38   StrClass    = [NSString class];
39   DictClass   = [NSMutableDictionary class];
40   NumberClass = [NSNumber      class];
41   
42   if (yesNum == nil) yesNum = [[NumberClass numberWithBool:YES] retain];
43   if (noNum  == nil) noNum  = [[NumberClass numberWithBool:NO]  retain];
44   
45   useUTF8 = [ud boolForKey:@"WOParsersUseUTF8"];
46 }
47
48 - (id)initWithHandler:(id<WODParserHandler,NSObject>)_handler {
49   ASSIGN(self->callback, _handler);
50   return self;
51 }
52 - (void)dealloc {
53   [self->callback release];
54   [super dealloc];
55 }
56
57 /* callbacks */
58
59 - (id)associationWithValue:(id)_value {
60   return [self->callback parser:self makeAssociationWithValue:_value];
61 }
62 - (id)associationWithKeyPath:(NSString *)_keyPath {
63   return [self->callback parser:self makeAssociationWithKeyPath:_keyPath];
64 }
65
66 - (id)elementDefinitionForComponent:(NSString *)_cname
67   associations:(id)_entry
68   elementName:(NSString *)_elemName
69 {
70   return [self->callback parser:self
71                          makeDefinitionForComponentNamed:_cname
72                          associations:_entry
73                          elementName:_elemName];
74 }
75
76 /* parser */
77
78 static id _parseProperty(NSZone *_zone, const char *_buffer, unsigned *_idx,
79                          unsigned _len, NSException **_exception,
80                          BOOL _allowAssoc, id self);
81 static id _parseWodEntry(NSZone *_zone, const char *_buffer, unsigned *_idx,
82                          unsigned _len, NSException **_exception,
83                          NSString **_name, NSString **_class,
84                          id self);
85
86 static NSString *_makeStringForBuffer(const unsigned char *_buf, unsigned _l) {
87   // TODO: duplicate code with WOHTMLParser, but probably isn't worth a
88   //       separate file
89   NSString *r;
90   NSData   *data;
91
92   if (_l == 0)
93     return @"";
94   
95   if (!useUTF8)
96     return [[StrClass alloc] initWithCString:_buf length:_l];
97   
98   // Note: we cast the pointer because we are not going to modify _buf for the
99   //       duration and we are never going to write the data - should work
100   //       with any Foundation, but isn't strictly API compatible
101   data = [[NSData alloc] initWithBytesNoCopy:(void *)_buf length:_l 
102                          freeWhenDone:NO];
103   r = [[StrClass alloc] initWithData:data encoding:NSUTF8StringEncoding];
104   [data release];
105   return r;
106 }
107
108 - (NSException *)parseDefinitionsFromBuffer:(const char *)_buffer
109   length:(unsigned)_len
110   mappings:(NSMutableDictionary *)_mappings
111 {
112   NSException *exception     = nil;
113   NSString    *elementName   = nil;
114   NSString    *componentName = nil;
115   unsigned    idx            = 0;
116   id          entry          = nil;
117   
118   [_mappings removeAllObjects];
119   
120   while ((entry = _parseWodEntry(NULL, _buffer, &idx, _len, &exception,
121                                  &elementName, &componentName,
122                                  self))) {
123     id def;
124     
125     if (exception) {
126       RELEASE(entry);         entry         = nil;
127       RELEASE(elementName);   elementName   = nil;
128       RELEASE(componentName); componentName = nil;
129       break;
130     }
131
132     if ([_mappings objectForKey:elementName] != nil)
133       NSLog(@"WARNING: duplicate definition of element %@  !", elementName);
134
135     def = [self elementDefinitionForComponent:componentName
136                 associations:entry
137                 elementName:elementName];
138     
139     RELEASE(componentName); componentName = nil;
140     RELEASE(entry);         entry         = nil;
141
142     if ((def != nil) && (elementName != nil))
143       [_mappings setObject:def forKey:elementName];
144 #if 0
145     NSLog(@"defined element %@ definition=%@", elementName, def);
146 #endif
147     RELEASE(elementName); elementName = nil;
148   }
149
150   return exception;
151 }
152
153 /* parsing */
154
155 - (NSDictionary *)parseDeclarationData:(NSData *)_decl {
156   NSMutableDictionary *defs;
157   NSException *ex;
158   
159   if (![self->callback parser:self willParseDeclarationData:_decl])
160     return nil;
161
162   defs = [NSMutableDictionary dictionaryWithCapacity:100];
163   
164   ex = [self parseDefinitionsFromBuffer:[_decl bytes]
165              length:[_decl length]
166              mappings:defs];
167   
168   if (ex)
169     [self->callback parser:self failedParsingDeclarationData:_decl exception:ex];
170   else {
171     [self->callback parser:self finishedParsingDeclarationData:_decl
172                     declarations:defs];
173   }
174   
175   return defs;
176 }
177
178
179 static int _numberOfLines(const char *_buffer, unsigned _lastIdx) {
180   register unsigned pos, lineCount = 1;
181
182   for (pos = 0; (pos < _lastIdx) && (_buffer[pos] != '\0'); pos++) {
183     if (_buffer[pos] == '\n')
184       lineCount++;
185   }
186   return lineCount;
187 }
188
189 static inline BOOL _isBreakChar(unsigned char _c) {
190   switch (_c) {
191     case ' ': case '\t': case '\n': case '\r':
192     case '=':  case ';':  case ',':
193     case '{': case '(':  case '"':  case '<':
194     case '.': case ':':
195     case ')': case '}':
196       return YES;
197
198     default:
199       return NO;
200   }
201 }
202 static inline BOOL _isIdChar(unsigned char _c) {
203   return (_isBreakChar(_c) && (_c != '.')) ? NO : YES;
204 }
205
206 static inline int _valueOfHexChar(unsigned char _c) {
207   switch (_c) {
208     case '0': case '1': case '2': case '3': case '4':
209     case '5': case '6': case '7': case '8': case '9':
210       return (_c - 48); // 0-9 (ascii-char)'0' - 48 => (int)0
211       
212     case 'A': case 'B': case 'C':
213     case 'D': case 'E': case 'F':
214       return (_c - 55); // A-F, A=10..F=15, 'A'=65..'F'=70
215       
216     case 'a': case 'b': case 'c':
217     case 'd': case 'e': case 'f':
218       return (_c - 87); // a-f, a=10..F=15, 'a'=97..'f'=102
219
220     default:
221       return -1;
222   }
223 }
224 static inline BOOL _isHexDigit(unsigned char _c) {
225   switch (_c) {
226     case '0': case '1': case '2': case '3': case '4':
227     case '5': case '6': case '7': case '8': case '9':
228     case 'A': case 'B': case 'C':
229     case 'D': case 'E': case 'F':
230     case 'a': case 'b': case 'c':
231     case 'd': case 'e': case 'f':
232       return YES;
233
234     default:
235       return NO;
236   }
237 }
238
239 static NSException *_makeException(NSException *_exception,
240                                    const char *_buffer, unsigned _idx,
241                                    unsigned _len, NSString *_text)
242 {
243   NSMutableDictionary *ui = nil;
244   NSException *exception = nil;
245   int         numLines;
246   BOOL        atEof;
247   
248   numLines   = _numberOfLines(_buffer, _idx);
249   atEof      = (_idx >= _len) ? YES : NO;
250   
251   if (_exception)
252     // error resulted from a previous error (exception already set)
253     return _exception;
254
255   if (atEof)
256     _text = [@"Unexpected end: " stringByAppendingString:[_text stringValue]];
257   else {
258     _text = [StrClass stringWithFormat:@"Syntax error in line %i: %@",
259                         numLines, _text];
260   }
261
262   // user info
263   ui = [[exception userInfo] mutableCopy];
264   if (ui == nil)
265     ui = [[DictClass alloc] initWithCapacity:8];
266
267   [ui setObject:[NumberClass numberWithInt:numLines] forKey:@"line"];
268   [ui setObject:[NumberClass numberWithInt:_len]     forKey:@"size"];
269   [ui setObject:[NumberClass numberWithInt:_idx]     forKey:@"position"];
270
271   if (!atEof && (_idx > 0)) {
272     register unsigned pos;
273     const char *startPos, *endPos;
274
275     for (pos = _idx; (pos >= 0) && (_buffer[pos] != '\n'); pos--)
276       ;
277     startPos = &(_buffer[pos + 1]);
278
279     for (pos = _idx; ((pos < _len) && (_buffer[pos] != '\n')); pos++)
280       ;
281     endPos = &(_buffer[pos - 1]);
282     
283     if (startPos < endPos) {
284       NSString *ll;
285
286       ll = _makeStringForBuffer(startPos, endPos - startPos);
287       [ui setObject:ll forKey:@"lastLine"];
288       [ll release];
289     }
290     else {
291       NSLog(@"%s: startPos=0x%08X endPos=0x%08X", __PRETTY_FUNCTION__,
292             startPos, endPos);
293     }
294   }
295
296   exception = [NSException exceptionWithName:@"SyntaxError"
297                            reason:_text
298                            userInfo:ui];
299   [ui release]; ui = nil;
300
301   return exception;
302 }
303
304 static BOOL _skipComments(const char *_buffer, unsigned *_idx, unsigned _len,
305                           NSException **_exception)
306 {
307   register unsigned pos = *_idx;
308   BOOL lookAgain;
309
310   if (pos >= _len)
311     return NO;
312
313   //NSLog(@"start at '%c' (%i)", _buffer[pos], pos);
314   
315   do { // until all comments are filtered ..
316     lookAgain = NO;
317     
318     if ((_buffer[pos] == '/') && (pos + 1 < _len)) {
319       if (_buffer[pos + 1] == '/') { // single line comments
320         pos += 2; // skip '//'
321
322         // search for '\n' ..
323         while ((pos < _len) && (_buffer[pos] != '\n'))
324           pos++;
325
326         if ((pos < _len) && (_buffer[pos] == '\n')) {
327           pos++; // skip newline, otherwise EOF was reached
328           lookAgain = YES;
329         }
330       }
331       else if (_buffer[pos + 1] == '*') { /* multiline comments */
332         BOOL commentIsClosed = NO;
333       
334         pos += 2; // skip '/*'
335
336         do { // search for '*/'
337           while ((pos < _len) && (_buffer[pos] != '*'))
338             pos++;
339
340           if (pos < _len) { // found '*'
341             if ((pos + 1) < _len) {
342               if (_buffer[pos + 1] == '/') { // found '*/'
343                 commentIsClosed = YES;
344                 pos += 2; // skip '*/'
345                 lookAgain = YES;
346                 break; // leave loop
347               }
348             }
349           }
350         }
351         while (pos < _len);
352
353         if (!commentIsClosed) {
354           // EOF found, comment wasn't closed
355           *_exception =
356             _makeException(*_exception, _buffer, *_idx, _len,
357                            @"comment was not closed (expected '*/')");
358           return NO;
359         }
360       }
361     }
362     else if (isspace((int)_buffer[pos])) {
363       pos++;
364       lookAgain = YES;
365     }
366   }
367   while (lookAgain && (pos < _len));
368   
369   // store position ..
370   *_idx = pos;
371   //NSLog(@"end at '%c' (%i)", _buffer[pos], pos);
372
373   return (pos < _len);
374 }
375
376 static NSString *_parseIdentifier(NSZone *_zone,
377                                   const char *_buffer, unsigned *_idx,
378                                   unsigned _len, NSException **_exception)
379 {
380   register unsigned pos = *_idx;
381   register unsigned len = 0;
382   unsigned startPos = pos;
383
384   // skip comments and spaces
385   if (!_skipComments(_buffer, _idx, _len, _exception)) {
386     // EOF reached during comment-skipping
387     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
388                         @"did not find an id (expected 'a-zA-Z0-9') !");
389     return nil;
390   }
391   
392   // loop until break char
393   while (_isIdChar(_buffer[pos]) && (pos < _len)) {
394     pos++;
395     len++;
396   }
397
398   if (len == 0) { // wasn't a string ..
399     *_exception =
400       _makeException(*_exception, _buffer, *_idx, _len,
401                      @"did not find an id (expected 'a-zA-Z0-9') !");
402     return nil;
403   }
404   else {
405     *_idx = pos;
406     return _makeStringForBuffer(&(_buffer[startPos]), len);
407   }
408 }
409 static NSString *_parseKeyPath(NSZone *_zone,
410                                const char *_buffer, unsigned *_idx,
411                                unsigned _len, NSException **_exception,
412                                id self)
413 {
414   NSMutableString *keypath   = nil;
415   NSString        *component = nil;
416   
417   if (!_skipComments(_buffer, _idx, _len, _exception)) {
418     // EOF reached during comment-skipping
419     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
420                                  @"did not find keypath (expected id)");
421     return nil;
422   }
423
424   component = _parseIdentifier(_zone, _buffer, _idx, _len, _exception);
425   if (component == nil) {
426     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
427                                  @"did not find keypath (expected id)");
428     return nil;
429   }
430   if (_buffer[*_idx] != '.') // single id-keypath
431     return component;
432
433   keypath = [[NSMutableString allocWithZone:_zone] init];
434   [keypath appendString:component];
435   
436   while ((_buffer[*_idx] == '.') && (component != nil)) {
437     *_idx += 1; // skip '.'
438     [keypath appendString:@"."];
439
440     RELEASE(component); component = nil;
441     component = _parseIdentifier(_zone, _buffer, _idx, _len, _exception);
442
443     if (component == nil) {
444       RELEASE(keypath); keypath = nil;
445       *_exception =
446         _makeException(*_exception, _buffer, *_idx, _len,
447                        @"expected component after '.' in keypath !");
448       break;
449     }
450
451     [keypath appendString:component];
452   }
453   RELEASE(component); component = nil;
454
455   return keypath;
456 }
457
458 static NSString *_parseQString(NSZone *_zone,
459                                const char *_buffer, unsigned *_idx,
460                                unsigned _len, NSException **_exception,
461                                id self)
462 {
463   // skip comments and spaces
464   if (!_skipComments(_buffer, _idx, _len, _exception)) {
465     // EOF reached during comment-skipping
466     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
467                         @"did not find a quoted string (expected '\"') !");
468     return nil;
469   }
470
471   if (_buffer[*_idx] != '"') { // it's not a quoted string that's follows
472     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
473                                  @"did not find quoted string (expected '\"')");
474     return nil;
475   }
476   else { // a quoted string
477     register unsigned pos = *_idx;
478     register unsigned len = 0;
479     unsigned startPos = pos + 1;
480     BOOL     containsEscaped = NO;
481     
482     pos++; // skip starting quote
483
484     // loop until closing quote
485     while ((_buffer[pos] != '"') && (pos < _len)) {
486       if (_buffer[pos] == '\\') {
487         containsEscaped = YES;
488         pos++; // skip following char
489         if (pos == _len) {
490           *_exception =
491             _makeException(*_exception, _buffer, *_idx, _len,
492                            @"escape in quoted string not finished !");
493           return nil;
494         }
495       }
496       pos++;
497       len++;
498     }
499
500     if (pos == _len) { // syntax error, quote not closed
501       *_idx = pos;
502       *_exception = _makeException(*_exception, _buffer, *_idx, _len,
503                                    @"quoted string not closed (expected '\"')");
504       return nil;
505     }
506
507     pos++;       // skip closing quote
508     *_idx = pos; // store pointer
509     pos = 0;
510     
511     if (len == 0) { // empty string
512       return @"";
513     }
514     else if (containsEscaped) {
515       register unsigned pos2;
516       id   ostr = nil;
517       unsigned char *str;
518       
519       NSCAssert(len > 0, @"invalid length ..");
520       str = malloc(len + 3);
521       
522       for (pos = startPos, pos2 = 0; _buffer[pos] != '"'; pos++, pos2++) {
523         //NSLog(@"char=%c pos=%i pos2=%i", _buffer[pos], pos2);
524         if (_buffer[pos] == '\\') {
525           pos++;
526           switch (_buffer[pos]) {
527             case 'a':  str[pos2] = '\a'; break;
528             case 'b':  str[pos2] = '\b'; break;
529             case 'f':  str[pos2] = '\f'; break;
530             case 'n':  str[pos2] = '\n'; break;
531             case 't':  str[pos2] = '\t'; break;
532             case 'v':  str[pos2] = '\v'; break;
533             case '\\': str[pos2] = '\\'; break;
534             
535             default:
536               str[pos2] = _buffer[pos];
537               break;
538           }
539         }
540         else {
541           str[pos2] = _buffer[pos];
542         }
543       }
544       str[pos2] = '\0';
545       NSCAssert(pos2 == len, @"invalid unescape ..");
546       
547       ostr = _makeStringForBuffer(str, pos2);
548       if (str) free(str); str = NULL;
549
550       return ostr;
551     }
552     else {
553       NSCAssert(len > 0, @"invalid length ..");
554       return _makeStringForBuffer(&(_buffer[startPos]), len);
555     }
556   }
557 }
558
559 static NSData *_parseData(NSZone *_zone, const char *_buffer,
560                           unsigned *_idx, unsigned _len,
561                           NSException **_exception,
562                           id self)
563 {
564   if (!_skipComments(_buffer, _idx, _len, _exception)) {
565     // EOF reached during comment-skipping
566     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
567                                  @"did not find a data (expected '<') !");
568     return nil;
569   }
570
571   if (_buffer[*_idx] != '<') { // it's not a data that's follows
572     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
573                                  @"did not find a data (expected '<') !");
574     return nil;
575   }
576   else {
577     register      unsigned pos = *_idx + 1;
578     register      unsigned len = 0;
579     unsigned      endPos = 0;
580     NSMutableData *data  = nil;
581     
582     *_idx += 1; // skip '<'
583
584     if (!_skipComments(_buffer, _idx, _len, _exception)) {
585       *_exception = _makeException(*_exception, _buffer, *_idx, _len,
586                                    @"data was not closed (expected '>') ..");
587       return nil; // EOF
588     }
589
590     if (_buffer[*_idx] == '>') { // empty data
591       *_idx += 1; // skip '>'
592       return [[NSData allocWithZone:_zone] init];
593     }
594
595     // count significant chars
596     while ((_buffer[pos] != '>') && (pos < _len)) {
597       if ((_buffer[pos] == ' ') || (_buffer[pos] == '\t'))
598         ;
599       else if (_isHexDigit(_buffer[pos]))
600         len++;
601       else {
602         *_idx = pos;
603         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
604                                      @"invalid char in data property");
605         return nil;
606       }
607       pos++;
608     }
609     if (pos == _len) {
610       *_idx = pos;
611       *_exception = _makeException(*_exception, _buffer, *_idx, _len,
612                                    @"data was not closed (expected '>')");
613       return nil; // EOF
614     }
615     endPos = pos; // store position of closing '>'
616
617     // if odd, then add one byte for trailing nibble
618     len = (len % 2 == 1) ? len / 2 + 1 : len / 2;
619     data = [[NSMutableData allocWithZone:_zone] initWithLength:len];
620
621     /* now copy bytes ... */
622     {
623       register unsigned i;
624       register int pending = -1;
625       char *buf = [data mutableBytes];
626       
627       for (pos = *_idx, i = 0; (pos < endPos) && (i < len); pos++) {
628         int value = _valueOfHexChar(_buffer[pos]);
629
630         if (value != -1) {
631           if (pending == -1)
632             pending = value;
633           else {
634             value = value * 16 + pending;
635             pending = -1;
636
637             buf[i] = value;
638             i++;
639           }
640         }
641       }
642       if (pending != -1) { // was odd, now add the trailer ..
643         NSCAssert(i < len, @"invalid length ..");
644         buf[i] = pending * 16;
645       }
646     }
647     
648     // update global position
649     *_idx = endPos + 1; // endPos + 1 (*endPos == '>', 1 => skips '>')
650
651     return data;
652   }
653 }
654
655 static NSDictionary *_parseDict(NSZone *_zone,
656                                 const char *_buffer, unsigned *_idx,
657                                 unsigned _len, NSException **_exception,
658                                 id self)
659 {
660   if (!_skipComments(_buffer, _idx, _len, _exception)) {
661     // EOF reached during comment-skipping
662     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
663                                  @"did not find dictionary (expected '{')");
664     return nil;
665   }
666   
667   if (_buffer[*_idx] != '{') { // it's not a dict that's follows
668     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
669                                  @"did not find dictionary (expected '{')");
670     return nil;
671   }
672   else {
673     NSMutableDictionary *result = nil;
674     id   key     = nil;
675     id   value   = nil;
676     BOOL didFail = NO;
677     
678     *_idx += 1; // skip '{'
679
680     if (!_skipComments(_buffer, _idx, _len, _exception)) {
681       *_exception = _makeException(*_exception, _buffer, *_idx, _len,
682                                    @"dictionary was not closed (expected '}')");
683       return nil; // EOF
684     }
685
686     if (_buffer[*_idx] == '}') { // an empty dictionary
687       *_idx += 1; // skip the '}'
688       return [[DictClass allocWithZone:_zone] init];
689     }
690
691     result = [[DictClass allocWithZone:_zone] init];
692     do {
693       key   = nil;
694       value = nil;
695       
696       if (!_skipComments(_buffer, _idx, _len, _exception)) {
697         *_exception =
698           _makeException(*_exception, _buffer, *_idx, _len,
699                          @"dictionary was not closed (expected '}')");
700         didFail = YES;
701         break; // unexpected EOF
702       }
703
704       if (_buffer[*_idx] == '}') { // dictionary closed
705         *_idx += 1; // skip the '}'
706         break;
707       }
708       
709       // read key property
710       key = _parseProperty(_zone, _buffer, _idx, _len, _exception, NO, self);
711       if (key == nil) { // syntax error
712         if (*_exception == nil) {
713           *_exception = _makeException(*_exception, _buffer, *_idx, _len,
714                                        @"got nil-key in dictionary ..");
715         }
716         didFail = YES;
717         break;
718       }
719
720       /* The following parses:  (comment|space)* '=' (comment|space)* */
721       if (!_skipComments(_buffer, _idx, _len, _exception)) {
722         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
723                                      @"expected '=' after key in dictionary");
724         didFail = YES;
725         break; // unexpected EOF
726       }
727       // no we need a '=' assignment
728       if (_buffer[*_idx] != '=') {
729         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
730                                      @"expected '=' after key in dictionary");
731         didFail = YES;
732         break;
733       }
734       *_idx += 1; // skip '='
735       if (!_skipComments(_buffer, _idx, _len, _exception)) {
736         *_exception =
737           _makeException(*_exception, _buffer, *_idx, _len,
738                          @"expected value after key '=' in dictionary");
739         didFail = YES;
740         break; // unexpected EOF
741       }
742
743       // read value property
744       value = _parseProperty(_zone, _buffer, _idx, _len, _exception, NO, self);
745 #if 1
746       if (*_exception) {
747         didFail = YES;
748         break;
749       }
750 #else
751       if (value == nil) { // syntax error
752         if (*_exception == nil) {
753           *_exception = _makeException(*_exception, _buffer, *_idx, _len,
754                                        @"got nil-value in dictionary");
755         }
756         didFail = YES;
757         break;
758       }
759 #endif
760       
761       if ((key != nil) && (value != nil))
762         [result setObject:value forKey:key];
763       
764       // release key and value
765       RELEASE(key);   key   = nil;
766       RELEASE(value); value = nil;
767
768       // read trailing ';' if available
769       if (!_skipComments(_buffer, _idx, _len, _exception)) {
770         *_exception =
771           _makeException(*_exception, _buffer, *_idx, _len,
772                          @"dictionary was not closed (expected '}')");
773         didFail = YES;
774         break; // unexpected EOF
775       }
776       if (_buffer[*_idx] == ';') {
777         *_idx += 1; // skip ';'
778       }
779       else { // no ';' at end of pair, only allowed at end of dictionary
780         if (!_skipComments(_buffer, _idx, _len, _exception)) {
781           *_exception =
782             _makeException(*_exception, _buffer, *_idx, _len,
783                            @"dictionary was not closed (expected '}')");
784           didFail = YES;
785           break; // unexpected EOF
786         }
787
788         if (_buffer[*_idx] != '}') { // dictionary wasn't closed
789           *_exception =
790             _makeException(*_exception, _buffer, *_idx, _len,
791                            @"key-value pair without ';' at the end");
792           didFail = YES;
793           break;
794         }
795       }
796     }
797     while ((*_idx < _len) && (result != nil) && !didFail);
798
799     RELEASE(key);    key    = nil;
800     RELEASE(value);  value  = nil;
801     if (didFail) {
802       [result release]; result = nil;
803       return nil;
804     }
805     else
806       return result;
807   }
808 }
809
810 static NSArray *_parseArray(NSZone *_zone, const char *_buffer, unsigned *_idx,
811                             unsigned _len, NSException **_exception,
812                             id self)
813 {
814   if (!_skipComments(_buffer, _idx, _len, _exception)) {
815     // EOF reached during comment-skipping
816     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
817                                  @"did not find array (expected '(')");
818     return nil;
819   }
820
821   if (_buffer[*_idx] != '(') { // it's not an array that's follows
822     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
823                                  @"did not find array (expected '(')");
824     return nil;
825   }
826   else {
827     NSMutableArray *result = nil;
828     id element = nil;
829
830     *_idx += 1; // skip '('
831
832     if (!_skipComments(_buffer, _idx, _len, _exception)) {
833       *_exception = _makeException(*_exception, _buffer, *_idx, _len,
834                                    @"array was not closed (expected ')')");
835       return nil; // EOF
836     }
837
838     if (_buffer[*_idx] == ')') { // an empty array
839       *_idx += 1; // skip the ')'
840       return [[NSArray allocWithZone:_zone] init];
841     }
842     
843     result = [[NSMutableArray allocWithZone:_zone] init];
844     do {
845       element = _parseProperty(_zone, _buffer, _idx, _len, _exception, NO, self);
846       if (element == nil) {
847         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
848                                      @"expected element in array");
849         [result release]; result = nil;
850         break;
851       }
852       [result addObject:element];
853       [element release]; element = nil;
854       
855       if (!_skipComments(_buffer, _idx, _len, _exception)) {
856         *_exception =
857           _makeException(*_exception, _buffer, *_idx, _len,
858                          @"array was not closed (expected ')' or ',')");
859         [result release];
860         result = nil;
861         break;
862       }
863       
864       if (_buffer[*_idx] == ')') { // closed array
865         *_idx += 1; // skip ')'
866         break;
867       }
868       else if (_buffer[*_idx] == ',') { // next element
869         *_idx += 1; // skip ','
870         
871         if (!_skipComments(_buffer, _idx, _len, _exception)) {
872           *_exception = _makeException(*_exception, _buffer, *_idx, _len,
873                                        @"array was not closed (expected ')')");
874           [result release];
875           result = nil;
876           break;
877         }
878         if (_buffer[*_idx] == ')') { // closed array, like this '(1,2,)'
879           *_idx += 1; // skip ')'
880           break;
881         }
882       }
883       else { // syntax error
884         *_exception =
885           _makeException(*_exception, _buffer, *_idx, _len,
886                          @"expected ')' or ',' after array element");
887         [result release]; result = nil;
888         break;
889       }
890     }
891     while ((*_idx < _len) && (result != nil));
892     
893     [element release]; element = nil;
894
895     return result;
896   }
897 }
898
899 static NSNumber *_parseDigitPath(NSString *digitPath) {
900   NSRange  r;
901
902   r = [digitPath rangeOfString:@"."];
903   return r.length > 0 
904     ? [NumberClass numberWithDouble:[digitPath doubleValue]]
905     : [NumberClass numberWithInt:[digitPath intValue]];
906 }
907
908 static id _parseProperty(NSZone *_zone, const char *_buffer, unsigned *_idx,
909                          unsigned _len,
910                          NSException **_exception, BOOL _allowAssoc,
911                          id self)
912 {
913   BOOL valueProperty = YES;
914   id   result = nil;
915   
916   if (!_skipComments(_buffer, _idx, _len, _exception))
917     return nil; // EOF
918
919   switch (_buffer[*_idx]) {
920     case '"': // quoted string
921       NSCAssert(result == nil, @"result is already set ..");
922       result = _parseQString(_zone, _buffer, _idx, _len, _exception, self);
923       break;
924
925     case '{': // dictionary
926       NSCAssert(result == nil, @"result is already set ..");
927       result = _parseDict(_zone, _buffer, _idx, _len, _exception, self);
928       break;
929
930     case '(': // array
931       NSCAssert(result == nil, @"result is already set ..");
932       result = _parseArray(_zone, _buffer, _idx, _len, _exception, self);
933       break;
934
935     case '<': // data
936       NSCAssert(result == nil, @"result is already set ..");
937       result = _parseData(_zone, _buffer, _idx, _len, _exception, self);
938       break;
939       
940     default:
941       NSCAssert(result == nil, @"result is already set ..");
942       
943       if (isdigit((int)_buffer[*_idx]) || (_buffer[*_idx] == '-')) {
944         id digitPath;
945         NSCAssert(result == nil, @"result is already set ..");
946         
947         digitPath = _parseKeyPath(_zone, _buffer, _idx, _len, _exception,self);
948         result = [_parseDigitPath(digitPath) retain];
949         [digitPath release]; digitPath = nil;
950         valueProperty = YES;
951       }
952       else if (_isIdChar(_buffer[*_idx])) {
953         valueProperty = NO;
954         
955         if ((_buffer[*_idx] == 'Y') || (_buffer[*_idx] == 'N')) {
956           // parse YES and NO
957           if ((*_idx + 4) < _len) {
958             if (strncmp(&(_buffer[*_idx]), "YES", 3) == 0 &&
959                 _isBreakChar(_buffer[*_idx + 3])) {
960               result = [yesNum retain];
961               valueProperty = YES;
962               *_idx += 3; // skip the YES
963             }
964           }
965           if (((*_idx + 3) < _len) && !valueProperty) {
966             if (strncmp(&(_buffer[*_idx]), "NO", 2) == 0 &&
967                 _isBreakChar(_buffer[*_idx + 2])) {
968               result = [noNum retain];
969               valueProperty = YES;
970               *_idx += 2; // skip the NO
971             }
972           }
973         }
974         else if ((_buffer[*_idx] == 't') || (_buffer[*_idx] == 'f')) {
975           // parse true and false
976           if ((*_idx + 5) < _len) {
977             if (strncmp(&(_buffer[*_idx]), "true", 4) == 0 &&
978                 _isBreakChar(_buffer[*_idx + 4])) {
979               result = [yesNum retain];
980               valueProperty = YES;
981               *_idx += 4; // skip the true
982             }
983           }
984           if (((*_idx + 6) < _len) && !valueProperty) {
985             if (strncmp(&(_buffer[*_idx]), "false", 5) == 0 &&
986                 _isBreakChar(_buffer[*_idx + 5])) {
987               result = [noNum retain];
988               valueProperty = YES;
989               *_idx += 5; // skip the false
990             }
991           }
992         }
993         
994         if (!valueProperty) {
995           NSCAssert(result == nil, @"result already set ..");
996           result = _parseKeyPath(_zone, _buffer, _idx, _len, _exception, self);
997         }
998       }
999       else {
1000         NSCAssert(result == nil, @"result already set ..");
1001         
1002         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1003                                      @"invalid char");
1004       }
1005       break;
1006   }
1007
1008   if (*_exception)
1009     return nil;
1010   
1011   if (result == nil) {
1012     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1013                                  @"error in property value");
1014   }
1015
1016   NSCAssert(result, @"missing property value ..");
1017
1018   if (_allowAssoc) {
1019     id old = result;
1020     
1021     result = valueProperty
1022       ? [self associationWithValue:result]
1023       : [self associationWithKeyPath:result];
1024     
1025 #if 0
1026     NSCAssert(result, @"got no association for property ..");
1027 #endif
1028     [old release]; old = nil;
1029     
1030     return [result retain];
1031   }
1032   
1033   /* result is already retained */
1034   return result;
1035 }
1036
1037 static NSDictionary *_parseWodConfig(NSZone *_zone, const char *_buffer,
1038                                      unsigned *_idx, unsigned _len,
1039                                      NSException **_exception,
1040                                      id self)
1041 {
1042   if (!_skipComments(_buffer, _idx, _len, _exception)) {
1043     // EOF reached during comment-skipping
1044     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1045                      @"did not find element configuration (expected '{')");
1046     return nil;
1047   }
1048   
1049   if (_buffer[*_idx] != '{') { // it's not a dict that's follows
1050     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1051                      @"did not find element configuration (expected '{')");
1052     return nil;
1053   }
1054   else { // found '{'
1055     NSMutableDictionary *result = nil;
1056     NSString *key     = nil;
1057     id       value    = nil;
1058     BOOL     didFail  = NO;
1059     
1060     *_idx += 1; // skip '{'
1061
1062     if (!_skipComments(_buffer, _idx, _len, _exception)) {
1063       *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1064                        @"element configuration was not closed (expected '}')");
1065       return nil; // EOF
1066     }
1067
1068     if (_buffer[*_idx] == '}') { // an empty configuration
1069       *_idx += 1; // skip the '}'
1070       return [[DictClass allocWithZone:_zone] init];
1071     }
1072
1073     result = [[DictClass allocWithZone:_zone] init];
1074     do {
1075       key   = nil;
1076       value = nil;
1077       
1078       if (!_skipComments(_buffer, _idx, _len, _exception)) {
1079         *_exception =
1080           _makeException(*_exception, _buffer, *_idx, _len,
1081                          @"dictionary was not closed (expected '}')");
1082         didFail = YES;
1083         break; // unexpected EOF
1084       }
1085
1086       if (_buffer[*_idx] == '}') { // dictionary closed
1087         *_idx += 1; // skip the '}'
1088         break;
1089       }
1090       
1091       // read key property
1092       key = _parseIdentifier(_zone, _buffer, _idx, _len, _exception);
1093       if (key == nil) { // syntax error
1094         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1095                          @"expected identifier in element configuration ..");
1096         didFail = YES;
1097         break;
1098       }
1099
1100       /* The following parses:  (comment|space)* '=' (comment|space)* */
1101       if (!_skipComments(_buffer, _idx, _len, _exception)) {
1102         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1103                          @"expected '=' after id in element configuration");
1104         didFail = YES;
1105         break; // unexpected EOF
1106       }
1107       // no we need a '=' assignment
1108       if (_buffer[*_idx] != '=') {
1109         *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1110                          @"expected '=' after id in element configuration");
1111         didFail = YES;
1112         break;
1113       }
1114       *_idx += 1; // skip '='
1115       if (!_skipComments(_buffer, _idx, _len, _exception)) {
1116         *_exception =
1117           _makeException(*_exception, _buffer, *_idx, _len,
1118                          @"expected value after id '=' in "
1119                          @"element configuration");
1120         didFail = YES;
1121         break; // unexpected EOF
1122       }
1123
1124       // read value property
1125       value = _parseProperty(_zone, _buffer, _idx, _len, _exception, YES, self);
1126 #if 1
1127       if (*_exception) {
1128         didFail = YES;
1129         break;
1130       }
1131 #else
1132       if (value == nil) { // syntax error
1133         if (*_exception == nil) {
1134           *_exception =
1135             _makeException(*_exception, _buffer, *_idx, _len,
1136                            @"got nil-value in element configuration");
1137         }
1138         didFail = YES;
1139         break;
1140       }
1141       NSCAssert(key,   @"invalid key ..");
1142       NSCAssert(value, @"invalid value ..");
1143 #endif
1144
1145       if ((value != nil) && (key != nil))
1146         [result setObject:value forKey:key];
1147
1148       // release key and value
1149       RELEASE(key);   key   = nil;
1150       RELEASE(value); value = nil;
1151
1152       // read trailing ';' if available
1153       if (!_skipComments(_buffer, _idx, _len, _exception)) {
1154         *_exception =
1155           _makeException(*_exception, _buffer, *_idx, _len,
1156                          @"element configuration was not "
1157                          @"closed (expected '}')");
1158         didFail = YES;
1159         break; // unexpected EOF
1160       }
1161       if (_buffer[*_idx] == ';') {
1162         *_idx += 1; // skip ';'
1163       }
1164       else { // no ';' at end of pair, only allowed at end of dictionary
1165         if (!_skipComments(_buffer, _idx, _len, _exception)) {
1166           *_exception =
1167             _makeException(*_exception, _buffer, *_idx, _len,
1168                            @"element configuration was not "
1169                            @"closed (expected '}')");
1170           didFail = YES;
1171           break; // unexpected EOF
1172         }
1173
1174         if (_buffer[*_idx] != '}') { // config wasn't closed
1175           *_exception =
1176             _makeException(*_exception, _buffer, *_idx, _len,
1177                            @"key-value pair without ';' at the end");
1178           didFail = YES;
1179           break;
1180         }
1181       }
1182     }
1183     while ((*_idx < _len) && (result != nil) && !didFail);
1184
1185     [key   release]; key    = nil;
1186     [value release]; value  = nil;
1187     
1188     if (didFail) {
1189       [result release]; result = nil;
1190       return nil;
1191     }
1192     else
1193       return result;
1194   }
1195 }
1196
1197 static id _parseWodEntry(NSZone *_zone, const char *_buffer, unsigned *_idx,
1198                          unsigned _len, NSException **_exception,
1199                          NSString **_name, NSString **_class, id self)
1200 {
1201   NSString     *elementName   = nil;
1202   NSString     *componentName = nil;
1203   NSDictionary *config        = nil;
1204
1205   *_name  = nil;
1206   *_class = nil;
1207   
1208   if (!_skipComments(_buffer, _idx, _len, _exception))
1209     return nil; // EOF
1210
1211   // Element name
1212   elementName = _parseIdentifier(_zone, _buffer, _idx, _len, _exception);
1213   if (elementName == nil) {
1214     *_exception = _makeException(nil, _buffer, *_idx, _len,
1215                                  @"expected element name");
1216     goto failed;
1217   }
1218
1219   if (!_skipComments(_buffer, _idx, _len, _exception))
1220     goto failed;
1221
1222   // Element/Component separator
1223   if (_buffer[*_idx] == ':') {
1224     *_idx += 1; // skip ':'
1225   }
1226   else {
1227     *_exception = _makeException(*_exception, _buffer, *_idx, _len,
1228                                  @"expected ':' after element name");
1229     goto failed;
1230   }
1231
1232   if (!_skipComments(_buffer, _idx, _len, _exception))
1233     goto failed;
1234
1235   // Component Name
1236   componentName = _parseIdentifier(_zone, _buffer, _idx, _len, _exception);
1237   if (componentName == nil) {
1238     *_exception = _makeException(nil, _buffer, *_idx, _len,
1239                                  @"expected component name");
1240     goto failed;
1241   }
1242
1243   if (!_skipComments(_buffer, _idx, _len, _exception))
1244     goto failed;
1245
1246   // Configuration
1247   config = _parseWodConfig(_zone, _buffer, _idx, _len, _exception, self);
1248   if (config == nil)
1249     goto failed;
1250   
1251   //NSLog(@"%@ : %@ %@", elementName, componentName, config);
1252
1253   // read trailing ';' if available
1254   if (_skipComments(_buffer, _idx, _len, _exception)) {
1255     if (_buffer[*_idx] == ';') {
1256       *_idx += 1; // skip ';'
1257     }
1258   }
1259
1260   *_name  = elementName;
1261   *_class = componentName;
1262   return config;
1263   
1264  failed:
1265 #if 0
1266   NSLog(@"failed at %@:%@ ..", elementName, componentName);
1267 #endif
1268   [elementName   release]; elementName   = nil;
1269   [componentName release]; componentName = nil;
1270   [config        release]; config        = nil;
1271   return nil;
1272 }
1273
1274 @end /* WODParser */