/* Copyright (C) 2000-2005 SKYRIX Software AG This file is part of SOPE. SOPE is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. SOPE is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with SOPE; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "NGMailAddressParser.h" #include "NGMailAddress.h" #include "NGMailAddressList.h" #include "common.h" @interface NGMailAddressParser(PrivateMethods) - (id)parseQuotedString:(BOOL)_guestMode; - (id)parseWord:(BOOL)_guestMode; - (id)parsePhrase:(BOOL)_guestMode; - (id)parseLocalPart:(BOOL)_guestMode; - (id)parseDomain:(BOOL)_guestMode; - (id)parseAddrSpec:(BOOL)_guestMode; - (id)parseRouteAddr:(BOOL)_guessMode; - (id)parseGroup:(BOOL)_guessMode; - (id)parseMailBox:(BOOL)_guessMode; - (id)parseAddress:(BOOL)_guessMode; @end @implementation NGMailAddressParser static Class StrClass = Nil; static NSNumber *yesNum = nil; + (int)version { return 2; } + (void)initialize { if (yesNum == nil) yesNum = [[NSNumber numberWithBool:YES] retain]; StrClass = [NSString class]; } static inline NSString *mkStrObj(const unsigned char *s, unsigned int l) { // TODO: unicode return [(NSString *)[StrClass alloc] initWithCString:(char *)s length:l]; } static inline id parseWhiteSpaces(NGMailAddressParser *self, BOOL _guessMode) { id returnValue = nil; char text[self->maxLength]; int length = 0; while ((self->data[self->dataPos] == ' ') || (self->data[self->dataPos] == '\n')) { text[length++] = ' '; self->dataPos++; } if (length) { if (_guessMode) returnValue = yesNum; else { returnValue = [[(NSString *)[StrClass alloc] initWithCString:text length:length] autorelease]; } } return returnValue; } static inline id parseAtom(NGMailAddressParser *self, BOOL _guessMode) { int keepPos = self->dataPos; // keep reference for backtracking id returnValue = nil; BOOL isAtom = YES; unsigned char text[self->maxLength + 2]; // token text int length = 0; // token text length BOOL done = NO; do { if (self->dataPos == self->maxLength) { // end of text is reached isAtom = (length > 0); done = YES; } else { register unsigned char c = self->data[self->dataPos]; switch (c) { case '(' : case ')': case '<': case '>': case '@' : case ',': case ';': case ':': case '\\': case '"': case '.': case '[': case ']' : case ' ': case 127: isAtom = (length > 0); done = YES; break; default: if (c < 32) { isAtom = (length > 0); done = YES; } else { text[length] = c; // store char in text length++; // increase text size (self->dataPos)++; // go ahead } } } } while (!done); if (isAtom) { if (_guessMode) { NSCAssert(length > 0, @"no atom with length=0"); returnValue = yesNum; } else { NSCAssert(length > 0, @"no atom with length=0"); returnValue = [mkStrObj(text, length) autorelease]; NSCAssert([returnValue isKindOfClass:StrClass], @"got no string .."); } } else { self->dataPos = keepPos; returnValue = nil; } return returnValue; } static inline id parseQuotedPair(NGMailAddressParser *self, BOOL _guessMode) { id returnValue = nil; if ((self->maxLength - (self->dataPos)) < 3) { returnValue = nil; } else { if (self->data[self->dataPos] == '\\') { self->dataPos = self->dataPos + 2; if (_guessMode) returnValue = yesNum; else { returnValue = [mkStrObj(&(self->data[self->dataPos - 1]), 1) autorelease]; } } } return returnValue; } static inline id parseQText(NGMailAddressParser *self, BOOL _guessMode) { int keepPos = self->dataPos; // keep reference for backtracking id returnValue = nil; BOOL isQText = YES; unsigned char text[self->maxLength + 4]; // token text int length = 0; // token text length BOOL done = YES; do { if (self->dataPos == self->maxLength) { // end of text is reached isQText = (length > 0); done = YES; } else { register char c = self->data[self->dataPos]; switch ((int)c) { case '"' : case '\\': case 13 : isQText = (length > 0); done = YES; break; default: { text[length] = c; // store char in text length++; // increase text size (self->dataPos)++; // go ahead } } } } while (!done); if (isQText) { if (_guessMode) { NSCAssert(length > 0, @"no qtext with length=0"); returnValue = yesNum; } else { NSCAssert(length > 0, @"no qtext with length=0"); returnValue = [mkStrObj(text, length) autorelease]; NSCAssert([returnValue isKindOfClass:StrClass], @"got no string .."); } } else { self->dataPos = keepPos; returnValue = nil; } return returnValue; } static inline id parseDText(NGMailAddressParser *self, BOOL _guessMode) { int keepPos = self->dataPos; // keep reference for backtracking id returnValue = nil; BOOL isDText = YES; unsigned char text[self->maxLength]; // token text int length = 0; // token text length BOOL done = YES; do { if (self->dataPos == self->maxLength) { // end of text is reached isDText = (length > 0); done = YES; } else { register char c = self->data[self->dataPos]; switch ((int)c) { case '[': case ']': case '\\': case 13: isDText = (length > 0); done = YES; break; default: { text[length] = c; // store char in text length++; // increase text size (self->dataPos)++; // go ahead } } } } while (!done); if (isDText) { if (_guessMode) { NSCAssert(length > 0, @"no dtext with length=0"); returnValue = yesNum; } else { NSCAssert(length > 0, @"no dtext with length=0"); returnValue = [mkStrObj(text, length) autorelease]; NSCAssert([returnValue isKindOfClass:StrClass], @"got no string .."); } } else { self->dataPos = keepPos; returnValue = nil; } return returnValue; } static inline id parseDomainLiteral(NGMailAddressParser *self, BOOL _guessMode) { int keepPos = self->dataPos; id returnValue = nil; BOOL returnOK = NO; if (_guessMode) { if (self->data[self->dataPos] != '[') return nil; (self->dataPos)++; // skip starting '"' // parses: "suafdjksfd \"sdafsadf" while (self->data[self->dataPos] != ']') { if (self->data[self->dataPos] == '\\') {// skip quoted chars (self->dataPos)++; } (self->dataPos)++; if (self->dataPos >= self->maxLength) { return nil; } } (self->dataPos)++; // skip closing '"' returnValue = yesNum; } else { if (self->data[self->dataPos++] == '[') { NSMutableString *ms; id result = nil; ms = [NSMutableString stringWithCapacity:10]; do { if ((result = parseQuotedPair(self, NO))) [ms appendString:result]; else { if ((result = parseDText(self, NO))) [ms appendString:result]; } } while (result); returnValue = ms; if (self->data[self->dataPos++] == ']') returnOK = YES; } if (!returnOK) { if (returnValue) returnValue = nil; self->dataPos = keepPos; } } return returnValue; } /* constructors */ + (id)mailAddressParserWithData:(NSData *)_data { return [[(NGMailAddressParser *)[self alloc] initWithCString:[_data bytes] length:[_data length]] autorelease]; } + (id)mailAddressParserWithCString:(char *)_cString { return [[(NGMailAddressParser *)[self alloc] initWithCString:(unsigned char *)_cString length:strlen(_cString)] autorelease]; } - (id)initWithCString:(const unsigned char *)_cstr length:(int unsigned)_len { if ((self = [super init])) { // TODO: remember some string encoding? self->data = (unsigned char *)_cstr; self->maxLength = _len; self->dataPos = 0; self->errorPos = -1; } return self; } - (id)initWithString:(NSString *)_str { // TODO: unicode return [self initWithCString:(unsigned char *)[_str cString] length:[_str cStringLength]]; } - (id)init { return [self initWithCString:NULL length:0]; } + (id)mailAddressParserWithString:(NSString *)_string { return [[(NGMailAddressParser *)[self alloc] initWithString:_string] autorelease]; } - (void)dealloc { self->data = NULL; self->maxLength = 0; self->dataPos = 0; [super dealloc]; } /* parsing */ - (id)_parseQuotedStringInGuessMode { int keepPos; if (self->data[self->dataPos] != '"') return nil; keepPos = self->dataPos; (self->dataPos)++; // skip starting '"' // parses: "suafdjksfd \"sdafsadf" while (self->data[self->dataPos] != '"') { if (self->data[self->dataPos] == '\\') /* skip quoted chars */ (self->dataPos)++; (self->dataPos)++; if (self->dataPos >= self->maxLength) { self->dataPos = keepPos; return nil; } } (self->dataPos)++; // skip closing '"' return yesNum; } - (id)parseQuotedString:(BOOL)_guessMode { int keepPos = self->dataPos; id returnValue = nil; BOOL returnOK = NO; if (_guessMode) return [self _parseQuotedStringInGuessMode]; if (data[dataPos++] == '"') { NSMutableString *ms; id result = nil; ms = [NSMutableString stringWithCapacity:10]; do { if ((result = parseQuotedPair(self, NO))) [ms appendString:result]; else { if ((result = parseQText(self, NO))) [ms appendString:result]; } } while (result); returnValue = ms; if (data[dataPos++] == '"') returnOK = YES; } if (!returnOK) { returnValue = nil; dataPos = keepPos; } return returnValue; } - (id)parseWord:(BOOL)_guessMode { id returnValue; if ((returnValue = [self parseQuotedString:_guessMode]) == nil) returnValue = parseAtom(self, _guessMode); return returnValue; } - (id)_parsePhraseInGuessMode { BOOL isPhrase = NO; id returnValue = nil; id result; do { if ((result = parseWhiteSpaces(self, YES))) { isPhrase = YES; continue; } if ((result = [self parseWord:YES])) { isPhrase = YES; [(NSMutableString *)returnValue appendString:result]; result = parseWhiteSpaces(self, YES); } } while (result); return !isPhrase ? nil : yesNum; } - (id)parsePhrase:(BOOL)_guessMode { BOOL isPhrase = NO; id returnValue = nil; id result = nil; NSString *tmp; if (_guessMode) return [self _parsePhraseInGuessMode]; returnValue = [NSMutableString stringWithCapacity:10]; tmp = nil; do { if ((result = parseWhiteSpaces(self, _guessMode))) { tmp = result; ; // isPhrase = YES; // [returnValue appendString:result]; } else if ((result = [self parseWord:_guessMode])) { isPhrase = YES; if (tmp) [(NSMutableString *)returnValue appendString:tmp]; tmp = nil; [(NSMutableString *)returnValue appendString:result]; if (self->dataPos < self->maxLength) { if (self->data[self->dataPos] == '.') { [(NSMutableString *)returnValue appendString:@"."]; self->dataPos++; } } } } while (result); if (!isPhrase || ([returnValue length] == 0)) returnValue = nil; return returnValue; } - (id)_parseLocalPartInGuessMode { id result; if (![self parseWord:YES]) return nil; do { result = nil; if (self->data[self->dataPos] == '.') { self->dataPos++; result = [self parseWord:YES]; } } while (result); return yesNum; } - (id)parseLocalPart:(BOOL)_guessMode { NSMutableString *ms; id returnValue = nil; NSString *result = nil; if (_guessMode) return [self _parseLocalPartInGuessMode]; if ((returnValue = [self parseWord:NO]) == nil) return nil; ms = [[returnValue mutableCopy] autorelease]; do { if (self->data[self->dataPos] == '.') { self->dataPos++; result = [self parseWord:NO]; if (result) { NSAssert([result isKindOfClass:StrClass], @"parseWord should return string"); [ms appendString:@"."]; [ms appendString:result]; } } else result = nil; } while (result != nil); return ms; } - (id)_parseDomainInGuessMode { id returnValue = nil; id result = nil; returnValue = parseAtom(self, YES); if (!result) returnValue = parseDomainLiteral(self, YES); if (returnValue) { do { result = nil; if (self->data[self->dataPos] == '.') { self->dataPos++; result = parseAtom(self,YES); if (!result) result = parseDomainLiteral(self, YES); } } while (result); } return returnValue; } - (id)parseDomain:(BOOL)_guessMode { NSMutableString *ms; id result; if (_guessMode) return [self _parseDomainInGuessMode]; if ((result = parseAtom(self, NO)) == nil) result = parseDomainLiteral(self, NO); if (result == nil) return nil; ms = [[result mutableCopyWithZone:[self zone]] autorelease]; do { if (self->data[self->dataPos] == '.') { self->dataPos++; result = parseAtom(self,NO); if (result == nil) result = parseDomainLiteral(self, NO); if (result) { [ms appendString:@"."]; [ms appendString:result]; } } else result = nil; } while (result); return ms; } - (id)parseAddrSpec:(BOOL)_guessMode { NSMutableString *returnValue = nil; id result; int keepPos = self->dataPos; BOOL returnStatus = NO; if (_guessMode) { id ret; ret = nil; if ([self parseLocalPart:YES]) { if (self->data[self->dataPos] == '@') { dataPos++; if ([self parseDomain:YES]) { ret = yesNum; } } } return ret; } if ((result = [self parseLocalPart:NO]) != nil) { returnValue = [[result mutableCopy] autorelease]; result = nil; if (self->data[self->dataPos] == '@') { self->dataPos++; if ((result = [self parseDomain:NO])) { [returnValue appendString:@"@"]; [returnValue appendString:result]; returnStatus = YES; } } } if (!returnStatus) { returnValue = nil; dataPos = keepPos; } return returnValue; } - (id)_parseRouteInGuessMode { id result = nil; int keepPos = self->dataPos; BOOL status = YES; if (self->data[self->dataPos] == '@') { status = NO; if ((result = [self parseDomain:YES])) status = YES; } if (status) { parseWhiteSpaces(self,YES); status = (self->data[self->dataPos] == ':') ? YES : NO; } if (status) return yesNum; self->dataPos = keepPos; return nil; } - (id)parseRoute:(BOOL)_guessMode { NSMutableString *returnValue; id result = nil; int keepPos; BOOL status = YES; if (_guessMode) return [self _parseRouteInGuessMode]; keepPos = self->dataPos; returnValue = [NSMutableString stringWithCapacity:10]; if (self->data[self->dataPos] == '@') { status = NO; self->dataPos++; if ((result = [self parseDomain:NO])) { status = YES; [returnValue appendString:result]; } } if (status) { parseWhiteSpaces(self,NO); if (self->data[self->dataPos] == ':') { status = YES; self->dataPos++; } else { status = NO; } } if (!status) { returnValue = nil; self->dataPos = keepPos; } return returnValue; } - (id)_parseRouteAddrInGuessMode { int keepPos = self->dataPos; id returnValue = nil; id result = nil; BOOL returnStatus = NO; if (self->data[self->dataPos] == '<') { dataPos++; result = [self parseRoute:YES]; parseWhiteSpaces(self, YES); if ((result = [self parseAddrSpec:YES])) { parseWhiteSpaces(self, YES); if (self->data[self->dataPos] == '>') { self->dataPos++; returnStatus = YES; } } else if ((result = [self parseWord:YES])) { parseWhiteSpaces(self, YES); if (self->data[self->dataPos] == '>') { self->dataPos++; returnStatus = YES; } } } if (returnStatus) { returnValue = yesNum; } else { returnValue = nil; dataPos = keepPos; } return returnValue; } - (id)parseRouteAddr:(BOOL)_guessMode { NSMutableDictionary *returnValue = nil; int keepPos; id result = nil; BOOL returnStatus = NO; if (_guessMode) return [self _parseRouteAddrInGuessMode]; keepPos = self->dataPos; returnValue = [NSMutableDictionary dictionaryWithCapacity:2]; if (self->data[self->dataPos] == '<') { dataPos++; if ((result = [self parseRoute:NO])) [returnValue setObject:result forKey:@"route"]; parseWhiteSpaces(self, NO); if ((result = [self parseAddrSpec:NO])) { parseWhiteSpaces(self, NO); if (self->data[self->dataPos] == '>') { self->dataPos++; [returnValue setObject:result forKey:@"address"]; returnStatus = YES; } } else if ((result = [self parseWord:NO])) { parseWhiteSpaces(self, NO); if (self->data[self->dataPos] == '>') { self->dataPos++; [returnValue setObject:result forKey:@"address"]; returnStatus = YES; } } } if (!returnStatus) { returnValue = nil; if (!(self->errorPos == -1)) self->errorPos = self->dataPos; self->dataPos = keepPos; } return returnValue; } - (id)parseMailBox:(BOOL)_guessMode { id returnValue = nil; id result = nil; int keepPos = self->dataPos; BOOL returnStatus = NO; if (_guessMode) { if ((result = [self parseAddrSpec:YES])) { returnStatus = YES; } else { if ((result = [self parsePhrase:YES])) { parseWhiteSpaces(self, YES); if ((result = [self parseRouteAddr:YES])) { returnStatus = YES; } } } if (!returnStatus) { self->dataPos = keepPos; returnValue = nil; } else { returnValue = yesNum; } } else { if ((result = [self parseAddrSpec:NO])) { returnValue = [NGMailAddress mailAddressWithAddress:result displayName:nil route:nil]; returnStatus = YES; } else if ((result = [self parseRouteAddr:NO])) { returnValue = [NGMailAddress mailAddressWithAddress: [(NSDictionary *)result objectForKey:@"address"] displayName:nil route:nil]; returnStatus = YES; } else { returnValue = [[[NGMailAddress alloc] init] autorelease]; if ((result = [self parsePhrase:NO])) { [returnValue setDisplayName:result]; parseWhiteSpaces(self, NO); if ((result = [self parseRouteAddr:NO])) { [returnValue setAddress: [(NSDictionary *)result objectForKey:@"address"]]; [returnValue setRoute: [(NSDictionary *)result objectForKey:@"route"]]; returnStatus = YES; } } } if (!returnStatus) { /* try to read until eof or next ',' */ self->dataPos = keepPos; if ((result = [self parseRouteAddr:NO])) { returnValue = [[[NGMailAddress alloc] init] autorelease]; [returnValue setAddress: [(NSDictionary *)result objectForKey:@"address"]]; returnStatus = YES; } } if (!returnStatus) { /* try to read until eof or next ',' */ self->dataPos = keepPos; if ((result = [self parseWord:NO])) { returnValue = [[[NGMailAddress alloc] init] autorelease]; [returnValue setAddress:result]; returnStatus = YES; } } if (!returnStatus) { if (!(self->errorPos == -1)) self->errorPos = self->dataPos; self->dataPos = keepPos; returnValue = nil; } } return returnValue; } - (id)parseGroup:(BOOL)_guessMode { id returnValue = nil; id result = nil; int keepPos = self->dataPos; BOOL returnStatus = NO; if (_guessMode) { if ((result = [self parsePhrase:YES])) { if (self->data[self->dataPos] == ':') { self->dataPos++; parseWhiteSpaces(self, YES); if ((result = [self parseMailBox:YES])) { do { parseWhiteSpaces(self, YES); result = nil; if (self->data[self->dataPos] == ',') { self->dataPos++; parseWhiteSpaces(self, YES); result = [self parseMailBox:YES]; } } while (result); parseWhiteSpaces(self, YES); if (self->data[self->dataPos] == ';') { self->dataPos++; returnStatus = YES; } } } } if (!returnStatus) { returnValue = nil; self->dataPos = keepPos; } else { returnValue = yesNum; } } else { returnValue = [[[NGMailAddressList alloc] init] autorelease]; if ((result = [self parsePhrase:NO])) { [returnValue setGroupName:result]; if (self->data[self->dataPos] == ':') { self->dataPos++; parseWhiteSpaces(self, NO); if ((result = [self parseMailBox:NO])) { [returnValue addAddress:result]; do { parseWhiteSpaces(self, NO); result = nil; if (self->data[self->dataPos] == ',') { self->dataPos++; parseWhiteSpaces(self, NO); result = [self parseMailBox:NO]; if (result) { [returnValue addAddress:result]; } } } while (result); parseWhiteSpaces(self, NO); if (self->data[self->dataPos] == ';') { self->dataPos++; returnStatus = YES; } } } } if (!returnStatus) { returnValue = nil; self->dataPos = keepPos; } } return returnValue; } - (id)parseAddress:(BOOL)_guessMode { id returnValue = nil; int keepPos = self->dataPos; if (_guessMode) { returnValue = [self parseMailBox:YES]; if (!returnValue) returnValue = [self parseGroup:YES]; if (!returnValue) self->dataPos = keepPos; } else { returnValue = [self parseMailBox:NO]; if (!returnValue) returnValue = [self parseGroup:NO]; if (!returnValue) self->dataPos = keepPos; } return returnValue; } - (NSArray *)parseAddressList { NGMailAddress *address = nil; NSMutableArray *addrs = nil; addrs = [NSMutableArray arrayWithCapacity:16]; while (self->dataPos < self->maxLength) { address = [self parseAddress:NO]; if (address) [addrs addObject:address]; else break; if (self->dataPos < self->maxLength) { parseWhiteSpaces(self, NO); if (self->dataPos < self->maxLength) { if (self->data[self->dataPos] == ',') { self->dataPos++; if (self->dataPos < self->maxLength) parseWhiteSpaces(self, NO); } } } } return [[addrs copy] autorelease]; } - (id)parse { dataPos = 0; errorPos = -1; return [self parseAddress:NO]; } - (int)errorPosition { return self->errorPos; } /* description */ - (NSString *)description { return [StrClass stringWithFormat:@"<%@[0x%08X]>", NSStringFromClass([self class]), (unsigned)self]; } @end /* NGMailAddressParser */