Milán Major

netscape-revival

1 branch
Code

lib/libnet/imappars.cpp

#include "imap4pvt.h"


/* 45678901234567890123456789012345678901234567890123456789012345678901234567890
*/ 


TSearchResultSequence::TSearchResultSequence()
	: fListOfLines(nil)
{
}

TSearchResultSequence::~TSearchResultSequence()
{
	if (fListOfLines)
		XP_ListDestroy(fListOfLines);
}

void TSearchResultSequence::ResetSequence()
{
	if (fListOfLines)
		XP_ListDestroy(fListOfLines);
	fListOfLines = nil;
}

void TSearchResultSequence::AddSearchResultLine(const char *searchLine)
{
	char *copiedSequence = XP_STRDUP(searchLine + 9); // 9 == "* SEARCH "

	if (!fListOfLines)
	{
		fListOfLines = XP_ListNew();
		fListOfLines->object = copiedSequence;
	}
	else
		XP_ListAddObjectToEnd(fListOfLines, copiedSequence);
}


TSearchResultIterator::TSearchResultIterator(TSearchResultSequence &sequence) :
	fSequence(sequence)
{
	ResetIterator();
}

TSearchResultIterator::~TSearchResultIterator()
{}

void  TSearchResultIterator::ResetIterator()
{
	fCurrentLine = fSequence.fListOfLines;
	if (fCurrentLine)
		fPositionInCurrentLine = (char *) fCurrentLine->object;
	else
		fPositionInCurrentLine = nil;
}

int32 TSearchResultIterator::GetNextMessageNumber()
{
	int32 returnValue = 0;
	if (fPositionInCurrentLine)
	{	
		returnValue = atol(fPositionInCurrentLine);
		
		// eat the current number
		while (isdigit(*++fPositionInCurrentLine))
			;
		
		if (*fPositionInCurrentLine == 0xD)	// found CR, no more digits on line
		{
			fCurrentLine = fCurrentLine->next;
			if (fCurrentLine)
				fPositionInCurrentLine = (char *) fCurrentLine->object;
			else
				fPositionInCurrentLine = nil;
		}
		else	// eat the space
			fPositionInCurrentLine++;
	}
	
	return returnValue;
}

/* member functions for TImapServerState response parser */
TImapServerState::TImapServerState(TIMAP4BlockingConnection &imapConnection) :
	fServerConnection(imapConnection),
	fCurrentCommandTag(nil),
	fNextToken(nil),
	fCurrentLine(nil),
	fLineOfTokens(nil),
	fStartOfLineOfTokens(nil),
	fCurrentFolderReadOnly(FALSE),
	fNumberOfExistingMessages(0),
	fNumberOfRecentMessages(0),
	fNumberOfUnseenMessages(0),
	fIMAPstate(kNonAuthenticated)
{
}

TImapServerState::~TImapServerState()
{
	delete fCurrentCommandTag;
}
/* 
 response        ::= *response_data response_done
*/

void TImapServerState::ParseIMAPServerResponse(const char *currentCommand)
{
	char *copyCurrentCommand = XP_STRDUP(currentCommand);
	char *tagToken           = XP_STRTOK(copyCurrentCommand, " ");
	char *commandToken       = XP_STRTOK(nil, " ");
	if (tagToken)
	{
		delete fCurrentCommandTag;
		fCurrentCommandTag = XP_STRDUP(tagToken);
	}
	
	if (commandToken)
		PreProcessCommandToken(commandToken);
	
	fProcessingTaggedResponse = FALSE;
	fCurrentCommandFailed 	  = FALSE;
	fDisconnected			  = FALSE;
	fSyntaxError              = FALSE;
	
	fSyntaxErrorLine = nil;
	
	ResetLexAnalyzer();
	
	fNextToken = GetNextToken();
	
	while (ContinueParse() && !XP_STRCMP(fNextToken, "*") )
		response_data();
	
	if (ContinueParse())
		response_done();
	
	if (ContinueParse() && !CommandFailed())
	{
		// a sucessful command may change the eIMAPstate
		ProcessOkCommand(commandToken);
	}
	else if (CommandFailed())
	{
		// a failed command may change the eIMAPstate
		ProcessBadCommand(commandToken);
	}
}

// SEARCH is the only command that requires pre-processing for now.
// others will be added here.
void TImapServerState::PreProcessCommandToken(const char *commandToken)
{
	if (!XP_STRCASECMP(commandToken, "SEARCH"))
		fSearchResults.ResetSequence();
}

TSearchResultIterator *TImapServerState::CreateSearchResultIterator()
{
	return new TSearchResultIterator(fSearchResults);
}

TImapServerState::eIMAPstate TImapServerState::GetIMAPstate()
{
	return fIMAPstate;
}

void TImapServerState::ProcessOkCommand(const char *commandToken)
{
	if (!XP_STRCASECMP(commandToken, "LOGIN"))
		fIMAPstate = kAuthenticated;
	else if (!XP_STRCASECMP(commandToken, "LOGOUT"))
		fIMAPstate = kNonAuthenticated;
	else if (!XP_STRCASECMP(commandToken, "SELECT") ||
	         !XP_STRCASECMP(commandToken, "EXAMINE"))
		fIMAPstate = kFolderSelected;
	else if (!XP_STRCASECMP(commandToken, "CLOSE"))
		fIMAPstate = kAuthenticated;
}

void TImapServerState::ProcessBadCommand(const char *commandToken)
{
	if (!XP_STRCASECMP(commandToken, "LOGIN"))
		fIMAPstate = kNonAuthenticated;
	else if (!XP_STRCASECMP(commandToken, "LOGOUT"))
		fIMAPstate = kNonAuthenticated;	// ??
	else if (!XP_STRCASECMP(commandToken, "SELECT") ||
	         !XP_STRCASECMP(commandToken, "EXAMINE"))
		fIMAPstate = kAuthenticated;	// nothing selected
	else if (!XP_STRCASECMP(commandToken, "CLOSE"))
		fIMAPstate = kAuthenticated;	// nothing selected
}
/*
 response_data   ::= "*" SPACE (resp_cond_state / resp_cond_bye /
                              mailbox_data / message_data / capability_data)
                              CRLF
 
 The RFC1730 grammar spec did not allow one symbol look ahead to determine
 between mailbox_data / message_data so I combined the numeric possibilities
 of mailbox_data and all of message_data into numeric_mailbox_data.
 
 The production implemented here is 

 response_data   ::= "*" SPACE (resp_cond_state / resp_cond_bye /
                              mailbox_data / numeric_mailbox_data / 
                              capability_data)
                              CRLF

*/
void TImapServerState::response_data()
{
	fNextToken = GetNextToken();
	
	if (ContinueParse())
	{
		if (!XP_STRCASECMP(fNextToken, "OK") ||
		    !XP_STRCASECMP(fNextToken, "NO") ||
		    !XP_STRCASECMP(fNextToken, "BAD") )
			resp_cond_state();
		else if (!XP_STRCASECMP(fNextToken, "BYE") )
			resp_cond_bye();
		else if (!XP_STRCASECMP(fNextToken, "FLAGS") ||
		    	 !XP_STRCASECMP(fNextToken, "LIST")  ||
		    	 !XP_STRCASECMP(fNextToken, "LSUB")  ||
		    	 !XP_STRCASECMP(fNextToken, "MAILBOX")  ||
		    	 !XP_STRCASECMP(fNextToken, "SEARCH"))
			mailbox_data();
		else if (IsNumericString(fNextToken))
		    numeric_mailbox_data();
		else if (!XP_STRCASECMP(fNextToken, "CAPABILITY"))
			capability_data();
		else
			SetSyntaxError(TRUE);
		
		if (ContinueParse())
			end_of_line();
	}
}

void TImapServerState::end_of_line()
{
	if (!at_end_of_line())
		SetSyntaxError(TRUE);
	else if (fProcessingTaggedResponse)
		ResetLexAnalyzer();	// no more tokens until we send a command
	else
		fNextToken = GetNextToken();
}

void TImapServerState::skip_to_CRLF()
{
	while (Connected() && !at_end_of_line())
		fNextToken = GetNextToken();
}

/*
 mailbox_data    ::=  "FLAGS" SPACE flag_list /
                               "LIST" SPACE mailbox_list /
                               "LSUB" SPACE mailbox_list /
                               "MAILBOX" SPACE text /
                               "SEARCH" [SPACE 1#nz_number] /
                               number SPACE "EXISTS" / number SPACE "RECENT"

This production was changed to accomodate predictive parsing 

 mailbox_data    ::=  "FLAGS" SPACE flag_list /
                               "LIST" SPACE mailbox_list /
                               "LSUB" SPACE mailbox_list /
                               "MAILBOX" SPACE text /
                               "SEARCH" [SPACE 1#nz_number]
*/
void TImapServerState::mailbox_data()
{
	// implement later!
	if (!XP_STRCASECMP(fNextToken, "FLAGS"))
		skip_to_CRLF();
	else if (!XP_STRCASECMP(fNextToken, "LIST") ||
	         !XP_STRCASECMP(fNextToken, "LSUB"))
	{
		fNextToken = GetNextToken();
		if (ContinueParse())
			mailbox_list();
	}
	else if (!XP_STRCASECMP(fNextToken, "MAILBOX"))
		skip_to_CRLF();
	else if (!XP_STRCASECMP(fNextToken, "SEARCH"))
	{
		fSearchResults.AddSearchResultLine(fCurrentLine);
		skip_to_CRLF();
	}
}

/*
 mailbox_list    ::= "(" #("\Marked" / "\Noinferiors" /
                              "\Noselect" / "\Unmarked" / flag_extension) ")"
                              SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox
*/

void TImapServerState::mailbox_list()
{
	XP_Bool endOfFlags = FALSE;
	fNextToken++;	// eat the first "("
	do {
		if (!XP_STRNCASECMP(fNextToken, "\\Marked", 7))
			;	// wait for folder data base stuff
		else if (!XP_STRNCASECMP(fNextToken, "\\Unmarked", 9))
			;	// wait for folder data base stuff
		else if (!XP_STRNCASECMP(fNextToken, "\\Noinferiors", 12))
			;	// wait for folder data base stuff
		else if (!XP_STRNCASECMP(fNextToken, "\\Noselect", 9))
			;	// wait for folder data base stuff
		else
			;	// we ignore flag extensions
		
		endOfFlags = *(fNextToken + strlen(fNextToken) - 1) == ')';
		fNextToken = GetNextToken();
	} while (!endOfFlags && ContinueParse());
	
	if (ContinueParse())
	{
		if (*fNextToken == '"')
		{
			// this "x" character is the dir separator for server machine
			char separator = *(fNextToken + 1);	// handle this ;ater
		}
		fNextToken = GetNextToken();	
		if (ContinueParse())
			mailbox();	
	}
	
	
}

/* mailbox         ::= "INBOX" / astring
*/
void TImapServerState::mailbox()
{
	if (!XP_STRCASECMP(fNextToken, "INBOX"))
		fNextToken = GetNextToken();
	else if (*fNextToken == '{')
		char *boxname = CreateLiteral();	// literal
	else if (*fNextToken == '"')
		char *boxname = CreateQuoted();		// quoted
	else
	{
		char *boxname = XP_STRDUP(fNextToken);	// atomic string
		fNextToken = GetNextToken();
	}
}

// this seems very involved for finding a close ".  But I have to go
// character by character because I cannot tokenize a quoted  string.  It
// may have white space in it. Also, if the current line ends without the
// closed quote then I have to fetch another line from the server!

char *TImapServerState::CreateQuoted()
{
	char *currentChar = fCurrentLine + 
					    (fNextToken - fStartOfLineOfTokens)
					    + 1;	// one char past opening '"'

	char *returnString = XP_STRDUP(currentChar);
	int  charIndex = 0;
	XP_Bool closeQuoteFound = FALSE;
	
	while (!closeQuoteFound && ContinueParse() )
	{
		if (!*(returnString + charIndex))
		{
			AdvanceToNextLine();
			returnString = XP_STRCAT(returnString, fCurrentLine);
			charIndex++;
		}
		else if (*(returnString + charIndex) == '"')
		{
			if (*(returnString + charIndex - 1) == '\\')
				charIndex++;
			else
				closeQuoteFound = TRUE;
		}
		else
			charIndex++;
	}
	
	if (closeQuoteFound)
	{
		*(returnString + charIndex) = 0;
		skip_to_CRLF();
	}
	return returnString;
}

char *TImapServerState::CreateLiteral()
{
	int32 numberOfCharsInMessage = atol(fNextToken + 1);
	int32 charsReadSoFar = 0;
	
	char *returnString = new char[numberOfCharsInMessage + 1];
	
	while (ContinueParse() && (charsReadSoFar < numberOfCharsInMessage))
	{
		AdvanceToNextLine();
		if (ContinueParse())
		{
			XP_STRCPY(returnString + charsReadSoFar, fCurrentLine);
			charsReadSoFar += strlen(fCurrentLine);	
		}
	}
	
	if (ContinueParse())
	{
		skip_to_CRLF();		// GetNextToken will pick it up at CRLF
		*(returnString + numberOfCharsInMessage) = 0;
	}
	
	return returnString;
}


/*
 message_data    ::= nz_number SPACE ("EXPUNGE" /
                              ("FETCH" SPACE msg_fetch) / msg_obsolete)

was changed to

numeric_mailbox_data ::=  number SPACE "EXISTS" / number SPACE "RECENT"
 / nz_number SPACE ("EXPUNGE" /
                              ("FETCH" SPACE msg_fetch) / msg_obsolete)

*/
void TImapServerState::numeric_mailbox_data()
{
	int32 tokenNumber = atol(fNextToken);
	fNextToken = GetNextToken();
	
	if (ContinueParse())
	{
		if (!XP_STRCASECMP(fNextToken, "EXISTS"))
		{
			fNumberOfExistingMessages = tokenNumber;
			fNextToken = GetNextToken();
		}
		else if (!XP_STRCASECMP(fNextToken, "RECENT"))
		{
			fNumberOfRecentMessages = tokenNumber;
			fNextToken = GetNextToken();
		}
		else if (!XP_STRCASECMP(fNextToken, "EXPUNGE"))
			skip_to_CRLF(); // later
		else if (!XP_STRCASECMP(fNextToken, "FETCH"))
		{
			fNextToken = GetNextToken();
			if (ContinueParse())
				msg_fetch(); 
		}
		else
			msg_obsolete();
	}
}

/*
 msg_fetch       ::= "(" 1#("BODY" SPACE body /
                              "BODYSTRUCTURE" SPACE body /
                              "BODY[" section "]" SPACE nstring /
                              "ENVELOPE" SPACE envelope /
                              "FLAGS" SPACE "(" #(flag / "\Recent") ")" /
                              "INTERNALDATE" SPACE date_time /
                              "RFC822" [".HEADER" / ".TEXT"] SPACE nstring /
                              "RFC822.SIZE" SPACE number /
                              "UID" SPACE uniqueid) ")"

*/

void TImapServerState::msg_fetch()
{
	fNextToken++;	// eat the '(' character
	
	// some of these productions are ignored for now
	while (ContinueParse() && (*fNextToken != ')') )
	{
		// I only fetch RFC822 so I should never see these BODY responses
		if (!XP_STRCASECMP(fNextToken, "BODY"))
			skip_to_CRLF(); // I never ask for this
		else if (!XP_STRCASECMP(fNextToken, "BODYSTRUCTURE"))
			skip_to_CRLF(); // I never ask for this
		else if (!XP_STRNCASECMP(fNextToken, "BODY[", 6))
			skip_to_CRLF(); // I never ask for this
		else if (!XP_STRCASECMP(fNextToken, "ENVELOPE"))
			skip_to_CRLF(); // I never ask for this
		else if (!XP_STRCASECMP(fNextToken, "INTERNALDATE"))
			skip_to_CRLF(); // I will need to implement this
		else if (!XP_STRCASECMP(fNextToken, "FLAGS"))
		{
			fNextToken = GetNextToken();
			if (ContinueParse())
				flags();
			
			if (ContinueParse())
			{	// eat the closing ')'
				fNextToken++;
				// there may be another ')' to close out
				// msg_fetch.  If there is then don't advance
				if (*fNextToken != ')')
					fNextToken = GetNextToken();
			}
		}
		else if (!XP_STRCASECMP(fNextToken, "RFC822") ||
				 !XP_STRCASECMP(fNextToken, "RFC822.HEADER") ||
				 !XP_STRCASECMP(fNextToken, "RFC822.TEXT") )
		{
			fNextToken = GetNextToken();
			if (ContinueParse())
				msg_fetch_nstring();
		}
		else if (!XP_STRCASECMP(fNextToken, "RFC822.SIZE"))
		{
			fNextToken = GetNextToken();
			if (ContinueParse())
			{
				int32 msgSizeDropOnFloorForNow = atol(fNextToken);
				// if this token ends in ')', then it is the last token
				// else we advance
				if ( *(fNextToken + strlen(fNextToken) - 1) == ')')
					fNextToken += strlen(fNextToken) - 1;
				else
					fNextToken = GetNextToken();
			}
		}
		else if (!XP_STRCASECMP(fNextToken, "UID"))
		{
			fNextToken = GetNextToken();
			if (ContinueParse())
			{
				int32 msgUIDDropOnFloorForNow = atol(fNextToken);
				// if this token ends in ')', then it is the last token
				// else we advance
				if ( *(fNextToken + strlen(fNextToken) - 1) == ')')
					fNextToken += strlen(fNextToken) - 1;
				else
					fNextToken = GetNextToken();
			}
		}
	}
	
	if (ContinueParse())
		fNextToken = GetNextToken();	// eat the ')' ending token
										// should be at end of line
}

void TImapServerState::flags()
{
	while (ContinueParse() && !strcasestr(fNextToken, ")"))
		fNextToken = GetNextToken();
	
	if (ContinueParse())
		while(*fNextToken != ')')
			fNextToken++;
}
	
XP_Bool		TImapServerState::CommandFailed()
{
	return fCurrentCommandFailed;
}

/* 
 resp_cond_state ::= ("OK" / "NO" / "BAD") SPACE resp_text
*/
void TImapServerState::resp_cond_state()
{
	if ((!XP_STRCASECMP(fNextToken, "NO") ||
	     !XP_STRCASECMP(fNextToken, "BAD") ) &&
	    	fProcessingTaggedResponse)
		fCurrentCommandFailed = TRUE;
	
	fNextToken = GetNextToken();
	if (ContinueParse())
		resp_text();
}

/*
 resp_text       ::= ["[" resp_text_code "]" SPACE] (text_mime2 / text)
 
 was changed to in order to enable a one symbol look ahead predictive
 parser.
 
 resp_text       ::= ["[" resp_text_code  SPACE] (text_mime2 / text)
 */
void TImapServerState::resp_text()
{
	if (ContinueParse() && (*fNextToken == '['))
		resp_text_code();
		
	if (ContinueParse())
	{
		if (!XP_STRCMP(fNextToken, "=?"))
			text_mime2();
		else
			text();
	}
}
/*
 text_mime2       ::= "=?" <charset> "?" <encoding> "?"
                               <encoded-text> "?="
                               ;; Syntax defined in [MIME-2]
*/
void TImapServerState::text_mime2()
{
	skip_to_CRLF();
}

/*
 text            ::= 1*TEXT_CHAR

*/
void TImapServerState::text()
{
	skip_to_CRLF();
}


/*
 resp_text_code  ::= "ALERT" / "PARSE" /
                              "PERMANENTFLAGS" SPACE "(" #(flag / "\*") ")" /
                              "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
                              "UIDVALIDITY" SPACE nz_number /
                              "UNSEEN" SPACE nz_number /
                              atom [SPACE 1*<any TEXT_CHAR except "]">]
                              
 was changed to in order to enable a one symbol look ahead predictive
 parser.
 
  resp_text_code  ::= ("ALERT" / "PARSE" /
                              "PERMANENTFLAGS" SPACE "(" #(flag / "\*") ")" /
                              "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
                              "UIDVALIDITY" SPACE nz_number /
                              "UNSEEN" SPACE nz_number /
                              atom [SPACE 1*<any TEXT_CHAR except "]">] )
                      "]"

 
*/
void TImapServerState::resp_text_code()
{
	// this is a special case way of advancing the token
	// strtok won't break up "[ALERT]" into separate tokens
	if (XP_STRLEN(fNextToken) > 1)
		fNextToken++;
	else 
		fNextToken = GetNextToken();
	
	if (ContinueParse())
	{
		if (!XP_STRCASECMP(fNextToken,"ALERT]"))
		{
			// do nothing for now
			fNextToken = GetNextToken();
		}
		else if (!XP_STRCASECMP(fNextToken,"PARSE]"))
		{
			// do nothing for now
			fNextToken = GetNextToken();
		}
		else if (!XP_STRCASECMP(fNextToken,"PERMANENTFLAGS"))
		{
			// do nothing but eat tokens until we see the ] or CRLF
			// we should see the ] but we don't want to go into an
			// endless loop if the CRLF is not there
			do {
				fNextToken = GetNextToken();
			} while (!strcasestr(fNextToken, "]") && 
					 !at_end_of_line() &&
					 ContinueParse());
		}
		else if (!XP_STRCASECMP(fNextToken,"READ-ONLY]"))
		{
			fCurrentFolderReadOnly = TRUE;
			fNextToken = GetNextToken();
		}
		else if (!XP_STRCASECMP(fNextToken,"READ-WRITE]"))
		{
			fCurrentFolderReadOnly = FALSE;
			fNextToken = GetNextToken();
		}
		else if (!XP_STRCASECMP(fNextToken,"TRYCREATE]"))
		{
			// do nothing for now
			fNextToken = GetNextToken();
		}
		else if (!XP_STRCASECMP(fNextToken,"UIDVALIDITY"))
		{
			fNextToken = GetNextToken();
			if (ContinueParse())
			{
				fFolderUIDValidity = atol(fNextToken);
				fNextToken = GetNextToken();
			}
		}
		else if (!XP_STRCASECMP(fNextToken,"UNSEEN"))
		{
			fNextToken = GetNextToken();
			if (ContinueParse())
			{
				fNumberOfUnseenMessages = atol(fNextToken);
				fNextToken = GetNextToken();
			}
		}
		else 	// just text
		{
			// do nothing but eat tokens until we see the ] or CRLF
			// we should see the ] but we don't want to go into an
			// endless loop if the CRLF is not there
			do {
				fNextToken = GetNextToken();
			} while (!strcasestr(fNextToken, "]") && 
			         !at_end_of_line() &&
			         ContinueParse());
		}
	}
}

/*
 response_done   ::= response_tagged / response_fatal
 */
void TImapServerState::response_done()
{
	if (ContinueParse())
	{
		if (!XP_STRCASECMP(fNextToken, fCurrentCommandTag))
			response_tagged();
		else
			response_fatal();
	}
}

/*  response_tagged ::= tag SPACE resp_cond_state CRLF
*/
void TImapServerState::response_tagged()
{
	// eat the tag
	fNextToken = GetNextToken();
	if (ContinueParse())
	{
		fProcessingTaggedResponse = TRUE;
		resp_cond_state();
		if (ContinueParse())
			end_of_line();
	}
}

/* response_fatal  ::= "*" SPACE resp_cond_bye CRLF
*/
void TImapServerState::response_fatal()
{
	// eat the "*"
	fNextToken = GetNextToken();
	if (ContinueParse())
	{
		resp_cond_bye();
		if (ContinueParse())
			end_of_line();
	}
}
/*
resp_cond_bye   ::= "BYE" SPACE resp_text
                              ;; Server will disconnect condition
                    */
void TImapServerState::resp_cond_bye()
{
	SetConnected(FALSE);
	skip_to_CRLF();
}

/* nstring         ::= string / nil
string          ::= quoted / literal
 nil             ::= "NIL"

*/
void TImapServerState::msg_fetch_nstring()
{
	if (XP_STRCASECMP(fNextToken, "NIL"))
	{
		if (*fNextToken == '"')
			msg_fetch_quoted();
		else
			msg_fetch_literal();
	}
	else
		fNextToken = GetNextToken();	// eat "NIL"
}

/*
 quoted          ::= <"> *QUOTED_CHAR <">

          QUOTED_CHAR     ::= <any TEXT_CHAR except quoted_specials> /
                              "\" quoted_specials

          quoted_specials ::= <"> / "\"
*/
// This algorithm seems gross.  Isn't there an easier way?
void TImapServerState::msg_fetch_quoted()
{
	XP_Bool closeQuoteFound = FALSE;
	char *scanLine = fCurrentLine++;  // eat the first "
	// this won't work!  The '"' char does not start
	// the line!  why would anybody format a message like this!
	while (!closeQuoteFound && ContinueParse())
	{
		char *quoteSubString = strcasestr(scanLine, "\"");
		while (quoteSubString && !closeQuoteFound)
		{
			if (quoteSubString > scanLine)
			{
				closeQuoteFound = *(quoteSubString - 1) != '\\';
				if (!closeQuoteFound)
					quoteSubString = strcasestr(quoteSubString+1, "\"");
			}
			else
				closeQuoteFound = TRUE;	// 1st char is a '"'
		}
		
		// send the string to the connection object so he can display or
		// cache or whatever
		fServerConnection.HandleMessageLine(fCurrentLine);
		AdvanceToNextLine();
		scanLine = fCurrentLine;
	}
}
/* msg_obsolete    ::= "COPY" / ("STORE" SPACE msg_fetch)
                              ;; OBSOLETE untagged data responses */
void TImapServerState::msg_obsolete()
{
	if (!XP_STRCASECMP(fNextToken, "COPY"))
		fNextToken = GetNextToken();
	else if (!XP_STRCASECMP(fNextToken, "STORE"))
	{
		fNextToken = GetNextToken();
		if (ContinueParse())
			msg_fetch();
	}
	else
		SetSyntaxError(TRUE);
	
}
void TImapServerState::capability_data()
{
	skip_to_CRLF();
}
/*
 literal         ::= "{" number "}" CRLF *CHAR8
                              ;; Number represents the number of CHAR8 octets
*/
void TImapServerState::msg_fetch_literal()
{
	int32 numberOfCharsInMessage = atol(fNextToken + 1);
	int32 charsReadSoFar = 0;
	
	while (ContinueParse() && (charsReadSoFar < numberOfCharsInMessage))
	{
		AdvanceToNextLine();
		if (ContinueParse())
		{
			charsReadSoFar += strlen(fCurrentLine);	
			char *adoptedMessageLine = XP_STRDUP(fCurrentLine);
			fServerConnection.HandleMessageLine(adoptedMessageLine);
		}
	}
	
	if (ContinueParse())
	{
		skip_to_CRLF();
		fNextToken = GetNextToken();
	}
}

void TImapServerState::ResetLexAnalyzer()
{
	delete fCurrentLine;
	delete fStartOfLineOfTokens;
	
	fCurrentLine = fNextToken = fLineOfTokens = fStartOfLineOfTokens = nil;
	fAtEndOfLine = FALSE;
}

#define WHITESPACE " \015\012"     // token delimiter

char *TImapServerState::GetNextToken()
{
	if (!fCurrentLine || fAtEndOfLine)
		AdvanceToNextLine();
	else if (Connected())
	{
		fNextToken = strtok(nil, WHITESPACE);
		if (!fNextToken)
		{
			fAtEndOfLine = TRUE;
			fNextToken = CRLF;
		}
	}
	
	return fNextToken;
}

void TImapServerState::AdvanceToNextLine()
{
	delete fCurrentLine;
	delete fStartOfLineOfTokens;
	
	int readStatus;
	fCurrentLine = fServerConnection.CreateNewLineFromSocket(readStatus);
	if (readStatus < 0)
	{
		SetConnected(FALSE);
		fStartOfLineOfTokens = nil;
		fLineOfTokens = nil;
		fNextToken = nil;
	}
	else
	{
		fStartOfLineOfTokens = XP_STRDUP(fCurrentLine);
		fLineOfTokens = fStartOfLineOfTokens;
		fNextToken = strtok(fLineOfTokens, WHITESPACE);
		if (!fNextToken)
		{
			fAtEndOfLine = TRUE;
			fNextToken = CRLF;
		}
		else
			fAtEndOfLine = FALSE;
	}
}

XP_Bool TImapServerState::LastCommandSuccessful()
{
	return Connected() && !SyntaxError() && !CommandFailed();
}

void TImapServerState::SetSyntaxError(XP_Bool error)
{
	fSyntaxError = error;
	fSyntaxErrorLine = XP_STRDUP(fCurrentLine);
}

char *TImapServerState::CreateSyntaxErrorLine()
{
	return XP_STRDUP(fSyntaxErrorLine);
}

XP_Bool TImapServerState::CurrentFolderReadOnly()
{
	return fCurrentFolderReadOnly;
}

int	TImapServerState::NumberOfMessages()
{
	return fNumberOfExistingMessages;
}

int 	TImapServerState::NumberOfRecentMessages()
{
	return fNumberOfRecentMessages;
}

int 	TImapServerState::NumberOfUnseenMessages()
{
	return fNumberOfUnseenMessages;
}

int32 TImapServerState::FolderUID()
{
	return fFolderUIDValidity;
}


XP_Bool TImapServerState::SyntaxError()
{
	return fSyntaxError;
}

void TImapServerState::SetConnected(XP_Bool connected)
{
	fDisconnected = !connected;
}

XP_Bool TImapServerState::Connected()
{
	return !fDisconnected;
}

XP_Bool TImapServerState::ContinueParse()
{
	return !fSyntaxError && !fDisconnected;
}


XP_Bool TImapServerState::at_end_of_line()
{
	return XP_STRCMP(fNextToken, CRLF) == 0;
}

XP_Bool TImapServerState::IsNumericString(const char *string)
{
	int length = strlen(string);
	XP_Bool numeric=TRUE;
	
	for(int index=0; (index < length) && numeric; index++)
		numeric = isdigit(*(string+index));
	
	return numeric;
}