pjs/network/protocol/imap4/imappars.cpp

3299 строки
87 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
#include "mkutils.h"
#include "imap4pvt.h"
#include "prlog.h"
#include "imapbody.h"
extern "C" {
#include "xpgetstr.h"
extern int MK_OUT_OF_MEMORY;
extern int MK_SERVER_DISCONNECTED;
extern int MK_IMAP_DOWNLOADING_MESSAGE;
}
#define WHITESPACE " \015\012" // token delimiter
/* 45678901234567890123456789012345678901234567890123456789012345678901234567890
*/
TImapFlagAndUidState::TImapFlagAndUidState(int numberOfMessages, XP_Bool bSupportUserFlag)
{
fNumberOfMessagesAdded = 0;
fNumberOfMessageSlotsAllocated = numberOfMessages;
if (!fNumberOfMessageSlotsAllocated)
fNumberOfMessageSlotsAllocated = kFlagEntrySize;
fFlags = (imapMessageFlagsType*) XP_ALLOC(sizeof(imapMessageFlagsType) * fNumberOfMessageSlotsAllocated); // new imapMessageFlagsType[fNumberOfMessageSlotsAllocated];
fUids.SetSize(fNumberOfMessageSlotsAllocated);
XP_MEMSET(fFlags, 0, sizeof(imapMessageFlagsType) * fNumberOfMessageSlotsAllocated);
fSupportUserFlag = bSupportUserFlag;
fNumberDeleted = 0;
}
TImapFlagAndUidState::TImapFlagAndUidState(const TImapFlagAndUidState& state, XP_Bool bSupportUserFlag)
{
fNumberOfMessagesAdded = state.fNumberOfMessagesAdded;
fNumberOfMessageSlotsAllocated = state.fNumberOfMessageSlotsAllocated;
fFlags = (imapMessageFlagsType*) XP_ALLOC(sizeof(imapMessageFlagsType) * fNumberOfMessageSlotsAllocated); // new imapMessageFlagsType[fNumberOfMessageSlotsAllocated];
fUids.CopyArray((IDArray*) &state.fUids);
XP_MEMCPY(fFlags, state.fFlags, sizeof(imapMessageFlagsType) * fNumberOfMessageSlotsAllocated);
fSupportUserFlag = bSupportUserFlag;
fNumberDeleted = 0;
}
TImapFlagAndUidState::~TImapFlagAndUidState()
{
FREEIF(fFlags);
}
// we need to reset our flags, (re-read all) but chances are the memory allocation needed will be
// very close to what we were already using
void TImapFlagAndUidState::Reset(uint32 howManyLeft)
{
if (!howManyLeft)
fNumberOfMessagesAdded = fNumberDeleted = 0; // used space is still here
}
// Remove (expunge) a message from our array, since now it is gone for good
void TImapFlagAndUidState::ExpungeByIndex(uint32 index)
{
uint32 counter = 0;
if ((uint32) fNumberOfMessagesAdded < index)
return;
index--;
fNumberOfMessagesAdded--;
if (fFlags[index] & kImapMsgDeletedFlag) // see if we already had counted this one as deleted
fNumberDeleted--;
for (counter = index; counter < (uint32) fNumberOfMessagesAdded; counter++)
{
fUids.SetAt(counter, fUids[counter + 1]);
fFlags[counter] = fFlags[counter + 1];
}
}
// adds to sorted list. protects against duplicates and going past fNumberOfMessageSlotsAllocated
void TImapFlagAndUidState::AddUidFlagPair(imap_uid uid, imapMessageFlagsType flags)
{
// make sure there is room for this pair
if (fNumberOfMessagesAdded >= fNumberOfMessageSlotsAllocated)
{
fNumberOfMessageSlotsAllocated += kFlagEntrySize;
fUids.SetSize(fNumberOfMessageSlotsAllocated);
fFlags = (imapMessageFlagsType*) XP_REALLOC(fFlags, sizeof(imapMessageFlagsType) * fNumberOfMessageSlotsAllocated); // new imapMessageFlagsType[fNumberOfMessageSlotsAllocated];
}
// optimize the common case of placing on the end
if (!fNumberOfMessagesAdded || (uid > (int32) fUids[fNumberOfMessagesAdded - 1]))
{
fUids.SetAt(fNumberOfMessagesAdded, uid);
fFlags[fNumberOfMessagesAdded] = flags;
fNumberOfMessagesAdded++;
if (flags & kImapMsgDeletedFlag)
fNumberDeleted++;
return;
}
// search for the slot for this uid-flag pair
int32 insertionIndex = -1;
XP_Bool foundIt = FALSE;
GetMessageFlagsFromUID(uid, &foundIt, &insertionIndex);
// Hmmm, is the server sending back unordered fetch responses?
if (((int32) fUids[insertionIndex]) != uid)
{
// shift the uids and flags to the right
for (int32 shiftIndex = fNumberOfMessagesAdded; shiftIndex > insertionIndex; shiftIndex--)
{
fUids.SetAt(shiftIndex, fUids[shiftIndex - 1]);
fFlags[shiftIndex] = fFlags[shiftIndex - 1];
}
fFlags[insertionIndex] = flags;
fUids.SetAt(insertionIndex, uid);
fNumberOfMessagesAdded++;
if (fFlags[insertionIndex] & kImapMsgDeletedFlag)
fNumberDeleted++;
} else {
if ((fFlags[insertionIndex] & kImapMsgDeletedFlag) && !(flags & kImapMsgDeletedFlag))
fNumberDeleted--;
else
if (!(fFlags[insertionIndex] & kImapMsgDeletedFlag) && (flags & kImapMsgDeletedFlag))
fNumberDeleted++;
fFlags[insertionIndex] = flags;
}
}
int TImapFlagAndUidState::GetNumberOfMessages()
{
return fNumberOfMessagesAdded;
}
int TImapFlagAndUidState::GetNumberOfDeletedMessages()
{
return fNumberDeleted;
}
// since the uids are sorted, start from the back (rb)
imap_uid TImapFlagAndUidState::GetHighestNonDeletedUID()
{
uint32 index = fNumberOfMessagesAdded;
do {
if (index <= 0)
return(0);
index--;
if (fUids[index] && !(fFlags[index] & kImapMsgDeletedFlag))
return fUids[index];
} while (index > 0);
return 0;
}
// Has the user read the last message here ? Used when we first open the inbox to see if there
// really is new mail there.
XP_Bool TImapFlagAndUidState::IsLastMessageUnseen()
{
uint32 index = fNumberOfMessagesAdded;
if (index <= 0)
return FALSE;
index--;
// if last message is deleted, it was probably filtered the last time around
if (fUids[index] && (fFlags[index] & (kImapMsgSeenFlag | kImapMsgDeletedFlag)))
return FALSE;
return TRUE;
}
imap_uid TImapFlagAndUidState::GetUidOfMessage(int zeroBasedIndex)
{
if (zeroBasedIndex < fNumberOfMessagesAdded)
return fUids[zeroBasedIndex];
return (-1); // so that value is non-zero and we don't ask for bad msgs
}
// find a message flag given a key with non-recursive binary search, since some folders
// may have thousand of messages, once we find the key set its index, or the index of
// where the key should be inserted
imapMessageFlagsType TImapFlagAndUidState::GetMessageFlagsFromUID(imap_uid uid, XP_Bool *foundIt, int32 *ndx)
{
int index = 0;
int hi = fNumberOfMessagesAdded - 1;
int lo = 0;
*foundIt = FALSE;
*ndx = -1;
while (lo <= hi)
{
index = (lo + hi) / 2;
if (fUids[index] == (uint32) uid)
{
int returnFlags = fFlags[index];
*foundIt = TRUE;
*ndx = index;
if (fSupportUserFlag)
returnFlags |= kImapMsgSupportUserFlag;
return returnFlags;
}
if (fUids[index] > (uint32) uid)
hi = index -1;
else if (fUids[index] < (uint32) uid)
lo = index + 1;
}
index = lo;
while ((index > 0) && (fUids[index] > (uint32) uid))
index--;
if (index < 0)
index = 0;
*ndx = index;
return 0;
}
imapMessageFlagsType TImapFlagAndUidState::GetMessageFlags(int zeroBasedIndex)
{
imapMessageFlagsType returnFlags = kNoImapMsgFlag;
if (zeroBasedIndex < fNumberOfMessagesAdded)
returnFlags = fFlags[zeroBasedIndex];
if (fSupportUserFlag)
returnFlags |= kImapMsgSupportUserFlag;
return returnFlags;
}
extern "C" {
TImapFlagAndUidState *IMAP_CreateFlagState(int numberOfMessages)
{
return new TImapFlagAndUidState(numberOfMessages);
}
void IMAP_DeleteFlagState(TImapFlagAndUidState *state)
{
delete state;
}
int IMAP_GetFlagStateNumberOfMessages(TImapFlagAndUidState *state)
{
return state->GetNumberOfMessages();
}
imap_uid IMAP_GetUidOfMessage(int zeroBasedIndex, TImapFlagAndUidState *state)
{
return state->GetUidOfMessage(zeroBasedIndex);
}
imapMessageFlagsType IMAP_GetMessageFlags(int zeroBasedIndex, TImapFlagAndUidState *state)
{
return state->GetMessageFlags(zeroBasedIndex);
}
imapMessageFlagsType IMAP_GetMessageFlagsFromUID(imap_uid uid, XP_Bool *foundIt, TImapFlagAndUidState *state)
{
int32 index = -1;
return state->GetMessageFlagsFromUID(uid, foundIt, &index);
}
} // extern "C"
TSearchResultSequence::TSearchResultSequence()
{
fListOfLines = XP_ListNew();
}
TSearchResultSequence *TSearchResultSequence::CreateSearchResultSequence()
{
TSearchResultSequence *returnObject = new TSearchResultSequence;
if (!returnObject ||
!returnObject->fListOfLines)
{
delete returnObject;
returnObject = nil;
}
return returnObject;
}
TSearchResultSequence::~TSearchResultSequence()
{
if (fListOfLines)
XP_ListDestroy(fListOfLines);
}
void TSearchResultSequence::ResetSequence()
{
if (fListOfLines)
XP_ListDestroy(fListOfLines);
fListOfLines = XP_ListNew();
}
void TSearchResultSequence::AddSearchResultLine(const char *searchLine)
{
// The first add becomes node 2. Fix this.
char *copiedSequence = XP_STRDUP(searchLine + 9); // 9 == "* SEARCH "
if (copiedSequence) // if we can't allocate this then the search won't hit
XP_ListAddObjectToEnd(fListOfLines, copiedSequence);
}
TSearchResultIterator::TSearchResultIterator(TSearchResultSequence &sequence) :
fSequence(sequence)
{
ResetIterator();
}
TSearchResultIterator::~TSearchResultIterator()
{}
void TSearchResultIterator::ResetIterator()
{
fCurrentLine = fSequence.fListOfLines->next;
if (fCurrentLine)
fPositionInCurrentLine = (char *) fCurrentLine->object;
else
fPositionInCurrentLine = nil;
}
int32 TSearchResultIterator::GetNextMessageNumber()
{
int32 returnValue = 0;
if (fPositionInCurrentLine)
{
returnValue = atoint32(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;
}
////////////////// TIMAPGenericParser /////////////////////////
TIMAPGenericParser::TIMAPGenericParser() :
fNextToken(nil),
fCurrentLine(nil),
fLineOfTokens(nil),
fCurrentTokenPlaceHolder(nil),
fStartOfLineOfTokens(nil),
fSyntaxErrorLine(nil),
fAtEndOfLine(FALSE),
fDisconnected(FALSE),
fSyntaxError(FALSE),
fTokenizerAdvanced(FALSE)
{
}
TIMAPGenericParser::~TIMAPGenericParser()
{
FREEIF( fCurrentLine );
FREEIF( fStartOfLineOfTokens);
FREEIF( fSyntaxErrorLine );
}
void TIMAPGenericParser::HandleMemoryFailure()
{
SetConnected(FALSE);
}
void TIMAPGenericParser::ResetLexAnalyzer()
{
FREEIF( fCurrentLine );
FREEIF( fStartOfLineOfTokens );
fTokenizerAdvanced = FALSE;
fCurrentLine = fNextToken = fLineOfTokens = fStartOfLineOfTokens = fCurrentTokenPlaceHolder = nil;
fAtEndOfLine = FALSE;
}
XP_Bool TIMAPGenericParser::LastCommandSuccessful()
{
return Connected() && !SyntaxError();
}
void TIMAPGenericParser::SetSyntaxError(XP_Bool error)
{
fSyntaxError = error;
FREEIF( fSyntaxErrorLine );
if (error)
{
XP_ASSERT(FALSE); // If you hit this assert, file a bug on chrisf. Promise to include a protocol log.
fSyntaxErrorLine = XP_STRDUP(fCurrentLine);
if (!fSyntaxErrorLine)
{
HandleMemoryFailure();
// PR_LOG(IMAP, out, ("PARSER: Internal Syntax Error: <no line>"));
}
else
{
// if (!XP_STRCMP(fSyntaxErrorLine, CRLF))
// PR_LOG(IMAP, out, ("PARSER: Internal Syntax Error: <CRLF>"));
// else
// PR_LOG(IMAP, out, ("PARSER: Internal Syntax Error: %s", fSyntaxErrorLine));
}
}
else
fSyntaxErrorLine = NULL;
}
char *TIMAPGenericParser::CreateSyntaxErrorLine()
{
return XP_STRDUP(fSyntaxErrorLine);
}
XP_Bool TIMAPGenericParser::SyntaxError()
{
return fSyntaxError;
}
void TIMAPGenericParser::SetConnected(XP_Bool connected)
{
fDisconnected = !connected;
}
XP_Bool TIMAPGenericParser::Connected()
{
return !fDisconnected;
}
XP_Bool TIMAPGenericParser::ContinueParse()
{
return !fSyntaxError && !fDisconnected;
}
XP_Bool TIMAPGenericParser::at_end_of_line()
{
return XP_STRCMP(fNextToken, CRLF) == 0;
}
void TIMAPGenericParser::skip_to_CRLF()
{
while (Connected() && !at_end_of_line())
fNextToken = GetNextToken();
}
// fNextToken initially should point to
// a string after the initial open paren ("(")
// After this call, fNextToken points to the
// first character after the matching close
// paren. Only call GetNextToken to get the NEXT
// token after the one returned in fNextToken.
void TIMAPGenericParser::skip_to_close_paren()
{
int numberOfCloseParensNeeded = 1;
if (fNextToken && *fNextToken == ')')
{
numberOfCloseParensNeeded--;
fNextToken++;
if (!fNextToken || !*fNextToken)
fNextToken = GetNextToken();
}
while (ContinueParse() && numberOfCloseParensNeeded > 0)
{
// go through fNextToken, count the number
// of open and close parens, to account
// for nested parens which might come in
// the response
char *loc = 0;
for (loc = fNextToken; loc && *loc; loc++)
{
if (*loc == '(')
numberOfCloseParensNeeded++;
else if (*loc == ')')
numberOfCloseParensNeeded--;
if (numberOfCloseParensNeeded == 0)
{
fNextToken = loc + 1;
if (!fNextToken || !*fNextToken)
fNextToken = GetNextToken();
break; // exit the loop
}
}
if (numberOfCloseParensNeeded > 0)
fNextToken = GetNextToken();
}
}
char *TIMAPGenericParser::GetNextToken()
{
if (!fCurrentLine || fAtEndOfLine)
AdvanceToNextLine();
else if (Connected())
{
if (fTokenizerAdvanced)
{
fNextToken = XP_STRTOK_R(fLineOfTokens, WHITESPACE, &fCurrentTokenPlaceHolder);
fTokenizerAdvanced = FALSE;
}
else
{
fNextToken = XP_STRTOK_R(nil, WHITESPACE, &fCurrentTokenPlaceHolder);
}
if (!fNextToken)
{
fAtEndOfLine = TRUE;
fNextToken = CRLF;
}
}
return fNextToken;
}
void TIMAPGenericParser::AdvanceToNextLine()
{
FREEIF( fCurrentLine );
FREEIF( fStartOfLineOfTokens);
fTokenizerAdvanced = FALSE;
XP_Bool ok = GetNextLineForParser(&fCurrentLine);
if (!ok)
{
SetConnected(FALSE);
fStartOfLineOfTokens = nil;
fLineOfTokens = nil;
fCurrentTokenPlaceHolder = nil;
fNextToken = CRLF;
}
else if (fCurrentLine) // might be NULL if we are would_block ?
{
fStartOfLineOfTokens = XP_STRDUP(fCurrentLine);
if (fStartOfLineOfTokens)
{
fLineOfTokens = fStartOfLineOfTokens;
fNextToken = XP_STRTOK_R(fLineOfTokens, WHITESPACE, &fCurrentTokenPlaceHolder);
if (!fNextToken)
{
fAtEndOfLine = TRUE;
fNextToken = CRLF;
}
else
fAtEndOfLine = FALSE;
}
else
HandleMemoryFailure();
}
else
HandleMemoryFailure();
}
void TIMAPGenericParser::AdvanceTokenizerStartingPoint(int32 bytesToAdvance)
{
FREEIF(fStartOfLineOfTokens);
if (fCurrentLine)
{
fStartOfLineOfTokens = XP_STRDUP(fCurrentLine);
if (fStartOfLineOfTokens && ((int32) XP_STRLEN(fStartOfLineOfTokens) >= bytesToAdvance))
{
fLineOfTokens = fStartOfLineOfTokens + bytesToAdvance;
fTokenizerAdvanced = TRUE;
}
else
HandleMemoryFailure();
}
else
HandleMemoryFailure();
}
// Lots of things in the IMAP protocol are defined as an "astring."
// An astring is either an atom or a string.
// An atom is just a series of one or more characters such as: hello
// A string can either be quoted or literal.
// Quoted: "Test Folder 1"
// Literal: {13}Test Folder 1
// This function leaves us off with fCurrentTokenPlaceHolder immediately after
// the end of the Astring. Call GetNextToken() to get the token after it.
char *TIMAPGenericParser::CreateAstring()
{
if (*fNextToken == '{')
{
return CreateLiteral(); // literal
}
else if (*fNextToken == '"')
{
return CreateQuoted(); // quoted
}
else
{
return CreateAtom(); // atom
}
}
// Create an atom
// This function does not advance the parser.
// Call GetNextToken() to get the next token after the atom.
char *TIMAPGenericParser::CreateAtom()
{
char *rv = XP_STRDUP(fNextToken);
//fNextToken = GetNextToken();
return (rv);
}
// CreateNilString creates either NIL (reutrns NULL) or a string
// Call with fNextToken pointing to the thing which we think is the nilstring.
// This function leaves us off with fCurrentTokenPlaceHolder immediately after
// the end of the string, if it is a string, or at the NIL.
// Regardless of type, call GetNextToken() to get the token after it.
char *TIMAPGenericParser::CreateNilString()
{
if (!XP_STRCASECMP(fNextToken, "NIL"))
{
//fNextToken = GetNextToken();
return NULL;
}
else
return CreateString();
}
// Create a string, which can either be quoted or literal,
// but not an atom.
// This function leaves us off with fCurrentTokenPlaceHolder immediately after
// the end of the String. Call GetNextToken() to get the token after it.
char *TIMAPGenericParser::CreateString()
{
if (*fNextToken == '{')
{
char *rv = CreateLiteral(); // literal
return (rv);
}
else if (*fNextToken == '"')
{
char *rv = CreateQuoted(); // quoted
//fNextToken = GetNextToken();
return (rv);
}
else
{
SetSyntaxError(TRUE);
return NULL;
}
}
// This function leaves us off with fCurrentTokenPlaceHolder immediately after
// the end of the closing quote. Call GetNextToken() to get the token after it.
// Note that if the current line ends without the
// closed quote then we have to fetch another line from the server, until
// we find the close quote.
char *TIMAPGenericParser::CreateQuoted(XP_Bool /*skipToEnd*/)
{
char *currentChar = fCurrentLine +
(fNextToken - fStartOfLineOfTokens)
+ 1; // one char past opening '"'
int charIndex = 0;
int tokenIndex = 0;
XP_Bool closeQuoteFound = FALSE;
char *returnString = XP_STRDUP(currentChar);
while (returnString && !closeQuoteFound && ContinueParse())
{
if (!*(returnString + charIndex))
{
AdvanceToNextLine();
StrAllocCat(returnString, fCurrentLine);
charIndex++;
}
else if (*(returnString + charIndex) == '"')
{
// don't check to see if it was escaped,
// that was handled in the next clause
closeQuoteFound = TRUE;
}
else if (*(returnString + charIndex) == '\\')
{
// eat the escape character
XP_STRCPY(returnString + charIndex, returnString + charIndex + 1);
// whatever the escaped character was, we want it
charIndex++;
// account for charIndex not reflecting the eat of the escape character
tokenIndex++;
}
else
charIndex++;
}
if (closeQuoteFound && returnString)
{
*(returnString + charIndex) = 0;
//if ((charIndex == 0) && skipToEnd) // it's an empty string. Why skip to end?
// skip_to_CRLF();
//else if (charIndex == XP_STRLEN(fCurrentLine)) // should we have this?
//AdvanceToNextLine();
//else
if (charIndex < (int) (XP_STRLEN(fNextToken) - 2)) // -2 because of the start and end quotes
{
// the quoted string was fully contained within fNextToken,
// and there is text after the quote in fNextToken that we
// still need
int charDiff = XP_STRLEN(fNextToken) - charIndex - 1;
fCurrentTokenPlaceHolder -= charDiff;
if (!XP_STRCMP(fCurrentTokenPlaceHolder, CRLF))
fAtEndOfLine = TRUE;
}
else
{
fCurrentTokenPlaceHolder += tokenIndex + charIndex + 2 - XP_STRLEN(fNextToken);
if (!XP_STRCMP(fCurrentTokenPlaceHolder, CRLF))
fAtEndOfLine = TRUE;
/*
tokenIndex += charIndex;
fNextToken = currentChar + tokenIndex + 1;
if (!XP_STRCMP(fNextToken, CRLF))
fAtEndOfLine = TRUE;
*/
}
}
return returnString;
}
// This function leaves us off with fCurrentTokenPlaceHolder immediately after
// the end of the literal string. Call GetNextToken() to get the token after it
// the literal string.
char *TIMAPGenericParser::CreateLiteral()
{
int32 numberOfCharsInMessage = atoint32(fNextToken + 1);
int32 charsReadSoFar = 0, currentLineLength = 0;
int32 bytesToCopy = 0;
char *returnString = (char *) XP_ALLOC(numberOfCharsInMessage + 1);
if (returnString)
{
*(returnString + numberOfCharsInMessage) = 0; // Null terminate it first
while (ContinueParse() && (charsReadSoFar < numberOfCharsInMessage))
{
AdvanceToNextLine();
currentLineLength = XP_STRLEN(fCurrentLine);
bytesToCopy = (currentLineLength > numberOfCharsInMessage - charsReadSoFar ?
numberOfCharsInMessage - charsReadSoFar : currentLineLength);
XP_ASSERT (bytesToCopy);
if (ContinueParse())
{
XP_MEMCPY(returnString + charsReadSoFar, fCurrentLine, bytesToCopy);
charsReadSoFar += bytesToCopy;
}
}
if (ContinueParse())
{
if (bytesToCopy == 0)
{
skip_to_CRLF();
fAtEndOfLine = TRUE;
//fNextToken = GetNextToken();
}
else if (currentLineLength == bytesToCopy)
{
fAtEndOfLine = TRUE;
//AdvanceToNextLine();
}
else
{
// Move fCurrentTokenPlaceHolder
//fCurrentTokenPlaceHolder = fStartOfLineOfTokens + bytesToCopy;
AdvanceTokenizerStartingPoint (bytesToCopy);
if (!*fCurrentTokenPlaceHolder) // landed on a token boundary
fCurrentTokenPlaceHolder++;
if (!XP_STRCMP(fCurrentTokenPlaceHolder, CRLF))
fAtEndOfLine = TRUE;
// The first token on the line might not
// be at the beginning of the line. There might be ONLY
// whitespace before it, in which case, fNextToken
// will be pointing to the right thing. Otherwise,
// we want to advance to the next token.
/*
int32 numCharsChecked = 0;
XP_Bool allWhitespace = TRUE;
while ((numCharsChecked < bytesToCopy)&& allWhitespace)
{
allWhitespace = (XP_STRCHR(WHITESPACE, fCurrentLine[numCharsChecked]) != NULL);
numCharsChecked++;
}
if (!allWhitespace)
{
//fNextToken = fCurrentLine + bytesToCopy;
fNextToken = GetNextToken();
if (!XP_STRCMP(fNextToken, CRLF))
fAtEndOfLine = TRUE;
}
*/
}
}
}
return returnString;
}
// Call this to create a buffer containing all characters within
// a given set of parentheses.
// Call this with fNextToken[0]=='(', that is, the open paren
// of the group.
// It will allocate and return all characters up to and including the corresponding
// closing paren, and leave the parser in the right place afterwards.
char *TIMAPGenericParser::CreateParenGroup()
{
int numOpenParens = 1;
// count the number of parens in the current token
int count, tokenLen = XP_STRLEN(fNextToken);
for (count = 1; (count < tokenLen) && (numOpenParens > 0); count++)
{
if (fNextToken[count] == '(')
numOpenParens++;
else if (fNextToken[count] == ')')
numOpenParens--;
}
// build up a buffer with the paren group.
char *buf = NULL;
if ((numOpenParens > 0) && ContinueParse())
{
// First, copy that first token from before
StrAllocCat(buf, fNextToken);
StrAllocCat(buf, " "); // space that got stripped off the token
// Go through the current line and look for the last close paren.
// We're not trying to parse it just yet, just separate it out.
int len = XP_STRLEN(fCurrentTokenPlaceHolder);
for (count = 0; (count < len) && (numOpenParens > 0); count++)
{
if (fCurrentTokenPlaceHolder[count] == '(')
numOpenParens++;
else if (fCurrentTokenPlaceHolder[count] == ')')
numOpenParens--;
}
if (count < len)
{
// we found the last close paren.
// Set fNextToken, fCurrentTokenPlaceHolder, etc.
char oldChar = fCurrentTokenPlaceHolder[count];
fCurrentTokenPlaceHolder[count] = 0;
StrAllocCat(buf, fCurrentTokenPlaceHolder);
fCurrentTokenPlaceHolder[count] = oldChar;
fCurrentTokenPlaceHolder = fCurrentTokenPlaceHolder + count;
fNextToken = GetNextToken();
}
else
{
// there should always be either a space or CRLF after the response, right?
SetSyntaxError(TRUE);
}
}
else if ((numOpenParens == 0) && ContinueParse())
{
// the whole paren group response was a single token
buf = XP_STRDUP(fNextToken);
}
if (numOpenParens < 0)
SetSyntaxError(TRUE);
return buf;
}
////////////////// TIMAPServerState /////////////////////////
TImapServerState::TImapServerState(TIMAP4BlockingConnection &imapConnection) :
TIMAPGenericParser(),
fServerConnection(imapConnection),
fCurrentCommandTag(nil),
fCurrentFolderReadOnly(FALSE),
fNumberOfExistingMessages(0),
fNumberOfRecentMessages(0),
fNumberOfUnseenMessages(0),
fSizeOfMostRecentMessage(0),
fTotalDownloadSize(0),
fIMAPstate(kNonAuthenticated),
fSelectedMailboxName(nil),
fFlagState(nil),
fCurrentLineContainedFlagInfo(FALSE),
fZeroLengthMessageUidString(nil),
fReportingErrors(TRUE),
fLastChunk(FALSE),
fServerIsNetscape3xServer(FALSE),
m_shell(NULL)
{
fSearchResults = TSearchResultSequence::CreateSearchResultSequence();
fMailAccountUrl = NULL;
fManageFiltersUrl = NULL;
fManageListsUrl = NULL;
fFolderAdminUrl = NULL;
fNetscapeServerVersionString = NULL;
fXSenderInfo = NULL;
fSupportsUserDefinedFlags = FALSE;
fCapabilityFlag = kCapabilityUndefined;
fLastAlert = NULL;
}
TImapServerState::~TImapServerState()
{
FREEIF( fCurrentCommandTag );
//delete fFlagState; // not our object
delete fSearchResults;
FREEIF( fZeroLengthMessageUidString );
FREEIF( fMailAccountUrl );
FREEIF( fFolderAdminUrl );
FREEIF( fNetscapeServerVersionString );
FREEIF( fXSenderInfo );
FREEIF( fLastAlert );
FREEIF( fManageListsUrl );
FREEIF( fManageFiltersUrl );
}
XP_Bool TImapServerState::LastCommandSuccessful()
{
return (!CommandFailed() &&
!fServerConnection.DeathSignalReceived() &&
TIMAPGenericParser::LastCommandSuccessful());
}
// returns TRUE if things look ok to continue
XP_Bool TImapServerState::GetNextLineForParser(char **nextLine)
{
XP_Bool rv = TRUE;
*nextLine = fServerConnection.CreateNewLineFromSocket();
if (fServerConnection.DeathSignalReceived() || (fServerConnection.GetConnectionStatus() <= 0))
rv = FALSE;
// we'd really like to try to silently reconnect, but we shouldn't put this
// message up just in the interrupt case
if (fServerConnection.GetConnectionStatus() <= 0 && !fServerConnection.DeathSignalReceived())
fServerConnection.AlertUserEvent_UsingId(MK_SERVER_DISCONNECTED);
return rv;
}
// This should probably be in the base class...
void TImapServerState::end_of_line()
{
// if current commands failed, don't reset the lex analyzer
// we need the info for the user
if (!at_end_of_line())
SetSyntaxError(TRUE);
else if (fProcessingTaggedResponse && !fCurrentCommandFailed)
ResetLexAnalyzer(); // no more tokens until we send a command
else if (!fCurrentCommandFailed)
fNextToken = GetNextToken();
}
XP_Bool TImapServerState::CommandFailed()
{
return fCurrentCommandFailed;
}
void TImapServerState::SetFlagState(TImapFlagAndUidState *state)
{
fFlagState = state;
}
int32 TImapServerState::SizeOfMostRecentMessage()
{
return fSizeOfMostRecentMessage;
}
// Call this when adding a pipelined command to the session
void TImapServerState::IncrementNumberOfTaggedResponsesExpected(const char *newExpectedTag)
{
fNumberOfTaggedResponsesExpected++;
FREEIF( fCurrentCommandTag );
fCurrentCommandTag = XP_STRDUP(newExpectedTag);
if (!fCurrentCommandTag)
HandleMemoryFailure();
}
/*
response ::= *response_data response_done
*/
void TImapServerState::InitializeState()
{
fProcessingTaggedResponse = FALSE;
fCurrentCommandFailed = FALSE;
}
void TImapServerState::ParseIMAPServerResponse(const char *currentCommand)
{
// Reinitialize the parser
SetConnected(TRUE);
SetSyntaxError(FALSE);
// Reinitialize our state
InitializeState();
// the default is to not pipeline
fNumberOfTaggedResponsesExpected = 1;
int numberOfTaggedResponsesReceived = 0;
char *copyCurrentCommand = XP_STRDUP(currentCommand);
if (copyCurrentCommand && !fServerConnection.DeathSignalReceived())
{
char *placeInTokenString = NULL;
char *tagToken = XP_STRTOK_R(copyCurrentCommand, WHITESPACE,&placeInTokenString);
char *commandToken = XP_STRTOK_R(nil, WHITESPACE,&placeInTokenString);
if (tagToken)
{
FREEIF( fCurrentCommandTag );
fCurrentCommandTag = XP_STRDUP(tagToken);
if (!fCurrentCommandTag)
HandleMemoryFailure();
}
if (commandToken && ContinueParse())
PreProcessCommandToken(commandToken, currentCommand);
if (ContinueParse())
{
// Clears any syntax error lines
SetSyntaxError(FALSE);
ResetLexAnalyzer();
do {
fNextToken = GetNextToken();
while (ContinueParse() && !XP_STRCMP(fNextToken, "*") )
{
response_data();
}
if (*fNextToken == '+') // never pipeline APPEND or AUTHENTICATE
{
XP_ASSERT((fNumberOfTaggedResponsesExpected - numberOfTaggedResponsesReceived) == 1);
numberOfTaggedResponsesReceived = fNumberOfTaggedResponsesExpected;
}
else
numberOfTaggedResponsesReceived++;
if (numberOfTaggedResponsesReceived < fNumberOfTaggedResponsesExpected)
{
response_tagged();
fProcessingTaggedResponse = FALSE;
}
} while (ContinueParse() && (numberOfTaggedResponsesReceived < fNumberOfTaggedResponsesExpected));
// check and see if the server is waiting for more input
if (*fNextToken == '+')
fIMAPstate = kWaitingForMoreClientInput;
else
{
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);
if (fReportingErrors)
fServerConnection.AlertUserEventFromServer(fCurrentLine);
}
}
}
}
else if (!fServerConnection.DeathSignalReceived())
HandleMemoryFailure();
FREEIF(copyCurrentCommand);
}
void TImapServerState::HandleMemoryFailure()
{
#ifdef DEBUG_chrisf
XP_ASSERT(FALSE);
#endif
fServerConnection.AlertUserEvent(XP_GetString(MK_OUT_OF_MEMORY));
TIMAPGenericParser::HandleMemoryFailure();
}
// SEARCH is the only command that requires pre-processing for now.
// others will be added here.
void TImapServerState::PreProcessCommandToken(const char *commandToken,
const char *currentCommand)
{
fCurrentCommandIsSingleMessageFetch = FALSE;
if (!XP_STRCASECMP(commandToken, "SEARCH"))
fSearchResults->ResetSequence();
else if (!XP_STRCASECMP(commandToken, "SELECT") && currentCommand)
{
// the mailbox name must be quoted, so strip the quotes
const char *openQuote = XP_STRSTR(currentCommand, "\"");
XP_ASSERT(openQuote);
FREEIF( fSelectedMailboxName);
fSelectedMailboxName = XP_STRDUP(openQuote + 1);
if (fSelectedMailboxName)
{
// strip the escape chars and the ending quote
char *currentChar = fSelectedMailboxName;
while (*currentChar)
{
if (*currentChar == '\\')
{
XP_STRCPY(currentChar, currentChar+1);
currentChar++; // skip what we are escaping
}
else if (*currentChar == '\"')
*currentChar = 0; // end quote
else
currentChar++;
}
}
else
HandleMemoryFailure();
// we don't want bogus info for this new box
//delete fFlagState; // not our object
//fFlagState = NULL;
}
else if (!XP_STRCASECMP(commandToken, "CLOSE"))
{
return; // just for debugging
// we don't want bogus info outside the selected state
//delete fFlagState; // not our object
//fFlagState = NULL;
}
else if (!XP_STRCASECMP(commandToken, "UID"))
{
char *copyCurrentCommand = XP_STRDUP(currentCommand);
if (copyCurrentCommand && !fServerConnection.DeathSignalReceived())
{
char *placeInTokenString = NULL;
char *tagToken = XP_STRTOK_R(copyCurrentCommand, WHITESPACE,&placeInTokenString);
char *uidToken = XP_STRTOK_R(nil, WHITESPACE,&placeInTokenString);
char *fetchToken = XP_STRTOK_R(nil, WHITESPACE,&placeInTokenString);
if (!XP_STRCASECMP(fetchToken, "FETCH") )
{
char *uidStringToken = XP_STRTOK_R(nil, WHITESPACE,&placeInTokenString);
if (!XP_STRCHR(uidStringToken, ',') && !XP_STRCHR(uidStringToken, ':')) // , and : are uid delimiters
{
fCurrentCommandIsSingleMessageFetch = TRUE;
fUidOfSingleMessageFetch = atoint32(uidStringToken);
}
}
XP_FREE(copyCurrentCommand);
}
}
}
const char *TImapServerState::GetSelectedMailboxName()
{
return fSelectedMailboxName;
}
TSearchResultIterator *TImapServerState::CreateSearchResultIterator()
{
return new TSearchResultIterator(*fSearchResults);
}
TImapServerState::eIMAPstate TImapServerState::GetIMAPstate()
{
return fIMAPstate;
}
void TImapServerState::PreauthSetAuthenticatedState()
{
fIMAPstate = kAuthenticated;
}
void TImapServerState::ProcessOkCommand(const char *commandToken)
{
if (!XP_STRCASECMP(commandToken, "LOGIN") ||
!XP_STRCASECMP(commandToken, "AUTHENTICATE"))
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;
else if ((!XP_STRCASECMP(commandToken, "LIST")) ||
(!XP_STRCASECMP(commandToken, "LSUB")))
{
//fServerConnection.MailboxDiscoveryFinished();
// This used to be reporting that we were finished
// discovering folders for each time we issued a
// LIST or LSUB. So if we explicitly listed the
// INBOX, or Trash, or namespaces, we would get multiple
// "done" states, even though we hadn't finished.
// Move this to be called from the connection object
// itself.
}
else if (!XP_STRCASECMP(commandToken, "FETCH"))
{
if (fZeroLengthMessageUidString)
{
// "Deleting zero length message");
fServerConnection.Store(fZeroLengthMessageUidString, "+Flags (\\Deleted)", TRUE);
if (LastCommandSuccessful())
fServerConnection.Expunge();
FREEIF( fZeroLengthMessageUidString );
fZeroLengthMessageUidString = nil;
}
}
if (GetFillingInShell())
{
// There is a BODYSTRUCTURE response. Now let's generate the stream...
// that is, if we're not doing it already
if (!m_shell->IsBeingGenerated())
{
TNavigatorImapConnection *navCon = fServerConnection.GetNavigatorConnection();
XP_ASSERT(navCon); // we should always have this
m_shell->Generate(navCon ? navCon->GetCurrentUrl()->GetIMAPPartToFetch() : (char *)NULL);
if ((navCon && navCon->GetPseudoInterrupted())
|| fServerConnection.DeathSignalReceived())
{
// we were pseudointerrupted or interrupted
if (!m_shell->IsShellCached())
{
// if it's not in the cache, then we were (pseudo)interrupted while generating
// for the first time. Delete it.
delete m_shell;
}
navCon->PseudoInterrupt(FALSE);
}
else if (m_shell->GetIsValid())
{
// If we have a valid shell that has not already been cached, then cache it.
if (!m_shell->IsShellCached()) // cache is responsible for destroying it
{
// PR_LOG(IMAP, out, ("BODYSHELL: Adding shell to cache."));
TIMAPHostInfo::AddShellToCacheForHost(fServerConnection.GetHostName(), m_shell);
}
}
else
{
// The shell isn't valid, so we don't cache it.
// Therefore, we have to destroy it here.
delete m_shell;
}
m_shell = NULL;
}
}
}
void TImapServerState::ProcessBadCommand(const char *commandToken)
{
if (!XP_STRCASECMP(commandToken, "LOGIN") ||
!XP_STRCASECMP(commandToken, "AUTHENTICATE"))
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
if (GetFillingInShell())
{
if (!m_shell->IsBeingGenerated())
{
delete m_shell;
m_shell = NULL;
}
}
}
/*
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
Instead of comparing lots of strings and make function calls, try to pre-flight
the possibilities based on the first letter of the token.
*/
void TImapServerState::response_data()
{
fNextToken = GetNextToken();
if (ContinueParse())
{
switch (toupper(fNextToken[0]))
{
case 'O': // OK
if (toupper(fNextToken[1]) == 'K')
resp_cond_state();
else SetSyntaxError(TRUE);
break;
case 'N': // NO
if (toupper(fNextToken[1]) == 'O')
resp_cond_state();
else if (!XP_STRCASECMP(fNextToken, "NAMESPACE"))
namespace_data();
else SetSyntaxError(TRUE);
break;
case 'B': // BAD
if (!XP_STRCASECMP(fNextToken, "BAD"))
resp_cond_state();
else if (!XP_STRCASECMP(fNextToken, "BYE"))
resp_cond_bye();
else SetSyntaxError(TRUE);
break;
case 'F':
if (!XP_STRCASECMP(fNextToken, "FLAGS"))
mailbox_data();
else SetSyntaxError(TRUE);
break;
case 'P':
if (XP_STRCASECMP(fNextToken, "PERMANENTFLAGS"))
mailbox_data();
else SetSyntaxError(TRUE);
break;
case 'L':
if (!XP_STRCASECMP(fNextToken, "LIST") || !XP_STRCASECMP(fNextToken, "LSUB"))
mailbox_data();
else SetSyntaxError(TRUE);
break;
case 'M':
if (!XP_STRCASECMP(fNextToken, "MAILBOX"))
mailbox_data();
else if (!XP_STRCASECMP(fNextToken, "MYRIGHTS"))
myrights_data();
else SetSyntaxError(TRUE);
break;
case 'S':
if (!XP_STRCASECMP(fNextToken, "SEARCH"))
mailbox_data();
else if (!XP_STRCASECMP(fNextToken, "STATUS"))
{
XP_Bool gotMailboxName = FALSE;
while ( ContinueParse() &&
!at_end_of_line() )
{
fNextToken = GetNextToken();
if (!fNextToken)
break;
if (!gotMailboxName) // if we haven't got the mailbox name, get it
{
// this couldn't be more bogus, but I don't know how to parse the status response.
// I need to find the next open parenthesis, and it looks like folder names with
// parentheses are quoted...but not ones with spaces...
fCurrentTokenPlaceHolder = XP_STRCHR(fCurrentTokenPlaceHolder, '(');
gotMailboxName = TRUE;
continue;
}
if (*fNextToken == '(') fNextToken++;
if (!XP_STRCASECMP(fNextToken, "UIDNEXT"))
{
fNextToken = GetNextToken();
if (fNextToken)
{
fCurrentResponseUID = atoint32(fNextToken);
// if this token ends in ')', then it is the last token
// else we advance
if ( *(fNextToken + XP_STRLEN(fNextToken) - 1) == ')')
fNextToken += XP_STRLEN(fNextToken) - 1;
}
}
else if (!XP_STRCASECMP(fNextToken, "MESSAGES"))
{
fNextToken = GetNextToken();
if (fNextToken)
{
fNumberOfExistingMessages = atoint32(fNextToken);
// if this token ends in ')', then it is the last token
// else we advance
if ( *(fNextToken + XP_STRLEN(fNextToken) - 1) == ')')
fNextToken += XP_STRLEN(fNextToken) - 1;
}
}
else if (!XP_STRCASECMP(fNextToken, "UNSEEN"))
{
fNextToken = GetNextToken();
if (fNextToken)
{
fNumberOfUnseenMessages = atoint32(fNextToken);
// if this token ends in ')', then it is the last token
// else we advance
if ( *(fNextToken + XP_STRLEN(fNextToken) - 1) == ')')
fNextToken += XP_STRLEN(fNextToken) - 1;
}
}
else if (*fNextToken == ')')
break;
else if (!at_end_of_line())
SetSyntaxError(TRUE);
}
} else SetSyntaxError(TRUE);
break;
case 'C':
if (!XP_STRCASECMP(fNextToken, "CAPABILITY"))
capability_data();
else SetSyntaxError(TRUE);
break;
case 'V':
if (!XP_STRCASECMP(fNextToken, "VERSION"))
{
// figure out the version of the Netscape server here
FREEIF(fNetscapeServerVersionString);
fNextToken = GetNextToken();
if (! fNextToken)
SetSyntaxError(TRUE);
else
{
fNetscapeServerVersionString = CreateAstring();
fNextToken = GetNextToken();
if (fNetscapeServerVersionString)
{
fServerIsNetscape3xServer = (*fNetscapeServerVersionString == '3');
}
}
skip_to_CRLF();
}
else SetSyntaxError(TRUE);
break;
case 'A':
if (!XP_STRCASECMP(fNextToken, "ACL"))
{
acl_data();
}
else if (!XP_STRCASECMP(fNextToken, "ACCOUNT-URL"))
{
FREEIF(fMailAccountUrl);
fNextToken = GetNextToken();
if (! fNextToken)
SetSyntaxError(TRUE);
else
{
fMailAccountUrl = CreateAstring();
fNextToken = GetNextToken();
}
}
else SetSyntaxError(TRUE);
break;
case 'X':
if (!XP_STRCASECMP(fNextToken, "XSERVERINFO"))
xserverinfo_data();
else if (!XP_STRCASECMP(fNextToken, "XMAILBOXINFO"))
xmailboxinfo_data();
else
SetSyntaxError(TRUE);
break;
default:
if (IsNumericString(fNextToken))
numeric_mailbox_data();
else
SetSyntaxError(TRUE);
break;
}
if (ContinueParse())
{
PostProcessEndOfLine();
end_of_line();
}
}
}
void TImapServerState::PostProcessEndOfLine()
{
// for now we only have to do one thing here
// a fetch response to a 'uid store' command might return the flags
// before it returns the uid of the message. So we need both before
// we report the new flag info to the front end
// also check and be sure that there was a UID in the current response
if (fCurrentLineContainedFlagInfo && CurrentResponseUID())
{
fCurrentLineContainedFlagInfo = FALSE;
fServerConnection.NotifyMessageFlags(fSavedFlagInfo, CurrentResponseUID());
}
}
/*
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()
{
XP_Bool userDefined = FALSE;
XP_Bool perm = FALSE;
if (!XP_STRCASECMP(fNextToken, "FLAGS")) {
skip_to_CRLF();
}
else if (!XP_STRCASECMP(fNextToken, "LIST"))
{
fNextToken = GetNextToken();
if (ContinueParse())
mailbox_list(FALSE);
}
else if (!XP_STRCASECMP(fNextToken, "LSUB"))
{
fNextToken = GetNextToken();
if (ContinueParse())
mailbox_list(TRUE);
}
else if (!XP_STRCASECMP(fNextToken, "MAILBOX"))
skip_to_CRLF();
else if (!XP_STRCASECMP(fNextToken, "SEARCH"))
{
fSearchResults->AddSearchResultLine(fCurrentLine);
fServerConnection.NotifySearchHit(fCurrentLine);
skip_to_CRLF();
}
}
/*
mailbox_list ::= "(" #("\Marked" / "\Noinferiors" /
"\Noselect" / "\Unmarked" / flag_extension) ")"
SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox
*/
void TImapServerState::mailbox_list(XP_Bool discoveredFromLsub)
{
mailbox_spec *boxSpec = (mailbox_spec *)XP_ALLOC(sizeof(mailbox_spec));
if (!boxSpec)
HandleMemoryFailure();
else
{
boxSpec->folderSelected = FALSE;
boxSpec->box_flags = kNoFlags;
boxSpec->allocatedPathName = NULL;
boxSpec->hostName = NULL;
boxSpec->connection = fServerConnection.GetNavigatorConnection();
boxSpec->flagState = NULL;
boxSpec->discoveredFromLsub = discoveredFromLsub;
boxSpec->onlineVerified = TRUE;
XP_Bool endOfFlags = FALSE;
fNextToken++; // eat the first "("
do {
if (!XP_STRNCASECMP(fNextToken, "\\Marked", 7))
boxSpec->box_flags |= kMarked;
else if (!XP_STRNCASECMP(fNextToken, "\\Unmarked", 9))
boxSpec->box_flags |= kUnmarked;
else if (!XP_STRNCASECMP(fNextToken, "\\Noinferiors", 12))
boxSpec->box_flags |= kNoinferiors;
else if (!XP_STRNCASECMP(fNextToken, "\\Noselect", 9))
boxSpec->box_flags |= kNoselect;
// we ignore flag extensions
endOfFlags = *(fNextToken + XP_STRLEN(fNextToken) - 1) == ')';
fNextToken = GetNextToken();
} while (!endOfFlags && ContinueParse());
if (ContinueParse())
{
if (*fNextToken == '"')
{
fNextToken++;
if (*fNextToken == '\\') // handle escaped char
boxSpec->hierarchySeparator = *(fNextToken + 1);
else
boxSpec->hierarchySeparator = *fNextToken;
}
else // likely NIL. Discovered late in 4.02 that we do not handle literals here (e.g. {10} <10 chars>), although this is almost impossibly unlikely
boxSpec->hierarchySeparator = kOnlineHierarchySeparatorUnknown;
fNextToken = GetNextToken();
if (ContinueParse())
mailbox(boxSpec);
}
}
}
/* mailbox ::= "INBOX" / astring
*/
void TImapServerState::mailbox(mailbox_spec *boxSpec)
{
char *boxname = NULL;
if (!XP_STRCASECMP(fNextToken, "INBOX"))
{
boxname = XP_STRDUP("INBOX");
fNextToken = GetNextToken();
}
else
{
boxname = CreateAstring();
fNextToken = GetNextToken();
}
if (boxname)
{
// sometimes the uwash server will return a list response with a '/' hanging on the
// end.
/*
// chrisf: I think we should "discover" whatever the server tells us, and do any munging in
// libmsg if it is necessary.
if (XP_STRLEN(boxname) && *(boxname + XP_STRLEN(boxname) - 1) == TIMAPUrl::GetOnlineSubDirSeparator())
*(boxname + XP_STRLEN(boxname) - 1) = '\0';
*/
// should the namespace check go before or after the Utf7 conversion?
TIMAPHostInfo::SetNamespaceHierarchyDelimiterFromMailboxForHost(fServerConnection.GetHostName(), boxname, boxSpec->hierarchySeparator);
TIMAPNamespace *ns = TIMAPHostInfo::GetNamespaceForMailboxForHost(fServerConnection.GetHostName(),boxname);
if (ns)
{
switch (ns->GetType())
{
case kPersonalNamespace:
boxSpec->box_flags |= kPersonalMailbox;
break;
case kPublicNamespace:
boxSpec->box_flags |= kPublicMailbox;
break;
case kOtherUsersNamespace:
boxSpec->box_flags |= kOtherUsersMailbox;
break;
default: // (kUnknownNamespace)
break;
}
}
char *convertedName = fServerConnection.CreateUtf7ConvertedString(boxname, FALSE);
XP_FREE(boxname);
boxname = convertedName;
}
if (!boxname)
{
if (!fServerConnection.DeathSignalReceived())
HandleMemoryFailure();
}
else
{
XP_ASSERT(boxSpec->connection);
XP_ASSERT(boxSpec->connection->GetCurrentUrl());
//boxSpec->hostName = NULL;
//if (boxSpec->connection && boxSpec->connection->GetCurrentUrl())
boxSpec->allocatedPathName = boxSpec->connection->GetCurrentUrl()->AllocateCanonicalPath(boxname);
boxSpec->hostName = boxSpec->connection->GetCurrentUrl()->GetUrlHost();
FREEIF( boxname);
// storage for the boxSpec is now owned by server connection
fServerConnection.DiscoverMailboxSpec(boxSpec);
// if this was cancelled by the user,then we sure don't want to
// send more mailboxes their way
if (fServerConnection.GetConnectionStatus() < 0)
SetConnected(FALSE);
}
}
/*
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 = atoint32(fNextToken);
fNextToken = GetNextToken();
if (ContinueParse())
{
if (!XP_STRCASECMP(fNextToken, "FETCH"))
{
fFetchResponseIndex = tokenNumber;
fNextToken = GetNextToken();
if (ContinueParse())
msg_fetch();
}
else 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"))
{
fFlagState->ExpungeByIndex((uint32) tokenNumber);
skip_to_CRLF();
}
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()
{
// we have not seen a uid response or flags for this fetch, yet
fCurrentResponseUID = 0;
fCurrentLineContainedFlagInfo = FALSE;
// show any incremental progress, for instance, for header downloading
fServerConnection.ShowProgress();
fNextToken++; // eat the '(' character
// some of these productions are ignored for now
while (ContinueParse() && (*fNextToken != ')') )
{
if (!XP_STRCASECMP(fNextToken, "FLAGS"))
{
if (fCurrentResponseUID == 0)
fCurrentResponseUID = fFlagState->GetUidOfMessage(fFetchResponseIndex - 1);
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, "UID"))
{
fNextToken = GetNextToken();
if (ContinueParse())
{
fCurrentResponseUID = atoint32(fNextToken);
if (fCurrentResponseUID > fHighestRecordedUID)
fHighestRecordedUID = fCurrentResponseUID;
// if this token ends in ')', then it is the last token
// else we advance
if ( *(fNextToken + XP_STRLEN(fNextToken) - 1) == ')')
fNextToken += XP_STRLEN(fNextToken) - 1;
else
fNextToken = GetNextToken();
}
}
else if (!XP_STRCASECMP(fNextToken, "RFC822") ||
!XP_STRCASECMP(fNextToken, "RFC822.HEADER") ||
!XP_STRNCASECMP(fNextToken, "BODY[HEADER",11) ||
!XP_STRNCASECMP(fNextToken, "BODY[]", 6) ||
!XP_STRCASECMP(fNextToken, "RFC822.TEXT") ||
(!XP_STRNCASECMP(fNextToken, "BODY[", 5) &&
XP_STRSTR(fNextToken, "HEADER"))
)
{
if (fCurrentResponseUID == 0)
fCurrentResponseUID = fFlagState->GetUidOfMessage(fFetchResponseIndex - 1);
if (!XP_STRCASECMP(fNextToken, "RFC822.HEADER") ||
!XP_STRCASECMP(fNextToken, "BODY[HEADER]"))
{
// all of this message's headers
fNextToken = GetNextToken();
fDownloadingHeaders = TRUE;
if (ContinueParse())
msg_fetch_headers(NULL);
}
else if (!XP_STRNCASECMP(fNextToken, "BODY[HEADER.FIELDS",19))
{
fDownloadingHeaders = TRUE;
// specific message header fields
while (ContinueParse() && fNextToken[XP_STRLEN(fNextToken)-1] != ']')
fNextToken = GetNextToken();
if (ContinueParse())
{
fNextToken = GetNextToken();
if (ContinueParse())
msg_fetch_headers(NULL);
}
}
else
{
char *whereHeader = XP_STRSTR(fNextToken, "HEADER");
if (whereHeader)
{
char *startPartNum = fNextToken + 5;
char *partNum = (char *)XP_ALLOC((whereHeader - startPartNum) * sizeof (char));
if (partNum)
{
XP_STRNCPY_SAFE(partNum, startPartNum, (whereHeader - startPartNum));
if (ContinueParse())
{
if (XP_STRSTR(fNextToken, "FIELDS"))
{
while (ContinueParse() && fNextToken[XP_STRLEN(fNextToken)-1] != ']')
fNextToken = GetNextToken();
}
if (ContinueParse())
{
fNextToken = GetNextToken();
if (ContinueParse())
msg_fetch_headers(partNum);
}
}
XP_FREE(partNum);
}
}
else
{
fDownloadingHeaders = FALSE;
XP_Bool chunk = FALSE;
int32 origin = 0;
if (!XP_STRNCASECMP(fNextToken, "BODY[]<", 7))
{
char *tokenCopy = 0;
StrAllocCopy(tokenCopy, fNextToken);
if (tokenCopy)
{
char *originString = tokenCopy + 7; // where the byte number starts
char *closeBracket = XP_STRCHR(tokenCopy,'>');
if (closeBracket && originString && *originString)
{
*closeBracket = 0;
origin = atoint32(originString);
chunk = TRUE;
}
XP_FREE(tokenCopy);
}
}
fNextToken = GetNextToken();
if (ContinueParse())
{
msg_fetch_content(chunk, origin, MESSAGE_RFC822);
}
}
}
}
else if (!XP_STRCASECMP(fNextToken, "RFC822.SIZE"))
{
fNextToken = GetNextToken();
if (ContinueParse())
{
fSizeOfMostRecentMessage = atoint32(fNextToken);
if (fSizeOfMostRecentMessage == 0 && CurrentResponseUID())
{
// on no, bogus Netscape 2.0 mail server bug
char uidString[100];
sprintf(uidString, "%ld", (long)CurrentResponseUID());
if (!fZeroLengthMessageUidString)
fZeroLengthMessageUidString = XP_STRDUP(uidString);
else
{
StrAllocCat(fZeroLengthMessageUidString, ",");
StrAllocCat(fZeroLengthMessageUidString, uidString);
}
}
// if this token ends in ')', then it is the last token
// else we advance
if ( *(fNextToken + XP_STRLEN(fNextToken) - 1) == ')')
fNextToken += XP_STRLEN(fNextToken) - 1;
else
fNextToken = GetNextToken();
}
}
else if (!XP_STRCASECMP(fNextToken, "XSENDER"))
{
FREEIF(fXSenderInfo);
fNextToken = GetNextToken();
if (! fNextToken)
SetSyntaxError(TRUE);
else
{
fXSenderInfo = CreateAstring();
fNextToken = GetNextToken();
}
}
// I only fetch RFC822 so I should never see these BODY responses
else if (!XP_STRCASECMP(fNextToken, "BODY"))
skip_to_CRLF(); // I never ask for this
else if (!XP_STRCASECMP(fNextToken, "BODYSTRUCTURE"))
{
bodystructure_data();
}
else if (!XP_STRNCASECMP(fNextToken, "BODY[", 5) && XP_STRNCASECMP(fNextToken, "BODY[]", 6))
{
fDownloadingHeaders = FALSE;
// A specific MIME part, or MIME part header
mime_data();
}
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
SetSyntaxError(TRUE);
}
if (ContinueParse())
{
if (CurrentResponseUID() && fCurrentLineContainedFlagInfo && fFlagState)
{
fFlagState->AddUidFlagPair(CurrentResponseUID(), fSavedFlagInfo);
fCurrentLineContainedFlagInfo = FALSE; // do not fire if in PostProcessEndOfLine
}
fCurrentLineContainedFlagInfo = FALSE; // do not fire if in PostProcessEndOfLine
fNextToken = GetNextToken(); // eat the ')' ending token
// should be at end of line
}
}
void TImapServerState::flags()
{
imapMessageFlagsType messageFlags = kNoImapMsgFlag;
// eat the opening '('
fNextToken++;
while (ContinueParse() && (*fNextToken != ')'))
{
switch (toupper(fNextToken[1])) {
case 'S':
if (!XP_STRNCASECMP(fNextToken, "\\Seen",5))
messageFlags |= kImapMsgSeenFlag;
break;
case 'A':
if (!XP_STRNCASECMP(fNextToken, "\\Answered",9))
messageFlags |= kImapMsgAnsweredFlag;
break;
case 'F':
if (!XP_STRNCASECMP(fNextToken, "\\Flagged",8))
messageFlags |= kImapMsgFlaggedFlag;
else if (fSupportsUserDefinedFlags && !XP_STRNCASECMP(fNextToken, "\\Forwarded",10))
messageFlags |= kImapMsgForwardedFlag;
break;
case 'D':
if (!XP_STRNCASECMP(fNextToken, "\\Deleted",8))
messageFlags |= kImapMsgDeletedFlag;
else if (!XP_STRNCASECMP(fNextToken, "\\Draft",6))
messageFlags |= kImapMsgDraftFlag;
break;
case 'R':
if (!XP_STRNCASECMP(fNextToken, "\\Recent",7))
messageFlags |= kImapMsgRecentFlag;
break;
case 'M':
if (fSupportsUserDefinedFlags && !XP_STRNCASECMP(fNextToken, "\\MDNSent",7))
messageFlags |= kImapMsgMDNSentFlag;
break;
default:
break;
}
if (strcasestr(fNextToken, ")"))
{
// eat token chars until we get the ')'
while (*fNextToken != ')')
fNextToken++;
}
else
fNextToken = GetNextToken();
}
if (ContinueParse())
while(*fNextToken != ')')
fNextToken++;
fCurrentLineContainedFlagInfo = TRUE; // handled in PostProcessEndOfLine
fSavedFlagInfo = messageFlags;
}
/*
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]"))
{
char *alertMsg = fCurrentTokenPlaceHolder; // advance past ALERT]
if (alertMsg && *alertMsg && (!fLastAlert || XP_STRCMP(fNextToken, fLastAlert)))
{
fServerConnection.AlertUserEvent(alertMsg);
FREEIF(fLastAlert);
fLastAlert = XP_STRDUP(alertMsg);
}
fNextToken = GetNextToken();
}
else if (!XP_STRCASECMP(fNextToken,"PARSE]"))
{
// do nothing for now
fNextToken = GetNextToken();
}
else if (!XP_STRCASECMP(fNextToken,"NETSCAPE]"))
{
skip_to_CRLF();
}
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
#if 0
do {
fNextToken = GetNextToken();
} while (!strcasestr(fNextToken, "]") &&
!at_end_of_line() &&
ContinueParse());
#else
fSupportsUserDefinedFlags = FALSE; // assume no unless told
do {
fNextToken = GetNextToken();
if (!XP_STRNCASECMP(fNextToken, "\\*)]", 4))
fSupportsUserDefinedFlags = TRUE;
} while (!at_end_of_line() && ContinueParse());
#endif
}
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 = atoint32(fNextToken);
fHighestRecordedUID = 0;
fNextToken = GetNextToken();
}
}
else if (!XP_STRCASECMP(fNextToken,"UNSEEN"))
{
fNextToken = GetNextToken();
if (ContinueParse())
{
fNumberOfUnseenMessages = atoint32(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_STRCMP(fCurrentCommandTag, fNextToken))
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);
fIMAPstate = kNonAuthenticated;
skip_to_CRLF();
}
void TImapServerState::msg_fetch_headers(const char *partNum)
{
if (GetFillingInShell())
{
char *headerData = CreateAstring();
fNextToken = GetNextToken();
m_shell->AdoptMessageHeaders(headerData, partNum);
}
else
{
msg_fetch_content(FALSE, 0, MESSAGE_RFC822);
}
}
/* nstring ::= string / nil
string ::= quoted / literal
nil ::= "NIL"
*/
void TImapServerState::msg_fetch_content(XP_Bool chunk, int32 origin, const char *content_type)
{
// setup the stream for downloading this message.
// Don't do it if we are filling in a shell or downloading a part.
// DO do it if we are downloading a whole message as a result of
// an invalid shell trying to generate.
if ((!chunk || (origin == 0)) &&
(GetFillingInShell() ? m_shell->GetGeneratingWholeMessage() : TRUE))
{
XP_ASSERT(fSizeOfMostRecentMessage > 0);
fServerConnection.BeginMessageDownLoad(fSizeOfMostRecentMessage,
content_type);
}
if (XP_STRCASECMP(fNextToken, "NIL"))
{
if (*fNextToken == '"')
fLastChunk = msg_fetch_quoted(chunk, origin);
else
fLastChunk = msg_fetch_literal(chunk, origin);
}
else
fNextToken = GetNextToken(); // eat "NIL"
if (fLastChunk && (GetFillingInShell() ? m_shell->GetGeneratingWholeMessage() : TRUE))
{
// complete the message download
if (ContinueParse())
fServerConnection.NormalMessageEndDownload();
else
fServerConnection.AbortMessageDownLoad();
}
}
/*
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?
XP_Bool TImapServerState::msg_fetch_quoted(XP_Bool chunk, int32 origin)
{
uint32 numberOfCharsInThisChunk = 0;
XP_Bool closeQuoteFound = FALSE;
XP_Bool lastChunk = TRUE; // whether or not this is the last chunk of this message
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.HandleMessageDownLoadLine(fCurrentLine, FALSE);
numberOfCharsInThisChunk += XP_STRLEN(fCurrentLine);
AdvanceToNextLine();
scanLine = fCurrentLine;
}
lastChunk = !chunk || ((origin + numberOfCharsInThisChunk) >= (unsigned int) fTotalDownloadSize);
return lastChunk;
}
/* 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()
{
fCapabilityFlag = fCapabilityFlag | kCapabilityDefined;
do {
fNextToken = GetNextToken();
// for now we only care about AUTH=LOGIN
if (fNextToken) {
if(! XP_STRCASECMP(fNextToken, "AUTH=LOGIN"))
fCapabilityFlag |= kHasAuthLoginCapability;
else if (! XP_STRCASECMP(fNextToken, "X-NETSCAPE"))
fCapabilityFlag |= kHasXNetscapeCapability;
else if (! XP_STRCASECMP(fNextToken, "XSENDER"))
fCapabilityFlag |= kHasXSenderCapability;
else if (! XP_STRCASECMP(fNextToken, "IMAP4"))
fCapabilityFlag |= kIMAP4Capability;
else if (! XP_STRCASECMP(fNextToken, "IMAP4rev1"))
fCapabilityFlag |= kIMAP4rev1Capability;
else if (! XP_STRNCASECMP(fNextToken, "IMAP4", 5))
fCapabilityFlag |= kIMAP4other;
else if (! XP_STRCASECMP(fNextToken, "X-NO-ATOMIC-RENAME"))
fCapabilityFlag |= kNoHierarchyRename;
else if (! XP_STRCASECMP(fNextToken, "X-NON-HIERARCHICAL-RENAME"))
fCapabilityFlag |= kNoHierarchyRename;
else if (! XP_STRCASECMP(fNextToken, "NAMESPACE"))
fCapabilityFlag |= kNamespaceCapability;
else if (! XP_STRCASECMP(fNextToken, "MAILBOXDATA"))
fCapabilityFlag |= kMailboxDataCapability;
else if (! XP_STRCASECMP(fNextToken, "ACL"))
fCapabilityFlag |= kACLCapability;
else if (! XP_STRCASECMP(fNextToken, "XSERVERINFO"))
fCapabilityFlag |= kXServerInfoCapability;
}
} while (fNextToken &&
!at_end_of_line() &&
ContinueParse());
TIMAPHostInfo::SetCapabilityForHost(fServerConnection.GetHostName(), fCapabilityFlag);
TNavigatorImapConnection *navCon = fServerConnection.GetNavigatorConnection();
XP_ASSERT(navCon); // we should always have this
if (navCon)
navCon->CommitCapabilityForHostEvent();
skip_to_CRLF();
}
void TImapServerState::xmailboxinfo_data()
{
fNextToken = GetNextToken();
if (!fNextToken)
return;
char *mailboxName = CreateAstring(); // XP_STRDUP(fNextToken);
if (mailboxName)
{
do
{
fNextToken = GetNextToken();
if (fNextToken)
{
if (!XP_STRCMP("MANAGEURL", fNextToken))
{
fNextToken = GetNextToken();
fFolderAdminUrl = CreateAstring();
}
else if (!XP_STRCMP("POSTURL", fNextToken))
{
fNextToken = GetNextToken();
// ignore this for now...
}
}
} while (fNextToken && !at_end_of_line() && ContinueParse());
}
}
void TImapServerState::xserverinfo_data()
{
do
{
fNextToken = GetNextToken();
if (!fNextToken)
break;
if (!XP_STRCMP("MANAGEACCOUNTURL", fNextToken))
{
fNextToken = GetNextToken();
fMailAccountUrl = CreateAstring();
}
else if (!XP_STRCMP("MANAGELISTSURL", fNextToken))
{
fNextToken = GetNextToken();
fManageListsUrl = CreateAstring();
}
else if (!XP_STRCMP("MANAGEFILTERSURL", fNextToken))
{
fNextToken = GetNextToken();
fManageFiltersUrl = CreateAstring();
}
} while (fNextToken && !at_end_of_line() && ContinueParse());
}
void TImapServerState::namespace_data()
{
// flush the old one and create a new one, initialized with no namespaces
TIMAPHostInfo::ClearServerAdvertisedNamespacesForHost(fServerConnection.GetHostName());
EIMAPNamespaceType namespaceType = kPersonalNamespace;
while ((namespaceType != kUnknownNamespace) && ContinueParse())
{
fNextToken = GetNextToken();
while (at_end_of_line() && ContinueParse())
fNextToken = GetNextToken();
if (!XP_STRCASECMP(fNextToken,"NIL"))
{
// No namespace for this type.
// Don't add anything to the Namespace object.
}
else if (fNextToken[0] == '(')
{
// There may be multiple namespaces of the same type.
// Go through each of them and add them to our Namespace object.
fNextToken++;
while (fNextToken[0] == '(' && ContinueParse())
{
// we have another namespace for this namespace type
fNextToken++;
if (fNextToken[0] != '"')
{
SetSyntaxError(TRUE);
}
else
{
char *namespacePrefix = CreateQuoted(FALSE);
fNextToken = GetNextToken();
char *quotedDelimiter = fNextToken;
char namespaceDelimiter = '\0';
if (quotedDelimiter[0] == '"')
{
quotedDelimiter++;
namespaceDelimiter = quotedDelimiter[0];
}
else if (!XP_STRNCASECMP(quotedDelimiter, "NIL", 3))
{
// NIL hierarchy delimiter. Leave namespace delimiter NULL.
}
else
{
// not quoted or NIL.
SetSyntaxError(TRUE);
}
if (ContinueParse())
{
TIMAPNamespace *newNamespace = new TIMAPNamespace(namespaceType, namespacePrefix, namespaceDelimiter, FALSE);
if (newNamespace)
TIMAPHostInfo::AddNewNamespaceForHost(fServerConnection.GetHostName(), newNamespace);
skip_to_close_paren(); // Ignore any extension data
XP_Bool endOfThisNamespaceType = (fNextToken[0] == ')');
if (!endOfThisNamespaceType && fNextToken[0] != '(') // no space between namespaces of the same type
{
SetSyntaxError(TRUE);
}
}
}
}
}
else
{
SetSyntaxError(TRUE);
}
switch (namespaceType)
{
case kPersonalNamespace:
namespaceType = kOtherUsersNamespace;
break;
case kOtherUsersNamespace:
namespaceType = kPublicNamespace;
break;
default:
namespaceType = kUnknownNamespace;
break;
}
}
if (ContinueParse())
{
TNavigatorImapConnection *navCon = fServerConnection.GetNavigatorConnection();
XP_ASSERT(navCon); // we should always have this
if (navCon)
navCon->CommitNamespacesForHostEvent();
}
skip_to_CRLF();
}
void TImapServerState::myrights_data()
{
fNextToken = GetNextToken();
if (ContinueParse() && !at_end_of_line())
{
char *mailboxName = CreateAstring(); // XP_STRDUP(fNextToken);
if (mailboxName)
{
fNextToken = GetNextToken();
if (ContinueParse())
{
char *myrights = CreateAstring(); // XP_STRDUP(fNextToken);
if (myrights)
{
TNavigatorImapConnection *navCon = fServerConnection.GetNavigatorConnection();
XP_ASSERT(navCon); // we should always have this
if (navCon)
navCon->AddFolderRightsForUser(mailboxName, NULL /* means "me" */, myrights);
XP_FREE(myrights);
}
else
{
HandleMemoryFailure();
}
if (ContinueParse())
fNextToken = GetNextToken();
}
XP_FREE(mailboxName);
}
else
{
HandleMemoryFailure();
}
}
else
{
SetSyntaxError(TRUE);
}
}
void TImapServerState::acl_data()
{
fNextToken = GetNextToken();
if (ContinueParse() && !at_end_of_line())
{
char *mailboxName = CreateAstring(); // XP_STRDUP(fNextToken);
if (mailboxName && ContinueParse())
{
fNextToken = GetNextToken();
while (ContinueParse() && !at_end_of_line())
{
char *userName = CreateAstring(); // XP_STRDUP(fNextToken);
if (userName && ContinueParse())
{
fNextToken = GetNextToken();
if (ContinueParse())
{
char *rights = CreateAstring(); // XP_STRDUP(fNextToken);
if (rights)
{
TNavigatorImapConnection *navCon = fServerConnection.GetNavigatorConnection();
XP_ASSERT(navCon); // we should always have this
if (navCon)
navCon->AddFolderRightsForUser(mailboxName, userName, rights);
XP_FREE(rights);
}
else
HandleMemoryFailure();
if (ContinueParse())
fNextToken = GetNextToken();
}
XP_FREE(userName);
}
else
HandleMemoryFailure();
}
}
else
HandleMemoryFailure();
}
}
void TImapServerState::mime_data()
{
if (XP_STRSTR(fNextToken, "MIME"))
mime_header_data();
else
mime_part_data();
}
// mime_header_data should not be streamed out; rather, it should be
// buffered in the TIMAPBodyShell.
// This is because we are still in the process of generating enough
// information from the server (such as the MIME header's size) so that
// we can construct the final output stream.
void TImapServerState::mime_header_data()
{
char *partNumber = XP_STRDUP(fNextToken);
if (partNumber)
{
char *start = partNumber+5, *end = partNumber+5; // 5 == XP_STRLEN("BODY[")
while (ContinueParse() && end && *end != 'M' && *end != 'm')
{
end++;
}
if (end && (*end == 'M' || *end == 'm'))
{
*(end-1) = 0;
fNextToken = GetNextToken();
char *mimeHeaderData = CreateAstring(); // is it really this simple?
fNextToken = GetNextToken();
if (m_shell)
{
m_shell->AdoptMimeHeader(start, mimeHeaderData);
}
}
else
{
SetSyntaxError(TRUE);
}
XP_FREE(partNumber); // partNumber is not adopted by the body shell.
}
else
{
HandleMemoryFailure();
}
}
// Actual mime parts are filled in on demand (either from shell generation
// or from explicit user download), so we need to stream these out.
void TImapServerState::mime_part_data()
{
char *checkOriginToken = XP_STRDUP(fNextToken);
if (checkOriginToken)
{
uint32 origin = 0;
XP_Bool originFound = FALSE;
char *whereStart = XP_STRCHR(checkOriginToken, '<');
if (whereStart)
{
char *whereEnd = XP_STRCHR(whereStart, '>');
if (whereEnd)
{
*whereEnd = 0;
whereStart++;
origin = atoint32(whereStart);
originFound = TRUE;
}
}
XP_FREE(checkOriginToken);
fNextToken = GetNextToken();
msg_fetch_content(originFound, origin, MESSAGE_RFC822); // keep content type as message/rfc822, even though the
// MIME part might not be, because then libmime will
// still handle and decode it.
}
else
HandleMemoryFailure();
}
// FETCH BODYSTRUCTURE parser
// After exit, set fNextToken and fCurrentLine to the right things
void TImapServerState::bodystructure_data()
{
fNextToken = GetNextToken();
// separate it out first
if (fNextToken && *fNextToken == '(') // It has to start with an open paren.
{
char *buf = CreateParenGroup();
if (ContinueParse())
{
if (!buf)
HandleMemoryFailure();
else
{
// Looks like we have what might be a valid BODYSTRUCTURE response.
// Try building the shell from it here.
TNavigatorImapConnection *navCon = fServerConnection.GetNavigatorConnection();
XP_ASSERT(navCon);
m_shell = new TIMAPBodyShell(navCon, buf, CurrentResponseUID(), GetSelectedMailboxName());
/*
if (m_shell)
{
if (!m_shell->GetIsValid())
{
SetSyntaxError(TRUE);
}
}
*/
XP_FREE(buf);
}
}
}
else
SetSyntaxError(TRUE);
}
XP_Bool TImapServerState::GetFillingInShell()
{
return (m_shell != NULL);
}
// Tells the server state parser to use a previously cached shell.
void TImapServerState::UseCachedShell(TIMAPBodyShell *cachedShell)
{
// We shouldn't already have another shell we're dealing with.
if (m_shell && cachedShell)
{
// PR_LOG(IMAP, out, ("PARSER: Shell Collision"));
XP_ASSERT(FALSE);
}
m_shell = cachedShell;
}
void TImapServerState::ResetCapabilityFlag()
{
TIMAPHostInfo::SetCapabilityForHost(fServerConnection.GetHostName(), kCapabilityUndefined);
}
/*
literal ::= "{" number "}" CRLF *CHAR8
;; Number represents the number of CHAR8 octets
*/
// returns TRUE if this is the last chunk and we should close the stream
XP_Bool TImapServerState::msg_fetch_literal(XP_Bool chunk, int32 origin)
{
numberOfCharsInThisChunk = atoint32(fNextToken + 1); // might be the whole message
charsReadSoFar = 0;
static XP_Bool lastCRLFwasCRCRLF = FALSE;
XP_Bool lastChunk = !chunk || (origin + numberOfCharsInThisChunk >= fTotalDownloadSize);
TNavigatorImapConnection *navCon = fServerConnection.GetNavigatorConnection();
if (navCon)
{
if (!lastCRLFwasCRCRLF && navCon->GetIOTunnellingEnabled() && (numberOfCharsInThisChunk > navCon->GetTunnellingThreshold()))
{
// One day maybe we'll make this smarter and know how to handle CR/LF boundaries across tunnels.
// For now, we won't, even though it might not be too hard, because it is very rare and will add
// some complexity.
charsReadSoFar = navCon->OpenTunnel(numberOfCharsInThisChunk);
}
}
// If we opened a tunnel, finish everything off here. Otherwise, get everything here.
// (just like before)
while (ContinueParse() && (charsReadSoFar < numberOfCharsInThisChunk))
{
AdvanceToNextLine();
if (ContinueParse())
{
if (lastCRLFwasCRCRLF && (*fCurrentLine == CR))
{
char *usableCurrentLine = 0;
StrAllocCopy(usableCurrentLine, fCurrentLine + 1);
FREEIF(fCurrentLine);
if (usableCurrentLine)
fCurrentLine = usableCurrentLine;
else
fCurrentLine = 0;
}
if (ContinueParse())
{
charsReadSoFar += XP_STRLEN(fCurrentLine);
if (!fDownloadingHeaders && fCurrentCommandIsSingleMessageFetch)
{
fServerConnection.ProgressEventFunction_UsingId(MK_IMAP_DOWNLOADING_MESSAGE);
if (fTotalDownloadSize > 0)
fServerConnection.PercentProgressUpdateEvent(0,(100*(charsReadSoFar + origin))/fTotalDownloadSize);
}
if (charsReadSoFar > numberOfCharsInThisChunk)
{ // this is rare. If this msg ends in the middle of a line then only display the actual message.
char *displayEndOfLine = (fCurrentLine + XP_STRLEN(fCurrentLine) - (charsReadSoFar - numberOfCharsInThisChunk));
char saveit = *displayEndOfLine;
*displayEndOfLine = 0;
fServerConnection.HandleMessageDownLoadLine(fCurrentLine, !lastChunk);
*displayEndOfLine = saveit;
lastCRLFwasCRCRLF = (*(displayEndOfLine - 1) == CR);
}
else
{
lastCRLFwasCRCRLF = (*(fCurrentLine + XP_STRLEN(fCurrentLine) - 1) == CR);
fServerConnection.HandleMessageDownLoadLine(fCurrentLine, !lastChunk && (charsReadSoFar == numberOfCharsInThisChunk));
}
}
}
}
// This would be a good thing to log.
// if (lastCRLFwasCRCRLF)
// PR_LOG(IMAP, out, ("PARSER: CR/LF fell on chunk boundary."));
if (ContinueParse())
{
if (charsReadSoFar > numberOfCharsInThisChunk)
{
// move the lexical analyzer state to the end of this message because this message
// fetch ends in the middle of this line.
//fCurrentTokenPlaceHolder = fLineOfTokens + XP_STRLEN(fCurrentLine) - (charsReadSoFar - numberOfCharsInThisChunk);
AdvanceTokenizerStartingPoint(XP_STRLEN(fCurrentLine) - (charsReadSoFar - numberOfCharsInThisChunk));
fNextToken = GetNextToken();
}
else
{
skip_to_CRLF();
fNextToken = GetNextToken();
}
}
else
{
lastCRLFwasCRCRLF = FALSE;
}
return lastChunk;
}
XP_Bool TImapServerState::CurrentFolderReadOnly()
{
return fCurrentFolderReadOnly;
}
int32 TImapServerState::NumberOfMessages()
{
return fNumberOfExistingMessages;
}
int32 TImapServerState::NumberOfRecentMessages()
{
return fNumberOfRecentMessages;
}
int32 TImapServerState::NumberOfUnseenMessages()
{
return fNumberOfUnseenMessages;
}
int32 TImapServerState::FolderUID()
{
return fFolderUIDValidity;
}
uint32 TImapServerState::CurrentResponseUID()
{
return fCurrentResponseUID;
}
uint32 TImapServerState::HighestRecordedUID()
{
return fHighestRecordedUID;
}
XP_Bool TImapServerState::IsNumericString(const char *string)
{
int i;
for(i = 0; i < (int) strlen(string); i++)
{
if (! isdigit(string[i]))
{
return FALSE;
}
}
return TRUE;
}
struct mailbox_spec *TImapServerState::CreateCurrentMailboxSpec(const char *mailboxName /* = NULL */)
{
mailbox_spec *returnSpec = (mailbox_spec *) XP_CALLOC(1, sizeof(mailbox_spec) );
if (returnSpec)
{
char *convertedMailboxName = NULL;
const char *mailboxNameToConvert = (mailboxName) ? mailboxName : fSelectedMailboxName;
if (mailboxNameToConvert)
{
char *convertedName = fServerConnection.CreateUtf7ConvertedString(mailboxNameToConvert, FALSE);
if (convertedName)
{
convertedMailboxName = fServerConnection.GetNavigatorConnection()->GetCurrentUrl()->AllocateCanonicalPath(convertedName);
XP_FREE(convertedName);
}
}
returnSpec->folderSelected = TRUE;
returnSpec->folder_UIDVALIDITY = fFolderUIDValidity;
returnSpec->number_of_messages = fNumberOfExistingMessages;
returnSpec->number_of_unseen_messages = fNumberOfUnseenMessages;
returnSpec->number_of_recent_messages = fNumberOfRecentMessages;
returnSpec->box_flags = kNoFlags; // stub
returnSpec->onlineVerified = FALSE; // we're fabricating this. The flags aren't verified.
returnSpec->allocatedPathName = convertedMailboxName;
returnSpec->connection = fServerConnection.GetNavigatorConnection();
if (returnSpec->connection)
returnSpec->hostName = returnSpec->connection->GetCurrentUrl()->GetUrlHost();
else
returnSpec->hostName = NULL;
if (fFlagState)
returnSpec->flagState = fFlagState; //copies flag state
else
returnSpec->flagState = NULL;
}
else
HandleMemoryFailure();
return returnSpec;
}
// zero stops a list recording of flags and causes the flags for
// each individual message to be sent back to libmsg
void TImapServerState::ResetFlagInfo(int numberOfInterestingMessages)
{
if (fFlagState)
fFlagState->Reset(numberOfInterestingMessages);
}
XP_Bool TImapServerState::GetLastFetchChunkReceived()
{
return fLastChunk;
}
void TImapServerState::ClearLastFetchChunkReceived()
{
fLastChunk = FALSE;
}
//////////////////// TIMAPNamespace /////////////////////////////////////////////////////////////
TIMAPNamespace::TIMAPNamespace(EIMAPNamespaceType type, const char *prefix, char delimiter, XP_Bool from_prefs)
{
m_namespaceType = type;
m_prefix = XP_STRDUP(prefix);
m_fromPrefs = from_prefs;
m_delimiter = delimiter;
m_delimiterFilledIn = !m_fromPrefs; // if it's from the prefs, we can't be sure about the delimiter until we list it.
}
TIMAPNamespace::~TIMAPNamespace()
{
FREEIF(m_prefix);
}
void TIMAPNamespace::SetDelimiter(char delimiter)
{
m_delimiter = delimiter;
m_delimiterFilledIn = TRUE;
}
// returns -1 if this box is not part of this namespace,
// or the length of the prefix if it is part of this namespace
int TIMAPNamespace::MailboxMatchesNamespace(const char *boxname)
{
if (!boxname) return -1;
// If the namespace is part of the boxname
if (XP_STRSTR(boxname, m_prefix) == boxname)
return XP_STRLEN(m_prefix);
// If the boxname is part of the prefix
// (Used for matching Personal mailbox with Personal/ namespace, etc.)
if (XP_STRSTR(m_prefix, boxname) == m_prefix)
return XP_STRLEN(boxname);
return -1;
}
TIMAPNamespaceList *TIMAPNamespaceList::CreateTIMAPNamespaceList()
{
TIMAPNamespaceList *rv = new TIMAPNamespaceList();
if (rv)
{
if (!rv->m_NamespaceList)
{
delete rv;
rv = 0;
}
}
return rv;
}
TIMAPNamespaceList::TIMAPNamespaceList()
{
// Create a new list
m_NamespaceList = XP_ListNew();
}
int TIMAPNamespaceList::GetNumberOfNamespaces()
{
return m_NamespaceList ? XP_ListCount(m_NamespaceList) : 0;
}
int TIMAPNamespaceList::GetNumberOfNamespaces(EIMAPNamespaceType type)
{
int nodeIndex = 0, count = 0;
for (nodeIndex=XP_ListCount(m_NamespaceList); nodeIndex > 0; nodeIndex--)
{
TIMAPNamespace *nspace = (TIMAPNamespace *)XP_ListGetObjectNum(m_NamespaceList, nodeIndex);
if (nspace->GetType() == type)
{
count++;
}
}
return count;
}
int TIMAPNamespaceList::AddNewNamespace(TIMAPNamespace *ns)
{
if (!m_NamespaceList)
return -1;
// If the namespace is from the NAMESPACE response, then we should see if there
// are any namespaces previously set by the preferences, or the default namespace. If so, remove these.
if (!ns->GetIsNamespaceFromPrefs())
{
int nodeIndex = 0;
for (nodeIndex=XP_ListCount(m_NamespaceList); nodeIndex > 0; nodeIndex--)
{
TIMAPNamespace *nspace = (TIMAPNamespace *)XP_ListGetObjectNum(m_NamespaceList, nodeIndex);
if (nspace->GetIsNamespaceFromPrefs())
{
XP_ListRemoveObject(m_NamespaceList, nspace);
delete nspace;
}
}
}
// Add the new namespace to the list. This must come after the removing code,
// or else we could never add the initial kDefaultNamespace type to the list.
XP_ListAddObjectToEnd(m_NamespaceList, ns);
return 0;
}
// chrisf - later, fix this to know the real concept of "default" namespace of a given type
TIMAPNamespace *TIMAPNamespaceList::GetDefaultNamespaceOfType(EIMAPNamespaceType type)
{
TIMAPNamespace *rv = 0, *firstOfType = 0;
if (!m_NamespaceList)
return 0;
int nodeIndex = 1, count = XP_ListCount(m_NamespaceList);
for (nodeIndex= 1; nodeIndex <= count && !rv; nodeIndex++)
{
TIMAPNamespace *ns = (TIMAPNamespace *)XP_ListGetObjectNum(m_NamespaceList, nodeIndex);
if (ns->GetType() == type)
{
if (!firstOfType)
firstOfType = ns;
if (!(*(ns->GetPrefix())))
{
// This namespace's prefix is ""
// Therefore it is the default
rv = ns;
}
}
}
if (!rv)
rv = firstOfType;
return rv;
}
TIMAPNamespaceList::~TIMAPNamespaceList()
{
ClearNamespaces(TRUE, TRUE);
if (m_NamespaceList)
XP_ListDestroy (m_NamespaceList);
}
// ClearNamespaces removes and deletes the namespaces specified, and if there are no namespaces left,
void TIMAPNamespaceList::ClearNamespaces(XP_Bool deleteFromPrefsNamespaces, XP_Bool deleteServerAdvertisedNamespaces)
{
int nodeIndex = 0;
if (m_NamespaceList)
{
for (nodeIndex=XP_ListCount(m_NamespaceList); nodeIndex > 0; nodeIndex--)
{
TIMAPNamespace *ns = (TIMAPNamespace *) XP_ListGetObjectNum(m_NamespaceList, nodeIndex);
if (ns->GetIsNamespaceFromPrefs())
{
if (deleteFromPrefsNamespaces)
{
XP_ListRemoveObject(m_NamespaceList, ns);
delete ns;
}
}
else if (deleteServerAdvertisedNamespaces)
{
XP_ListRemoveObject(m_NamespaceList, ns);
delete ns;
}
}
}
}
TIMAPNamespace *TIMAPNamespaceList::GetNamespaceNumber(int nodeIndex)
{
int total = GetNumberOfNamespaces();
XP_ASSERT(nodeIndex >= 1 && nodeIndex <= total);
if (nodeIndex < 1) nodeIndex = 1;
if (nodeIndex > total) nodeIndex = total;
XP_ASSERT(m_NamespaceList);
if (m_NamespaceList)
{
return (TIMAPNamespace *)XP_ListGetObjectNum(m_NamespaceList, nodeIndex);
}
return NULL;
}
TIMAPNamespace *TIMAPNamespaceList::GetNamespaceNumber(int nodeIndex, EIMAPNamespaceType type)
{
int nodeCount = 0, count = 0;
for (nodeCount=XP_ListCount(m_NamespaceList); nodeCount > 0; nodeCount--)
{
TIMAPNamespace *nspace = (TIMAPNamespace *)XP_ListGetObjectNum(m_NamespaceList, nodeCount);
if (nspace->GetType() == type)
{
count++;
if (count == nodeIndex)
return nspace;
}
}
return NULL;
}
TIMAPNamespace *TIMAPNamespaceList::GetNamespaceForMailbox(const char *boxname)
{
// We want to find the LONGEST substring that matches the beginning of this mailbox's path.
// This accounts for nested namespaces (i.e. "Public/" and "Public/Users/")
// Also, we want to match the namespace's mailbox to that namespace also:
// The Personal box will match the Personal/ namespace, etc.
// these lists shouldn't be too long (99% chance there won't be more than 3 or 4)
// so just do a linear search
int lengthMatched = -1;
int currentMatchedLength = -1;
TIMAPNamespace *rv = NULL;
int nodeIndex = 0;
if (!XP_STRCASECMP(boxname, "INBOX"))
return GetDefaultNamespaceOfType(kPersonalNamespace);
if (m_NamespaceList)
{
for (nodeIndex=XP_ListCount(m_NamespaceList); nodeIndex > 0; nodeIndex--)
{
TIMAPNamespace *nspace = (TIMAPNamespace *)XP_ListGetObjectNum(m_NamespaceList, nodeIndex);
currentMatchedLength = nspace->MailboxMatchesNamespace(boxname);
if (currentMatchedLength > lengthMatched)
{
rv = nspace;
lengthMatched = currentMatchedLength;
}
}
}
return rv;
}