//
// The developer of the original code and/or files is Tripwire, Inc.
// Portions created by Tripwire, Inc. are copyright (C) 2000 Tripwire,
// Inc. Tripwire is a registered trademark of Tripwire, Inc.  All rights
// reserved.
// 
// This program is free software.  The contents of this file are subject
// to the terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2 of the License, or (at your
// option) any later version.  You may redistribute it and/or modify it
// only in compliance with the GNU General Public License.
// 
// This program is distributed in the hope that it will be useful.
// However, this program is distributed AS-IS WITHOUT ANY
// WARRANTY; INCLUDING THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS
// FOR A PARTICULAR PURPOSE.  Please see the GNU General Public License
// for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
// USA.
// 
// Nothing in the GNU General Public License or any other license to use
// the code or files shall permit you to use Tripwire's trademarks,
// service marks, or other intellectual property without Tripwire's
// prior written consent.
// 
// If you have any questions, please contact Tripwire, Inc. at either
// info@tripwire.org or www.tripwire.org.
//
///////////////////////////////////////////////////////////////////////////////
// parserhelper.cpp -- helper classes that are called by yacc parser
//

//=========================================================================
// OTHER DIRECTIVES
//=========================================================================

#include "stdtwparser.h"

//=========================================================================
// INCLUDES
//=========================================================================

#include "parserhelper.h"
#include "core/fsservices.h"
#include "fco/fcopropvector.h"
#include "fco/parsergenreutil.h"
#include "fco/genreswitcher.h"
#include "fco/twfactory.h"
#include "fco/fcospechelper.h"
#include "core/twlimits.h"
#include "core/stringutil.h"

//=========================================================================
// UTIL FUNCTION PROTOTYES
//=========================================================================

static int  util_ConvertHex( const char* psz, int* const pnCharsRead );
static int  util_ConvertUnicode( const char* psz, int* const pnCharsRead );
static int  util_ConvertOctal( const char* psz, int* const pnCharsRead );
static bool util_IsOctal( const char ch );
static int  util_GetEscapeValueOfChar( char ch );
static int  util_GetRecurseDepth( const cParseNamedAttrList* pList ); //throw( eParserHelper )
static void util_EatAllSpaces( TSTRING& str );
static void util_LoseSurroundingWS( TSTRING& str );
#ifdef _DEBUG
static bool util_AsciiCharsActLikeTheyShould();
#endif

// finds first '&', '+', '-'  on or after str[i]
static TSTRING::size_type util_FindNextDelim( const TSTRING& str, TSTRING::size_type i );


//=========================================================================
// STATIC DATA MEMBERS
//=========================================================================

cErrorBucket*                           cParserHelper::mpError;
int                                     cParserHelper::miLineNum;
cPreprocessor::AcceptStack              cPreprocessor::mStateStack;
bool                                    cPreprocessor::mfIgnoreSection;

cParserHelper::ScopedAttrContainer      cParserHelper::mScopedAttrs;
cParserHelper::GenreContainer           cParserHelper::mAph;
cGenreParseInfo*                        cParserHelper::pCurrentGenreInfo;
cSymbolTable							cParserHelper::mGlobalVarTable;
bool                                    cParserHelper::mfParseOnly;


//=========================================================================
// METHOD CODE
//=========================================================================

eParserHelper::eParserHelper( const TSTRING& strMsg, int nLine /*= CURRENT_LINE */)
{
    TOSTRINGSTREAM strErr;

    strErr << strMsg;

    if( NO_LINE != nLine )
    {
        // separate the message from the line number
        if( ! strMsg.empty() )
            strErr << _T(": ");

        // output the line number
        strErr << TSS_GetString( cTWParser, twparser::STR_LINE_NUMBER );
    
        if( CURRENT_LINE == nLine )
            strErr << cParserHelper::GetLineNumber();
        else
            strErr << nLine;
    }

    mMsg = strErr.str();
}
    

void cParserHelper::Init( cErrorBucket* pE )
{
    // start off in default genre 
    cGenreSwitcher::GetInstance()->SelectGenre( cGenreSwitcher::GetInstance()->GetDefaultGenre() );

    mpError = pE;
    cPreprocessor::PushState( cPreprocessor::STATE_ACCEPT );
    miLineNum = 1;
    cPreprocessor::ReadSection();
    pCurrentGenreInfo = NULL;
    mfParseOnly = false;
}

void cParserHelper::Finit( cGenreSpecListVector* pPolicy )
{
    //
    // don't build spec list if just checking syntax
    //
    if( ! pPolicy )
    {
        CleanUp();
        return;
    }

    int nRulesInPolicy = 0;

    GenreContainer::iterator i;
    for( i = mAph.begin(); i != mAph.end(); i++ )
    {
        cGenreSpecListPair slp;
        cGenre::Genre g = i->first;
        cGenreParseInfo* pgpi = i->second;

        //
        // create the specs from rules
        //
        cParserUtil::CreateFCOSpecs( g, pgpi, slp.GetSpecList() );
        slp.SetGenre( g );
        
        //
        // get rule count for section
        //
        int nRulesInSection = slp.GetSpecList().Size();
        if( 0 == nRulesInSection )
        {
            TSTRING str = TSS_GetString( cTWParser, twparser::STR_ERR2_PARSER_NO_RULES_IN_SECTION);
            str.append( cGenreSwitcher::GetInstance()->GenreToString( slp.GetGenre(), true ) );

            eParserNoRulesInSection e( str );
            e.SetFatality( false );
            cParserHelper::GetErrorBucket()->AddError( e );
        }

        //
        // increment rule count for policy file
        //
        nRulesInPolicy += slp.GetSpecList().Size();

        //
        // add to policy
        //
        pPolicy->push_back( slp );
    }

    CleanUp();

    //
    // check that we have some rules
    //
    if( 0 == nRulesInPolicy )
        throw eParserNoRules( _T(""), eParserHelper::NO_LINE );
}

void cParserHelper::CleanUp()
{
    GenreContainer::iterator i;
    for( i = mAph.begin(); i != mAph.end(); i++ )
        delete i->second;
    while( ! cPreprocessor::Empty() )
        cPreprocessor::PopState();    
    while( ! cParserHelper::ScopeEmpty() )
        cParserHelper::PopScope();
}

cPreprocessor::cPreprocessor()
{
    // start in the accept state
    mStateStack.push( STATE_ACCEPT );
}

void cPreprocessor::PushState( AcceptState state )
{
    mStateStack.push( state );

    cDebug d("cPreprocessor::PushState");

    if( state == STATE_ACCEPT )
        d.TraceDebug(_T("State == STATE_ACCEPT\n"));
    else
        d.TraceDebug(_T("State == STATE_IGNORE\n"));
}

void cPreprocessor::PopState()
{
    cDebug d("cPreprocessor::PopState");

    ASSERT( ! mStateStack.empty() );
    mStateStack.pop();

#ifdef _DEBUG    
    if( !Empty() && TopState() == STATE_ACCEPT )
        d.TraceDebug(_T("State == STATE_ACCEPT\n"));
    else
        d.TraceDebug(_T("State == STATE_IGNORE\n"));
#endif
}

cPreprocessor::AcceptState cPreprocessor::GetState()
{ 
    if( STATE_ACCEPT == TopState() && false == mfIgnoreSection )
        return STATE_ACCEPT;
    else
        return STATE_IGNORE;
}

cPreprocessor::AcceptState cPreprocessor::TopState()
{
    ASSERT( ! mStateStack.empty() );

    return mStateStack.top();
}

cPreprocessor::AcceptState cPreprocessor::UnderneathTopState()
{
    ASSERT( mStateStack.size() > 1 );

    AcceptState stateRet, stateSave;

    stateSave = mStateStack.top();
    mStateStack.pop();
    stateRet = mStateStack.top();
    mStateStack.push( stateSave );

    return( stateRet );
}

void cPreprocessor::ToggleTopState()
{
    ASSERT( ! mStateStack.empty() );
    cDebug d("cPreprocessor::ToggleState");

    AcceptState state = TopState();
    
    mStateStack.pop();

    // toggle state
    mStateStack.push( GetOppositeState( state ) );
    
    if( GetOppositeState( state ) == STATE_ACCEPT )
        d.TraceDebug(_T("State == STATE_ACCEPT\n"));
    else
        d.TraceDebug(_T("State == STATE_IGNORE\n"));
}


cPreprocessor::AcceptState cPreprocessor::GetOppositeState( AcceptState state )
{
    if( state == STATE_IGNORE )
        return STATE_ACCEPT;
    else
        return STATE_IGNORE;
}


bool cPreprocessor::AtTopLevel()
{
    ASSERT( ! mStateStack.empty() );

    bool fAtTop;
    AcceptState stateSave;

    // save and pop current state
    stateSave = mStateStack.top();
    mStateStack.pop();

    // we're at top level if there was only one item in stack
    fAtTop = mStateStack.empty();

    // return item to stack
    mStateStack.push( stateSave );

    return( fAtTop );
}

// pushes cPreprocessor::STATE_IGNORE onto the state stack if 
// strSection is not an acceptable genre for this OS

void cParserHelper::SetSection( TSTRING& strSection ) // throw( eParserHelper )
{
    //
    // do we recognize the section string?
    //
    cGenre::Genre g = cGenreSwitcher::GetInstance()->StringToGenre( strSection.c_str() );
    if( cGenre::GENRE_INVALID != g && cGenreSwitcher::GetInstance()->IsGenreAppropriate( g ) )
    {       
        cGenreSwitcher::GetInstance()->SelectGenre( g );
                    
        // set current genre info pointer    
        GenreContainer::iterator i = mAph.find( cGenreSwitcher::GetInstance()->CurrentGenre() );
        if( mAph.end() == i )
        {
            pCurrentGenreInfo = new cGenreParseInfo;
            mAph.insert( GenreContainer::value_type( cGenreSwitcher::GetInstance()->CurrentGenre(), pCurrentGenreInfo ) );

            cPreprocessor::ReadSection();
        }
        else 
        {
            throw eParserSectionAlreadyDefined( strSection );
		}
    }
    else // ignore
    {
        if( ! ParseOnly() )
        {
            eParserIgnoringSection e( strSection );
            e.SetFatality( false );
            if( mpError ) mpError->AddError( e );
        }

        cPreprocessor::IgnoreSection();
    }
}

// A function for inserting a global variable into the global variable table.  Throws
// an exception if they try to redefine one of the predefined variables.  Returns
// the return value of the hash table insert.
bool cParserHelper::InsertGlobalVariable( const TSTRING& var, const TSTRING& val )
{
	TSTRING dummy;
    /*  if( cParserHelper::GetGenreInfo()->GetPredefVarTable().Lookup(var, dummy) )
			throw eParserRedefineVar( var );
	*/
	// TODO : Verify that there is no feasible way to search the predefines without 
	// messing everything up, given our current architecture.

	return mGlobalVarTable.Insert( var, val );
}

cGenreParseInfo* cParserHelper::GetGenreInfo()
{
    if( ! pCurrentGenreInfo )
    {        
        pCurrentGenreInfo = new cGenreParseInfo;
        mAph.insert( GenreContainer::value_type( cGenreSwitcher::GetInstance()->CurrentGenre(), pCurrentGenreInfo ) );
    }

    return pCurrentGenreInfo;
}

bool cParserUtil::AnyOfTheseHostsExists( cParseStringList* pList )
{
    TSTRING strHostName;
    cDebug d("cPreprocessor::AnyOfTheseHostsExists");

    // this throws an exception.  let it go on up, since we can't do any ifhosting
    // if we don't know what machine we're on
    iFSServices::GetInstance()->GetMachineName( strHostName ); 

    // want to do a case-insensitive compare
    std::transform( strHostName.begin(), strHostName.end(), strHostName.begin(), _totlower );
    
    // if the host name matches any in the list, return true
    for( std::list<TSTRING>::iterator iter = pList->begin(); iter != pList->end(); iter++ )
    {
        // want to do case-insensitive compare
        std::transform( (*iter).begin(), (*iter).end(), (*iter).begin(), _totlower );

        if( 0 == strHostName.compare( (*iter) ) )
        {
            d.TraceDebug(_T("host exists\n"));
            return true;
        }
    }

    d.TraceDebug(_T("host doesn't exist\n"));
    return false;
}

// converts escaped character sequences in strEscapedString into their escape characters in strInterpretedString 
//( e.g. turns "Hello World!\n" (literal backslash) into "Hello World!<return char>"
// all C++ escape sequences are recognized: 
//          (1) octal numbers \012 (1, 2, or 3 octal digits), 
//          (2) hex numbers \x2A ( 'x' followed by one or two hex digits ), 
//          (3) unicode characters \uABCD ( 'u' followed by exactly 4 hex digits )
//          (4) chars: \t, \v, \b, \r, \f, \a, \\, \?, \', and \"
//          (5) all other escaped chars are treated as if not escaped
void cParserUtil::InterpretEscapedString( const std::string& strEscapedString, TSTRING& strInterpretedString )
{    
    cDebug d("cParserHelper::InterpretEscapedString");

    ASSERT( (void*)&strEscapedString != (void*)&strInterpretedString ); // don't let us read and write to same string

    // The source string may contain literal multibyte characters, escaped MB characters, and escaped Unicode characters.
    // On Unix TCHAR == char always, therefore literal and esacped mb chars can be stored in strInterpretedString
    // directly, and everythign will be hunky dory (escased Unicode will cause an error).
    // But on Windows we need to build an intermediate narrow string because it needs to be passed through
    // cStringUtil::StrToTstr() to convert literal and escaped mb chars to TCHARs (which are wchar_t in this case).
    // Escaped Unicode chars are included in this intermediate string specially encoded so that StrToTstr()
    // leaves it alone.  After we have the wide string, we convert these encoded Unicode chars into true 16 bit 
    // Unicode chars.

    #ifdef _UNICODE
    typedef char INTERCHAR;
    const char unicodeEscape = '\x01'; // This value must be chosen carefully so not to conflict with escape chars or mb chars
    const char unicodeBase = 'A';    // This value must not fool the convertion from single to multibyte
    bool unicodeWasEscaped = false;
    std::string strIntermediateString;
    #else
    // for unix we cheat a bit and do the initial interpretation 
    // directly to the final string.
    typedef TCHAR INTERCHAR;
    TSTRING& strIntermediateString = strInterpretedString;
    #endif
    
    const char* pchCur = strEscapedString.c_str();

    for( strIntermediateString.erase(); 
         *pchCur != 0; 
         pchCur++ ) // only single byte in this part of the policy file
    {
        if( *pchCur != '\\' ) // just regular char
        {
            #ifdef _UNICODE
            // check for our special unicode escape char, and escape it if necessary
            if ( *pchCur == unicodeEscape )
            {
                strIntermediateString += unicodeEscape;
                unicodeWasEscaped = true;
            }
            #endif

            strIntermediateString += *pchCur;
        }
        else // deal with escaped character sequence
        {
            // make sure the '\' isn't the end of the string
            // ( if it is, the "for" will ++ pchCur, see it's 0, then break )
            if( *(pchCur + 1 ) )
            {
                int nCharsRead;

                pchCur++; // go to char past '\'

                if (*pchCur == 'x' && std::isxdigit<TCHAR>(*(pchCur+1), std::locale() ) ) // deal with \xXXXX where 'x' is the character 'x', and 'X' is a hex number
                {
                    pchCur++; // go to char past 'x'
                    char cEscapedChar = static_cast<char>( util_ConvertHex( pchCur, &nCharsRead ) );
                    pchCur += ( nCharsRead == 0 ) ? 0 : nCharsRead - 1; //increment pointer to last char we read 

                    if (cEscapedChar == 0) // null characters are not allowed
                    {
                        throw eParserBadHex( cStringUtil::StrToTstr( pchCur - (( nCharsRead == 0 ) ? 0 : nCharsRead - 1) ) );
                    }
                    else
                    {
                        strIntermediateString += static_cast<INTERCHAR>(cEscapedChar);
                    }
                }
                else if (*pchCur == 'u') // unicode escape
                {
                    pchCur++; // go to char past 'x'
                    wchar_t wcEscapedChar = static_cast<wchar_t>( util_ConvertUnicode( pchCur, &nCharsRead ) );
                    pchCur += ( nCharsRead == 0 ) ? 0 : nCharsRead - 1; //increment pointer to last char we read 

                    if (wcEscapedChar == 0) // null characters are not allowed
                    {
                        throw eParserBadHex( cStringUtil::StrToTstr( pchCur - (( nCharsRead == 0 ) ? 0 : nCharsRead - 1) ) );
                    }

                    // For non-unicode Unix, we don't support > 0xff characters.
                    // Unicode Windows supports it just fine.
                    #ifndef _UNICODE
                    else if (wcEscapedChar > 0xff)
                    {
                        throw eParserBadHex( cStringUtil::StrToTstr( pchCur - (( nCharsRead == 0 ) ? 0 : nCharsRead - 1) ) );
                    }
                    else
                    {
                        strIntermediateString += static_cast<INTERCHAR>(wcEscapedChar);
                    }
                    #else
                    else if (wcEscapedChar <= 0xff)
                    {
                        strIntermediateString += static_cast<INTERCHAR>(wcEscapedChar);
                    }
                    else
                    {
                        strIntermediateString += unicodeEscape;
                        strIntermediateString += static_cast<INTERCHAR>((wcEscapedChar >> 12 & 0xf)  + unicodeBase);
                        strIntermediateString += static_cast<INTERCHAR>((wcEscapedChar >> 8 & 0xf) + unicodeBase);
                        strIntermediateString += static_cast<INTERCHAR>((wcEscapedChar >> 4 & 0xf) + unicodeBase);
                        strIntermediateString += static_cast<INTERCHAR>((wcEscapedChar & 0xf) + unicodeBase);
                        unicodeWasEscaped = true;
                    }
                    #endif
                }
                else if( util_IsOctal( *pchCur ) ) // deal with \xxx where 'x' is an octal digit
                {
                    strIntermediateString += static_cast<INTERCHAR>( util_ConvertOctal( pchCur, &nCharsRead ) );
                    pchCur += ( nCharsRead == 0 ) ? 0 : nCharsRead - 1; //increment pointer to last char we read
                }
                else // deal with \x where 'x' is any other character
                {
                    strIntermediateString += static_cast<INTERCHAR>( util_GetEscapeValueOfChar( *pchCur ) );
                }
            }
        }
    }

    #ifdef _UNICODE
    // now convert to the wide vertion
    strInterpretedString = cStringUtil::StrToTstr(strIntermediateString);

    // search for unicode escapes
    if (unicodeWasEscaped)
    {
        TSTRING::iterator i;
        for (i = strInterpretedString.begin(); i != strInterpretedString.end(); ++i)
        {
            if (*i == static_cast<TCHAR>(unicodeEscape))
            {
                i = strInterpretedString.erase(i);
                ASSERT(i != strInterpretedString.end());  // This would indicate a logic error above

                if (*i != static_cast<TCHAR>(unicodeEscape)) // check that this is escaped unicode
                {
                    wchar_t wc;
                    TSTRING::iterator j = i;

                    ASSERT(*j >= unicodeBase && *j < unicodeBase + 16);
                    wc = static_cast<wchar_t>((*j++ - unicodeBase) << 12);
                    ASSERT(j != strInterpretedString.end());

                    ASSERT(*j >= unicodeBase && *j < unicodeBase + 16);
                    wc |= static_cast<wchar_t>((*j++ - unicodeBase) << 8);
                    ASSERT(j != strInterpretedString.end());

                    ASSERT(*j >= unicodeBase && *j < unicodeBase + 16);
                    wc |= static_cast<wchar_t>((*j++ - unicodeBase) << 4);
                    ASSERT(j != strInterpretedString.end());

                    ASSERT(*j >= unicodeBase && *j < unicodeBase + 16);
                    wc |= static_cast<wchar_t>((*j++ - unicodeBase));
                    i = strInterpretedString.erase(i, j);
                    i = strInterpretedString.insert(i, wc);
                }
            }
        }
    }
    #endif

#ifdef _DEBUG
    std::string str;

    str = "before: <";
    str += strEscapedString;
    str += ">\n";
    d.TraceDebug( str.c_str() );
    
    TSTRING tstr;
    tstr = _T("after: <");
    tstr += strInterpretedString;
    tstr += _T(">\n");
    d.TraceDebug( tstr.c_str() );
#endif
}

///////////////////////////////////////////////////////////////////////////////
// CreateFCOSpecs -- foreach rule, create an FCOSpec.  then attach the stop
//			points.
///////////////////////////////////////////////////////////////////////////////
void cParserUtil::CreateFCOSpecs( cGenre::Genre g, cGenreParseInfo* pgpi, cFCOSpecList &fcospeclist) //throw( eParserHelper )
{
    cGenreSwitcher::GetInstance()->SelectGenre( g );
    iParserGenreUtil* pHelper = iTWFactory::GetInstance()->CreateParserGenreUtil();

    cDebug d("cGenreParseInfo::CreateFCOSpecs");

	// foreach rule
	std::list<const cParseRule *>::iterator rule;
	for (rule = pgpi->GetRules()->begin(); rule != pgpi->GetRules()->end(); rule++) 
    {
        //
		// create the spec with its the last element of its start point as its name.
        // if the attribute 'rulename' is specified, we'll set it as the spec name later
        // Need to set the start point after the helper is set
        //        
		cFCOName startpoint = (*rule)->GetName();
        iFCOSpec *pfcospec = iTWFactory::GetInstance()->CreateSpec( startpoint.GetShortName() ); // we'll set the helper below

        //////////////////////////////////////////////////////////////
        // look to see if no recurse was specified.  if it is, set the helper to a cFCOSpecNoChildren,
        // otherwise set it to a cFCOSpecStopPointSet  
        int depth = util_GetRecurseDepth( (*rule)->GetAttrList() );
        if( 0 == depth )
        {
            // don't use stop point set.  say don't recurse
            cFCOSpecNoChildren* pNoKids = new cFCOSpecNoChildren;
            pfcospec->SetHelper( pNoKids );
        }
        else
        {
            cFCOSpecStopPointSet* pStopPtSet = new cFCOSpecStopPointSet;
            
		    // set stop points
            cGenreParseInfo::StopListType::iterator stop;
		    for (stop = pgpi->GetStopList()->begin(); stop != pgpi->GetStopList()->end(); stop++) 
            {
			    // add stop point if below start point
			    cFCOName::Relationship relate = startpoint.GetRelationship(*stop);

			    if (relate == cFCOName::REL_ABOVE) 
			    {
				    pStopPtSet->Add( *stop );
			    }
		    }

            pStopPtSet->SetRecurseDepth( -1 == depth ? cFCOSpecStopPointSet::RECURSE_INFINITE : depth );
            pfcospec->SetHelper( pStopPtSet );
        }
        //
        //////////////////////////////////////////////////////////////

        //
		// set start point
        //
		pfcospec->SetStartPoint( startpoint );

        //
		// set default mask
        //
		const iFCOSpecMask *pdefspecmask = iFCOSpecMask::GetDefaultMask();
		ASSERT(pdefspecmask != 0);

        //
		// build property vector
        //
        cFCOPropVector v = (*rule)->GetDefSpecMask().GetPropVector();        
		pHelper->AddSubTypeProps( v );
		pfcospec->SetPropVector( pdefspecmask, v);

        //
        // add attributes
        //
        TSTRING strAttrVal;
        cParseNamedAttrList* pal = (*rule)->GetAttrList();
		cFCOSpecAttr* pAttr	= new cFCOSpecAttr;
        if( pal != NULL )
        {
            // TODO: make storage place for these keywords
            // attribute names are stored in lowercase
            const cParseNamedAttr* pa;

            //
            // get rulename
            //
            pa = pal->Lookup( TSS_GetString( cTWParser, twparser::STR_PARSER_RULENAME ) );
            if( pa )
            {
				// TODO -- at some future date, remove the name in the spec 
				// and only set it in pAttr
                pfcospec->SetName( pa->GetValue() );
				pAttr->SetName( pa->GetValue() );
            }
            
            //
            // get severity
            //
			pa = pal->Lookup( TSS_GetString( cTWParser, twparser::STR_PARSER_SEVERITY ) );
            if( pa )
			{
                int iSev;
                cSeverityLimits sl;
                if( ! sl.InterpretInt( pa->GetValue(), &iSev ) )
                    throw eParserBadSevVal( pa->GetValue(), pa->GetLineNum() );

                pAttr->SetSeverity( iSev );
			}

            //
            // get emailto
            //
            pa = pal->Lookup( TSS_GetString( cTWParser, twparser::STR_PARSER_EMAILTO ) ) ;
            if( pa )
			{
                TSTRING strAddressee;
                int index = 0;
                while( cParserUtil::GetNextMailName( pa->GetValue(), strAddressee, index++ ) )
                {
                    d.TraceDebug( _T("email to recip: <%s>\n"), strAddressee.c_str() );
                    pAttr->AddEmail( strAddressee );
                }
			}
        }

        //
		// store rule in the spec list
        //
		fcospeclist.Add( pfcospec, pAttr );
		pfcospec->Release();
		pAttr->Release();
	}

    delete pHelper;
}
///////////////////////////////////////////////////////////////////////////////
// CreatePropVector -- given the string representation of the attribute vector,
//			create an FCOPropVector
///////////////////////////////////////////////////////////////////////////////
void cParserUtil::CreatePropVector( const TSTRING& strPropListC, class cFCOPropVector& v, iParserGenreUtil* pHelper )
{
    ASSERT( pHelper != NULL );

	// state: turning modes on or off
	enum Mode { MO_TURNON, MO_TURNOFF };
	Mode mode = MO_TURNON;

    // clear out all spaces in the string    
    TSTRING strPropList = strPropListC;
    util_EatAllSpaces( strPropList );

	// zero it out
	v.Clear();

    TSTRING::const_iterator iter;
    TSTRING::size_type i;  // index into string
    for ( iter = strPropList.begin(), i = 0; iter != strPropList.end(); iter++, i++ ) 
    {
		int propIndex = -1;	                	// index into propvector

		switch (*iter) 
        {
            /////////////////////////////////////////////////////////////
		    // parsing modes
		    case '+':
			    mode = MO_TURNON;
			    continue;
		    case '-':
			    mode = MO_TURNOFF;
			    continue;
            /////////////////////////////////////////////////////////////
                
            /////////////////////////////////////////////////////////////
		    // long attribute names
            case '&':
                {
                    //
                    // collect string
                    //

                    // find next '&', '+', or '-'
                    TSTRING::size_type next = util_FindNextDelim( strPropList, i+1 );

                    // get attribute name                    
                    TSTRING strProp;
                    if( next == TSTRING::npos )
                        strProp = strPropList.substr( i+1 );
                    else
                        strProp = strPropList.substr( i+1, next - i - 1 );

                    // increment past string
                    iter += strProp.length();
                    i += strProp.length();

                    // map attribute
                    if( ! pHelper->MapStringToProperty( strProp, propIndex ) )
                        throw eParserPropChar( strProp );
                }
                break;
            /////////////////////////////////////////////////////////////

            /////////////////////////////////////////////////////////////
            // not '+' or '-' or '&' so map the char to a property
            default:
                if( ! pHelper->MapStringToProperty( TSTRING( 1, *iter ), propIndex ) )
                    throw eParserPropChar( TSTRING( 1, *iter ) );
            break;
            //
            /////////////////////////////////////////////////////////////
		}

		// now turn on or turn off bit, according to mode
        ASSERT( propIndex != -1 );
		if (mode == MO_TURNON)
			v.AddItemAndGrow( propIndex );
		else 
			v.RemoveItem( propIndex );
	}

    /* for 1.5, allow no properties (just track file existence)
    // if v is empty, error
    cFCOPropVector emptyPropVector;
    emptyPropVector.Clear();    
    if( v == emptyPropVector )
    {
		d.TraceError("CreatePropVector failed!!\n");
        throw eError( ERR_BAD_PROP_STRING, strPropV.c_str() );
    }
    */

	return;
}

// gets semicolon-delimited words in a string. index starts at 0.
// strNameRet has surronding WS discarded
bool cParserUtil::GetNextMailName( const TSTRING& strNameList, TSTRING& strNameRet, int index )
{
    ASSERT( index >= 0 );

    // go to nth name
    TSTRING::size_type nextSC, lastSC = (TSTRING::size_type)-1; // algorithm starts searching for next ';'
                                            // at lastSC+1
    for( ; index >= 0; index-- )
    {     
        nextSC = strNameList.find( _T(';'), ( lastSC + 1 ) );
        
        if( 0 == index ) // if the name # we're looking for
        {
            strNameRet = strNameList.substr( ( lastSC + 1 ), TSTRING::npos == nextSC ? TSTRING::npos : nextSC - ( lastSC + 1 ) );
            util_LoseSurroundingWS( strNameRet );
            return true;
        }
        else if( TSTRING::npos == nextSC ) // didn't get to index'th naem: no names left
        {
            return false;
        }

        lastSC = nextSC;
    }
    
    // unreachable code
    ASSERT( false );
    return false; // just to placate the compiler
}

void cParserUtil::MergeMailLists( TSTRING& tstrMergeIntoMe, const TSTRING& tstrRHS )
{
    // foreach word in rhs
    TSTRING tstrWordToAdd;
    int index = 0;
    while( cParserUtil::GetNextMailName( tstrRHS, tstrWordToAdd, index++ ) )
    {
        // if it isn't in lhs, add it
        bool fAlreadyInList = false;
        int index2 = 0;
        TSTRING tstrWordInLHS;
        while( !fAlreadyInList && cParserUtil::GetNextMailName( tstrMergeIntoMe, tstrWordInLHS, index2++ ) )
        {
            if( 0 == tstrWordInLHS.compare( tstrWordToAdd ) )
                fAlreadyInList = true;
        }

        if( ! fAlreadyInList )
        {            
            tstrMergeIntoMe += _T(";");
            tstrMergeIntoMe += tstrWordToAdd;
        }
    }
}


bool cParserHelper::AtTopScope()
{
    return( mScopedAttrs.empty() );
}

void cParserHelper::PopScope() 
{ 
    ASSERT( ! mScopedAttrs.empty() ); 

    delete mScopedAttrs.back(); 
    mScopedAttrs.pop_back(); 
}

cParseNamedAttrList* cParserHelper::GetGlobalAttrList() 
{ 
    if( ! mScopedAttrs.empty() )
        return mScopedAttrs.back()->GetAttrList();
    else
        return NULL;
}

void cParserHelper::IncrementScopeStatementCount()
{ 
    // must add count to ALL previous scopes.
    for( ScopedAttrContainer::iterator i = mScopedAttrs.begin(); i != mScopedAttrs.end(); i++  )
        (*i)->IncrementStatementCount();
}

int cParserHelper::GetScopeStatementCount()
{ 
    if( ! mScopedAttrs.empty() )
    {
        return mScopedAttrs.back()->GetStatementCount();
    }
    else
    {
        // should never ask for this when we aren't in a scope
        ASSERT( false );
        return 0;
    }
}


void cParserHelper::PushScope( cAttrScopeInfo* pSI )
{
    // add previous scope's info to this one
    if( ! mScopedAttrs.empty() )
    {
        pSI->GetAttrList()->MergeNoOverwrite( mScopedAttrs.back()->GetAttrList() );
    }
    
    mScopedAttrs.push_back( pSI ); 
}


//=========================================================================
// UTIL FUNCTION CODE
//=========================================================================

int util_ConvertHex(const char* const cpsz, int* const pnCharsRead)
{
    ASSERT( util_AsciiCharsActLikeTheyShould() ); 
    ASSERT( cpsz && pnCharsRead );

    if (*cpsz == 0 || !std::isxdigit<TCHAR>( *cpsz, std::locale() ))
        throw eParserBadHex( cStringUtil::StrToTstr( cpsz ) );

    int iValue;
    const char* psz = cpsz;
    for( 
         *pnCharsRead = 0, iValue = 0; 
         *psz && std::isxdigit<TCHAR>( *psz, std::locale() ) && (*pnCharsRead < 2);
         psz++, (*pnCharsRead)++ 
       )
    {
        iValue *= 0x10;
        
        if( std::isdigit<TCHAR>( *psz, std::locale() ) )
        {
            iValue += ( *psz - '0' );
        }
        else
        {
            if( std::islower<TCHAR>( *psz, std::locale() ) )
                iValue += ( *psz - 'a' + 10 ); // so that A=10, B=11, ..., F=15
            else // is uppercase
                iValue += ( *psz - 'A' + 10 ); // so that a=10, a=11, ..., f=15
        }
    }

    return iValue;
}

int util_ConvertUnicode(const char* const cpsz, int* const pnCharsRead)
{
    ASSERT( util_AsciiCharsActLikeTheyShould() ); 
    ASSERT( cpsz && pnCharsRead );

    if (*cpsz == 0 || !std::isxdigit<TCHAR>( *cpsz, std::locale() ))
        throw eParserBadHex( cStringUtil::StrToTstr( cpsz ) );

    int iValue;
    const char* psz = cpsz;
    for( 
         *pnCharsRead = 0, iValue = 0; 
         *pnCharsRead < 4;
         psz++, (*pnCharsRead)++ 
       )
    {
        // we require 4 chars for unicode escapes
        if (*psz == 0 || !std::isxdigit<TCHAR>( *psz, std::locale() ))
            throw eParserBadHex( cStringUtil::StrToTstr( cpsz ) );

        iValue *= 0x10;
        
        if( std::isdigit<TCHAR>( *psz, std::locale() ) )
        {
            iValue += ( *psz - '0' );
        }
        else
        {
            if( std::islower<TCHAR>( *psz, std::locale() ) )
                iValue += ( *psz - 'a' + 10 ); // so that A=10, B=11, ..., F=15
            else // is uppercase
                iValue += ( *psz - 'A' + 10 ); // so that a=10, a=11, ..., f=15
        }
    }

    return iValue;
}

// only 3 octal chars allowed
int util_ConvertOctal( const char* psz, int* const pnCharsRead )
{
    ASSERT( util_AsciiCharsActLikeTheyShould() ); 
    ASSERT( psz && pnCharsRead );
    ASSERT( util_IsOctal( *psz ) ); // at least one oct char
    
    int iValue;
    for( 
         iValue = 0, *pnCharsRead = 0; 
         *psz && util_IsOctal( *psz ) && *pnCharsRead < 3; // limit of 3 octal chars
         psz++, (*pnCharsRead)++ 
       )
    {
        iValue *= 010; 
        iValue += ( *psz - '0' );        
    }

    return( iValue );
}

bool util_IsOctal( const char ch )
{
    ASSERT( util_AsciiCharsActLikeTheyShould() ); 

    return( ch >= '0' && ch <= '7' );
}

int util_GetEscapeValueOfChar( char ch )
{
    int iValue = -1; 
    
    switch( ch )
    {
        case 'n':
            iValue = 10;
        break;
        case 't':
            iValue = 9;
        break;
        case 'v':
            iValue = 11;
        break;
        case 'b':
            iValue = 8;
        break;
        case 'r':
            iValue = 13;
        break;
        case 'f':
            iValue = 12;
        break;
        case 'a':
            iValue = 7;
        break;
        case '\\':
            iValue = 92;
        break;
        case '\?':
            iValue = 63;
        break;
        case '\'':
            iValue = 39;
        break;
        case '\"':
            iValue = 34;
        break;
        default:
        {         
           // unrecognized escape char: just return character
           iValue = static_cast<int>( ch );
        }
        break;
    }

    ASSERT( iValue != -1 ); //should have *some* value at this point

    return( iValue );
}


// -1 means recurse all levels
int util_GetRecurseDepth( const cParseNamedAttrList* pList ) //throw( eParserHelper )
{
    if( pList != NULL )
    {        
        const cParseNamedAttr* pa = pList->Lookup( TSS_GetString( cTWParser, twparser::STR_PARSER_RECURSE ) );
        if( pa )
	    {
            TSTRING str = pa->GetValue();

            std::transform( str.begin(), str.end(), str.begin(), _totlower );

            if( 0 == str.compare( TSS_GetString( cTWParser, twparser::STR_PARSER_FALSE ) ) )
            {
                return 0;
            }
            else if( 0 == str.compare( TSS_GetString( cTWParser, twparser::STR_PARSER_TRUE ) ) )
            {
                return -1;
            }
            else 
            {
                // must be number
                int i;
                cRecurseDepthLimits rdl;
                if( ! rdl.InterpretInt( str, &i ) )
                    throw eParserUnrecognizedAttrVal( str, pa->GetLineNum() );                
                return i;
            }
	    }
    }

    return( -1 ); // defaults to recurse all levels
}

// finds first '&', '+', '-'  on or after str[i]
TSTRING::size_type util_FindNextDelim( const TSTRING& str, TSTRING::size_type i )
{
    TSTRING::size_type min = TSTRING::npos;
    TSTRING::size_type amp = str.find( '&', i );
    TSTRING::size_type plus = str.find( '+', i );
    TSTRING::size_type minus = str.find( '-', i );

    // now find minimum of the three
    if( amp != TSTRING::npos )
        min = amp;
    if( min != TSTRING::npos && plus != TSTRING::npos && plus < min )
        min = plus;
    if( min != TSTRING::npos && minus != TSTRING::npos && minus < min )
        min = minus;

    return min;
}

// deletes each space in the string
void util_EatAllSpaces( TSTRING& str )
{
    for( TSTRING::iterator i = str.begin(); i != str.end(); i++ )
    {
        if( *i == _T(' ') )
        {
            str.erase( i );
        }
    }
}

void util_LoseSurroundingWS( TSTRING& str )
{
    TSTRING::size_type nonWSStart = str.find_first_not_of( _T(" \t"), 0 );
    TSTRING::size_type nonWSEnd = str.find_last_not_of( _T(" \t") );
    
    if( TSTRING::npos == nonWSStart ) // if only whitespace
    {
        ASSERT( TSTRING::npos == nonWSEnd );
        str = str.erase();
    }
    else // has non WS chars
    {
        ASSERT( TSTRING::npos != nonWSEnd );
        str = str.substr( nonWSStart, nonWSEnd - nonWSStart + 1 );
    }
}


#ifdef _DEBUG
bool util_AsciiCharsActLikeTheyShould()
{
    // we need numbers whose character 
    // representation increase by one
    return(
            ( '0' < '1' ) && 
            ( '0' + 1 == '1' ) &&
            ( 'a' < 'b' ) && 
            ( 'a' + 1 == 'b' ) &&
            ( 'A' < 'B' ) && 
            ( 'A' + 1 == 'B' )
          ); 
}
#endif

