This is MailAddress.m in view mode; [Download] [Up]
//--------------------------------------------------------------------------------------- // 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 //-------------------------------------------------------------------------------------------
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.