]> err.no Git - sope/blob - sope-mime/NGMail/NGMailAddressParser.m
synced with latest additions and bumped framework versions
[sope] / sope-mime / NGMail / NGMailAddressParser.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE 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   SOPE 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 SOPE; 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 "NGMailAddressParser.h"
23 #include "NGMailAddress.h"
24 #include "NGMailAddressList.h"
25 #include "common.h"
26
27 @interface NGMailAddressParser(PrivateMethods)
28 - (id)parseQuotedString:(BOOL)_guestMode;
29 - (id)parseWord:(BOOL)_guestMode;
30 - (id)parsePhrase:(BOOL)_guestMode;
31 - (id)parseLocalPart:(BOOL)_guestMode;
32 - (id)parseDomain:(BOOL)_guestMode;
33 - (id)parseAddrSpec:(BOOL)_guestMode;
34 - (id)parseRouteAddr:(BOOL)_guessMode;
35 - (id)parseGroup:(BOOL)_guessMode;
36 - (id)parseMailBox:(BOOL)_guessMode;
37 - (id)parseAddress:(BOOL)_guessMode;
38 @end
39
40 @implementation NGMailAddressParser
41
42 static Class    StrClass = Nil;
43 static NSNumber *yesNum  = nil;
44
45 + (int)version {
46   return 2;
47 }
48
49 + (void)initialize {
50   if (yesNum == nil) yesNum = [[NSNumber numberWithBool:YES] retain];
51   StrClass = [NSString class];
52 }
53
54 static inline NSString *mkStrObj(const unsigned char *s, unsigned int l) {
55   // TODO: unicode
56   return [(NSString *)[StrClass alloc] initWithCString:(char *)s length:l];
57 }
58
59 static inline id parseWhiteSpaces(NGMailAddressParser *self, BOOL _guessMode) {
60   id   returnValue = nil;
61   char text[self->maxLength];
62   int  length      = 0;
63   
64   while ((self->data[self->dataPos] == ' ')  ||
65          (self->data[self->dataPos] == '\n')) {
66     text[length++] = ' ';    
67     self->dataPos++;
68   }
69   if (length) {
70     if (_guessMode)
71       returnValue = yesNum;
72     else {
73       returnValue =
74         [[(NSString *)[StrClass alloc] initWithCString:text length:length] 
75           autorelease];
76     }
77   }
78   return returnValue;
79 }
80
81
82 static inline id parseAtom(NGMailAddressParser *self, BOOL _guessMode) {
83   int  keepPos     = self->dataPos; // keep reference for backtracking
84   id   returnValue = nil;
85   BOOL isAtom      = YES;
86   unsigned char text[self->maxLength + 2];  // token text
87   int  length      = 0;  // token text length
88   BOOL done        = NO;
89
90   do {
91     if (self->dataPos == self->maxLength) { // end of text is reached
92       isAtom = (length > 0);
93       done   = YES;
94     }
95     else {
96       register unsigned char c = self->data[self->dataPos];
97       
98       switch (c) {
99         case '(' :  case ')': case '<': case '>':
100         case '@' :  case ',': case ';': case ':':
101         case '\\':  case '"': case '.': case '[':
102         case ']' :  case ' ': case 127:
103           isAtom = (length > 0);
104           done   = YES;
105           break;
106
107         default:
108           if (c < 32) {
109             isAtom = (length > 0);
110             done   = YES;
111           }
112           else {
113             text[length] = c;  // store char in text
114             length++;          // increase text size
115             (self->dataPos)++; // go ahead
116           }
117       }
118     }
119   }
120   while (!done);
121   
122   if (isAtom) {
123     if (_guessMode) {
124       NSCAssert(length > 0, @"no atom with length=0");
125       returnValue =  yesNum;
126     }
127     else {
128       NSCAssert(length > 0, @"no atom with length=0");
129       returnValue = [mkStrObj(text, length) autorelease];
130       NSCAssert([returnValue isKindOfClass:StrClass], @"got no string ..");
131     }
132   }
133   else {
134     self->dataPos = keepPos;
135     returnValue = nil;
136   }
137   return returnValue;
138 }
139
140 static inline id parseQuotedPair(NGMailAddressParser *self, BOOL _guessMode) {
141   id   returnValue  = nil;
142
143   if ((self->maxLength - (self->dataPos)) < 3) {
144     returnValue =  nil;
145   }
146   else {
147     if (self->data[self->dataPos] == '\\') {
148       self->dataPos = self->dataPos + 2;      
149       if (_guessMode)
150         returnValue = yesNum;
151       else {
152         returnValue =
153           [mkStrObj(&(self->data[self->dataPos - 1]), 1) autorelease];
154       }
155     }
156   }
157   return returnValue;
158 }
159
160 static inline id parseQText(NGMailAddressParser *self, BOOL _guessMode) {
161   int  keepPos     = self->dataPos; // keep reference for backtracking
162   id   returnValue = nil;
163   BOOL isQText    = YES;
164   unsigned char text[self->maxLength + 4];  // token text
165   int  length      = 0;  // token text length
166   BOOL done        = YES;
167
168   do {
169     if (self->dataPos == self->maxLength) { // end of text is reached
170       isQText = (length > 0);
171       done    = YES;
172     }
173     else {
174       register char c = self->data[self->dataPos];
175       
176       switch ((int)c) {
177         case '"' :  
178         case '\\':
179         case 13  :
180           isQText = (length > 0);
181           done    = YES;
182           break;
183
184         default: {
185             text[length] = c;  // store char in text
186             length++;          // increase text size
187             (self->dataPos)++; // go ahead
188           }
189       }
190     }
191   }
192   while (!done);
193
194   if (isQText) {
195     if (_guessMode) {
196       NSCAssert(length > 0, @"no qtext with length=0");
197       returnValue = yesNum;
198     }
199     else {
200       NSCAssert(length > 0, @"no qtext with length=0");
201       returnValue = [mkStrObj(text, length) autorelease];
202       NSCAssert([returnValue isKindOfClass:StrClass],
203                @"got no string ..");
204     }
205   }
206   else {
207     self->dataPos = keepPos;
208     returnValue = nil;
209   }
210   return returnValue;
211 }
212
213 static inline id parseDText(NGMailAddressParser *self, BOOL _guessMode) {
214   int  keepPos     = self->dataPos; // keep reference for backtracking
215   id   returnValue = nil;
216   BOOL isDText    = YES;
217   unsigned char text[self->maxLength];  // token text
218   int  length      = 0;  // token text length
219   BOOL done        = YES;
220
221   do {
222     if (self->dataPos == self->maxLength) { // end of text is reached
223       isDText = (length > 0);
224       done    = YES;
225     }
226     else {
227       register char c = self->data[self->dataPos];
228       
229       switch ((int)c) {
230         case '[':  case ']':
231         case '\\': case 13:
232           isDText = (length > 0);
233           done    = YES;
234           break;
235
236         default: {
237             text[length] = c;  // store char in text
238             length++;          // increase text size
239             (self->dataPos)++; // go ahead
240           }
241       }
242     }
243   }
244   while (!done);
245
246   if (isDText) {
247     if (_guessMode) {
248       NSCAssert(length > 0, @"no dtext with length=0");
249       returnValue =  yesNum;
250     }
251     else {
252       NSCAssert(length > 0, @"no dtext with length=0");
253       returnValue = [mkStrObj(text, length) autorelease];
254       NSCAssert([returnValue isKindOfClass:StrClass],
255                @"got no string ..");
256     }
257   }
258   else {
259     self->dataPos = keepPos;
260     returnValue = nil;
261   }
262   return returnValue;
263 }
264
265 static inline id parseDomainLiteral(NGMailAddressParser *self, BOOL _guessMode) {
266   int             keepPos          = self->dataPos;
267   id              returnValue      = nil;
268   BOOL            returnOK         = NO;
269
270   if (_guessMode) {
271     if (self->data[self->dataPos] != '[')
272       return nil;
273
274     (self->dataPos)++; // skip starting '"'
275
276     // parses: "suafdjksfd \"sdafsadf"
277     while (self->data[self->dataPos] != ']') {
278       if (self->data[self->dataPos] == '\\') {// skip quoted chars 
279         (self->dataPos)++;
280       }
281       (self->dataPos)++;
282       if (self->dataPos >= self->maxLength) {
283         return nil;
284       }
285     }
286     (self->dataPos)++; // skip closing '"'
287     returnValue = yesNum;
288   }
289   else {
290     if (self->data[self->dataPos++] == '[') {
291       NSMutableString *ms;
292       id result = nil;
293       
294       ms = [NSMutableString stringWithCapacity:10];
295       do {
296         if ((result = parseQuotedPair(self, NO)))
297           [ms appendString:result];
298         else {
299           if ((result = parseDText(self, NO))) 
300             [ms appendString:result];        
301         }
302       }
303       while (result);
304       returnValue = ms;
305       
306       if (self->data[self->dataPos++] == ']')
307         returnOK = YES;
308     }
309     if (!returnOK) {
310       if (returnValue)
311         returnValue = nil;
312       
313       self->dataPos = keepPos;
314     }
315   }
316   return returnValue;
317 }
318
319 /* constructors */
320
321 + (id)mailAddressParserWithData:(NSData *)_data {
322   return [[(NGMailAddressParser *)[self alloc] 
323                                   initWithCString:[_data bytes]
324                                   length:[_data length]] autorelease];
325 }
326 + (id)mailAddressParserWithCString:(char *)_cString {
327   return [[(NGMailAddressParser *)[self alloc] 
328                                   initWithCString:(unsigned char *)_cString
329                                   length:strlen(_cString)] autorelease];
330 }
331 - (id)initWithCString:(const unsigned char *)_cstr length:(int unsigned)_len {
332   if ((self = [super init])) {
333     // TODO: remember some string encoding?
334     self->data      = (unsigned char *)_cstr;
335     self->maxLength = _len;
336     self->dataPos   = 0;
337     self->errorPos  = -1;
338   }
339   return self;
340 }
341
342 - (id)initWithString:(NSString *)_str {
343   // TODO: unicode
344   return [self initWithCString:(unsigned char *)[_str cString] 
345                length:[_str cStringLength]];
346 }
347
348 - (id)init {
349   return [self initWithCString:NULL length:0];
350 }
351
352 + (id)mailAddressParserWithString:(NSString *)_string {
353   return [[(NGMailAddressParser *)[self alloc] initWithString:_string] 
354            autorelease];
355 }
356
357 - (void)dealloc {
358   self->data      = NULL;
359   self->maxLength = 0;
360   self->dataPos   = 0;
361   [super dealloc];
362 }
363
364 /* parsing */
365
366 - (id)_parseQuotedStringInGuessMode {
367   int keepPos;
368   
369   if (self->data[self->dataPos] != '"')
370     return nil;
371
372   keepPos = self->dataPos;
373   (self->dataPos)++; // skip starting '"'
374
375   // parses: "suafdjksfd \"sdafsadf"
376   while (self->data[self->dataPos] != '"') {
377     if (self->data[self->dataPos] == '\\') /* skip quoted chars  */
378       (self->dataPos)++;
379
380     (self->dataPos)++;
381     if (self->dataPos >= self->maxLength) {
382       self->dataPos = keepPos;
383       return nil;
384     }
385   }
386   (self->dataPos)++; // skip closing '"'
387   return yesNum;
388 }
389
390 - (id)parseQuotedString:(BOOL)_guessMode {
391   int  keepPos     = self->dataPos;
392   id   returnValue = nil;
393   BOOL returnOK    = NO;
394
395   if (_guessMode)
396     return [self _parseQuotedStringInGuessMode];
397
398   if (data[dataPos++] == '"') {
399     NSMutableString *ms;
400     id result = nil;
401     
402     ms = [NSMutableString stringWithCapacity:10];
403     do {
404       if ((result = parseQuotedPair(self, NO)))
405         [ms appendString:result];
406       else {
407         if ((result = parseQText(self, NO))) 
408           [ms appendString:result];        
409       }
410     }
411     while (result);
412     returnValue = ms;
413     
414     if (data[dataPos++] == '"')
415       returnOK = YES;
416   }
417   if (!returnOK) {
418     returnValue = nil;
419     dataPos = keepPos;
420   }
421   return returnValue;  
422 }
423
424 - (id)parseWord:(BOOL)_guessMode {
425   id returnValue;
426   
427   if ((returnValue = [self parseQuotedString:_guessMode]) == nil)
428     returnValue = parseAtom(self, _guessMode);
429   
430   return returnValue;
431 }
432
433 - (id)_parsePhraseInGuessMode {
434   BOOL isPhrase    = NO;
435   id   returnValue = nil;
436   id   result;
437   
438   do {
439     if ((result = parseWhiteSpaces(self, YES))) {
440       isPhrase = YES;
441       continue;
442     }
443     
444     if ((result = [self parseWord:YES])) {
445       isPhrase = YES;
446       [(NSMutableString *)returnValue appendString:result];
447       result = parseWhiteSpaces(self, YES);
448     }
449   }
450   while (result);
451   
452   return !isPhrase ? nil : yesNum;
453 }
454
455 - (id)parsePhrase:(BOOL)_guessMode {
456   BOOL isPhrase    = NO;
457   id   returnValue = nil;
458   id   result      = nil;      
459   NSString *tmp;
460
461   if (_guessMode)
462     return [self _parsePhraseInGuessMode];
463
464   returnValue = [NSMutableString stringWithCapacity:10];
465   tmp         = nil;
466
467   do {
468     if ((result = parseWhiteSpaces(self, _guessMode))) {
469         tmp = result;
470         ;
471         //        isPhrase = YES;
472         //        [returnValue appendString:result];
473     }
474     else if ((result = [self parseWord:_guessMode])) {
475         isPhrase = YES;
476
477         if (tmp)
478           [(NSMutableString *)returnValue appendString:tmp];
479
480         tmp = nil;
481           
482         [(NSMutableString *)returnValue appendString:result];
483         if (self->dataPos < self->maxLength) {
484           if (self->data[self->dataPos] == '.') {
485             [(NSMutableString *)returnValue appendString:@"."];
486             self->dataPos++;
487           }
488         }
489     }
490   } 
491   while (result);
492   
493   if (!isPhrase || ([returnValue length] == 0))
494       returnValue = nil;
495   
496   return returnValue;
497 }
498
499 - (id)_parseLocalPartInGuessMode {
500   id result;
501   
502   if (![self parseWord:YES])
503     return nil;
504
505   do {
506     result = nil;
507     if (self->data[self->dataPos] == '.') {
508       self->dataPos++;
509       result = [self parseWord:YES];
510     }
511   }
512   while (result);
513   
514   return yesNum;
515 }
516
517 - (id)parseLocalPart:(BOOL)_guessMode {
518   NSMutableString *ms;
519   id       returnValue = nil;
520   NSString *result = nil;
521   
522   if (_guessMode)
523     return [self _parseLocalPartInGuessMode];
524   
525   if ((returnValue = [self parseWord:NO]) == nil)
526     return nil;
527   
528   ms = [[returnValue mutableCopy] autorelease];
529       
530   do {
531     if (self->data[self->dataPos] == '.') {
532       self->dataPos++;
533       result = [self parseWord:NO];
534       
535       if (result) {
536         NSAssert([result isKindOfClass:StrClass],
537                  @"parseWord should return string");
538             
539         [ms appendString:@"."];
540         [ms appendString:result];
541       }
542     }
543     else
544       result = nil;
545   } 
546   while (result != nil);
547   
548   return ms;
549 }
550
551 - (id)_parseDomainInGuessMode {
552   id returnValue = nil;
553   id result      = nil;
554
555     returnValue = parseAtom(self, YES);
556     if (!result)
557       returnValue = parseDomainLiteral(self, YES);
558     if (returnValue) {
559       do {
560         result = nil;
561         if (self->data[self->dataPos] == '.') {
562           self->dataPos++;
563           result = parseAtom(self,YES);
564           if (!result)
565             result = parseDomainLiteral(self, YES);
566         }
567       } while (result);
568     }
569     return returnValue;
570 }
571
572 - (id)parseDomain:(BOOL)_guessMode {
573   NSMutableString *ms;
574   id result;
575
576   if (_guessMode)
577     return [self _parseDomainInGuessMode];
578
579   if ((result = parseAtom(self, NO)) == nil)
580     result = parseDomainLiteral(self, NO);
581   
582   if (result == nil)
583     return nil;
584
585   ms = [[result mutableCopyWithZone:[self zone]] autorelease];
586   do {
587     if (self->data[self->dataPos] == '.') {
588       self->dataPos++;
589           
590       result = parseAtom(self,NO);
591       if (result == nil)
592         result = parseDomainLiteral(self, NO);
593           
594       if (result) {
595         [ms appendString:@"."];
596         [ms appendString:result];
597       }
598     }
599     else
600       result = nil;
601   }
602   while (result);
603   return ms;
604 }
605
606 - (id)parseAddrSpec:(BOOL)_guessMode {
607   NSMutableString *returnValue  = nil;
608   id   result;
609   int  keepPos      = self->dataPos;
610   BOOL returnStatus = NO;
611   
612   if (_guessMode) {
613     id ret;
614     
615     ret = nil;
616     if ([self parseLocalPart:YES]) {
617       if (self->data[self->dataPos] == '@') {
618         dataPos++;
619         if ([self parseDomain:YES]) {
620           ret = yesNum;
621         }
622       }
623     }
624     return ret;
625   }
626   
627   if ((result = [self parseLocalPart:NO]) != nil) {
628     returnValue = [[result mutableCopy] autorelease];
629     result = nil;
630     
631     if (self->data[self->dataPos] == '@') {
632       self->dataPos++;
633         
634       if ((result = [self parseDomain:NO])) {
635         [returnValue appendString:@"@"];
636         [returnValue appendString:result];
637         returnStatus = YES;
638       }
639     }
640   }
641   if (!returnStatus) {
642       returnValue = nil;
643       dataPos = keepPos;
644   }
645   return returnValue;
646 }
647
648 - (id)_parseRouteInGuessMode {
649   id   result  = nil;
650   int  keepPos = self->dataPos;
651   BOOL status  = YES;
652     
653   if (self->data[self->dataPos] == '@') {
654     status = NO;
655     if ((result = [self parseDomain:YES]))
656       status = YES;
657   }
658   if (status) {
659     parseWhiteSpaces(self,YES);
660     status = (self->data[self->dataPos] == ':') ? YES : NO;
661   }
662   if (status)
663     return yesNum;
664
665   self->dataPos = keepPos;
666   return nil;
667 }
668 - (id)parseRoute:(BOOL)_guessMode {
669   NSMutableString *returnValue;
670   id   result      = nil;
671   int  keepPos;
672   BOOL status = YES;
673   
674   if (_guessMode)
675     return [self _parseRouteInGuessMode];
676
677   keepPos     = self->dataPos;
678   returnValue = [NSMutableString stringWithCapacity:10];
679   if (self->data[self->dataPos] == '@') {
680       status = NO;
681       self->dataPos++;
682       if ((result = [self parseDomain:NO])) {
683         status = YES;
684         [returnValue appendString:result];
685       }
686   }
687   if (status) {
688       parseWhiteSpaces(self,NO);
689       if (self->data[self->dataPos] == ':') {
690         status = YES;
691         self->dataPos++;        
692       }
693       else {
694         status = NO;
695       }
696   }
697   if (!status) {
698     returnValue = nil;
699     self->dataPos = keepPos;
700   }
701   return returnValue;
702 }
703
704 - (id)_parseRouteAddrInGuessMode {
705   int  keepPos      = self->dataPos;
706   id   returnValue  = nil;
707   id   result       = nil;
708   BOOL returnStatus = NO;
709
710     if (self->data[self->dataPos] == '<') {
711       dataPos++;
712       result = [self parseRoute:YES];
713       parseWhiteSpaces(self, YES);      
714       if ((result = [self parseAddrSpec:YES])) {
715         parseWhiteSpaces(self, YES);
716         if (self->data[self->dataPos] == '>') {
717           self->dataPos++;
718           returnStatus = YES;
719         }
720       }
721       else if ((result = [self parseWord:YES])) {
722         parseWhiteSpaces(self, YES);
723         if (self->data[self->dataPos] == '>') {
724           self->dataPos++;
725           returnStatus = YES;
726         }
727       }
728     }
729     if (returnStatus) {
730       returnValue = yesNum;
731     }
732     else {
733       returnValue = nil;
734       dataPos     = keepPos;      
735     }
736     return returnValue;
737 }
738
739 - (id)parseRouteAddr:(BOOL)_guessMode {
740   NSMutableDictionary *returnValue  = nil;
741   int  keepPos;
742   id   result       = nil;
743   BOOL returnStatus = NO;
744   
745   if (_guessMode) 
746     return [self _parseRouteAddrInGuessMode];
747
748   keepPos     = self->dataPos;
749   returnValue = [NSMutableDictionary dictionaryWithCapacity:2];
750   if (self->data[self->dataPos] == '<') {
751     dataPos++;
752     if ((result = [self parseRoute:NO]))
753       [returnValue setObject:result forKey:@"route"];
754
755     parseWhiteSpaces(self, NO);
756     if ((result = [self parseAddrSpec:NO])) {
757         parseWhiteSpaces(self, NO);
758         if (self->data[self->dataPos] == '>') {
759           self->dataPos++;
760           [returnValue setObject:result forKey:@"address"];
761           returnStatus = YES;
762         }
763     }
764     else if ((result = [self parseWord:NO])) {
765         parseWhiteSpaces(self, NO);
766         if (self->data[self->dataPos] == '>') {
767           self->dataPos++;
768           [returnValue setObject:result forKey:@"address"];
769           returnStatus = YES;
770         }
771     }
772   }
773   if (!returnStatus) {
774     returnValue = nil;
775     if (!(self->errorPos == -1))
776       self->errorPos = self->dataPos;
777     self->dataPos    = keepPos;
778   }
779   return returnValue;
780 }
781
782 - (id)parseMailBox:(BOOL)_guessMode {
783   id   returnValue  = nil;
784   id   result       = nil;
785   int  keepPos      = self->dataPos;
786   BOOL returnStatus = NO;
787
788   if (_guessMode) {
789     if ((result = [self parseAddrSpec:YES])) {
790       returnStatus = YES;
791     }
792     else {
793       if ((result = [self parsePhrase:YES])) {
794         parseWhiteSpaces(self, YES);
795         if ((result = [self parseRouteAddr:YES])) {
796           returnStatus = YES;
797         }
798       }
799     }
800     if (!returnStatus) {
801       self->dataPos = keepPos;
802       returnValue  = nil;
803     }
804     else {
805       returnValue = yesNum;
806     }
807   }
808   else {
809     if ((result = [self parseAddrSpec:NO])) {
810       returnValue = [NGMailAddress mailAddressWithAddress:result
811                                    displayName:nil
812                                    route:nil];
813       returnStatus = YES;
814     }
815     else if ((result = [self parseRouteAddr:NO])) {
816       returnValue =
817         [NGMailAddress mailAddressWithAddress:
818                          [(NSDictionary *)result objectForKey:@"address"]
819                        displayName:nil
820                        route:nil];
821       returnStatus = YES;
822     }
823     else {
824       returnValue = [[[NGMailAddress alloc] init] autorelease];
825       
826       if ((result = [self parsePhrase:NO])) {
827         [returnValue setDisplayName:result];
828         parseWhiteSpaces(self, NO);        
829         if ((result = [self parseRouteAddr:NO])) {
830           [returnValue setAddress:
831                          [(NSDictionary *)result objectForKey:@"address"]];
832           [returnValue setRoute:
833                          [(NSDictionary *)result objectForKey:@"route"]];
834           returnStatus = YES;
835         }
836       }
837     }
838     if (!returnStatus) { /* try to read until eof or next ',' */
839       self->dataPos = keepPos;
840       
841       if ((result = [self parseRouteAddr:NO])) {
842         returnValue = [[[NGMailAddress alloc] init] autorelease];
843         [returnValue setAddress:
844                        [(NSDictionary *)result objectForKey:@"address"]];
845         returnStatus = YES;
846       }
847     }
848     if (!returnStatus) { /* try to read until eof or next ',' */
849       self->dataPos = keepPos;
850       
851       if ((result = [self parseWord:NO])) {
852         returnValue = [[[NGMailAddress alloc] init] autorelease];
853         [returnValue setAddress:result];
854         returnStatus = YES;
855       }
856     }
857     if (!returnStatus) {
858       if (!(self->errorPos == -1))
859         self->errorPos = self->dataPos;
860
861       self->dataPos = keepPos;
862       returnValue  = nil;
863     }
864   }
865   return returnValue;
866 }
867
868 - (id)parseGroup:(BOOL)_guessMode {
869   id   returnValue  = nil;
870   id   result       = nil;
871   int  keepPos      = self->dataPos;
872   BOOL returnStatus = NO;
873   
874   if (_guessMode) {
875     if ((result = [self parsePhrase:YES])) {
876       if (self->data[self->dataPos] == ':') { 
877         self->dataPos++;
878         parseWhiteSpaces(self, YES);                      
879         if ((result = [self parseMailBox:YES])) {
880           do {
881             parseWhiteSpaces(self, YES);              
882             result = nil;
883             if (self->data[self->dataPos] == ',') {
884               self->dataPos++;
885               parseWhiteSpaces(self, YES);              
886               result = [self parseMailBox:YES];
887             }
888           } while (result);
889           parseWhiteSpaces(self, YES);                        
890           if (self->data[self->dataPos] == ';') {
891             self->dataPos++;
892             returnStatus = YES;
893           }
894         }
895       }
896     }
897     if (!returnStatus) {
898       returnValue = nil;
899       self->dataPos = keepPos;
900     }
901     else {
902       returnValue = yesNum;
903     }
904   }
905   else {
906     returnValue = [[[NGMailAddressList alloc] init] autorelease];
907     if ((result = [self parsePhrase:NO])) {
908       [returnValue setGroupName:result];
909       if (self->data[self->dataPos] == ':') {
910         self->dataPos++;
911         parseWhiteSpaces(self, NO);
912         if ((result = [self parseMailBox:NO])) {
913           [returnValue addAddress:result];
914           do {
915             parseWhiteSpaces(self, NO);
916             result = nil;            
917             if (self->data[self->dataPos] == ',') {
918               self->dataPos++;
919               parseWhiteSpaces(self, NO);              
920               result = [self parseMailBox:NO];
921               if (result) {
922                 [returnValue addAddress:result];
923               }
924             }
925           } while (result);
926           parseWhiteSpaces(self, NO);                        
927           if (self->data[self->dataPos] == ';') {
928             self->dataPos++;
929             returnStatus = YES;
930           }
931         }
932       }
933     }
934     if (!returnStatus) {
935       returnValue = nil;
936       self->dataPos = keepPos;
937     }
938   }
939   return returnValue;
940 }
941
942 - (id)parseAddress:(BOOL)_guessMode {
943   id  returnValue = nil;
944   int keepPos     = self->dataPos;
945   
946   if (_guessMode) {
947     returnValue = [self parseMailBox:YES];
948     if (!returnValue)
949       returnValue = [self parseGroup:YES];
950     if (!returnValue)
951       self->dataPos = keepPos;
952   }
953   else {
954     returnValue = [self parseMailBox:NO];
955     if (!returnValue)
956       returnValue = [self parseGroup:NO];
957     if (!returnValue)
958       self->dataPos = keepPos;
959   }
960   return returnValue;
961 }
962
963 - (NSArray *)parseAddressList {
964   NGMailAddress  *address = nil;
965   NSMutableArray *addrs   = nil;
966
967   addrs = [NSMutableArray arrayWithCapacity:16];
968   while (self->dataPos < self->maxLength) {
969     address = [self parseAddress:NO];
970     if (address)
971       [addrs addObject:address];
972     else
973       break;
974     
975     if (self->dataPos < self->maxLength) {
976       parseWhiteSpaces(self, NO);
977       if (self->dataPos < self->maxLength) {
978         if (self->data[self->dataPos] == ',') {
979           self->dataPos++;
980           if (self->dataPos < self->maxLength)
981             parseWhiteSpaces(self, NO);
982         }
983       }
984     }
985   }
986   return [[addrs copy] autorelease];
987 }
988
989 - (id)parse { 
990   dataPos  = 0;
991   errorPos = -1;
992   return [self parseAddress:NO];
993 }
994
995 - (int)errorPosition {
996   return self->errorPos;
997 }
998
999 /* description */
1000
1001 - (NSString *)description {
1002   return [StrClass stringWithFormat:@"<%@[0x%08X]>",
1003                      NSStringFromClass([self class]),
1004                      (unsigned)self];
1005 }
1006
1007 @end /* NGMailAddressParser */