You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2171 lines
50 KiB
2171 lines
50 KiB
/*
|
|
* Copyright (c) 1985 Regents of the University of California.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms are permitted provided
|
|
* that: (1) source distributions retain this entire copyright notice and
|
|
* comment, and (2) distributions including binaries display the following
|
|
* acknowledgement: ``This product includes software developed by the
|
|
* University of California, Berkeley and its contributors'' in the
|
|
* documentation or other materials provided with the distribution and in
|
|
* all advertising materials mentioning features or use of this software.
|
|
* Neither the name of the University nor the names of its contributors may
|
|
* be used to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
|
|
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|
*/
|
|
|
|
/*++
|
|
|
|
Copyright (c) 1994 Microsoft Corporation
|
|
|
|
Module Name :
|
|
|
|
address.cxx
|
|
|
|
Abstract:
|
|
|
|
This module defines the module for the address (CAddr)
|
|
class.
|
|
|
|
Author:
|
|
|
|
Rohan Phillips ( Rohanp ) 11-Dec-1995
|
|
|
|
Project:
|
|
|
|
SMTP Server DLL
|
|
|
|
Functions Exported:
|
|
Revision History:
|
|
|
|
|
|
--*/
|
|
|
|
|
|
/************************************************************
|
|
* Include Headers
|
|
************************************************************/
|
|
#include <windows.h>
|
|
#include <dbgtrace.h>
|
|
#include <cpool.h>
|
|
#include <string.h>
|
|
#include <listmacr.h>
|
|
#include <abtype.h>
|
|
#include <abook.h>
|
|
#include <address.hxx>
|
|
|
|
//initialize the pool
|
|
CPool CAddr::Pool(ADDRESS_SIGNATURE_VALID);
|
|
#if defined(TDC)
|
|
LPFNAB_FREE_MEMORY CAddr::pfnABFreeMemory = NULL;
|
|
#endif
|
|
|
|
//
|
|
// Statics
|
|
//
|
|
static BOOL pStripAddrQuotes( char *lpszAddress, char **lpDomain );
|
|
static BOOL pStripAddrSpaces(char *lpszAddress);
|
|
|
|
// Quick and dirty string validation
|
|
static BOOL pValidateStringPtr(LPSTR lpwszString, DWORD dwMaxLength)
|
|
{
|
|
if (IsBadStringPtr((LPCTSTR)lpwszString, dwMaxLength))
|
|
return(FALSE);
|
|
while (dwMaxLength--)
|
|
if (*lpwszString++ == 0)
|
|
return(TRUE);
|
|
return(FALSE);
|
|
}
|
|
|
|
|
|
//This routine scans an address for illegal characters
|
|
//in a name
|
|
BOOL IsInvalidAddr(char *Address)
|
|
{
|
|
char * addr = Address;
|
|
|
|
for (; *addr != '\0'; addr++)
|
|
{
|
|
if ((*addr & 0340) == 0200)
|
|
break;
|
|
}
|
|
if (*addr == '\0')
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
SetLastError (ERROR_INVALID_DATA);
|
|
return TRUE;
|
|
}
|
|
|
|
// Forward declaration of pValidateLocalPartOrDomain
|
|
BOOL pValidateLocalPartOrDomain(char *lpszStart,
|
|
DWORD dwLength,
|
|
BOOL fLocalPart);
|
|
|
|
|
|
/*++
|
|
Name :
|
|
CAddr:CAddr
|
|
|
|
Description:
|
|
This is the default constructor for this class.
|
|
It just initializes the member variables.
|
|
|
|
Arguments: None
|
|
|
|
Returns:
|
|
|
|
nothing
|
|
|
|
Limitations:
|
|
|
|
--*/
|
|
CAddr::CAddr(void)
|
|
{
|
|
m_Signature = ADDRESS_SIGNATURE_VALID;
|
|
m_Flags = ADDRESS_NO_DOMAIN;
|
|
m_PlainAddrSize = 0;
|
|
m_DomainOffset = NULL;
|
|
m_HashInfo = NULL;
|
|
m_Error = 0;
|
|
m_listEntry.Flink = NULL;
|
|
m_listEntry.Blink = NULL;
|
|
}
|
|
|
|
CAddr::~CAddr(VOID)
|
|
{
|
|
m_Signature = ADDRESS_SIGNATURE_FREE;
|
|
m_Flags = 0;
|
|
m_listEntry.Flink = NULL;
|
|
m_listEntry.Blink = NULL;
|
|
m_HashInfo = NULL;
|
|
}
|
|
|
|
/*++
|
|
Name :
|
|
CAddr:CAddr(char * address)
|
|
|
|
Description:
|
|
This is the default constructor for this class.
|
|
It just initializes the member variables.
|
|
|
|
Arguments: None
|
|
|
|
Returns:
|
|
|
|
nothing
|
|
|
|
Limitations:
|
|
|
|
--*/
|
|
CAddr::CAddr(char * Address)
|
|
{
|
|
m_Signature = ADDRESS_SIGNATURE_VALID;
|
|
m_Flags = ADDRESS_NO_DOMAIN;
|
|
m_PlainAddrSize = 0;
|
|
m_DomainOffset = NULL;
|
|
m_HashInfo= NULL;
|
|
m_Error = 0;
|
|
m_listEntry.Flink = NULL;
|
|
m_listEntry.Blink = NULL;
|
|
|
|
|
|
if(Address == NULL)
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return;
|
|
}
|
|
|
|
// This is an email name, extract its clean representation
|
|
ExtractCleanEmailName( m_PlainAddress,
|
|
&m_DomainOffset,
|
|
&m_PlainAddrSize,
|
|
Address);
|
|
|
|
if (m_PlainAddrSize > MAX_INTERNET_NAME)
|
|
{
|
|
m_PlainAddrSize = 0;
|
|
SetLastError (ERROR_INVALID_DATA);
|
|
}
|
|
else
|
|
{
|
|
if(m_DomainOffset)
|
|
m_Flags &= (DWORD) ~ADDRESS_NO_DOMAIN; //turn off the no domain flag.
|
|
}
|
|
}
|
|
|
|
/*++
|
|
Name :
|
|
CAddr::InitializeAddress
|
|
|
|
Description:
|
|
This function parses an RFC address, stripping out
|
|
all comments, and gets back an internet address
|
|
|
|
Arguments:
|
|
Address - RCF address from client
|
|
ADDRTYPE - specify if address came from FROM or RCPT command
|
|
|
|
Returns:
|
|
|
|
TRUE if the RFC addressed was correctly parsed.
|
|
FALSE otherwise.
|
|
|
|
--*/
|
|
BOOL CAddr::InitializeAddress(char * Address, ADDRTYPE NameType)
|
|
{
|
|
|
|
if(::IsInvalidAddr(Address))
|
|
return(FALSE);
|
|
|
|
m_PlainAddress[0] = '\0';
|
|
m_DomainOffset = NULL;
|
|
m_PlainAddrSize = 0;
|
|
|
|
// We have a special case for domains treated as addresses
|
|
if (NameType == CLEANDOMAIN)
|
|
{
|
|
DWORD AddressSize = 0;
|
|
char szCleanAddress[MAX_DOMAIN_NAME+1];
|
|
|
|
AddressSize = lstrlen(Address);
|
|
|
|
if (AddressSize > MAX_DOMAIN_NAME)
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return(FALSE);
|
|
}
|
|
|
|
// We want an ABSOLUTELY clean domain name, but we will strip
|
|
// out leading and trailing spaces for them
|
|
lstrcpy(szCleanAddress, Address);
|
|
if (!pStripAddrSpaces(szCleanAddress))
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return(FALSE);
|
|
}
|
|
AddressSize = lstrlen(szCleanAddress);
|
|
if (!pValidateLocalPartOrDomain(szCleanAddress, AddressSize, FALSE))
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return(FALSE);
|
|
}
|
|
|
|
// This is a clean domain name, fill in the fields, and
|
|
// return a positively
|
|
lstrcpy(m_PlainAddress, szCleanAddress);
|
|
m_PlainAddrSize = lstrlen(szCleanAddress);
|
|
m_Flags &= (DWORD) ~ADDRESS_NO_DOMAIN;
|
|
return(TRUE);
|
|
}
|
|
|
|
// This is an email name, extract its clean representation
|
|
if (!ExtractCleanEmailName( m_PlainAddress,
|
|
&m_DomainOffset,
|
|
&m_PlainAddrSize,
|
|
Address))
|
|
return(FALSE);
|
|
|
|
// Take care of the flag
|
|
if (!m_DomainOffset)
|
|
m_Flags &= (DWORD) ~ADDRESS_NO_DOMAIN;
|
|
|
|
// Further, we validate the whole local-part[@domain] email name
|
|
// Special case: if the address is <>, then we skip the validation
|
|
if (strcmp(m_PlainAddress, "<>"))
|
|
{
|
|
if (!ValidateCleanEmailName(m_PlainAddress, m_DomainOffset))
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return(FALSE);
|
|
}
|
|
}
|
|
else if (NameType != FROMADDR)
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return(FALSE);
|
|
}
|
|
|
|
// Take care of other anomalies ...
|
|
if (m_DomainOffset)
|
|
{
|
|
// Make sure there is a local-part
|
|
if (m_DomainOffset <= m_PlainAddress)
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return(FALSE);
|
|
}
|
|
|
|
// EMPIRE WTOP Bug 47233: Need to accept longer email names if they add up
|
|
// to less than MAX_INTERNET_NAME. This is for replication of users across
|
|
// a site connector. Due to this reason, we removed the user name check to
|
|
// make sure it is less than MAX_EMAIL_NAME.
|
|
}
|
|
else
|
|
{
|
|
// We have no domain, make sure it is not an empty string
|
|
if (m_PlainAddress[0] == '\0')
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return(FALSE);
|
|
}
|
|
|
|
// EMPIRE WTOP Bug 47233: Need to accept longer email names if they add up
|
|
// to less than MAX_INTERNET_NAME. This is for replication of users across
|
|
// a site connector. Due to this reason, we removed the user name check to
|
|
// make sure it is less than MAX_EMAIL_NAME.
|
|
}
|
|
return(TRUE);
|
|
}
|
|
|
|
/*++
|
|
Name :
|
|
CAddr::CreateAddress
|
|
|
|
Description:
|
|
This function allocates memory for a CAddr
|
|
class and calls StripAddrComments to
|
|
|
|
Arguments:
|
|
pszDest - Destination where new address should go
|
|
pszSrc - Source buffer of address to strip comments from
|
|
|
|
Returns:
|
|
|
|
a pointer to a CAddr if comments were stripped and the format
|
|
of the address is legal.
|
|
NULL otherwise
|
|
|
|
--*/
|
|
CAddr * CAddr::CreateAddress(char * Address, ADDRTYPE NameType)
|
|
{
|
|
CAddr * NewAddress;
|
|
|
|
//create the memory for the address
|
|
NewAddress = new CAddr ();
|
|
if (!NewAddress)
|
|
{
|
|
SetLastError (ERROR_NOT_ENOUGH_MEMORY);
|
|
return NULL;
|
|
}
|
|
|
|
//now initialize it
|
|
if (!NewAddress->InitializeAddress(Address, NameType))
|
|
{
|
|
delete NewAddress;
|
|
return NULL;
|
|
}
|
|
|
|
//we have a good address...or so we think
|
|
return NewAddress;
|
|
}
|
|
|
|
/*++
|
|
Name :
|
|
CAddr::CreateKnownAddress
|
|
|
|
Description:
|
|
This function allocates memory for a CAddr
|
|
class and sets "Address" as the internet
|
|
address for this class. No error checking
|
|
is done. It is assumed that the caller
|
|
performed all necessary error checking
|
|
|
|
Arguments:
|
|
Address - Internet Address to initialize
|
|
the class with
|
|
|
|
Returns:
|
|
a pointer to a CAddr if memory could be allocated
|
|
NULL otherwise
|
|
|
|
--*/
|
|
CAddr * CAddr::CreateKnownAddress(char * Address)
|
|
{
|
|
CAddr * NewAddress = NULL;
|
|
|
|
//create the memory for the address
|
|
NewAddress = new CAddr (Address);
|
|
if (!NewAddress)
|
|
{
|
|
SetLastError (ERROR_NOT_ENOUGH_MEMORY);
|
|
return NULL;
|
|
}
|
|
|
|
if(!NewAddress->GetAddrSize())
|
|
{
|
|
delete NewAddress;
|
|
NewAddress = NULL;
|
|
}
|
|
|
|
return NewAddress;
|
|
}
|
|
|
|
|
|
/*++
|
|
Name :
|
|
CAddr::ReplaceAddress
|
|
|
|
Description:
|
|
Replaces the address stored in this
|
|
CAddr with the one passed in
|
|
Arguments:
|
|
Address - new address
|
|
|
|
Returns:
|
|
TRUE if the address could be replaced.
|
|
FALSE if we run out of memory, the new
|
|
address is NULL, or the size of the
|
|
address is zero
|
|
|
|
--*/
|
|
BOOL CAddr::ReplaceAddress(const char * Address)
|
|
{
|
|
_ASSERT(IsValid());
|
|
|
|
if(Address == NULL)
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return FALSE;
|
|
}
|
|
|
|
ExtractCleanEmailName( m_PlainAddress,
|
|
&m_DomainOffset,
|
|
&m_PlainAddrSize,
|
|
(char *)Address);
|
|
|
|
if (m_PlainAddrSize > MAX_INTERNET_NAME)
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return FALSE;
|
|
}
|
|
|
|
if(m_DomainOffset)
|
|
m_Flags &= (DWORD) ~ADDRESS_NO_DOMAIN; //turn off the no domain flag.
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Name :
|
|
CAddr::CAddr
|
|
|
|
Description:
|
|
returns a pointer to the 1st CAddr in the local list
|
|
|
|
Arguments:
|
|
a pointer to a PLIST_ENTRY
|
|
|
|
Returns:
|
|
|
|
--*/
|
|
CAddr * CAddr::GetFirstAddress(PLIST_ENTRY HeadOfList, PLIST_ENTRY * AddressLink)
|
|
{
|
|
PLIST_ENTRY ListEntry = NULL;
|
|
CAddr * FirstAddress = NULL;
|
|
|
|
//if the list is not empty, get the first address
|
|
if(!IsListEmpty (HeadOfList))
|
|
{
|
|
ListEntry = HeadOfList->Flink;
|
|
FirstAddress = CONTAINING_RECORD( ListEntry, CAddr, m_listEntry);
|
|
_ASSERT(FirstAddress->IsValid());
|
|
|
|
*AddressLink = ListEntry->Flink; //get the next link
|
|
}
|
|
|
|
return FirstAddress;
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
CAddr::GetNextAddress
|
|
|
|
Description:
|
|
returns a pointer to the next CAddr in the local list
|
|
|
|
Arguments:
|
|
a pointer to a PLIST_ENTRY
|
|
|
|
Returns:
|
|
|
|
--*/
|
|
CAddr * CAddr::GetNextAddress(PLIST_ENTRY HeadOfList, PLIST_ENTRY * AddressLink)
|
|
{
|
|
CAddr * NextAddress = NULL;
|
|
|
|
//make sure we are not at the end of the list
|
|
if((*AddressLink) != HeadOfList)
|
|
{
|
|
NextAddress = CONTAINING_RECORD(*AddressLink, CAddr, m_listEntry);
|
|
_ASSERT(NextAddress->IsValid());
|
|
*AddressLink = (*AddressLink)->Flink;
|
|
}
|
|
|
|
return NextAddress;
|
|
}
|
|
|
|
|
|
|
|
/*++
|
|
|
|
Name :
|
|
CAddr::RemoveAllAddrs
|
|
|
|
Description:
|
|
Deletes all address from a CAddr list
|
|
|
|
Arguments:
|
|
a pointer to the head of the list
|
|
|
|
Returns:
|
|
|
|
--*/
|
|
void CAddr::RemoveAllAddrs(PLIST_ENTRY HeadOfList)
|
|
{
|
|
PLIST_ENTRY pEntry;
|
|
CAddr * pAddr;
|
|
|
|
// Remove all addresses
|
|
while (!IsListEmpty (HeadOfList))
|
|
{
|
|
pEntry = RemoveHeadList(HeadOfList);
|
|
pAddr = CONTAINING_RECORD( pEntry, CAddr, m_listEntry);
|
|
delete pAddr;
|
|
}
|
|
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
CAddr::RemoveAddress
|
|
|
|
Description:
|
|
removes an address from a list
|
|
|
|
Arguments:
|
|
a pointer to a PLIST_ENTRY
|
|
|
|
Returns:
|
|
|
|
--*/
|
|
void CAddr::RemoveAddress(IN OUT CAddr * pEntry)
|
|
{
|
|
if(pEntry != NULL)
|
|
{
|
|
_ASSERT(pEntry->IsValid());
|
|
|
|
//Remove from list of addresses
|
|
RemoveEntryList( &pEntry->QueryListEntry());
|
|
}
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Name :
|
|
CAddr::InsertAddrHeadList
|
|
|
|
Description:
|
|
insert an address into the head of a list
|
|
|
|
Arguments:
|
|
a pointer to a PLIST_ENTRY
|
|
|
|
Returns:
|
|
|
|
--*/
|
|
|
|
void CAddr::InsertAddrHeadList(PLIST_ENTRY HeadOfList, CAddr *pEntry)
|
|
{
|
|
_ASSERT(pEntry);
|
|
_ASSERT(pEntry->IsValid());
|
|
|
|
InsertHeadList(HeadOfList, &pEntry->QueryListEntry());
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
CAddr::InsertAddrTailList
|
|
|
|
Description:
|
|
insert an address into the head of a list
|
|
|
|
Arguments:
|
|
a pointer to a PLIST_ENTRY
|
|
|
|
Returns:
|
|
|
|
--*/
|
|
|
|
void CAddr::InsertAddrTailList(PLIST_ENTRY HeadOfList, CAddr *pEntry)
|
|
{
|
|
_ASSERT(pEntry);
|
|
_ASSERT(pEntry->IsValid());
|
|
|
|
InsertTailList(HeadOfList, &pEntry->QueryListEntry());
|
|
}
|
|
|
|
// ========================================================================
|
|
//
|
|
// Validation Parser stuff added by KeithLau on 7/25/96
|
|
//
|
|
|
|
#define QUOTE_SQUARE 0x1
|
|
#define QUOTE_ANGLE 0x2
|
|
#define QUOTE_QUOTES 0x4
|
|
#define QUOTE_PARENTHESES 0x8
|
|
#define MAX_QUOTE_TYPES 3
|
|
|
|
static char acOpen[MAX_QUOTE_TYPES] = { '[', '<', '\"' };
|
|
static char acClose[MAX_QUOTE_TYPES] = { ']', '>', '\"' };
|
|
|
|
static char *pFindNextUnquotedOccurrence(char *lpszString,
|
|
char cSearch,
|
|
LPBOOL lpfNotFound,
|
|
DWORD dwDefaultState);
|
|
|
|
static inline BOOL IsControl(char ch)
|
|
{
|
|
return( ((ch >= 0) && (ch <= 31)) || (ch == 127) );
|
|
}
|
|
|
|
static BOOL IsSpecial(char ch)
|
|
{
|
|
switch (ch)
|
|
{
|
|
case '(':
|
|
case ')':
|
|
case '<':
|
|
case '>':
|
|
case '@':
|
|
case ',':
|
|
case ':':
|
|
case ';':
|
|
case '\\':
|
|
case '\"':
|
|
case '.':
|
|
case '[':
|
|
case ']':
|
|
return(TRUE);
|
|
default:
|
|
return(FALSE);
|
|
}
|
|
}
|
|
|
|
static BOOL pIsSpecialOrSpace(char ch)
|
|
{
|
|
return((ch == ' ') || (ch == '\t') || (ch == '\0') || IsSpecial(ch));
|
|
|
|
}
|
|
|
|
static BOOL pValidateAsciiString(char *lpszString)
|
|
{
|
|
// Verifies that a string only contains ASCII chars (0-127)
|
|
// Relies totally on upstream pointer checking
|
|
_ASSERT(lpszString);
|
|
if (!lpszString)
|
|
return(FALSE);
|
|
|
|
while (*lpszString)
|
|
{
|
|
if ((*lpszString >= 0) && (*lpszString <= 127))
|
|
lpszString++;
|
|
else
|
|
return(FALSE);
|
|
}
|
|
return(TRUE);
|
|
}
|
|
|
|
static BOOL pValidateAtom(char *lpszStart, DWORD dwLength)
|
|
{
|
|
// Atoms can be any ASCII char, except specials, controls, and spaces
|
|
// Note zero-length atom is invalid
|
|
_ASSERT(!IsBadStringPtr(lpszStart, dwLength));
|
|
|
|
if (!dwLength)
|
|
return(FALSE);
|
|
|
|
while (dwLength)
|
|
{
|
|
dwLength--;
|
|
if ((*lpszStart == ' ') ||
|
|
(IsSpecial(*lpszStart)) ||
|
|
(IsControl(*lpszStart))
|
|
)
|
|
{
|
|
// Process quoted (escape) char
|
|
if (*lpszStart == '\\')
|
|
{
|
|
if (!dwLength)
|
|
return(FALSE);
|
|
else
|
|
{
|
|
lpszStart++;
|
|
dwLength--;
|
|
}
|
|
}
|
|
else
|
|
return(FALSE);
|
|
}
|
|
|
|
lpszStart++;
|
|
}
|
|
return(TRUE);
|
|
}
|
|
|
|
static BOOL pValidateAtomNoWildcard(char *lpszStart, DWORD dwLength)
|
|
{
|
|
// Atoms can be any ASCII char, except specials, controls, and spaces
|
|
// Note zero-length atom is invalid
|
|
_ASSERT(!IsBadStringPtr(lpszStart, dwLength));
|
|
|
|
if (!dwLength)
|
|
return(FALSE);
|
|
|
|
while (dwLength)
|
|
{
|
|
// Apart from the usual, we also dislike the asterisk wildcard character.
|
|
// This is just for domains.
|
|
dwLength--;
|
|
if ((*lpszStart == ' ') ||
|
|
(*lpszStart == '*') ||
|
|
(IsSpecial(*lpszStart)) ||
|
|
(IsControl(*lpszStart))
|
|
)
|
|
return(FALSE);
|
|
lpszStart++;
|
|
}
|
|
return(TRUE);
|
|
}
|
|
|
|
static BOOL pValidateQtext(char *lpszStart, DWORD dwLength)
|
|
{
|
|
// Qtext can be any ASCII char, except '\"', '\\', and CR
|
|
// Note zero-length is valid
|
|
_ASSERT(!IsBadStringPtr(lpszStart, dwLength));
|
|
|
|
while (dwLength)
|
|
{
|
|
dwLength--;
|
|
if ((*lpszStart == '\"') ||
|
|
(*lpszStart == '\r')
|
|
)
|
|
return(FALSE);
|
|
|
|
// Process quoted (escape) char
|
|
if (*lpszStart == '\\')
|
|
{
|
|
if (!dwLength)
|
|
return(FALSE);
|
|
else
|
|
{
|
|
lpszStart++;
|
|
dwLength--;
|
|
}
|
|
}
|
|
|
|
lpszStart++;
|
|
}
|
|
return(TRUE);
|
|
}
|
|
|
|
static BOOL pValidateDtext(char *lpszStart, DWORD dwLength)
|
|
{
|
|
// Dtext can be any ASCII char, except '\"', '\\', and CR
|
|
// Note zero-length is valid
|
|
_ASSERT(!IsBadStringPtr(lpszStart, dwLength));
|
|
|
|
while (dwLength)
|
|
{
|
|
dwLength--;
|
|
if ((*lpszStart == '[') ||
|
|
(*lpszStart == ']') ||
|
|
(*lpszStart == '\r')
|
|
)
|
|
return(FALSE);
|
|
|
|
// Process quoted (escape) char
|
|
if (*lpszStart == '\\')
|
|
{
|
|
if (!dwLength)
|
|
return(FALSE);
|
|
else
|
|
{
|
|
lpszStart++;
|
|
dwLength--;
|
|
}
|
|
}
|
|
|
|
lpszStart++;
|
|
}
|
|
return(TRUE);
|
|
}
|
|
|
|
static BOOL pValidateQuotedString(char *lpszStart, DWORD dwLength)
|
|
{
|
|
// Quoted-stirngs are quotes between Qtext
|
|
// an empty Qtext is valid
|
|
_ASSERT(!IsBadStringPtr(lpszStart, dwLength));
|
|
|
|
if (dwLength < 2)
|
|
return(FALSE);
|
|
|
|
if ((*lpszStart != '\"') ||
|
|
(lpszStart[dwLength-1] != '\"'))
|
|
return(FALSE);
|
|
|
|
return(pValidateQtext(lpszStart + 1, dwLength - 2));
|
|
}
|
|
|
|
static BOOL pValidateDomainLiteral(char *lpszStart, DWORD dwLength)
|
|
{
|
|
// Domain-literals are square braces between Dtext
|
|
// an empty Dtext is valid
|
|
_ASSERT(!IsBadStringPtr(lpszStart, dwLength));
|
|
|
|
if (dwLength < 2)
|
|
return(FALSE);
|
|
|
|
if ((*lpszStart != '[') ||
|
|
(lpszStart[dwLength-1] != ']'))
|
|
return(FALSE);
|
|
|
|
return(pValidateDtext(lpszStart + 1, dwLength - 2));
|
|
}
|
|
|
|
static BOOL pValidateWord(char *lpszStart, DWORD dwLength)
|
|
{
|
|
// A word is any sequence of atoms and quoted-strings
|
|
// words may not be zero-length by inheritance
|
|
_ASSERT(!IsBadStringPtr(lpszStart, dwLength));
|
|
|
|
if ((pValidateAtom(lpszStart, dwLength)) ||
|
|
(pValidateQuotedString(lpszStart, dwLength)))
|
|
return(TRUE);
|
|
|
|
return(FALSE);
|
|
}
|
|
|
|
static BOOL pValidateSubdomain(char *lpszStart, DWORD dwLength)
|
|
{
|
|
// A subdomain may be an atom or domain-literal
|
|
// words may not be zero-length by inheritance
|
|
_ASSERT(!IsBadStringPtr(lpszStart, dwLength));
|
|
|
|
if ((pValidateAtomNoWildcard(lpszStart, dwLength)) ||
|
|
(pValidateDomainLiteral(lpszStart, dwLength)))
|
|
return(TRUE);
|
|
|
|
return(FALSE);
|
|
}
|
|
|
|
//
|
|
// Since validating the local part is so similar to validating the
|
|
// domain part, we write one function to do both. If fLocalPart
|
|
// is TRUE, we treat it as the local part of an email address, if
|
|
// it is FALSE, we treat it as the domain part.
|
|
//
|
|
static BOOL pValidateLocalPartOrDomain(char *lpszStart, DWORD dwLength, BOOL fLocalPart)
|
|
{
|
|
// A domain is one or more dot-delimited sub-domains, where a local-part
|
|
// is one or more dot-delimited words
|
|
// We rely on upstream calls to make sure the string is properly
|
|
// NULL-terminated
|
|
char *lpszBegin, *lpszEnd;
|
|
BOOL fNotFound;
|
|
|
|
_ASSERT(!IsBadStringPtr(lpszStart, dwLength));
|
|
|
|
lpszBegin = lpszStart;
|
|
lpszEnd = (char *)NULL;
|
|
|
|
while (lpszEnd = pFindNextUnquotedOccurrence(lpszBegin, '.', &fNotFound, 0))
|
|
{
|
|
// If it starts with a dot or has 2 dots in a row, we fail!
|
|
if (lpszBegin == lpszEnd)
|
|
return(FALSE);
|
|
|
|
// Now, check if the chunk is indeed a valid token
|
|
if (fLocalPart)
|
|
{
|
|
if (!pValidateWord(lpszBegin, (DWORD)(lpszEnd - lpszBegin)))
|
|
return(FALSE);
|
|
}
|
|
else
|
|
{
|
|
if (!pValidateSubdomain(lpszBegin, (DWORD)(lpszEnd - lpszBegin)))
|
|
return(FALSE);
|
|
}
|
|
|
|
// Allright, go to the next guy
|
|
lpszBegin = lpszEnd + 1;
|
|
}
|
|
|
|
if (!fNotFound)
|
|
return(FALSE);
|
|
|
|
// If it ends with a dot, it's also bad
|
|
lpszEnd = lpszStart + dwLength;
|
|
if (lpszEnd == lpszBegin)
|
|
return(FALSE);
|
|
|
|
// Don't forget the last chunk
|
|
if (fLocalPart)
|
|
return(pValidateWord(lpszBegin, (DWORD)(lpszEnd - lpszBegin)));
|
|
else
|
|
return(pValidateSubdomain(lpszBegin, (DWORD)(lpszEnd - lpszBegin)));
|
|
}
|
|
|
|
static BOOL pValidatePhrase(char *lpszStart, DWORD dwLength)
|
|
{
|
|
// A phrase is a collection of words, possibly separated
|
|
// by spaces
|
|
// We don't validate for now ...
|
|
_ASSERT(!IsBadStringPtr(lpszStart, dwLength));
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
//
|
|
// The following functions attempt to extract a clean addr-spec
|
|
// in the form of local-part[@domain] from a generic address
|
|
// Note that groups are not supported at this point
|
|
//
|
|
static BOOL pExtractAddressFromRouteAddress(char *lpszStart,
|
|
DWORD dwLength,
|
|
char *lpCleanAddress)
|
|
{
|
|
// A route address is in the form:
|
|
// phrase < [1#(@ domain) :] addr-spec >
|
|
char *lpMarker;
|
|
char *lpRoute;
|
|
BOOL fNotFound;
|
|
|
|
_ASSERT(!IsBadStringPtr(lpszStart, dwLength));
|
|
|
|
// First, find the opening angle brace
|
|
lpMarker = pFindNextUnquotedOccurrence(lpszStart, '<', &fNotFound, 0);
|
|
if (!lpMarker)
|
|
return(FALSE);
|
|
|
|
// Between lpStart and lpMarker is the phrase
|
|
_ASSERT(lpMarker >= lpszStart);
|
|
if (!pValidatePhrase(lpszStart, (DWORD)(lpMarker - lpszStart)))
|
|
return(FALSE);
|
|
|
|
// Now, find the closing angle bracket
|
|
_ASSERT(*lpMarker == '<');
|
|
lpszStart = lpMarker + 1;
|
|
lpMarker = pFindNextUnquotedOccurrence(lpszStart, '>',
|
|
&fNotFound, QUOTE_ANGLE);
|
|
if (!lpMarker)
|
|
return(FALSE);
|
|
|
|
_ASSERT(*lpMarker == '>');
|
|
|
|
// There should be nothing but white space after the closing brace
|
|
//Trim the whitespace or flag an error
|
|
char * lpTemp = lpMarker;
|
|
while(*++lpTemp != '\0')
|
|
{
|
|
if(*lpTemp != ' ' && *lpTemp != '\t')
|
|
return(FALSE);
|
|
}
|
|
*(lpMarker + 1) = '\0';
|
|
|
|
// The special address <> is reserved for NDR, and should be
|
|
// allowed
|
|
if (lpszStart == lpMarker)
|
|
{
|
|
lpCleanAddress[0] = '<';
|
|
lpCleanAddress[1] = '>';
|
|
lpCleanAddress[2] = '\0';
|
|
return(TRUE);
|
|
}
|
|
|
|
// The stuff enclosed in the angle braces is an addr-spec, and
|
|
// optionally preceded by route specifiers. We don't care about
|
|
// route specifiers, but we have to skip them.
|
|
lpRoute = pFindNextUnquotedOccurrence(lpszStart, ':', &fNotFound, 0);
|
|
|
|
if (!lpRoute)
|
|
{
|
|
// If we have no colon, then the whole lump should be the
|
|
// addr-spec
|
|
_ASSERT(lpMarker >= lpszStart);
|
|
*lpMarker++ = '\0';
|
|
lstrcpyn(lpCleanAddress, lpszStart, (DWORD)(lpMarker - lpszStart));
|
|
return(TRUE);
|
|
}
|
|
else
|
|
{
|
|
// We found a colon, the stuff to its right should be the
|
|
// addr-spec. As usual, we don't validate the route specifiers
|
|
lpRoute++;
|
|
_ASSERT(lpMarker >= lpRoute);
|
|
*lpMarker++ = '\0';
|
|
lstrcpyn(lpCleanAddress, lpRoute, (DWORD)(lpMarker - lpRoute));
|
|
return(TRUE);
|
|
}
|
|
}
|
|
|
|
static BOOL pExtractAddressFromMailbox( char *lpszStart,
|
|
DWORD dwLength,
|
|
char *lpCleanAddress)
|
|
{
|
|
// A mailbox can either be an addr-spec, or a route address
|
|
_ASSERT(!IsBadStringPtr(lpszStart, dwLength));
|
|
|
|
if (pExtractAddressFromRouteAddress(lpszStart, dwLength, lpCleanAddress))
|
|
return(TRUE);
|
|
|
|
// If it's not a route address, trhen it should ba an addr-spec
|
|
lstrcpyn(lpCleanAddress, lpszStart, dwLength + 1);
|
|
return(TRUE);
|
|
}
|
|
|
|
static BOOL pExtractAddressFromGroup( char *lpszStart,
|
|
DWORD dwLength,
|
|
char *lpCleanAddress)
|
|
{
|
|
// We always return false
|
|
return(FALSE);
|
|
|
|
/*
|
|
// A group is in the form:
|
|
// phrase : [#mailbox] ;
|
|
char *lpMarker;
|
|
char *lpSemiColon;
|
|
BOOL fNotFound;
|
|
|
|
_ASSERT(!IsBadStringPtr(lpszStart, dwLength));
|
|
|
|
// First, find the opening angle brace
|
|
lpMarker = pFindNextUnquotedOccurrence(lpszStart, ':', &fNotFound, 0);
|
|
if (!lpMarker || fNotFound)
|
|
return(FALSE);
|
|
|
|
// Between lpStart and lpMarker is the phrase
|
|
_ASSERT(lpMarker >= lpszStart);
|
|
if (!pValidatePhrase(lpszStart, (DWORD)(lpMarker - lpszStart)))
|
|
return(FALSE);
|
|
|
|
// Between the colon
|
|
lpMarker++;
|
|
lpSemiColon = pFindNextUnquotedOccurrence(lpMarker, ';', &fNotFound, 0);
|
|
if (!lpSemiColon || fNotFound)
|
|
return(FALSE);
|
|
|
|
_ASSERT(lpSemiColon >= lpMarker);
|
|
if (lpSemiColon == lpMarker)
|
|
;
|
|
*/
|
|
}
|
|
|
|
static BOOL pExtractAddress( char *lpszStart,
|
|
DWORD dwLength,
|
|
char *lpCleanAddress)
|
|
{
|
|
// A address is either an mailbox, or a group
|
|
_ASSERT(!IsBadStringPtr(lpszStart, dwLength));
|
|
|
|
*lpCleanAddress = '\0';
|
|
if (pExtractAddressFromMailbox(lpszStart, dwLength, lpCleanAddress) ||
|
|
pExtractAddressFromGroup(lpszStart, dwLength, lpCleanAddress))
|
|
return(TRUE);
|
|
return(FALSE);
|
|
}
|
|
|
|
|
|
// This function finds braces pairs in a given string, and returns
|
|
// pointers to the start and end of the first occurence of a
|
|
// [nested] pair of braces. The starting and ending character
|
|
// may be specified by the caller (starting and ending chars
|
|
// must be unique).
|
|
static char *pFindNextUnquotedOccurrence(char *lpszString,
|
|
char cSearch,
|
|
LPBOOL lpfNotFound,
|
|
DWORD dwDefaultState)
|
|
{
|
|
DWORD dwState = dwDefaultState;
|
|
DWORD i;
|
|
char ch;
|
|
char *lpStart = lpszString;
|
|
BOOL fFallThru;
|
|
|
|
*lpfNotFound = FALSE;
|
|
|
|
// If dwState is 0, then we are not inside any kind of quotes
|
|
while (ch = *lpStart)
|
|
{
|
|
// If we are not in any quotes, and the char is found,
|
|
// then we are done!
|
|
if (!dwState && (ch == cSearch))
|
|
return(lpStart);
|
|
|
|
// Another disgusting kludge, BUT WORKS!!
|
|
// For closing parentheses, we don't it in the
|
|
// acClose[] set, so we have to explicitly check for them
|
|
// here, since pMatchParentheses is very dependent on this.
|
|
// For open parentheses, similar case: since parentheses
|
|
// nest, we allow multiple open braces
|
|
if (dwState == QUOTE_PARENTHESES)
|
|
{
|
|
if (((cSearch == ')') || (cSearch == '(')) &&
|
|
(ch == cSearch))
|
|
return(lpStart);
|
|
}
|
|
|
|
// If it is a quoted char, we can skip it and the following
|
|
// char right away ... If the char following a quote '\' is
|
|
// the terminating NULL, we have an error.
|
|
if (ch == '\\')
|
|
{
|
|
lpStart++;
|
|
if (!*lpStart)
|
|
return(NULL);
|
|
|
|
// Quoted pairs not allowed outside quotes!
|
|
// if (!dwState)
|
|
// return(NULL);
|
|
}
|
|
else
|
|
{
|
|
// See if we have a state change ...
|
|
for (i = 0; i < MAX_QUOTE_TYPES; i++)
|
|
{
|
|
// Check the close case, too
|
|
fFallThru = TRUE;
|
|
|
|
// See if we have an opening quote of any sort
|
|
if (ch == acOpen[i])
|
|
{
|
|
// If it is an open brace, it shouldn't be a close brace
|
|
// EXCEPT: quotes.
|
|
fFallThru = FALSE;
|
|
|
|
// Special case for quotes: open = close
|
|
if (dwState & (1 << i))
|
|
{
|
|
// This is not a quoted pair, error!
|
|
// If it is a quote, and if the current state is
|
|
// inside quotes, then we let it
|
|
if ((ch == '\"') && (dwState == QUOTE_QUOTES))
|
|
fFallThru = TRUE;
|
|
else
|
|
return(NULL);
|
|
}
|
|
else if (!dwState)
|
|
{
|
|
// We are not in any quotes, so we can safely
|
|
// claim we are now inside quotes
|
|
dwState |= (1 << i);
|
|
}
|
|
}
|
|
|
|
// See if we have an closing quote of any sort
|
|
if (fFallThru && (ch == acClose[i]))
|
|
{
|
|
if (dwState & (1 << i))
|
|
{
|
|
// We are closing the correct kind of quote,
|
|
// so we cancel it ...
|
|
dwState = 0;
|
|
|
|
// Do a second check, in case we are looking
|
|
// for a close quote
|
|
if (ch == cSearch)
|
|
return(lpStart);
|
|
}
|
|
else if (!dwState)
|
|
{
|
|
// We are not in any quotes, so we have
|
|
// unmatched quotes!
|
|
return(NULL);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
lpStart++;
|
|
}
|
|
|
|
*lpfNotFound = TRUE;
|
|
return(NULL);
|
|
}
|
|
|
|
static BOOL pMatchParentheses( char **ppszStart,
|
|
char **ppszEnd,
|
|
LPDWORD lpdwPairs)
|
|
{
|
|
DWORD dwIteration = 0;
|
|
DWORD dwState = 0;
|
|
char *lpszString = *ppszStart;
|
|
char *lpStart;
|
|
char *lpEnd;
|
|
BOOL fNotFound = FALSE;
|
|
|
|
*lpdwPairs = 0;
|
|
lpStart = *ppszStart;
|
|
lpEnd = *ppszEnd;
|
|
|
|
for (;;)
|
|
{
|
|
// Find open brace
|
|
if (!(lpStart = pFindNextUnquotedOccurrence(lpStart, '(', &fNotFound, dwState)))
|
|
{
|
|
// If it's not found, we're done, else it's a format error!
|
|
if (fNotFound)
|
|
break;
|
|
else
|
|
return(FALSE);
|
|
}
|
|
|
|
// Save the start position, and since we are inside the first open
|
|
// parenthesis, we force suppress all quotes mode in subsequent
|
|
// open parenthesis searches
|
|
if (!dwIteration)
|
|
{
|
|
*ppszStart = lpStart;
|
|
dwState = QUOTE_PARENTHESES;
|
|
}
|
|
|
|
// If iteration > 0 and the start pointer surpasses
|
|
// the end pointer, we have the end of the first set
|
|
// of [nested] braces
|
|
if (dwIteration && (lpStart > lpEnd))
|
|
break;
|
|
|
|
// Find close brace
|
|
if (!(lpEnd = pFindNextUnquotedOccurrence(lpEnd, ')',
|
|
&fNotFound, QUOTE_PARENTHESES)))
|
|
return(FALSE);
|
|
|
|
// Open brace pointer must always be in front of close
|
|
// brace.
|
|
if (lpStart > lpEnd)
|
|
return(FALSE);
|
|
|
|
// Next iteration
|
|
lpStart++;
|
|
lpEnd++;
|
|
dwIteration++;
|
|
}
|
|
|
|
// Fill in the end pointer and leave (start ptr already
|
|
// filled in)
|
|
if (dwIteration)
|
|
lpEnd--;
|
|
*lpdwPairs = dwIteration;
|
|
*ppszEnd = lpEnd;
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
/*++
|
|
Name :
|
|
pStripAddrComments
|
|
|
|
Description:
|
|
This function strips comments from an RFC address
|
|
|
|
Arguments:
|
|
lpszAddress - Original address comes in, and clean addres comes out
|
|
|
|
Returns:
|
|
|
|
TRUE if comments were stripped and the format
|
|
of the address is legal.
|
|
FALSE otherwise
|
|
|
|
--*/
|
|
static BOOL pStripAddrComments(char *lpszAddress)
|
|
{
|
|
|
|
char *lpCopyStart;
|
|
char *lpStart, *lpEnd;
|
|
DWORD dwPairs;
|
|
DWORD dwCopyLen;
|
|
|
|
// First, we strip the comments
|
|
// We call the function above to find matching parenthesis pairs
|
|
lpStart = lpszAddress;
|
|
lpEnd = lpszAddress;
|
|
do
|
|
{
|
|
// Mark the actual start
|
|
lpCopyStart = lpEnd;
|
|
|
|
if (!pMatchParentheses(&lpStart, &lpEnd, &dwPairs))
|
|
{
|
|
// Failed!
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return(FALSE);
|
|
}
|
|
|
|
if (dwPairs)
|
|
{
|
|
// If fFound, then we found some comments
|
|
_ASSERT(*lpStart == '(');
|
|
_ASSERT(*lpEnd == ')');
|
|
|
|
// Copy the stuff over, excluding comments
|
|
_ASSERT(lpStart >= lpCopyStart);
|
|
dwCopyLen = (DWORD)(lpStart - lpCopyStart);
|
|
|
|
// Reset the pointer, and match again ...
|
|
lpEnd++;
|
|
lpStart = lpEnd;
|
|
}
|
|
else
|
|
{
|
|
dwCopyLen = lstrlen(lpCopyStart);
|
|
}
|
|
|
|
while (dwCopyLen--)
|
|
{
|
|
*lpszAddress++ = *lpCopyStart++;
|
|
}
|
|
|
|
} while (dwPairs);
|
|
|
|
// Terminate the string
|
|
*lpszAddress = '\0';
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
/*++
|
|
Name :
|
|
pStripAddrSpaces
|
|
|
|
Description:
|
|
This function strips extraneous spaces from an RFC address
|
|
An extraneous space is one that is not in a quoted pair, and
|
|
not inside any quoting pairs
|
|
|
|
Arguments:
|
|
lpszAddress - Original address comes in, and clean addres comes out
|
|
|
|
Returns:
|
|
|
|
TRUE if spaces were stripped
|
|
FALSE if any quotes/braces mismatch, or parameter error
|
|
|
|
--*/
|
|
static BOOL pStripAddrSpaces(char *lpszAddress)
|
|
{
|
|
char *lpszWrite;
|
|
char *lpszCopyStart;
|
|
char *lpszSearch;
|
|
DWORD dwCopyLen, i;
|
|
BOOL fNotFound;
|
|
BOOL fValidSpace;
|
|
char cSet[2] = { ' ', '\t' };
|
|
|
|
// First, get rid of spaces, then TABs
|
|
for (i = 0; i < 2; i++)
|
|
{
|
|
lpszWrite = lpszAddress;
|
|
lpszCopyStart = lpszAddress;
|
|
lpszSearch = lpszAddress;
|
|
|
|
do
|
|
{
|
|
lpszCopyStart = lpszSearch;
|
|
|
|
// Find unquoted space
|
|
lpszSearch = pFindNextUnquotedOccurrence(lpszSearch,
|
|
cSet[i], &fNotFound, 0);
|
|
|
|
// We cannot just allow casual spaces; An unquoted space
|
|
// must satisfy one or more of the following:
|
|
// 1) Leading space
|
|
// 2) Trailiing space
|
|
// 3) A space or TAB is in either side or both sides of the space
|
|
// 4) A special character is on either or both sides of the space
|
|
if (lpszSearch)
|
|
{
|
|
// Make sure it satisfies the above
|
|
fValidSpace = FALSE;
|
|
if (lpszSearch > lpszAddress)
|
|
{
|
|
if (pIsSpecialOrSpace(*(lpszSearch - 1)))
|
|
fValidSpace = TRUE;
|
|
}
|
|
else
|
|
fValidSpace = TRUE;
|
|
if (pIsSpecialOrSpace(*(lpszSearch + 1)))
|
|
fValidSpace = TRUE;
|
|
|
|
if (!fValidSpace)
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return(FALSE);
|
|
}
|
|
|
|
_ASSERT(lpszSearch >= lpszCopyStart);
|
|
dwCopyLen = (DWORD)(lpszSearch - lpszCopyStart);
|
|
lpszSearch++;
|
|
}
|
|
else
|
|
dwCopyLen = lstrlen(lpszCopyStart);
|
|
|
|
// 1) Leading spaces are automatically stripped!
|
|
while (dwCopyLen--)
|
|
{
|
|
*lpszWrite++ = *lpszCopyStart++;
|
|
}
|
|
|
|
} while (lpszSearch);
|
|
|
|
// If the reason it failed is not because it cannot find one,
|
|
// we have a formatting error here!
|
|
if (!fNotFound)
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return(FALSE);
|
|
}
|
|
*lpszWrite = '\0';
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
/*++
|
|
Name :
|
|
pStripAddrQuotes
|
|
|
|
Description:
|
|
This function strips all quotes in the local part of an address.
|
|
All quotes pairs within quotes are also collapsed.
|
|
|
|
Arguments:
|
|
lpszLocalPart - Local part comes in, and comes out without quotes
|
|
|
|
Returns:
|
|
|
|
TRUE if quotes were stripped
|
|
FALSE if any quotes/braces mismatch, or parameter error
|
|
|
|
--*/
|
|
static BOOL pStripAddrQuotes(char *lpszLocalPart, char **lpDomain)
|
|
{
|
|
char *lpszWrite;
|
|
char *lpszCopyStart;
|
|
char *lpszSearch;
|
|
DWORD dwCopyLen;
|
|
DWORD dwState = 0;
|
|
BOOL fNotFound;
|
|
|
|
_ASSERT(lpDomain);
|
|
|
|
// First, get rid of quotes
|
|
lpszWrite = lpszLocalPart;
|
|
lpszCopyStart = lpszLocalPart;
|
|
lpszSearch = lpszLocalPart;
|
|
|
|
do
|
|
{
|
|
lpszCopyStart = lpszSearch;
|
|
|
|
// Find next quote
|
|
lpszSearch = pFindNextUnquotedOccurrence(lpszSearch,
|
|
'\"', &fNotFound, dwState);
|
|
if (lpszSearch)
|
|
{
|
|
// Toggle the state
|
|
dwState = (dwState)?0:QUOTE_QUOTES;
|
|
|
|
// Found a quote, copy all the stuff before the space
|
|
_ASSERT(lpszSearch >= lpszCopyStart);
|
|
dwCopyLen = (DWORD)(lpszSearch - lpszCopyStart);
|
|
lpszSearch++;
|
|
|
|
// Move the domain offset back each time we find
|
|
// an unquoted quote
|
|
if (*lpDomain)
|
|
(*lpDomain)--;
|
|
}
|
|
else
|
|
dwCopyLen = lstrlen(lpszCopyStart);
|
|
|
|
while (dwCopyLen--)
|
|
{
|
|
// Another caveat: since we are stripping out the
|
|
// quotes, we must also take care of the quoted
|
|
// pairs. That is, we must remove the backslash
|
|
// delimiting each quoted pair.
|
|
if (*lpszCopyStart == '\\')
|
|
{
|
|
// If the last char of the string is a backslash, or
|
|
// if the backslash is not within quotes, error!
|
|
// CAUTION: if dwState == QUOTE_QUOTES, this means
|
|
// that the stuff we are copying is OUTSIDE of the quote,
|
|
// since we are copying stuff before the found quote
|
|
if ((dwState == QUOTE_QUOTES) || !dwCopyLen)
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return(FALSE);
|
|
}
|
|
lpszCopyStart++;
|
|
dwCopyLen--;
|
|
}
|
|
*lpszWrite++ = *lpszCopyStart++;
|
|
}
|
|
|
|
} while (lpszSearch);
|
|
|
|
*lpszWrite = '\0';
|
|
|
|
// If the reason it failed is not because it cannot find one,
|
|
// we have a formatting error here!
|
|
if (!fNotFound)
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return(FALSE);
|
|
}
|
|
|
|
// If we are left with no closing quote, then we also have an error
|
|
if (dwState == QUOTE_QUOTES)
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return(FALSE);
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
/*++
|
|
Name :
|
|
pStripUucpRoutes
|
|
|
|
Description:
|
|
This function strips all UUCP routing paths
|
|
|
|
Arguments:
|
|
lpszAddress - lpszAddress comes in, and comes out without UUCP paths
|
|
|
|
Returns:
|
|
|
|
TRUE if UUCP routes were stripped
|
|
FALSE if any quotes/braces mismatch, or parameter error
|
|
|
|
--*/
|
|
static BOOL pStripUucpRoutes(char *lpszAddress)
|
|
{
|
|
char *lpszDomainOffset;
|
|
char *lpszCopyStart;
|
|
char *lpszSearch;
|
|
BOOL fNotFound;
|
|
|
|
if (!(lpszDomainOffset = pFindNextUnquotedOccurrence(lpszAddress, '@', &fNotFound, 0)))
|
|
if (!fNotFound)
|
|
return(FALSE);
|
|
|
|
lpszSearch = lpszAddress;
|
|
lpszCopyStart = NULL;
|
|
|
|
// Find the last BANG
|
|
while (lpszSearch = pFindNextUnquotedOccurrence(lpszSearch, '!', &fNotFound, 0))
|
|
{
|
|
// If an unquoted bang occurs after the @ sign, we will return error
|
|
if (lpszDomainOffset && (lpszSearch > lpszDomainOffset))
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return(FALSE);
|
|
}
|
|
lpszCopyStart = lpszSearch++;
|
|
}
|
|
|
|
// If the reason is other than not found, we have an error
|
|
if (!fNotFound)
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
return(FALSE);
|
|
}
|
|
|
|
if (lpszCopyStart)
|
|
{
|
|
lpszCopyStart++;
|
|
while (*lpszCopyStart)
|
|
*lpszAddress++ = *lpszCopyStart++;
|
|
*lpszAddress = '\0';
|
|
}
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Name :
|
|
CAddr::ValidateDomainName
|
|
|
|
Description:
|
|
Determines whether a specified domain name is valid
|
|
|
|
Arguments:
|
|
lpszDomainName - ANSI domain name string to validate
|
|
|
|
Returns:
|
|
TRUE if valid, FALSE if not
|
|
|
|
--*/
|
|
BOOL CAddr::ValidateDomainName(char *lpszDomainName)
|
|
{
|
|
char szClean[MAX_INTERNET_NAME+1];
|
|
|
|
_ASSERT(lpszDomainName);
|
|
|
|
// Routine checking
|
|
if ((!lpszDomainName) ||
|
|
(!pValidateStringPtr(lpszDomainName, MAX_INTERNET_NAME+1)))
|
|
return(FALSE);
|
|
|
|
if (!pValidateAsciiString(lpszDomainName))
|
|
return(FALSE);
|
|
|
|
// Strip all comments
|
|
lstrcpy(szClean, lpszDomainName);
|
|
|
|
if (!pStripAddrComments(szClean))
|
|
return(FALSE);
|
|
|
|
if (!pStripAddrSpaces(szClean))
|
|
return(FALSE);
|
|
|
|
// Call our appropriate private
|
|
return(pValidateLocalPartOrDomain(szClean, lstrlen(szClean), FALSE));
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
CAddr::ExtractCleanEmailName
|
|
|
|
Description:
|
|
Extracts an absolutely clean email name from an address, quotes
|
|
are NOT stripped for the local part.
|
|
|
|
Arguments:
|
|
lpszCleanEmail - Pre allocated buffer to return the clean address
|
|
ppszDomainOffset - Pointer to '@' symbol separating the local-part
|
|
and the domain; NULL if not domain is specified
|
|
in the address.
|
|
lpdwCleanEmailLength - Length of the whole clean email returned
|
|
lpszSource - Source address to clean up
|
|
|
|
Returns:
|
|
TRUE if valid, FALSE if not
|
|
|
|
--*/
|
|
BOOL CAddr::ExtractCleanEmailName( char *lpszCleanEmail,
|
|
char **ppszDomainOffset,
|
|
DWORD *lpdwCleanEmailLength,
|
|
char *lpszSource)
|
|
{
|
|
char szClean[MAX_INTERNET_NAME+1];
|
|
char *lpDomainOffset;
|
|
BOOL fNotFound;
|
|
|
|
TraceFunctEnter("CAddr::ExtractCleanEmailName");
|
|
|
|
_ASSERT(lpszSource);
|
|
_ASSERT(lpszCleanEmail);
|
|
_ASSERT(ppszDomainOffset);
|
|
_ASSERT(lpdwCleanEmailLength);
|
|
|
|
// Routine checking
|
|
if (!lpszSource ||
|
|
!pValidateStringPtr(lpszSource, MAX_INTERNET_NAME+1) ||
|
|
!lpszCleanEmail ||
|
|
IsBadWritePtr(lpszCleanEmail, MAX_INTERNET_NAME+1) ||
|
|
!ppszDomainOffset ||
|
|
IsBadWritePtr(ppszDomainOffset, sizeof(char *)) ||
|
|
!lpdwCleanEmailLength ||
|
|
IsBadWritePtr(lpdwCleanEmailLength, sizeof(DWORD)))
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
goto LeaveWithError;
|
|
}
|
|
|
|
szClean[0] = '\0';
|
|
if (!pValidateAsciiString(lpszSource)) {
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
goto LeaveWithError;
|
|
}
|
|
|
|
// Strip all comments and spaces
|
|
lstrcpy(szClean, lpszSource);
|
|
|
|
StateTrace(0, " Source: %s", szClean);
|
|
|
|
if (!pStripAddrComments(szClean))
|
|
goto LeaveWithError;
|
|
|
|
StateTrace(0, " Comments stripped: %s", szClean);
|
|
|
|
// Extract the clean email name in a simple local-part@domain
|
|
// form. However, the local part may still have UUCP headers
|
|
if (!pExtractAddress(szClean, lstrlen(szClean), lpszCleanEmail))
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
goto LeaveWithError;
|
|
}
|
|
|
|
StateTrace(0, " Address: %s", lpszCleanEmail);
|
|
|
|
// Strip comments again ...
|
|
if (!pStripAddrComments(lpszCleanEmail)) {
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
goto LeaveWithError;
|
|
}
|
|
|
|
StateTrace(0, " Comments stripped (2): %s", lpszCleanEmail);
|
|
|
|
if (!pStripAddrSpaces(lpszCleanEmail)) {
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
goto LeaveWithError;
|
|
}
|
|
|
|
StateTrace(0, " Spaces stripped: %s", lpszCleanEmail);
|
|
|
|
// Now we examine the clean address, and try to locate the
|
|
// local-part and the domain
|
|
lpDomainOffset = pFindNextUnquotedOccurrence(lpszCleanEmail,
|
|
'@', &fNotFound, 0);
|
|
if (lpDomainOffset)
|
|
{
|
|
_ASSERT(lpDomainOffset >= lpszCleanEmail);
|
|
if (lpDomainOffset == lpszCleanEmail)
|
|
{
|
|
// First char cannot be '@'
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
goto LeaveWithError;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If it's not found, we assume there's no domain
|
|
if (!fNotFound)
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
goto LeaveWithError;
|
|
}
|
|
}
|
|
|
|
*lpdwCleanEmailLength = lstrlen(lpszCleanEmail);
|
|
*ppszDomainOffset = lpDomainOffset;
|
|
TraceFunctLeave();
|
|
return(TRUE);
|
|
|
|
LeaveWithError:
|
|
ErrorTrace(0, "CAddr::ExtractCleanEmailName failed");
|
|
TraceFunctLeave();
|
|
return(FALSE);
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
CAddr::ValidateCleanEmailName
|
|
|
|
Description:
|
|
Determines whether a specified email name is valid.
|
|
The input email name must be clean, i.e. no comments,
|
|
spaces, routing specifiers, etc.
|
|
|
|
Arguments:
|
|
lpszCleanEmailName - ANSI email name string to validate
|
|
lpszDomainOffset - Pointer to '@' sign in email string
|
|
|
|
Returns:
|
|
TRUE if valid, FALSE if not
|
|
|
|
--*/
|
|
BOOL CAddr::ValidateCleanEmailName( char *lpszCleanEmailName,
|
|
char *lpszDomainOffset)
|
|
{
|
|
DWORD dwLength, dwDomainLength;
|
|
|
|
TraceFunctEnterEx(0, "CAddr::ValidateCleanEmailName");
|
|
|
|
_ASSERT(lpszCleanEmailName);
|
|
|
|
if (!lpszCleanEmailName ||
|
|
!pValidateStringPtr(lpszCleanEmailName, MAX_INTERNET_NAME+1))
|
|
{
|
|
SetLastError(ERROR_INVALID_DATA);
|
|
goto LeaveWithError;
|
|
}
|
|
|
|
if (lpszDomainOffset)
|
|
{
|
|
_ASSERT(*lpszDomainOffset == '@');
|
|
_ASSERT(lpszDomainOffset > lpszCleanEmailName);
|
|
dwLength = (DWORD)(lpszDomainOffset - lpszCleanEmailName);
|
|
dwDomainLength = lstrlen(lpszCleanEmailName) - dwLength - 1;
|
|
*lpszDomainOffset++ = '\0';
|
|
}
|
|
else
|
|
dwLength = lstrlen(lpszCleanEmailName);
|
|
|
|
StateTrace(0, " Local-part: %s", lpszCleanEmailName);
|
|
|
|
if (!pValidateLocalPartOrDomain(lpszCleanEmailName,
|
|
dwLength,
|
|
TRUE))
|
|
{
|
|
ErrorTrace(0, "Invalid local part");
|
|
goto LeaveWithError;
|
|
}
|
|
|
|
if (lpszDomainOffset)
|
|
{
|
|
StateTrace(0, " Domain: %s", lpszDomainOffset);
|
|
if (!pValidateLocalPartOrDomain(lpszDomainOffset,
|
|
dwDomainLength, FALSE))
|
|
{
|
|
ErrorTrace(0, "Invalid domain");
|
|
goto LeaveWithError;
|
|
}
|
|
}
|
|
|
|
// Restore the string ...
|
|
if (lpszDomainOffset)
|
|
*--lpszDomainOffset = '@';
|
|
TraceFunctLeave();
|
|
return(TRUE);
|
|
|
|
LeaveWithError:
|
|
if (lpszDomainOffset)
|
|
if (*--lpszDomainOffset == '\0')
|
|
*lpszDomainOffset = '@';
|
|
ErrorTrace(0, "CAddr::ValidateCleanEmailName failed");
|
|
TraceFunctLeave();
|
|
return(FALSE);
|
|
}
|
|
|
|
|
|
/*++
|
|
|
|
Name :
|
|
CAddr::ValidateEmailName
|
|
|
|
Description:
|
|
Determines whether a specified email name is valid
|
|
|
|
Arguments:
|
|
lpszEmailName - ANSI email name string to validate
|
|
fDomainOptional - TRUE if domain is optional, FLASE forces
|
|
a domain to be included.
|
|
|
|
Returns:
|
|
TRUE if valid, FALSE if not
|
|
|
|
--*/
|
|
BOOL CAddr::ValidateEmailName( char *lpszEmailName,
|
|
BOOL fDomainOptional)
|
|
{
|
|
char szSource[MAX_INTERNET_NAME+1];
|
|
char *lpDomainOffset;
|
|
DWORD dwLength;
|
|
|
|
szSource[0] = '\0';
|
|
if (!ExtractCleanEmailName( szSource,
|
|
&lpDomainOffset,
|
|
&dwLength,
|
|
lpszEmailName))
|
|
return(FALSE);
|
|
|
|
if (!fDomainOptional && !lpDomainOffset)
|
|
return(FALSE);
|
|
|
|
return(ValidateCleanEmailName(szSource, lpDomainOffset));
|
|
}
|
|
|
|
/*++
|
|
|
|
Name :
|
|
CAddr::FindStartOfDomain
|
|
|
|
Description:
|
|
Finds the start of the domain part from a CLEAN email
|
|
address. The clean address may not contain
|
|
any comments, routing specifications, UUCP addresses,
|
|
and the such. No validation is done for the "clean
|
|
address".
|
|
|
|
Arguments:
|
|
lpszCleanEmail - ANSI CLEAN email name string whose local
|
|
part to extract
|
|
|
|
Returns:
|
|
A pointer to the '@' sign separating the local part and
|
|
the domain. NULL if the '@' sign is not found. Note that
|
|
'@' signs enclosed in quotes or in a proper quoted pair
|
|
are skipped.
|
|
|
|
--*/
|
|
CHAR * CAddr::FindStartOfDomain(CHAR *lpszCleanEmail)
|
|
{
|
|
BOOL fNotFound;
|
|
|
|
return(pFindNextUnquotedOccurrence(lpszCleanEmail,
|
|
'@', &fNotFound, 0));
|
|
}
|
|
|
|
|
|
//---[ CAddr::GetRFC822AddressCount ]------------------------------------------
|
|
//
|
|
//
|
|
// Description:
|
|
// Counts the number of addresses in a RFC822 list of addresses.
|
|
// Parameters:
|
|
// IN szAddressList List of addresses to count
|
|
// Returns:
|
|
// # of recipients in list
|
|
// History:
|
|
// 2/17/99 - MikeSwa Created
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
DWORD CAddr::GetRFC822AddressCount(char *szAddressList)
|
|
{
|
|
DWORD cRecips = 0;
|
|
LPSTR szCommentStart = szAddressList;
|
|
LPSTR szCommentEnd = szAddressList;
|
|
LPSTR szSearchStart = szAddressList;
|
|
LPSTR szSearchDelimiter = NULL; //ptr to delimiter
|
|
LPSTR szLastDelimiter = NULL; //ptr to last delimiter found
|
|
BOOL fSeenAlphaNum = FALSE;
|
|
BOOL fInQuote = TRUE;
|
|
DWORD dwPairs = 0;
|
|
CHAR chSaved = '\0';
|
|
CHAR *pchChanged = NULL;
|
|
BOOL fDelimiterNotFound = TRUE;
|
|
CHAR rgchDelimiters[] = {',', ';', '\0'};
|
|
|
|
|
|
//We look for RFC822 delimiters (, or ;) that are not in comments (which
|
|
//are delimited by ()'s or which are in quotes.
|
|
|
|
//No string... no recips
|
|
if (!szAddressList)
|
|
goto Exit;
|
|
|
|
//If we have any string... we start out with 1 recip
|
|
cRecips++;
|
|
|
|
// First, we strip the comments
|
|
// We call the function above to find matching parenthesis pairs
|
|
do
|
|
{
|
|
//Set start of search to start of string being scanned for ()'s
|
|
szSearchStart = szCommentStart;
|
|
|
|
if (!pMatchParentheses(&szCommentStart, &szCommentEnd, &dwPairs))
|
|
{
|
|
// Failed!
|
|
cRecips = 0;
|
|
goto Exit;
|
|
}
|
|
|
|
if (dwPairs) //we have comments
|
|
{
|
|
//We found some comments
|
|
_ASSERT(*szCommentStart == '(');
|
|
_ASSERT(*szCommentEnd == ')');
|
|
|
|
//If the first character of our search was a '('... then we
|
|
//should not bother searching for delimiters
|
|
if (szSearchStart == szCommentStart)
|
|
{
|
|
szCommentStart = szCommentEnd + 1;
|
|
continue;
|
|
}
|
|
|
|
//Set the end of our search for delimiters & set to NULL character
|
|
pchChanged = szCommentStart;
|
|
chSaved = *szCommentStart;
|
|
*szCommentStart = '\0';
|
|
|
|
// Reset the pointers, and match again ...
|
|
szCommentEnd++;
|
|
szCommentStart = szCommentEnd;
|
|
}
|
|
else
|
|
{
|
|
//We found no further comments... there is no need to save a character
|
|
chSaved = '\0';
|
|
pchChanged = NULL;
|
|
}
|
|
|
|
|
|
//Now we will search for unqoted delimiters in this uncommented section
|
|
|
|
//Iterate over all the delimiters we have
|
|
for (CHAR *pchDelimiter = rgchDelimiters; *pchDelimiter; pchDelimiter++)
|
|
{
|
|
szSearchDelimiter = szSearchStart;
|
|
do
|
|
{
|
|
szSearchDelimiter = pFindNextUnquotedOccurrence(
|
|
szSearchDelimiter, *pchDelimiter,
|
|
&fDelimiterNotFound, 0);
|
|
|
|
if (!fDelimiterNotFound)
|
|
{
|
|
_ASSERT(*pchDelimiter == *szSearchDelimiter);
|
|
cRecips++;
|
|
|
|
if (szSearchDelimiter && (szSearchDelimiter > szLastDelimiter))
|
|
szLastDelimiter = szSearchDelimiter;
|
|
}
|
|
else
|
|
{
|
|
//We know we won't find anymore in this section
|
|
break;
|
|
}
|
|
|
|
//Keep on looping while we are still finding delimiters and we are not
|
|
//at the end of the string
|
|
} while (!fDelimiterNotFound &&
|
|
szSearchDelimiter && *szSearchDelimiter &&
|
|
*(++szSearchDelimiter));
|
|
}
|
|
|
|
//Restore changed character
|
|
if (pchChanged && ('\0' != chSaved))
|
|
*pchChanged = chSaved;
|
|
|
|
} while (dwPairs);
|
|
|
|
//Make sure the last delimiter was not at the end of the buffer
|
|
if (szLastDelimiter && cRecips && *szLastDelimiter)
|
|
{
|
|
while (*(++szLastDelimiter))
|
|
{
|
|
//if it is not a space... count it as a recipient
|
|
if (!isspace((UCHAR)*szLastDelimiter))
|
|
goto Exit;
|
|
}
|
|
//Only whitespace after last delimiter... we have counted 1 too many recips
|
|
cRecips--;
|
|
}
|
|
|
|
Exit:
|
|
return cRecips;
|
|
}
|
|
|
|
|
|
//---[ IsRecipientInRFC822AddressList ]----------------------------------------
|
|
//
|
|
//
|
|
// Description:
|
|
// Determines if a given recipient is in the given RFC822 formatted
|
|
// recipient list.
|
|
// Parameters:
|
|
// IN szAddressList Address list to check in
|
|
// IN szRecip Recipient Address to check for
|
|
// Returns:
|
|
// TRUE if there was a match
|
|
// FALSE if there was no match
|
|
// History:
|
|
// 2/17/99 - MikeSwa Created
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
BOOL CAddr::IsRecipientInRFC822AddressList(char *szAddressList, char *szRecip)
|
|
{
|
|
LPSTR szRecipEnd = NULL;
|
|
CHAR chSaved = '\0';
|
|
LPSTR szCurrentAddress = szAddressList;
|
|
BOOL fFound = FALSE;
|
|
DWORD cbAddress;
|
|
|
|
if (!szAddressList || !szRecip)
|
|
goto Exit;
|
|
|
|
//Convert everything to lower case so we match correctly
|
|
szCurrentAddress = szAddressList;
|
|
do
|
|
{
|
|
*szCurrentAddress = (CHAR) tolower(*szCurrentAddress);
|
|
} while(*(++szCurrentAddress));
|
|
|
|
szCurrentAddress = szRecip;
|
|
do
|
|
{
|
|
*szCurrentAddress = (CHAR) tolower(*szCurrentAddress);
|
|
} while(*(++szCurrentAddress));
|
|
|
|
//skip past white space in recipient
|
|
while (*szRecip && isspace((UCHAR)*szRecip))
|
|
szRecip++;
|
|
|
|
//Find and skip past extranious trailing whitespace
|
|
cbAddress = strlen(szRecip);
|
|
szRecipEnd = szRecip + cbAddress/sizeof(CHAR);
|
|
szRecipEnd--;
|
|
|
|
while (isspace((UCHAR)*szRecipEnd))
|
|
{
|
|
cbAddress--;
|
|
szRecipEnd--;
|
|
}
|
|
|
|
//Make szRecipEnd point to last space
|
|
szRecipEnd++;
|
|
|
|
//Null terminate before trailing whitespace
|
|
chSaved = *szRecipEnd;
|
|
*szRecipEnd = '\0';
|
|
|
|
//Search for addresss as substring, and see if it looks like a lone address
|
|
for (szCurrentAddress = strstr(szAddressList, szRecip);
|
|
szCurrentAddress && !fFound;
|
|
szCurrentAddress = strstr(++szCurrentAddress, szRecip))
|
|
{
|
|
//look for surrounding characters to not match partial addresses
|
|
//We don't want "user" to match "user1" or "user@foo" or "foo@user"
|
|
if ((szCurrentAddress != szAddressList))
|
|
{
|
|
if (!pIsSpecialOrSpace(*(szCurrentAddress-1)) ||
|
|
('@' == *(szCurrentAddress-1)))
|
|
continue;
|
|
}
|
|
|
|
|
|
if (szCurrentAddress[cbAddress/sizeof(CHAR)])
|
|
{
|
|
if (!pIsSpecialOrSpace(szCurrentAddress[cbAddress/sizeof(CHAR)]) ||
|
|
('@' == szCurrentAddress[cbAddress/sizeof(CHAR)]))
|
|
continue;
|
|
}
|
|
|
|
//The address looks like a match
|
|
fFound = TRUE;
|
|
break;
|
|
|
|
}
|
|
|
|
Exit:
|
|
|
|
//Restore saved space
|
|
if (szRecipEnd && chSaved)
|
|
*szRecipEnd = chSaved;
|
|
|
|
return fFound;
|
|
}
|