//---------------------------------------------------------------------------------------
//	MailAddress.h created by moritz on Mon 09-Mar-1998
//	This code is part of the Alexandra Newsreader Project. For copyright details see
//	GNU public license version 2 or above. No warranties implied. Use at own risk.
//	More information can be found at <http://www.object-factory.com/Alexandra>.
//	@(#)$Id: MailAddress.m,v 1.7 1998/10/22 14:27:20 erik Exp $
//---------------------------------------------------------------------------------------

#import "Utilities.h"
#import "Message.h"
#import "MailAddress.h"


//-------------------------------------------------------------------------------------------
//	PRIVATE API
//-------------------------------------------------------------------------------------------

@interface MailAddress (Tokenizer)

- (NSString *)_scanToken:(NSScanner *)aScanner intoString:(NSString **)aToken expect:(NSString *)aString;
- (void)_scanToken:(NSScanner *)aScanner expect:(NSString *)aString withValue:(NSString *)aToken;

- (NSString *)_scanToken:(NSScanner *)aScanner withNonASCII:(BOOL)nonASCII intoString:(NSString **)aString;

- (NSString *)_scanDomain:(NSScanner *)aScanner;
- (NSString *)_scanPhrase:(NSScanner *)aScanner;
- (BOOL)_scan:(NSScanner *)aScanner special:(NSString *)aString;
- (NSString *)_scanWord:(NSScanner *)aScanner withNonASCII:(BOOL)nonASCII;

- (NSString *)_unquotedDomainLiteral:(NSString *)aString;
- (NSString *)_unquotedString:(NSString *)aString;
- (NSString *)_unqotedWord:(NSString *)aString;
- (NSString *)_unquotedPhrase:(NSString *)aString;

@end

@interface MailAddress (Parser)

- (void)__assertParseCompleted:(NSScanner *)aScanner;

- (NSString *)__parseLocalPart:(NSScanner *)aScanner;
- (NSString *)_parseLocalPart:(NSString *)aString;

- (NSString *)__parseDomain:(NSScanner *)aScanner;
- (NSString *)_parseDomain:(NSString *)aString;

- (void)__parseAddressSpecification:(NSScanner *)aScanner;
- (void)_parseAddressSpecification:(NSString *)aString;

- (void)__parseRouteAddress:(NSScanner *)aScanner;
- (void)_parseRouteAddress:(NSString *)aString;

- (NSString *)_stringByExtractingComments:(NSString *)aString;

@end

@interface MailAddress (Private)

- (void)setRouteAddress:(NSString *)aString;

- (void)setLocalPartKnownToBeValid:(NSString *)aString;
- (void)setDomainKnownToBeValid:(NSString *)aString;
- (void)setRealNameKnownToBeValid:(NSString *)aString;

@end



//-------------------------------------------------------------------------------------------
    @implementation MailAddress
//-------------------------------------------------------------------------------------------

NSString *RFC822ParseException = @"RFC822ParseError";
NSString *RFC822Scanner = @"RFC822Scanner";

static NSString		*RFCTokenSpecial = @"RFCSpecial";
static NSString		*RFCTokenQuotedString = @"RFCQuotedString";
static NSString		*RFCTokenDomainLiteral = @"RFCDomainLiteral";
static NSString		*RFCTokenComment = @"RFCComment";
static NSString		*RFCTokenAtom = @"RFCAtom";

static BOOL			sloppy = NO;

+ (BOOL)sloppyParser;
{
    return sloppy;
}

+ (void)setSloppyParser:(BOOL)flag;
{
    sloppy = flag;
}

//-------------------------------------------------------------------------------------------
//	FACTORY
//-------------------------------------------------------------------------------------------

+ (MailAddress *)mailAddressWithString:(NSString *)aString;
{
   return [[[self alloc] initWithString:aString] autorelease];
}


+ (MailAddress *)mailAddressWithLocalPart:(NSString *)aString domain:(NSString *)otherString;
{
    MailAddress *new;

    new = [[[self alloc] init] autorelease];
    [new setLocalPart:aString];
    [new setDomain:otherString];

    return new;
}


+ (MailAddress *)mailAddressWithAddressSpecification:(NSString *)aString realName:(NSString *)aName;
{
    MailAddress *new;

    new = [[[self alloc] init] autorelease];
    [new setAddressSpecification:aString];
    [new setRealName:aName];

    return new;
}


//-------------------------------------------------------------------------------------------
//	Initialiser
//-------------------------------------------------------------------------------------------

- init;
{
    [super init];
    routes = [[NSMutableArray alloc] init];
    comments = [[NSMutableArray alloc] init];
    return self;
}


- initWithString:(NSString *)string;
{
    [self init];

    string = [self _stringByExtractingComments:string];
#ifdef DEBUG
    NSLog(@"after removing comments %@: %@", comments, string);
#endif
    
    NS_DURING

    [self setAddressSpecification:string];

    NS_HANDLER
        [self setRouteAddress:string];
    NS_ENDHANDLER

    return self;
}


- initWithLocalPart:(NSString *)aString domain:(NSString *)otherString;
{    
    [self init];

    [self setLocalPart:aString];
    [self setDomain:otherString];

    return self;
}


//-------------------------------------------------------------------------------------------
//	ATTRIBUTES
//-------------------------------------------------------------------------------------------

- (NSString *)_localPart;
{
    return localPart;
}

- (NSString *)localPart;
{
    return localPart ? localPart : @"";
}

- (void)setLocalPartKnownToBeValid:(NSString *)aString;
{
    if (localPart != aString)
    {
        [self invalidateStringRep];
        [localPart release];
        aString = [self _unquotedDomainLiteral:aString];
        localPart = [aString copy];
    }
}

- (void)setLocalPart:(NSString *)aString;
{
    NSString	*s;

    s = [self _parseLocalPart:aString];
    [self setLocalPartKnownToBeValid:s];
}

- (NSString *)_domain;
{
    return domain;
}

- (NSString *)domain;
{
    return domain ? domain : @"";
}

- (NSArray *)domains;
{
    return [domain componentsSeparatedByString:@"."];
}

- (void)setDomainKnownToBeValid:(NSString *)aString;
{
    if (domain != aString)
    {
        [self invalidateStringRep];
        [domain release];
        aString = [self _unquotedDomainLiteral:aString];
        domain = [aString copy];
    }
}

- (void)setDomain:(NSString *)aString;
{
    NSString	*s;

    s = [self _parseDomain:aString];
    [self setDomainKnownToBeValid:s];
}

- (NSString *)_realName;
{
    if (!realName)
    {
        if ([comments count] > 0)
        {
            return [comments componentsJoinedByString:@" "];
        }

        return nil;
    }
    return realName;
}

- (NSString *)realName;
{
    NSString	*n = [self _realName];

    return n ? n : @"";
}

- (void)setRealNameKnownToBeValid:(NSString *)aString;
{
    if (realName != aString)
    {
        [self invalidateStringRep];
        [realName release];

        aString = [self _unquotedPhrase:aString];
        realName = [aString copy];
    }
}

- (void)setRealName:(NSString *)aString;
{
    [self setRealNameKnownToBeValid:aString];
}


//-------------------------------------------------------------------------------------------
//	DERIVED ATTRIBUTES
//-------------------------------------------------------------------------------------------

- (void)setAddressSpecification:(NSString *)aString;
{
    [self _parseAddressSpecification:aString];
}


- (void)setRouteAddress:(NSString *)aString;
{
    [self _parseRouteAddress:aString];
}


- (NSString *)addressSpecification
{
    if (domain)
        return [NSString stringWithFormat:@"%@@%@", [self localPart], [self domain]];
    else
        return [self localPart];
}


- (NSString *)stringRepresentation;
{
    NSString	*name;

    if(stringRep == nil)
        {
        if((name = [self _realName]))
            {
            NSRange			r1, r2;

            r1 = [name rangeOfCharacterFromSet:[NSCharacterSet MASpecialCharacterSet]];
            r2 = [name rangeOfCharacterFromSet:[NSCharacterSet controlCharacterSet]];

            if ((r1.length > 0) || (r2.length > 0))				// needs quoting
                name = [NSString stringWithFormat:@"\"%@\"", name];

            stringRep = [[NSString stringWithFormat:@"%@ <%@>", name, [self addressSpecification]] retain];
            }
        else
            {
            stringRep = [[self addressSpecification] retain];
            }
        }
    return stringRep;
}


//-------------------------------------------------------------------------------------------
//	TRANSFER REPRESENTATIONS
//-------------------------------------------------------------------------------------------

/*	We should really override initFromTransferRepresentation and only decode comments and
words/phrases, but the parser is string based and the decoding function will do the right
thing anyway. */


- (NSData *)transferRepresentation;
{
    NSMutableCharacterSet	*qCharset;
    NSData					*nameData, *transfRep;
    NSMutableData			*buffer;
    NSString				*name;
    const unsigned char		*cp, quote[1] = "\"", space[1] = " ", lt[1] = "<", gt[1] = ">";
    unsigned int			i;

    if((name = [self _realName]) != nil)
        {
        buffer = [NSMutableData data];
        nameData = [name dataForMIMEHeaderField];
        qCharset = [[[NSCharacterSet MASpecialCharacterSet] mutableCopy] autorelease];
        [qCharset formUnionWithCharacterSet:[NSCharacterSet controlCharacterSet]];
        cp = [nameData bytes];
        for(i = [nameData length]; i > 0; i--)
            if([qCharset characterIsMember:(unichar)*cp++])
                break;
                    
        if(i != 0)
            {
            [buffer appendBytes:quote length:1];
            [buffer appendData:nameData];
            [buffer appendBytes:quote length:1];
            }
        else
            {
            [buffer appendData:nameData];
            }
        [buffer appendBytes:space length:1];
        [buffer appendBytes:lt length:1];
        [buffer appendData:[[self addressSpecification] dataUsingEncoding:NSASCIIStringEncoding]];
        [buffer appendBytes:gt length:1];

        transfRep = [[buffer copy] autorelease];
        }
    else
        {
        transfRep = [[self addressSpecification] dataUsingEncoding:NSASCIIStringEncoding];
        }
    return transfRep;
}


//-------------------------------------------------------------------------------------------
//	WRAPPER (WebScript!)
//-------------------------------------------------------------------------------------------

+ (BOOL)isValidMailAddress:(NSString *)aString;
{
    BOOL	valid;

    NS_DURING
    valid = NO;
    [self mailAddressWithString:aString];
    valid = YES;
    NS_HANDLER
#ifdef LITTLE_FOUNDATION
	NSLog(@"invalid address '%@': %@, %@", aString, [exception exceptionName], [exception exceptionReason]);
#else
	NSLog(@"invalid address '%@': %@, %@", aString, [localException name], [localException reason]);
#endif
    NS_ENDHANDLER

    return valid;
}


//-------------------------------------------------------------------------------------------
//	NSObject Stuff
//-------------------------------------------------------------------------------------------

- (void)dealloc;
{
    [comments release];
    [routes release];
    
    [realName release];
    [domain release];
    [localPart release];
    [super dealloc];
}


//-------------------------------------------------------------------------------------------
    @end
//-------------------------------------------------------------------------------------------


//-------------------------------------------------------------------------------------------
    @implementation MailAddress (Tokenizer)
//-------------------------------------------------------------------------------------------

- (NSString *)_scanToken:(NSScanner *)aScanner intoString:(NSString **)aToken expect:(NSString *)aString;
{
    NSString	*type, *token;

    while ((type = [self _scanToken:aScanner withNonASCII:NO intoString:&token]))
    {
        if (type == aString)
        {
            if (aToken)
            {
                *aToken = token;
            }
            return type;
        }
        else //if (type != RFCTokenComment)
        {
            [NSException raise:RFC822ParseException format:@"expected token %@; found %@", aString, type];
        }
    }

    [NSException raise:RFC822ParseException format:@" expected %@; found end of string", aString];
    return nil;
}

- (void)_scanToken:(NSScanner *)aScanner expect:(NSString *)aString withValue:(NSString *)aToken;
{
    NSString	*type, *token = nil;

    if ((type = [self _scanToken:aScanner intoString:&token expect:aString]))
    {
        if (![token isEqual:aToken])
        {
            [NSException raise:RFC822ParseException format:@"expected %@; found %@", aToken, token];
        }
        return ;
    }

    [NSException raise:RFC822ParseException format:@" expected %@; found end of string", aToken];
}

- (NSString *)_scanQuotedPair:(NSScanner *)aScanner;
{
    NSRange		quote, character;

    quote = [[aScanner string] rangeOfComposedCharacterSequenceAtIndex:[aScanner scanLocation] - 1];
    character = [[aScanner string] rangeOfComposedCharacterSequenceAtIndex:[aScanner scanLocation]];
    quote.length += character.length;
    [aScanner setScanLocation:[aScanner scanLocation] + 1];
    return [[aScanner string] substringWithRange:quote];
}

- (NSString *)_scanQuotedString:(NSScanner *)aScanner;
{
    NSMutableString	*quoted = [NSMutableString string];
    NSString		*part;

    do
    {
        if ([aScanner scanString:@"\\" intoString:NULL])
        {
            [quoted appendString:[self _scanQuotedPair:aScanner]];
        }
        else if ([aScanner scanString:@"\"" intoString:NULL])
        {
            return [NSString stringWithFormat:@"\"%@\"", quoted];
        }
        else if ([aScanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"\"\n\\"] intoString:&part])
        //[NSCharacterSet MAQuoteCharacterSet] intoString:&part])
        {
            [quoted appendString:part];
        }
        else
        {
            if (![aScanner scanString:@"\"" intoString:NULL])
            {
                [NSException raise:RFC822ParseException format:@"expected \""];
            }
        }
    }
    while (1);

    // shoudln't come here
    return nil;
}

- (NSString *)_scanDomainLiteral:(NSScanner *)aScanner;
{
    NSMutableString	*quoted = [NSMutableString string];
    NSString		*part;

    do
    {
        if ([aScanner scanString:@"\\" intoString:NULL])
        {
            [quoted appendString:[self _scanQuotedPair:aScanner]];
        }
        else if ([aScanner scanString:@"]" intoString:NULL])
        {
            return [NSString stringWithFormat:@"[%@]", quoted];
        }
        else if ([aScanner scanUpToCharactersFromSet:[NSCharacterSet characterSetWithCharactersInString:@"[]\n\\"] intoString:&part])
        {
            [quoted appendString:part];
        }
        else
        {
            if (![aScanner scanString:@"]" intoString:NULL])
            {
                [NSException raise:RFC822ParseException format:@"expected ]" ];
            }
        }
    }
    while (1);

    // shoudln't come here
    return nil;
}

- (NSString *)_scanComment:(NSScanner *)aScanner;
{
    // does not yet handle nested comments
    NSMutableString	*comment;
    
    [aScanner scanUpToString:@")" intoString:&comment];

    if (![aScanner scanString:@")" intoString:NULL])
    {
        [NSException raise:RFC822ParseException format:@"expected )"];
    }
    return comment;
}

- (NSString *)_scanToken:(NSScanner *)aScanner withNonASCII:(BOOL)nonASCII intoString:(NSString **)aString;
{
    NSString	*tokenName;
    NSString	*token = nil;
    int			scanLocation;

    // eat whitespace
    // does this handle folding correctly?
    [aScanner setCharactersToBeSkipped:nil];
    [aScanner scanCharactersFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet] intoString:NULL];
    [aScanner setCharactersToBeSkipped:[NSCharacterSet whitespaceCharacterSet]];

    scanLocation = [aScanner scanLocation];
    if (scanLocation >= [[aScanner string] length])
    {
        tokenName = nil;
    }
    else if ([[self class] sloppyParser] && [aScanner scanString:@"\\" intoString:NULL])
    {
#ifdef DEBUG
        NSLog(@"quoted pair not allowed here (strictly speaking)");
#endif
        tokenName = RFCTokenQuotedString;
        token = [self _scanQuotedPair:aScanner];
    }
    else if ([aScanner scanString:@"\"" intoString:NULL])	// quoted-string 
    {
        tokenName = RFCTokenQuotedString;
        token = [self _scanQuotedString:aScanner];
    }
    else if ([aScanner scanString:@"[" intoString:NULL])	// domain-literal
    {
        tokenName = RFCTokenDomainLiteral;
        token = [self _scanDomainLiteral:aScanner];
    }
    else if ([aScanner scanString:@"(" intoString:NULL])	// comment
    {
        tokenName = RFCTokenComment;
        token = [self _scanComment:aScanner];
//        [comments addObject:token];
    }
    else
    {
        unichar	character;

        character = [[aScanner string] characterAtIndex:scanLocation];
        if ([[NSCharacterSet MASpecialCharacterSet] characterIsMember:character])
        {
            NSRange	range = [[aScanner string] rangeOfComposedCharacterSequenceAtIndex:scanLocation];

            tokenName = RFCTokenSpecial;
            token = [[aScanner string] substringWithRange:range];
            [aScanner setScanLocation:++scanLocation];
        }
        else
        {
            tokenName = RFCTokenAtom;
            if (![aScanner scanCharactersFromSet:nonASCII ? [NSCharacterSet MAEncodedAtomCharacterSet] : [NSCharacterSet MAAtomCharacterSet] intoString:&token])
            {
                [NSException raise:RFC822ParseException format:@"found illegal character 0x%x for 'atom' at %d", character, [aScanner scanLocation]];
            }
        }
    }

    if (aString)
    {
        *aString = token;
    }
    
    return tokenName;
}

- (NSString *)_scanDomain:(NSScanner *)aScanner;
{
    NSString	*type, *token;

    while ((type = [self _scanToken:aScanner withNonASCII:NO intoString:&token]))
    {
        if ((type == RFCTokenAtom) || (type == RFCTokenDomainLiteral))
        {
            return token;
        }
        else //if (type != RFCTokenComment)
        {
            [NSException raise:RFC822ParseException format:@"expexected atom or quoted string"];
        }
    }

    return nil;
}

- (NSString *)_scanWord:(NSScanner *)aScanner withNonASCII:(BOOL)nonASCII;
{
    NSString	*type, *token;

    while ((type = [self _scanToken:aScanner withNonASCII:nonASCII intoString:&token]))
    {
        if ((type == RFCTokenAtom) || (type == RFCTokenQuotedString))
        {
            return token;
        }
        else //if (type != RFCTokenComment)
        {
            [NSException raise:RFC822ParseException format:@"expexected atom or quoted string"];
        }
    }
    
    return nil;
}

- (NSString *)_scanPhrase:(NSScanner *)aScanner;
{
    NSMutableArray	*parts = [NSMutableArray array];
    NSString		*word;
    unsigned		scanLocation;
    
    NS_DURING
    {
        scanLocation = [aScanner scanLocation];
        while ((word = [self _scanWord:aScanner withNonASCII:YES]) != nil)
        {
            [parts addObject:word];
            scanLocation = [aScanner scanLocation];
        }
    }
    NS_HANDLER
    {
        [aScanner setScanLocation:scanLocation];
        return [parts componentsJoinedByString:@" "];
    }
    NS_ENDHANDLER
    return [parts componentsJoinedByString:@" "];
}


- (BOOL)_scan:(NSScanner *)aScanner special:(NSString *)aString;
{
    unsigned int	scanLocation = [aScanner scanLocation];

    NS_DURING
    {
        [self _scanToken:aScanner expect:RFCTokenSpecial withValue:aString];
    }
    NS_HANDLER
    {
        [aScanner setScanLocation:scanLocation];
        return NO;
    }
    NS_ENDHANDLER
    
    return YES;
}

- (NSString *)_unquotedDomainLiteral:(NSString *)aString;
{
    NSScanner		*scanner = [NSScanner scannerWithString:aString];
    NSMutableString	*unquoted = [NSMutableString string];
    NSCharacterSet	*cs = [NSCharacterSet characterSetWithCharactersInString:@"]\\"];
    NSString		*part;

    if ([scanner scanString:@"[" intoString:NULL])
    {
        while (![scanner scanString:@"]" intoString:NULL])
        {
            if ([scanner scanString:@"\\" intoString:NULL])
                [unquoted appendString:[self _scanQuotedPair:scanner]];
            if (([scanner scanUpToCharactersFromSet:cs intoString:&part]))
                [unquoted appendString:part];
        }
        return unquoted;
    }
    return aString;
}

- (NSString *)_unquotedString:(NSString *)aString;
{
    NSScanner		*scanner = [NSScanner scannerWithString:aString];
    NSMutableString	*unquoted = [NSMutableString string];
    NSCharacterSet	*cs = [NSCharacterSet characterSetWithCharactersInString:@"\"\\"];
    NSString		*part;

    if ([scanner scanString:@"\"" intoString:NULL])
    {
        while (![scanner scanString:@"\"" intoString:NULL])
        {
            if ([scanner scanString:@"\\" intoString:NULL])
                [unquoted appendString:[self _scanQuotedPair:scanner]];
            if (([scanner scanUpToCharactersFromSet:cs intoString:&part]))
                [unquoted appendString:part];
        }
        return unquoted;
    }
    return aString;
}

- (NSString *)_unqotedWord:(NSString *)aString;
{
    return [self _unquotedString:aString];
}

- (NSString *)_unquotedPhrase:(NSString *)aString;
{
    return [self _unquotedString:aString];
}

//-------------------------------------------------------------------------------------------
    @end
//-------------------------------------------------------------------------------------------


//-------------------------------------------------------------------------------------------
    @implementation MailAddress (Parser)
//-------------------------------------------------------------------------------------------

- (void)__assertParseCompleted:(NSScanner *)aScanner;
{
/*    NSString	*token, *type;

    while ((type = [self _scanToken:aScanner withNonASCII:YES intoString:&token]))
    {
        if (type != RFCTokenComment)
        {
            [NSException raise:RFC822ParseException format:@"'%@' not allowed after address specification", token];
        }
    }*/
    if ([aScanner scanLocation] != [[aScanner string] length])
    {
        [NSException raise:RFC822ParseException format:@"'%@' not allowed after address specification", [[aScanner string] substringFromIndex:[aScanner scanLocation]]];
    }
}

- (NSString *)__parseLocalPart:(NSScanner *)aScanner;
{
    NSMutableArray	*words = [NSMutableArray array];
    NSString		*word;

    do
    {
        if((word = [self _scanWord:aScanner withNonASCII:NO]) != nil)
	        [words addObject:word];
    }
    while ([self _scan:aScanner special:@"."]);
    
    return [words componentsJoinedByString:@"."];
}

- (NSString *)_parseLocalPart:(NSString *)aString;
{
    NSScanner	*scanner = [NSScanner scannerWithString:aString];

    return [self __parseLocalPart:scanner];
}


- (NSString *)__parseDomain:(NSScanner *)aScanner;
{
    NSMutableArray	*subdomains = [NSMutableArray array];
    NSString		*subdomain;

    do
    {
        if((subdomain = [self _scanDomain:aScanner]) != nil)
	        [subdomains addObject:subdomain];
    }
    while ([self _scan:aScanner special:@"."]);

    return [subdomains componentsJoinedByString:@"."];
}

- (NSString *)_parseDomain:(NSString *)aString;
{
    NSScanner	*scanner = [NSScanner scannerWithString:aString];

    return [self __parseDomain:scanner];
}

- (void)__parseAddressSpecification:(NSScanner *)aScanner;
{
    NSString	*_localPart, *_domain = nil;

    NS_DURING
    {
        _localPart = [self __parseLocalPart:aScanner];

        if ([self _scan:aScanner special:@"@"])
        {
            _domain = [self __parseDomain:aScanner];
        }
        else if (![[self class] sloppyParser])
        {
            [NSException raise:RFC822ParseException format:@"expected '@'"];
        }
    }
    NS_HANDLER
    {
#ifndef LITTLE_FOUNDATION
        [[NSException exceptionWithName:[localException name] reason:[localException reason] userInfo:[NSDictionary dictionaryWithObject:aScanner forKey:RFC822Scanner]] raise];
#else
        [[NSException exceptionWithName:[exception exceptionName] reason:[exception exceptionReason] userInfo:[NSDictionary dictionaryWithObject:aScanner forKey:RFC822Scanner]] raise];
#endif
    }
    NS_ENDHANDLER
    
    [self setLocalPartKnownToBeValid:_localPart];
    [self setDomainKnownToBeValid:_domain];
}

- (void)_parseAddressSpecification:(NSString *)aString;
{
    NSScanner	*theScanner;

    theScanner = [NSScanner scannerWithString:aString];
    [self __parseAddressSpecification:theScanner];
    [self __assertParseCompleted:theScanner];
}

- (NSArray	*)__parseRoutes:(NSScanner *)aScanner;
{
    unsigned		scanLocation = [aScanner scanLocation];
    NSMutableArray	*_routes = [NSMutableArray array];

    NS_DURING
    {
        do
        {
            [self _scanToken:aScanner expect:RFCTokenSpecial withValue:@"@"];
            [_routes addObject:[self _scanDomain:aScanner]];

            if ([self _scan:aScanner special:@":"])
                return _routes;
        }
        while ([self _scan:aScanner special:@","]);
    }
    NS_HANDLER
    {
        [aScanner setScanLocation:scanLocation];
        return nil;
    }
    NS_ENDHANDLER
    return _routes;
}

- (void)__parseRouteAddress:(NSScanner *)aScanner;
{
    NSString	*_phrase;

    NS_DURING
    {
        _phrase = [self _scanPhrase:aScanner];
        [self _scanToken:aScanner expect:RFCTokenSpecial withValue:@"<"];
        routes = [[self __parseRoutes:aScanner] copy];
        [self __parseAddressSpecification:aScanner];
        [self _scanToken:aScanner expect:RFCTokenSpecial withValue:@">"];
    }
    NS_HANDLER
    {
#ifndef LITTLE_FOUNDATION
        [[NSException exceptionWithName:[localException name] reason:[localException reason] userInfo:[NSDictionary dictionaryWithObject:aScanner forKey:RFC822Scanner]] raise];
#else
        [[NSException exceptionWithName:[exception exceptionName] reason:[exception exceptionReason] userInfo:[NSDictionary dictionaryWithObject:aScanner forKey:RFC822Scanner]] raise];
#endif
    }
    NS_ENDHANDLER
    
    [self setRealName:_phrase];
}

- (void)_parseRouteAddress:(NSString *)aString;
{
    NSScanner	*theScanner;

    theScanner = [NSScanner scannerWithString:aString];
    [self __parseRouteAddress:theScanner];
    [self __assertParseCompleted:theScanner];
}

- (NSString *)_stringByExtractingComments:(NSString *)aString;
{
    NSString		*token, *type;
    NSScanner		*scanner;
    NSMutableArray	*tokens = [NSMutableArray array];

    scanner = [NSScanner scannerWithString:aString];

    while ((type = [self _scanToken:scanner withNonASCII:YES intoString:&token]))
    {
        if (type == RFCTokenComment)
        {
            [comments addObject:token];
        }
        else
        {
            [tokens addObject:token];
        }
    }
    return [tokens componentsJoinedByString:@" "];
}

//-------------------------------------------------------------------------------------------
    @end
//-------------------------------------------------------------------------------------------
