2 Copyright (C) 2000-2005 SKYRIX Software AG
4 This file is part of SOPE.
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
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.
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
22 #include "NGMailAddressParser.h"
23 #include "NGMailAddress.h"
24 #include "NGMailAddressList.h"
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;
40 @implementation NGMailAddressParser
42 static Class StrClass = Nil;
43 static NSNumber *yesNum = nil;
50 if (yesNum == nil) yesNum = [[NSNumber numberWithBool:YES] retain];
51 StrClass = [NSString class];
54 static inline NSString *mkStrObj(const unsigned char *s, unsigned int l) {
56 return [(NSString *)[StrClass alloc] initWithCString:(char *)s length:l];
59 static inline id parseWhiteSpaces(NGMailAddressParser *self, BOOL _guessMode) {
61 char text[self->maxLength];
64 while ((self->data[self->dataPos] == ' ') ||
65 (self->data[self->dataPos] == '\n')) {
74 [[(NSString *)[StrClass alloc] initWithCString:text length:length]
82 static inline id parseAtom(NGMailAddressParser *self, BOOL _guessMode) {
83 int keepPos = self->dataPos; // keep reference for backtracking
86 unsigned char text[self->maxLength + 2]; // token text
87 int length = 0; // token text length
91 if (self->dataPos == self->maxLength) { // end of text is reached
92 isAtom = (length > 0);
96 register unsigned char c = self->data[self->dataPos];
99 case '(' : case ')': case '<': case '>':
100 case '@' : case ',': case ';': case ':':
101 case '\\': case '"': case '.': case '[':
102 case ']' : case ' ': case 127:
103 isAtom = (length > 0);
109 isAtom = (length > 0);
113 text[length] = c; // store char in text
114 length++; // increase text size
115 (self->dataPos)++; // go ahead
124 NSCAssert(length > 0, @"no atom with length=0");
125 returnValue = yesNum;
128 NSCAssert(length > 0, @"no atom with length=0");
129 returnValue = [mkStrObj(text, length) autorelease];
130 NSCAssert([returnValue isKindOfClass:StrClass], @"got no string ..");
134 self->dataPos = keepPos;
140 static inline id parseQuotedPair(NGMailAddressParser *self, BOOL _guessMode) {
141 id returnValue = nil;
143 if ((self->maxLength - (self->dataPos)) < 3) {
147 if (self->data[self->dataPos] == '\\') {
148 self->dataPos = self->dataPos + 2;
150 returnValue = yesNum;
153 [mkStrObj(&(self->data[self->dataPos - 1]), 1) autorelease];
160 static inline id parseQText(NGMailAddressParser *self, BOOL _guessMode) {
161 int keepPos = self->dataPos; // keep reference for backtracking
162 id returnValue = nil;
164 unsigned char text[self->maxLength + 4]; // token text
165 int length = 0; // token text length
169 if (self->dataPos == self->maxLength) { // end of text is reached
170 isQText = (length > 0);
174 register char c = self->data[self->dataPos];
180 isQText = (length > 0);
185 text[length] = c; // store char in text
186 length++; // increase text size
187 (self->dataPos)++; // go ahead
196 NSCAssert(length > 0, @"no qtext with length=0");
197 returnValue = yesNum;
200 NSCAssert(length > 0, @"no qtext with length=0");
201 returnValue = [mkStrObj(text, length) autorelease];
202 NSCAssert([returnValue isKindOfClass:StrClass],
203 @"got no string ..");
207 self->dataPos = keepPos;
213 static inline id parseDText(NGMailAddressParser *self, BOOL _guessMode) {
214 int keepPos = self->dataPos; // keep reference for backtracking
215 id returnValue = nil;
217 unsigned char text[self->maxLength]; // token text
218 int length = 0; // token text length
222 if (self->dataPos == self->maxLength) { // end of text is reached
223 isDText = (length > 0);
227 register char c = self->data[self->dataPos];
232 isDText = (length > 0);
237 text[length] = c; // store char in text
238 length++; // increase text size
239 (self->dataPos)++; // go ahead
248 NSCAssert(length > 0, @"no dtext with length=0");
249 returnValue = yesNum;
252 NSCAssert(length > 0, @"no dtext with length=0");
253 returnValue = [mkStrObj(text, length) autorelease];
254 NSCAssert([returnValue isKindOfClass:StrClass],
255 @"got no string ..");
259 self->dataPos = keepPos;
265 static inline id parseDomainLiteral(NGMailAddressParser *self, BOOL _guessMode) {
266 int keepPos = self->dataPos;
267 id returnValue = nil;
271 if (self->data[self->dataPos] != '[')
274 (self->dataPos)++; // skip starting '"'
276 // parses: "suafdjksfd \"sdafsadf"
277 while (self->data[self->dataPos] != ']') {
278 if (self->data[self->dataPos] == '\\') {// skip quoted chars
282 if (self->dataPos >= self->maxLength) {
286 (self->dataPos)++; // skip closing '"'
287 returnValue = yesNum;
290 if (self->data[self->dataPos++] == '[') {
294 ms = [NSMutableString stringWithCapacity:10];
296 if ((result = parseQuotedPair(self, NO)))
297 [ms appendString:result];
299 if ((result = parseDText(self, NO)))
300 [ms appendString:result];
306 if (self->data[self->dataPos++] == ']')
313 self->dataPos = keepPos;
321 + (id)mailAddressParserWithData:(NSData *)_data {
322 return [[(NGMailAddressParser *)[self alloc]
323 initWithCString:[_data bytes]
324 length:[_data length]] autorelease];
326 + (id)mailAddressParserWithCString:(char *)_cString {
327 return [[(NGMailAddressParser *)[self alloc]
328 initWithCString:(unsigned char *)_cString
329 length:strlen(_cString)] autorelease];
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;
342 - (id)initWithString:(NSString *)_str {
344 return [self initWithCString:(unsigned char *)[_str cString]
345 length:[_str cStringLength]];
349 return [self initWithCString:NULL length:0];
352 + (id)mailAddressParserWithString:(NSString *)_string {
353 return [[(NGMailAddressParser *)[self alloc] initWithString:_string]
366 - (id)_parseQuotedStringInGuessMode {
369 if (self->data[self->dataPos] != '"')
372 keepPos = self->dataPos;
373 (self->dataPos)++; // skip starting '"'
375 // parses: "suafdjksfd \"sdafsadf"
376 while (self->data[self->dataPos] != '"') {
377 if (self->data[self->dataPos] == '\\') /* skip quoted chars */
381 if (self->dataPos >= self->maxLength) {
382 self->dataPos = keepPos;
386 (self->dataPos)++; // skip closing '"'
390 - (id)parseQuotedString:(BOOL)_guessMode {
391 int keepPos = self->dataPos;
392 id returnValue = nil;
396 return [self _parseQuotedStringInGuessMode];
398 if (data[dataPos++] == '"') {
402 ms = [NSMutableString stringWithCapacity:10];
404 if ((result = parseQuotedPair(self, NO)))
405 [ms appendString:result];
407 if ((result = parseQText(self, NO)))
408 [ms appendString:result];
414 if (data[dataPos++] == '"')
424 - (id)parseWord:(BOOL)_guessMode {
427 if ((returnValue = [self parseQuotedString:_guessMode]) == nil)
428 returnValue = parseAtom(self, _guessMode);
433 - (id)_parsePhraseInGuessMode {
435 id returnValue = nil;
439 if ((result = parseWhiteSpaces(self, YES))) {
444 if ((result = [self parseWord:YES])) {
446 [(NSMutableString *)returnValue appendString:result];
447 result = parseWhiteSpaces(self, YES);
452 return !isPhrase ? nil : yesNum;
455 - (id)parsePhrase:(BOOL)_guessMode {
457 id returnValue = nil;
462 return [self _parsePhraseInGuessMode];
464 returnValue = [NSMutableString stringWithCapacity:10];
468 if ((result = parseWhiteSpaces(self, _guessMode))) {
472 // [returnValue appendString:result];
474 else if ((result = [self parseWord:_guessMode])) {
478 [(NSMutableString *)returnValue appendString:tmp];
482 [(NSMutableString *)returnValue appendString:result];
483 if (self->dataPos < self->maxLength) {
484 if (self->data[self->dataPos] == '.') {
485 [(NSMutableString *)returnValue appendString:@"."];
493 if (!isPhrase || ([returnValue length] == 0))
499 - (id)_parseLocalPartInGuessMode {
502 if (![self parseWord:YES])
507 if (self->data[self->dataPos] == '.') {
509 result = [self parseWord:YES];
517 - (id)parseLocalPart:(BOOL)_guessMode {
519 id returnValue = nil;
520 NSString *result = nil;
523 return [self _parseLocalPartInGuessMode];
525 if ((returnValue = [self parseWord:NO]) == nil)
528 ms = [[returnValue mutableCopy] autorelease];
531 if (self->data[self->dataPos] == '.') {
533 result = [self parseWord:NO];
536 NSAssert([result isKindOfClass:StrClass],
537 @"parseWord should return string");
539 [ms appendString:@"."];
540 [ms appendString:result];
546 while (result != nil);
551 - (id)_parseDomainInGuessMode {
552 id returnValue = nil;
555 returnValue = parseAtom(self, YES);
557 returnValue = parseDomainLiteral(self, YES);
561 if (self->data[self->dataPos] == '.') {
563 result = parseAtom(self,YES);
565 result = parseDomainLiteral(self, YES);
572 - (id)parseDomain:(BOOL)_guessMode {
577 return [self _parseDomainInGuessMode];
579 if ((result = parseAtom(self, NO)) == nil)
580 result = parseDomainLiteral(self, NO);
585 ms = [[result mutableCopyWithZone:[self zone]] autorelease];
587 if (self->data[self->dataPos] == '.') {
590 result = parseAtom(self,NO);
592 result = parseDomainLiteral(self, NO);
595 [ms appendString:@"."];
596 [ms appendString:result];
606 - (id)parseAddrSpec:(BOOL)_guessMode {
607 NSMutableString *returnValue = nil;
609 int keepPos = self->dataPos;
610 BOOL returnStatus = NO;
616 if ([self parseLocalPart:YES]) {
617 if (self->data[self->dataPos] == '@') {
619 if ([self parseDomain:YES]) {
627 if ((result = [self parseLocalPart:NO]) != nil) {
628 returnValue = [[result mutableCopy] autorelease];
631 if (self->data[self->dataPos] == '@') {
634 if ((result = [self parseDomain:NO])) {
635 [returnValue appendString:@"@"];
636 [returnValue appendString:result];
648 - (id)_parseRouteInGuessMode {
650 int keepPos = self->dataPos;
653 if (self->data[self->dataPos] == '@') {
655 if ((result = [self parseDomain:YES]))
659 parseWhiteSpaces(self,YES);
660 status = (self->data[self->dataPos] == ':') ? YES : NO;
665 self->dataPos = keepPos;
668 - (id)parseRoute:(BOOL)_guessMode {
669 NSMutableString *returnValue;
675 return [self _parseRouteInGuessMode];
677 keepPos = self->dataPos;
678 returnValue = [NSMutableString stringWithCapacity:10];
679 if (self->data[self->dataPos] == '@') {
682 if ((result = [self parseDomain:NO])) {
684 [returnValue appendString:result];
688 parseWhiteSpaces(self,NO);
689 if (self->data[self->dataPos] == ':') {
699 self->dataPos = keepPos;
704 - (id)_parseRouteAddrInGuessMode {
705 int keepPos = self->dataPos;
706 id returnValue = nil;
708 BOOL returnStatus = NO;
710 if (self->data[self->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] == '>') {
721 else if ((result = [self parseWord:YES])) {
722 parseWhiteSpaces(self, YES);
723 if (self->data[self->dataPos] == '>') {
730 returnValue = yesNum;
739 - (id)parseRouteAddr:(BOOL)_guessMode {
740 NSMutableDictionary *returnValue = nil;
743 BOOL returnStatus = NO;
746 return [self _parseRouteAddrInGuessMode];
748 keepPos = self->dataPos;
749 returnValue = [NSMutableDictionary dictionaryWithCapacity:2];
750 if (self->data[self->dataPos] == '<') {
752 if ((result = [self parseRoute:NO]))
753 [returnValue setObject:result forKey:@"route"];
755 parseWhiteSpaces(self, NO);
756 if ((result = [self parseAddrSpec:NO])) {
757 parseWhiteSpaces(self, NO);
758 if (self->data[self->dataPos] == '>') {
760 [returnValue setObject:result forKey:@"address"];
764 else if ((result = [self parseWord:NO])) {
765 parseWhiteSpaces(self, NO);
766 if (self->data[self->dataPos] == '>') {
768 [returnValue setObject:result forKey:@"address"];
775 if (!(self->errorPos == -1))
776 self->errorPos = self->dataPos;
777 self->dataPos = keepPos;
782 - (id)parseMailBox:(BOOL)_guessMode {
783 id returnValue = nil;
785 int keepPos = self->dataPos;
786 BOOL returnStatus = NO;
789 if ((result = [self parseAddrSpec:YES])) {
793 if ((result = [self parsePhrase:YES])) {
794 parseWhiteSpaces(self, YES);
795 if ((result = [self parseRouteAddr:YES])) {
801 self->dataPos = keepPos;
805 returnValue = yesNum;
809 if ((result = [self parseAddrSpec:NO])) {
810 returnValue = [NGMailAddress mailAddressWithAddress:result
815 else if ((result = [self parseRouteAddr:NO])) {
817 [NGMailAddress mailAddressWithAddress:
818 [(NSDictionary *)result objectForKey:@"address"]
824 returnValue = [[[NGMailAddress alloc] init] autorelease];
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"]];
838 if (!returnStatus) { /* try to read until eof or next ',' */
839 self->dataPos = keepPos;
841 if ((result = [self parseRouteAddr:NO])) {
842 returnValue = [[[NGMailAddress alloc] init] autorelease];
843 [returnValue setAddress:
844 [(NSDictionary *)result objectForKey:@"address"]];
848 if (!returnStatus) { /* try to read until eof or next ',' */
849 self->dataPos = keepPos;
851 if ((result = [self parseWord:NO])) {
852 returnValue = [[[NGMailAddress alloc] init] autorelease];
853 [returnValue setAddress:result];
858 if (!(self->errorPos == -1))
859 self->errorPos = self->dataPos;
861 self->dataPos = keepPos;
868 - (id)parseGroup:(BOOL)_guessMode {
869 id returnValue = nil;
871 int keepPos = self->dataPos;
872 BOOL returnStatus = NO;
875 if ((result = [self parsePhrase:YES])) {
876 if (self->data[self->dataPos] == ':') {
878 parseWhiteSpaces(self, YES);
879 if ((result = [self parseMailBox:YES])) {
881 parseWhiteSpaces(self, YES);
883 if (self->data[self->dataPos] == ',') {
885 parseWhiteSpaces(self, YES);
886 result = [self parseMailBox:YES];
889 parseWhiteSpaces(self, YES);
890 if (self->data[self->dataPos] == ';') {
899 self->dataPos = keepPos;
902 returnValue = yesNum;
906 returnValue = [[[NGMailAddressList alloc] init] autorelease];
907 if ((result = [self parsePhrase:NO])) {
908 [returnValue setGroupName:result];
909 if (self->data[self->dataPos] == ':') {
911 parseWhiteSpaces(self, NO);
912 if ((result = [self parseMailBox:NO])) {
913 [returnValue addAddress:result];
915 parseWhiteSpaces(self, NO);
917 if (self->data[self->dataPos] == ',') {
919 parseWhiteSpaces(self, NO);
920 result = [self parseMailBox:NO];
922 [returnValue addAddress:result];
926 parseWhiteSpaces(self, NO);
927 if (self->data[self->dataPos] == ';') {
936 self->dataPos = keepPos;
942 - (id)parseAddress:(BOOL)_guessMode {
943 id returnValue = nil;
944 int keepPos = self->dataPos;
947 returnValue = [self parseMailBox:YES];
949 returnValue = [self parseGroup:YES];
951 self->dataPos = keepPos;
954 returnValue = [self parseMailBox:NO];
956 returnValue = [self parseGroup:NO];
958 self->dataPos = keepPos;
963 - (NSArray *)parseAddressList {
964 NGMailAddress *address = nil;
965 NSMutableArray *addrs = nil;
967 addrs = [NSMutableArray arrayWithCapacity:16];
968 while (self->dataPos < self->maxLength) {
969 address = [self parseAddress:NO];
971 [addrs addObject:address];
975 if (self->dataPos < self->maxLength) {
976 parseWhiteSpaces(self, NO);
977 if (self->dataPos < self->maxLength) {
978 if (self->data[self->dataPos] == ',') {
980 if (self->dataPos < self->maxLength)
981 parseWhiteSpaces(self, NO);
986 return [[addrs copy] autorelease];
992 return [self parseAddress:NO];
995 - (int)errorPosition {
996 return self->errorPos;
1001 - (NSString *)description {
1002 return [StrClass stringWithFormat:@"<%@[0x%08X]>",
1003 NSStringFromClass([self class]),
1007 @end /* NGMailAddressParser */