/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "msgCore.h"  // for pre-compiled headers
#include "nsMimeTypes.h"
#include "nsImapCore.h"
#include "nsImapProtocol.h"
#include "nsImapServerResponseParser.h"
#include "nsIMAPBodyShell.h"
#include "nsImapFlagAndUidState.h"
#include "nsIMAPNamespace.h"
#include "nsImapStringBundle.h"
#include "nsImapUtils.h"
#include "nsCRT.h"
#include "nsMsgUtils.h"
#include "mozilla/Logging.h"

////////////////// nsImapServerResponseParser /////////////////////////

extern PRLogModuleInfo* IMAP;

nsImapServerResponseParser::nsImapServerResponseParser(nsImapProtocol &imapProtocolConnection)
                            : nsIMAPGenericParser(),
    fReportingErrors(true),
    fCurrentFolderReadOnly(false),
    fCurrentLineContainedFlagInfo(false),
    fServerIsNetscape3xServer(false),
    fNumberOfUnseenMessages(0),
    fNumberOfExistingMessages(0),
    fNumberOfRecentMessages(0),
    fSizeOfMostRecentMessage(0),
    fTotalDownloadSize(0),
    fCurrentCommandTag(nullptr),
    fSelectedMailboxName(nullptr),
    fIMAPstate(kNonAuthenticated),
    fLastChunk(false),
    fNextChunkStartsWithNewline(false),
    fServerConnection(imapProtocolConnection),
    fHostSessionList(nullptr)
{
  fSearchResults = nsImapSearchResultSequence::CreateSearchResultSequence();
  fFolderAdminUrl = nullptr;
  fNetscapeServerVersionString = nullptr;
  fXSenderInfo = nullptr;
  fSupportsUserDefinedFlags = 0;
  fSettablePermanentFlags = 0;
  fCapabilityFlag = kCapabilityUndefined;
  fLastAlert = nullptr;
  fDownloadingHeaders = false;
  fGotPermanentFlags = false;
  fFolderUIDValidity = 0;
  fHighestModSeq = 0;
  fAuthChallenge = nullptr;
  fStatusUnseenMessages = 0;
  fStatusRecentMessages = 0;
  fStatusNextUID = nsMsgKey_None;
  fStatusExistingMessages = 0;
  fReceivedHeaderOrSizeForUID = nsMsgKey_None;
  fCondStoreEnabled = false;
}

nsImapServerResponseParser::~nsImapServerResponseParser()
{
  PR_Free( fCurrentCommandTag );
  delete fSearchResults;
  PR_Free( fFolderAdminUrl );
  PR_Free( fNetscapeServerVersionString );
  PR_Free( fXSenderInfo );
  PR_Free( fLastAlert );
  PR_Free( fSelectedMailboxName );
  PR_Free(fAuthChallenge);

  NS_IF_RELEASE (fHostSessionList);
}

bool nsImapServerResponseParser::LastCommandSuccessful()
{
  return (!CommandFailed() &&
    !fServerConnection.DeathSignalReceived() &&
    nsIMAPGenericParser::LastCommandSuccessful());
}

// returns true if things look ok to continue
bool nsImapServerResponseParser::GetNextLineForParser(char **nextLine)
{
  bool rv = true;
  *nextLine = fServerConnection.CreateNewLineFromSocket();
  if (fServerConnection.DeathSignalReceived() ||
      NS_FAILED(fServerConnection.GetConnectionStatus()))
    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 (NS_FAILED(fServerConnection.GetConnectionStatus()) &&
      !fServerConnection.DeathSignalReceived())
    fServerConnection.AlertUserEventUsingName("imapServerDisconnected");
  return rv;
}

bool nsImapServerResponseParser::CommandFailed()
{
  return fCurrentCommandFailed;
}

void nsImapServerResponseParser::SetCommandFailed(bool failed)
{
  fCurrentCommandFailed = failed;
}

void nsImapServerResponseParser::SetFlagState(nsIImapFlagAndUidState *state)
{
  fFlagState = state;
}

uint32_t nsImapServerResponseParser::SizeOfMostRecentMessage()
{
  return fSizeOfMostRecentMessage;
}

// Call this when adding a pipelined command to the session
void nsImapServerResponseParser::IncrementNumberOfTaggedResponsesExpected(const char *newExpectedTag)
{
  fNumberOfTaggedResponsesExpected++;
  PR_Free( fCurrentCommandTag );
  fCurrentCommandTag = PL_strdup(newExpectedTag);
  if (!fCurrentCommandTag)
    HandleMemoryFailure();
}

void nsImapServerResponseParser::InitializeState()
{
  fCurrentCommandFailed = false;
  fNumberOfRecentMessages = 0;
  fReceivedHeaderOrSizeForUID = nsMsgKey_None;
}

// RFC3501:  response = *(continue-req / response-data) response-done
//           response-data = "*" SP (resp-cond-state / resp-cond-bye /
//                           mailbox-data / message-data / capability-data) CRLF
//           continue-req    = "+" SP (resp-text / base64) CRLF
void nsImapServerResponseParser::ParseIMAPServerResponse(const char *aCurrentCommand,
                                                         bool aIgnoreBadAndNOResponses,
                                                         char *aGreetingWithCapability)
{

  NS_ASSERTION(aCurrentCommand && *aCurrentCommand != '\r' &&
    *aCurrentCommand != '\n' && *aCurrentCommand != ' ', "Invailid command string");
  bool sendingIdleDone = !strcmp(aCurrentCommand, "DONE" CRLF);
  if (sendingIdleDone)
    fWaitingForMoreClientInput = false;

  // Reinitialize the parser
  SetConnected(true);
  SetSyntaxError(false);

  // Reinitialize our state
  InitializeState();

  // the default is to not pipeline
  fNumberOfTaggedResponsesExpected = 1;
  int numberOfTaggedResponsesReceived = 0;

  nsCString copyCurrentCommand(aCurrentCommand);
  if (!fServerConnection.DeathSignalReceived())
  {
    char *placeInTokenString = nullptr;
    char *tagToken = nullptr;
    const char *commandToken = nullptr;
    bool inIdle = false;
    if (!sendingIdleDone)
    {
      placeInTokenString = copyCurrentCommand.BeginWriting();
      tagToken = NS_strtok(WHITESPACE, &placeInTokenString);
      commandToken = NS_strtok(WHITESPACE, &placeInTokenString);
    }
    else
      commandToken = "DONE";
    if (tagToken)
    {
      PR_Free( fCurrentCommandTag );
      fCurrentCommandTag = PL_strdup(tagToken);
      if (!fCurrentCommandTag)
        HandleMemoryFailure();
      inIdle = commandToken && !strcmp(commandToken, "IDLE");
    }

    if (commandToken && ContinueParse())
      PreProcessCommandToken(commandToken, aCurrentCommand);

    // For checking expected response to IDLE command below.
    bool untagged = false;

    if (ContinueParse())
    {
      ResetLexAnalyzer();

      if (aGreetingWithCapability)
      {
        PR_FREEIF(fCurrentLine);
        fCurrentLine = aGreetingWithCapability;
      }

      do {
        AdvanceToNextToken();

        // untagged responses [RFC3501, Sec. 2.2.2]
        while (ContinueParse() && fNextToken && *fNextToken == '*')
        {
          response_data();
          if (ContinueParse())
          {
            if (!fAtEndOfLine)
              SetSyntaxError(true);
            else if (!inIdle && !fCurrentCommandFailed && !aGreetingWithCapability)
              AdvanceToNextToken();
          }
          untagged = true;
        }

        // command continuation request [RFC3501, Sec. 7.5]
        if (ContinueParse() && fNextToken && *fNextToken == '+')	// never pipeline APPEND or AUTHENTICATE
        {
          NS_ASSERTION((fNumberOfTaggedResponsesExpected - numberOfTaggedResponsesReceived) == 1,
            " didn't get the number of tagged responses we expected");
          numberOfTaggedResponsesReceived = fNumberOfTaggedResponsesExpected;
          if (commandToken && !PL_strcasecmp(commandToken, "authenticate") && placeInTokenString &&
            (!PL_strncasecmp(placeInTokenString, "CRAM-MD5", strlen("CRAM-MD5"))
             || !PL_strncasecmp(placeInTokenString, "NTLM", strlen("NTLM"))
             || !PL_strncasecmp(placeInTokenString, "GSSAPI", strlen("GSSAPI"))
             || !PL_strncasecmp(placeInTokenString, "MSN", strlen("MSN"))))
          {
            // we need to store the challenge from the server if we are using CRAM-MD5 or NTLM.
            authChallengeResponse_data();
          }
        }
        else
          numberOfTaggedResponsesReceived++;

        if (numberOfTaggedResponsesReceived < fNumberOfTaggedResponsesExpected)
          response_tagged();

      } while (ContinueParse() && !inIdle && (numberOfTaggedResponsesReceived < fNumberOfTaggedResponsesExpected));

      // check and see if the server is waiting for more input
      // it's possible that we ate this + while parsing certain responses (like cram data),
      // in these cases, the parsing routine for that specific command will manually set
      // fWaitingForMoreClientInput so we don't lose that information....
      if ((fNextToken && *fNextToken == '+') || inIdle)
      {
        if (inIdle && !((fNextToken && *fNextToken == '+') || untagged))
        {
          // IDLE "response" + will not be "eaten" as described above since it
          // is not an authentication response. So if IDLE response does not
          // begin with '+' (continuation) or '*' (untagged and probably useful
          // response) then something is wrong and it is probably a tagged
          // NO or BAD due to transient error or bad configuration of the server.
          if (!PL_strcmp(fCurrentCommandTag, fNextToken))
          {
            response_tagged();
          }
          else
          {
            // Expected tag doesn't match the received tag. Not good, start over.
            response_fatal();
          }
          // Show an alert notication containing the server response to bad IDLE.
          fServerConnection.AlertUserEventFromServer(fCurrentLine, true);
        }
        else
        {
          fWaitingForMoreClientInput = true;
        }
      }
      // if we aren't still waiting for more input....
      else if (!fWaitingForMoreClientInput && !aGreetingWithCapability)
      {
        if (ContinueParse())
          response_done();

        if (ContinueParse() && !CommandFailed())
        {
          // a successful command may change the eIMAPstate
          ProcessOkCommand(commandToken);
        }
        else if (CommandFailed())
        {
          // a failed command may change the eIMAPstate
          ProcessBadCommand(commandToken);
          if (fReportingErrors && !aIgnoreBadAndNOResponses)
            fServerConnection.AlertUserEventFromServer(fCurrentLine, false);
        }
      }
    }
  }
  else
    SetConnected(false);
}

void nsImapServerResponseParser::HandleMemoryFailure()
{
  fServerConnection.AlertUserEventUsingName("imapOutOfMemory");
  nsIMAPGenericParser::HandleMemoryFailure();
}


// SEARCH is the only command that requires pre-processing for now.
// others will be added here.
void nsImapServerResponseParser::PreProcessCommandToken(const char *commandToken,
                                                        const char *currentCommand)
{
  fCurrentCommandIsSingleMessageFetch = false;
  fWaitingForMoreClientInput = false;

  if (!PL_strcasecmp(commandToken, "SEARCH"))
    fSearchResults->ResetSequence();
  else if (!PL_strcasecmp(commandToken, "SELECT") && currentCommand)
  {
    // the mailbox name must be quoted, so strip the quotes
    const char *openQuote = PL_strchr(currentCommand, '"');
    NS_ASSERTION(openQuote, "expected open quote in imap server response");
    if (!openQuote)
    { // ill formed select command
      openQuote = PL_strchr(currentCommand, ' ');
    }
    PR_Free( fSelectedMailboxName);
    fSelectedMailboxName = PL_strdup(openQuote + 1);
    if (fSelectedMailboxName)
    {
      // strip the escape chars and the ending quote
      char *currentChar = fSelectedMailboxName;
      while (*currentChar)
      {
        if (*currentChar == '\\')
        {
          PL_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 = nullptr;
  }
  else if (!PL_strcasecmp(commandToken, "CLOSE"))
  {
    return;	// just for debugging
    // we don't want bogus info outside the selected state
    //delete fFlagState;	// not our object
    //fFlagState = nullptr;
  }
  else if (!PL_strcasecmp(commandToken, "UID"))
  {
    nsCString copyCurrentCommand(currentCommand);
    if (!fServerConnection.DeathSignalReceived())
    {
      char *placeInTokenString = copyCurrentCommand.BeginWriting();
      (void) NS_strtok(WHITESPACE, &placeInTokenString); // skip tag token
      (void) NS_strtok(WHITESPACE, &placeInTokenString); // skip uid token
      char *fetchToken = NS_strtok(WHITESPACE, &placeInTokenString);
      if (!PL_strcasecmp(fetchToken, "FETCH") )
      {
        char *uidStringToken = NS_strtok(WHITESPACE, &placeInTokenString);
        // , and : are uid delimiters
        if (!PL_strchr(uidStringToken, ',') && !PL_strchr(uidStringToken, ':'))
          fCurrentCommandIsSingleMessageFetch = true;
      }
    }
  }
}

const char *nsImapServerResponseParser::GetSelectedMailboxName()
{
  return fSelectedMailboxName;
}

nsImapSearchResultIterator *nsImapServerResponseParser::CreateSearchResultIterator()
{
  return new nsImapSearchResultIterator(*fSearchResults);
}

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

void nsImapServerResponseParser::PreauthSetAuthenticatedState()
{
  fIMAPstate = kAuthenticated;
}

void nsImapServerResponseParser::ProcessOkCommand(const char *commandToken)
{
  if (!PL_strcasecmp(commandToken, "LOGIN") ||
    !PL_strcasecmp(commandToken, "AUTHENTICATE"))
    fIMAPstate = kAuthenticated;
  else if (!PL_strcasecmp(commandToken, "LOGOUT"))
    fIMAPstate = kNonAuthenticated;
  else if (!PL_strcasecmp(commandToken, "SELECT") ||
    !PL_strcasecmp(commandToken, "EXAMINE"))
    fIMAPstate = kFolderSelected;
  else if (!PL_strcasecmp(commandToken, "CLOSE"))
  {
    fIMAPstate = kAuthenticated;
    // we no longer have a selected mailbox.
    PR_FREEIF( fSelectedMailboxName );
  }
  else if ((!PL_strcasecmp(commandToken, "LIST")) ||
          (!PL_strcasecmp(commandToken, "LSUB")) ||
          (!PL_strcasecmp(commandToken, "XLIST")))
  {
    //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 (!PL_strcasecmp(commandToken, "FETCH"))
  {
    if (!fZeroLengthMessageUidString.IsEmpty())
    {
      // "Deleting zero length message");
      fServerConnection.Store(fZeroLengthMessageUidString, "+Flags (\\Deleted)", true);
      if (LastCommandSuccessful())
        fServerConnection.Expunge();

      fZeroLengthMessageUidString.Truncate();
    }
  }
  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())
    {
      nsImapProtocol *navCon = &fServerConnection;

      char *imapPart = nullptr;

      fServerConnection.GetCurrentUrl()->GetImapPartToFetch(&imapPart);
      m_shell->Generate(imapPart);
      PR_Free(imapPart);

      if ((navCon && navCon->GetPseudoInterrupted())
        || fServerConnection.DeathSignalReceived())
      {
        // we were pseudointerrupted or interrupted
        // if it's not in the cache, then we were (pseudo)interrupted while generating
        // for the first time. Release it.
        if (!m_shell->IsShellCached())
          m_shell = nullptr;
        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() && fHostSessionList)	// cache is responsible for destroying it
        {
          MOZ_LOG(IMAP, mozilla::LogLevel::Info,
            ("BODYSHELL:  Adding shell to cache."));
          const char *serverKey = fServerConnection.GetImapServerKey();
          fHostSessionList->AddShellToCacheForHost(
            serverKey, m_shell);
        }
      }
      m_shell = nullptr;
    }
  }
}

void nsImapServerResponseParser::ProcessBadCommand(const char *commandToken)
{
  if (!PL_strcasecmp(commandToken, "LOGIN") ||
    !PL_strcasecmp(commandToken, "AUTHENTICATE"))
    fIMAPstate = kNonAuthenticated;
  else if (!PL_strcasecmp(commandToken, "LOGOUT"))
    fIMAPstate = kNonAuthenticated; // ??
  else if (!PL_strcasecmp(commandToken, "SELECT") ||
    !PL_strcasecmp(commandToken, "EXAMINE"))
    fIMAPstate = kAuthenticated; // nothing selected
  else if (!PL_strcasecmp(commandToken, "CLOSE"))
    fIMAPstate = kAuthenticated; // nothing selected
  if (GetFillingInShell() && !m_shell->IsBeingGenerated())
    m_shell = nullptr;
}



// RFC3501:  response-data = "*" SP (resp-cond-state / resp-cond-bye /
//                           mailbox-data / message-data / capability-data) CRLF
// These are ``untagged'' responses [RFC3501, Sec. 2.2.2]
/*
 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.

 It is assumed that the initial "*" is already consumed before calling this
 method. The production implemented here is
         response_data   ::= (resp_cond_state / resp_cond_bye /
                              mailbox_data / numeric_mailbox_data /
                              capability_data)
                              CRLF
*/
void nsImapServerResponseParser::response_data()
{
  AdvanceToNextToken();

  if (ContinueParse())
  {
    // Instead of comparing lots of strings and make function calls, try to
    // pre-flight the possibilities based on the first letter of the token.
    switch (NS_ToUpper(fNextToken[0]))
    {
    case 'O':   // OK
      if (NS_ToUpper(fNextToken[1]) == 'K')
        resp_cond_state(false);
      else SetSyntaxError(true);
      break;
    case 'N':   // NO
      if (NS_ToUpper(fNextToken[1]) == 'O')
        resp_cond_state(false);
      else if (!PL_strcasecmp(fNextToken, "NAMESPACE"))
        namespace_data();
      else SetSyntaxError(true);
      break;
    case 'B':   // BAD
      if (!PL_strcasecmp(fNextToken, "BAD"))
        resp_cond_state(false);
      else if (!PL_strcasecmp(fNextToken, "BYE"))
        resp_cond_bye();
      else SetSyntaxError(true);
      break;
    case 'F':
      if (!PL_strcasecmp(fNextToken, "FLAGS"))
        mailbox_data();
      else SetSyntaxError(true);
      break;
    case 'P':
      if (PL_strcasecmp(fNextToken, "PERMANENTFLAGS"))
        mailbox_data();
      else SetSyntaxError(true);
      break;
    case 'L':
      if (!PL_strcasecmp(fNextToken, "LIST") || !PL_strcasecmp(fNextToken, "LSUB"))
        mailbox_data();
      else if (!PL_strcasecmp(fNextToken, "LANGUAGE"))
        language_data();
      else
        SetSyntaxError(true);
      break;
    case 'M':
      if (!PL_strcasecmp(fNextToken, "MAILBOX"))
        mailbox_data();
      else if (!PL_strcasecmp(fNextToken, "MYRIGHTS"))
        myrights_data(false);
      else SetSyntaxError(true);
      break;
    case 'S':
      if (!PL_strcasecmp(fNextToken, "SEARCH"))
        mailbox_data();
      else if (!PL_strcasecmp(fNextToken, "STATUS"))
      {
        AdvanceToNextToken();
        if (fNextToken)
        {
          char *mailboxName = CreateAstring();
          PL_strfree( mailboxName);
        }
        while (ContinueParse() && !fAtEndOfLine)
        {
          AdvanceToNextToken();
          if (!fNextToken)
            break;

          if (*fNextToken == '(') fNextToken++;
          if (!PL_strcasecmp(fNextToken, "UIDNEXT"))
          {
            AdvanceToNextToken();
            if (fNextToken)
            {
              fStatusNextUID = strtoul(fNextToken, nullptr, 10);
              // if this token ends in ')', then it is the last token
              // else we advance
              if ( *(fNextToken + strlen(fNextToken) - 1) == ')')
                fNextToken += strlen(fNextToken) - 1;
            }
          }
          else if (!PL_strcasecmp(fNextToken, "MESSAGES"))
          {
            AdvanceToNextToken();
            if (fNextToken)
            {
              fStatusExistingMessages = strtoul(fNextToken, nullptr, 10);
              // if this token ends in ')', then it is the last token
              // else we advance
              if ( *(fNextToken + strlen(fNextToken) - 1) == ')')
                fNextToken += strlen(fNextToken) - 1;
            }
          }
          else if (!PL_strcasecmp(fNextToken, "UNSEEN"))
          {
            AdvanceToNextToken();
            if (fNextToken)
            {
              fStatusUnseenMessages = strtoul(fNextToken, nullptr, 10);
              // if this token ends in ')', then it is the last token
              // else we advance
              if ( *(fNextToken + strlen(fNextToken) - 1) == ')')
                fNextToken += strlen(fNextToken) - 1;
            }
          }
          else if (!PL_strcasecmp(fNextToken, "RECENT"))
          {
            AdvanceToNextToken();
            if (fNextToken)
            {
              fStatusRecentMessages = strtoul(fNextToken, nullptr, 10);
              // if this token ends in ')', then it is the last token
              // else we advance
              if ( *(fNextToken + strlen(fNextToken) - 1) == ')')
                fNextToken += strlen(fNextToken) - 1;
            }
          }
          else if (*fNextToken == ')')
            break;
          else if (!fAtEndOfLine)
            SetSyntaxError(true);
        }
      } else SetSyntaxError(true);
      break;
    case 'C':
      if (!PL_strcasecmp(fNextToken, "CAPABILITY"))
        capability_data();
      else SetSyntaxError(true);
      break;
    case 'V':
      if (!PL_strcasecmp(fNextToken, "VERSION"))
      {
        // figure out the version of the Netscape server here
        PR_FREEIF(fNetscapeServerVersionString);
        AdvanceToNextToken();
        if (! fNextToken)
          SetSyntaxError(true);
        else
        {
          fNetscapeServerVersionString = CreateAstring();
          AdvanceToNextToken();
          if (fNetscapeServerVersionString)
          {
            fServerIsNetscape3xServer = (*fNetscapeServerVersionString == '3');
          }
        }
        skip_to_CRLF();
      }
      else SetSyntaxError(true);
      break;
    case 'A':
      if (!PL_strcasecmp(fNextToken, "ACL"))
      {
        acl_data();
      }
      else if (!PL_strcasecmp(fNextToken, "ACCOUNT-URL"))
      {
        fMailAccountUrl.Truncate();
        AdvanceToNextToken();
        if (! fNextToken)
          SetSyntaxError(true);
        else
        {
          fMailAccountUrl.Adopt(CreateAstring());
          AdvanceToNextToken();
        }
      }
      else SetSyntaxError(true);
      break;
    case 'E':
      if (!PL_strcasecmp(fNextToken, "ENABLED"))
        enable_data();
        break;
    case 'X':
      if (!PL_strcasecmp(fNextToken, "XSERVERINFO"))
        xserverinfo_data();
      else if (!PL_strcasecmp(fNextToken, "XMAILBOXINFO"))
        xmailboxinfo_data();
      else if (!PL_strcasecmp(fNextToken, "XAOL-OPTION"))
        skip_to_CRLF();
      else if (!PL_strcasecmp(fNextToken, "XLIST"))
        mailbox_data();
      else
      {
        // check if custom command
        nsAutoCString customCommand;
        fServerConnection.GetCurrentUrl()->GetCommand(customCommand);
        if (customCommand.Equals(fNextToken))
        {
          nsAutoCString customCommandResponse;
          while (Connected() && !fAtEndOfLine)
          {
            AdvanceToNextToken();
            customCommandResponse.Append(fNextToken);
            customCommandResponse.Append(" ");
          }
          fServerConnection.GetCurrentUrl()->SetCustomCommandResult(customCommandResponse);
        }
        else
          SetSyntaxError(true);
      }
      break;
    case 'Q':
      if (!PL_strcasecmp(fNextToken, "QUOTAROOT")  || !PL_strcasecmp(fNextToken, "QUOTA"))
        quota_data();
      else
        SetSyntaxError(true);
      break;
    case 'I':
      id_data();
      break;
    default:
      if (IsNumericString(fNextToken))
        numeric_mailbox_data();
      else
        SetSyntaxError(true);
      break;
    }

    if (ContinueParse())
      PostProcessEndOfLine();
  }
}


void nsImapServerResponseParser::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;
    nsCString customFlags;
    fFlagState->GetCustomFlags(CurrentResponseUID(), getter_Copies(customFlags));
    fServerConnection.NotifyMessageFlags(fSavedFlagInfo, customFlags,
                                         CurrentResponseUID(), fHighestModSeq);
  }
}


/*
 mailbox_data    ::=  "FLAGS" SPACE flag_list /
                               "LIST" SPACE mailbox_list /
                               "LSUB" SPACE mailbox_list /
                               "XLIST" 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 /
                               "XLIST" SPACE mailbox_list /
                               "MAILBOX" SPACE text /
                               "SEARCH" [SPACE 1#nz_number]
*/
void nsImapServerResponseParser::mailbox_data()
{
  if (!PL_strcasecmp(fNextToken, "FLAGS"))
  {
    // this handles the case where we got the permanent flags response
    // before the flags response, in which case, we want to ignore thes flags.
    if (fGotPermanentFlags)
      skip_to_CRLF();
    else
      parse_folder_flags();
  }
  else if (!PL_strcasecmp(fNextToken, "LIST") ||
           !PL_strcasecmp(fNextToken, "XLIST"))
  {
    AdvanceToNextToken();
    if (ContinueParse())
      mailbox_list(false);
  }
  else if (!PL_strcasecmp(fNextToken, "LSUB"))
  {
    AdvanceToNextToken();
    if (ContinueParse())
      mailbox_list(true);
  }
  else if (!PL_strcasecmp(fNextToken, "MAILBOX"))
    skip_to_CRLF();
  else if (!PL_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 nsImapServerResponseParser::mailbox_list(bool discoveredFromLsub)
{
  nsImapMailboxSpec *boxSpec = new nsImapMailboxSpec;
  NS_ADDREF(boxSpec);
  bool needsToFreeBoxSpec = true;
  if (!boxSpec)
    HandleMemoryFailure();
  else
  {
    boxSpec->mFolderSelected = false;
    boxSpec->mBoxFlags = kNoFlags;
    boxSpec->mAllocatedPathName.Truncate();
    boxSpec->mHostName.Truncate();
    boxSpec->mConnection = &fServerConnection;
    boxSpec->mFlagState = nullptr;
    boxSpec->mDiscoveredFromLsub = discoveredFromLsub;
    boxSpec->mOnlineVerified = true;
    boxSpec->mBoxFlags &= ~kNameSpace;

    bool endOfFlags = false;
    fNextToken++;// eat the first "("
    do {
      if (!PL_strncasecmp(fNextToken, "\\Marked", 7))
        boxSpec->mBoxFlags |= kMarked;
      else if (!PL_strncasecmp(fNextToken, "\\Unmarked", 9))
        boxSpec->mBoxFlags |= kUnmarked;
      else if (!PL_strncasecmp(fNextToken, "\\Noinferiors", 12))
      {
        boxSpec->mBoxFlags |= kNoinferiors;
        // RFC 5258 \Noinferiors implies \HasNoChildren
        if (fCapabilityFlag & kHasListExtendedCapability)
          boxSpec->mBoxFlags |= kHasNoChildren;
      }
      else if (!PL_strncasecmp(fNextToken, "\\Noselect", 9))
        boxSpec->mBoxFlags |= kNoselect;
      else if (!PL_strncasecmp(fNextToken, "\\Drafts", 7))
        boxSpec->mBoxFlags |= kImapDrafts;
      else if (!PL_strncasecmp(fNextToken, "\\Trash", 6))
        boxSpec->mBoxFlags |= kImapXListTrash;
      else if (!PL_strncasecmp(fNextToken, "\\Sent", 5))
        boxSpec->mBoxFlags |= kImapSent;
      else if (!PL_strncasecmp(fNextToken, "\\Spam", 5) ||
               !PL_strncasecmp(fNextToken, "\\Junk", 5))
        boxSpec->mBoxFlags |= kImapSpam;
      else if (!PL_strncasecmp(fNextToken, "\\Archive", 8))
        boxSpec->mBoxFlags |= kImapArchive;
      else if (!PL_strncasecmp(fNextToken, "\\All", 4) ||
               !PL_strncasecmp(fNextToken, "\\AllMail", 8))
        boxSpec->mBoxFlags |= kImapAllMail;
      else if (!PL_strncasecmp(fNextToken, "\\Inbox", 6))
        boxSpec->mBoxFlags |= kImapInbox;
      else if (!PL_strncasecmp(fNextToken, "\\NonExistent", 11))
      {
        boxSpec->mBoxFlags |= kNonExistent;
        // RFC 5258 \NonExistent implies \Noselect
        boxSpec->mBoxFlags |= kNoselect;
      }
      else if (!PL_strncasecmp(fNextToken, "\\Subscribed", 10))
        boxSpec->mBoxFlags |= kSubscribed;
      else if (!PL_strncasecmp(fNextToken, "\\Remote", 6))
        boxSpec->mBoxFlags |= kRemote;
      else if (!PL_strncasecmp(fNextToken, "\\HasChildren", 11))
        boxSpec->mBoxFlags |= kHasChildren;
      else if (!PL_strncasecmp(fNextToken, "\\HasNoChildren", 13))
        boxSpec->mBoxFlags |= kHasNoChildren;
      // we ignore flag other extensions

      endOfFlags = *(fNextToken + strlen(fNextToken) - 1) == ')';
      AdvanceToNextToken();
    } while (!endOfFlags && ContinueParse());

    if (ContinueParse())
    {
      if (*fNextToken == '"')
      {
        fNextToken++;
        if (*fNextToken == '\\') // handle escaped char
          boxSpec->mHierarchySeparator = *(fNextToken + 1);
        else
          boxSpec->mHierarchySeparator = *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->mHierarchySeparator = kOnlineHierarchySeparatorNil;
      AdvanceToNextToken();
      if (ContinueParse())
      {
        // nsImapProtocol::DiscoverMailboxSpec() eventually frees the
        // boxSpec
        needsToFreeBoxSpec = false;
        mailbox(boxSpec);
      }
    }
  }
  if (needsToFreeBoxSpec)
    NS_RELEASE(boxSpec);
}

/* mailbox         ::= "INBOX" / astring
*/
void nsImapServerResponseParser::mailbox(nsImapMailboxSpec *boxSpec)
{
  char *boxname = nullptr;
  const char *serverKey = fServerConnection.GetImapServerKey();
  bool xlistInbox = boxSpec->mBoxFlags & kImapInbox;

  if (!PL_strcasecmp(fNextToken, "INBOX") || xlistInbox)
  {
    boxname = PL_strdup("INBOX");
    if (xlistInbox)
      PR_Free(CreateAstring());
    AdvanceToNextToken();
  }
  else
  {
    boxname = CreateAstring();
    AdvanceToNextToken();
  }

  if (boxname && fHostSessionList)
  {
    // should the namespace check go before or after the Utf7 conversion?
    fHostSessionList->SetNamespaceHierarchyDelimiterFromMailboxForHost(
      serverKey, boxname, boxSpec->mHierarchySeparator);


    nsIMAPNamespace *ns = nullptr;
    fHostSessionList->GetNamespaceForMailboxForHost(serverKey, boxname, ns);
    if (ns)
    {
      switch (ns->GetType())
      {
      case kPersonalNamespace:
        boxSpec->mBoxFlags |= kPersonalMailbox;
        break;
      case kPublicNamespace:
        boxSpec->mBoxFlags |= kPublicMailbox;
        break;
      case kOtherUsersNamespace:
        boxSpec->mBoxFlags |= kOtherUsersMailbox;
        break;
      default:	// (kUnknownNamespace)
        break;
      }
      boxSpec->mNamespaceForFolder = ns;
    }

    //    	char *convertedName =
    //            fServerConnection.CreateUtf7ConvertedString(boxname, false);
    //		char16_t *unicharName;
    //        unicharName = fServerConnection.CreatePRUnicharStringFromUTF7(boxname);
    //    	PL_strfree(boxname);
    //    	boxname = convertedName;
  }

  if (!boxname)
  {
    if (!fServerConnection.DeathSignalReceived())
      HandleMemoryFailure();
  }
  else if (boxSpec->mConnection && boxSpec->mConnection->GetCurrentUrl())
  {
    boxSpec->mConnection->GetCurrentUrl()->AllocateCanonicalPath(boxname, boxSpec->mHierarchySeparator,
                                                                 getter_Copies(boxSpec->mAllocatedPathName));
    nsIURI *aURL = nullptr;
    boxSpec->mConnection->GetCurrentUrl()->QueryInterface(NS_GET_IID(nsIURI), (void **) &aURL);
    if (aURL)
      aURL->GetHost(boxSpec->mHostName);

    NS_IF_RELEASE(aURL);
    if (boxname)
      PL_strfree( 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 (NS_FAILED(fServerConnection.GetConnectionStatus()))
      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 nsImapServerResponseParser::numeric_mailbox_data()
{
  int32_t tokenNumber = atoi(fNextToken);
  AdvanceToNextToken();

  if (ContinueParse())
  {
    if (!PL_strcasecmp(fNextToken, "FETCH"))
    {
      fFetchResponseIndex = tokenNumber;
      AdvanceToNextToken();
      if (ContinueParse())
        msg_fetch();
    }
    else if (!PL_strcasecmp(fNextToken, "EXISTS"))
    {
      fNumberOfExistingMessages = tokenNumber;
      AdvanceToNextToken();
    }
    else if (!PL_strcasecmp(fNextToken, "RECENT"))
    {
      fNumberOfRecentMessages = tokenNumber;
      AdvanceToNextToken();
    }
    else if (!PL_strcasecmp(fNextToken, "EXPUNGE"))
    {
      if (!fServerConnection.GetIgnoreExpunges())
        fFlagState->ExpungeByIndex((uint32_t) 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 /
"MODSEQ" SPACE "(" nz_number ")" /
"RFC822" [".HEADER" / ".TEXT"] SPACE nstring /
"RFC822.SIZE" SPACE number /
"UID" SPACE uniqueid) ")"

*/

void nsImapServerResponseParser::msg_fetch()
{
  bool bNeedEndMessageDownload = false;

  // we have not seen a uid response or flags for this fetch, yet
  fCurrentResponseUID = 0;
  fCurrentLineContainedFlagInfo = false;
  fSizeOfMostRecentMessage = 0;
  // 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 (!PL_strcasecmp(fNextToken, "FLAGS"))
    {
      if (fCurrentResponseUID == 0)
        fFlagState->GetUidOfMessage(fFetchResponseIndex - 1, &fCurrentResponseUID);

      AdvanceToNextToken();
      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 != ')')
          AdvanceToNextToken();
      }
    }
    else if (!PL_strcasecmp(fNextToken, "UID"))
    {
      AdvanceToNextToken();
      if (ContinueParse())
      {
        fCurrentResponseUID = strtoul(fNextToken, nullptr, 10);
        if (fCurrentResponseUID > fHighestRecordedUID)
          fHighestRecordedUID = fCurrentResponseUID;
        // size came before UID
        if (fSizeOfMostRecentMessage)
          fReceivedHeaderOrSizeForUID = CurrentResponseUID();
        // if this token ends in ')', then it is the last token
        // else we advance
        char lastTokenChar = *(fNextToken + strlen(fNextToken) - 1);
        if (lastTokenChar == ')')
          fNextToken += strlen(fNextToken) - 1;
        else if (lastTokenChar < '0' || lastTokenChar > '9')
        {
          // GIANT HACK
          // this is a corrupt uid - see if it's pre 5.08 Zimbra omitting
          // a space between the UID and MODSEQ
          if (strlen(fNextToken) > 6 &&
              !strcmp("MODSEQ", fNextToken + strlen(fNextToken) - 6))
            fNextToken += strlen(fNextToken) - 6;
        }
        else
          AdvanceToNextToken();
      }
    }
    else if (!PL_strcasecmp(fNextToken, "MODSEQ"))
    {
      AdvanceToNextToken();
      if (ContinueParse())
      {
        fNextToken++; // eat '('
        uint64_t modSeq =  ParseUint64Str(fNextToken);
        if (modSeq > fHighestModSeq)
          fHighestModSeq = modSeq;

        if (PL_strcasestr(fNextToken, ")"))
        {
          // eat token chars until we get the ')'
          fNextToken = strchr(fNextToken, ')');
          if (fNextToken)
          {
            fNextToken++;
            if (*fNextToken != ')')
              AdvanceToNextToken();
          }
          else
            SetSyntaxError(true);
        }
        else
        {
          SetSyntaxError(true);
        }
      }
    }
    else if (!PL_strcasecmp(fNextToken, "RFC822") ||
      !PL_strcasecmp(fNextToken, "RFC822.HEADER") ||
      !PL_strncasecmp(fNextToken, "BODY[HEADER",11) ||
      !PL_strncasecmp(fNextToken, "BODY[]", 6) ||
      !PL_strcasecmp(fNextToken, "RFC822.TEXT") ||
      (!PL_strncasecmp(fNextToken, "BODY[", 5) &&
				  PL_strstr(fNextToken, "HEADER"))
                                  )
    {
      if (fCurrentResponseUID == 0)
        fFlagState->GetUidOfMessage(fFetchResponseIndex - 1, &fCurrentResponseUID);

      if (!PL_strcasecmp(fNextToken, "RFC822.HEADER") ||
        !PL_strcasecmp(fNextToken, "BODY[HEADER]"))
      {
        // all of this message's headers
        AdvanceToNextToken();
        fDownloadingHeaders = true;
        BeginMessageDownload(MESSAGE_RFC822); // initialize header parser
        bNeedEndMessageDownload = false;
        if (ContinueParse())
          msg_fetch_headers(nullptr);
      }
      else if (!PL_strncasecmp(fNextToken, "BODY[HEADER.FIELDS",19))
      {
        fDownloadingHeaders = true;
        BeginMessageDownload(MESSAGE_RFC822); // initialize header parser
        // specific message header fields
        while (ContinueParse() && fNextToken[strlen(fNextToken)-1] != ']')
          AdvanceToNextToken();
        if (ContinueParse())
        {
          bNeedEndMessageDownload = false;
          AdvanceToNextToken();
          if (ContinueParse())
            msg_fetch_headers(nullptr);
        }
      }
      else
      {
        char *whereHeader = PL_strstr(fNextToken, "HEADER");
        if (whereHeader)
        {
          const char *startPartNum = fNextToken + 5;
          if (whereHeader > startPartNum)
          {
            int32_t partLength = whereHeader - startPartNum - 1; //-1 for the dot!
            char *partNum = (char *)PR_CALLOC((partLength + 1) * sizeof (char));
            if (partNum)
            {
              PL_strncpy(partNum, startPartNum, partLength);
              if (ContinueParse())
              {
                if (PL_strstr(fNextToken, "FIELDS"))
                {
                  while (ContinueParse() && fNextToken[strlen(fNextToken)-1] != ']')
                    AdvanceToNextToken();
                }
                if (ContinueParse())
                {
                  AdvanceToNextToken();
                  if (ContinueParse())
                    msg_fetch_headers(partNum);
                }
              }
              PR_Free(partNum);
            }
          }
          else
            SetSyntaxError(true);
        }
        else
        {
          fDownloadingHeaders = false;

          bool chunk = false;
          int32_t origin = 0;
          if (!PL_strncasecmp(fNextToken, "BODY[]<", 7))
          {
            char *tokenCopy = 0;
            tokenCopy = PL_strdup(fNextToken);
            if (tokenCopy)
            {
              char *originString = tokenCopy + 7;	// where the byte number starts
              char *closeBracket = PL_strchr(tokenCopy,'>');
              if (closeBracket && originString && *originString)
              {
                *closeBracket = 0;
                origin = atoi(originString);
                chunk = true;
              }
              PR_Free(tokenCopy);
            }
          }

          AdvanceToNextToken();
          if (ContinueParse())
          {
            msg_fetch_content(chunk, origin, MESSAGE_RFC822);
          }
        }
      }
      }
      else if (!PL_strcasecmp(fNextToken, "RFC822.SIZE") || !PL_strcasecmp(fNextToken, "XAOL.SIZE"))
      {
        AdvanceToNextToken();
        if (ContinueParse())
        {
          bool sendEndMsgDownload = (GetDownloadingHeaders()
                                        && fReceivedHeaderOrSizeForUID == CurrentResponseUID());
          fSizeOfMostRecentMessage = strtoul(fNextToken, nullptr, 10);
          fReceivedHeaderOrSizeForUID = CurrentResponseUID();
          if (sendEndMsgDownload)
          {
            fServerConnection.NormalMessageEndDownload();
            fReceivedHeaderOrSizeForUID = nsMsgKey_None;
          }

          if (fSizeOfMostRecentMessage == 0 && CurrentResponseUID())
          {
            // on no, bogus Netscape 2.0 mail server bug
            char uidString[100];
            sprintf(uidString, "%ld", (long)CurrentResponseUID());

            if (!fZeroLengthMessageUidString.IsEmpty())
              fZeroLengthMessageUidString += ",";

            fZeroLengthMessageUidString += uidString;
          }

          // if this token ends in ')', then it is the last token
          // else we advance
          if ( *(fNextToken + strlen(fNextToken) - 1) == ')')
            fNextToken += strlen(fNextToken) - 1;
          else
            AdvanceToNextToken();
        }
      }
      else if (!PL_strcasecmp(fNextToken, "XSENDER"))
      {
        PR_FREEIF(fXSenderInfo);
        AdvanceToNextToken();
        if (! fNextToken)
          SetSyntaxError(true);
        else
        {
          fXSenderInfo = CreateAstring();
          AdvanceToNextToken();
        }
      }
      else if (!PL_strcasecmp(fNextToken, "X-GM-MSGID"))
      {
        AdvanceToNextToken();
        if (!fNextToken)
          SetSyntaxError(true);
        else
        {
          fMsgID = CreateAtom();
          AdvanceToNextToken();
          nsCString msgIDValue;
          msgIDValue.Assign(fMsgID);
          if (fCurrentResponseUID == 0)
            fFlagState->GetUidOfMessage(fFetchResponseIndex - 1, &fCurrentResponseUID);
          fFlagState->SetCustomAttribute(fCurrentResponseUID,
                                         NS_LITERAL_CSTRING("X-GM-MSGID"), msgIDValue);
          PR_FREEIF(fMsgID);
        }
      }
      else if (!PL_strcasecmp(fNextToken, "X-GM-THRID"))
      {
        AdvanceToNextToken();
        if (!fNextToken)
          SetSyntaxError(true);
        else
        {
          fThreadID = CreateAtom();
          AdvanceToNextToken();
          nsCString threadIDValue;
          threadIDValue.Assign(fThreadID);
          if (fCurrentResponseUID == 0)
            fFlagState->GetUidOfMessage(fFetchResponseIndex - 1, &fCurrentResponseUID);
          fFlagState->SetCustomAttribute(fCurrentResponseUID,
                                         NS_LITERAL_CSTRING("X-GM-THRID"), threadIDValue);
          PR_FREEIF(fThreadID);
        }
      }
      else if (!PL_strcasecmp(fNextToken, "X-GM-LABELS"))
      {
        AdvanceToNextToken();
        if (!fNextToken)
          SetSyntaxError(true);
        else
        {
          fLabels = CreateParenGroup();
          nsCString labelsValue;
          labelsValue.Assign(fLabels);
          labelsValue.Cut(0, 1);
          labelsValue.Cut(labelsValue.Length()-1, 1);
          if (fCurrentResponseUID == 0)
            fFlagState->GetUidOfMessage(fFetchResponseIndex - 1, &fCurrentResponseUID);
          fFlagState->SetCustomAttribute(fCurrentResponseUID,
                                         NS_LITERAL_CSTRING("X-GM-LABELS"), labelsValue);
          PR_FREEIF(fLabels);
        }
      }

      // I only fetch RFC822 so I should never see these BODY responses
      else if (!PL_strcasecmp(fNextToken, "BODY"))
        skip_to_CRLF(); // I never ask for this
      else if (!PL_strcasecmp(fNextToken, "BODYSTRUCTURE"))
      {
        if (fCurrentResponseUID == 0)
          fFlagState->GetUidOfMessage(fFetchResponseIndex - 1, &fCurrentResponseUID);
        bodystructure_data();
      }
      else if (!PL_strncasecmp(fNextToken, "BODY[TEXT", 9))
      {
        mime_data();
      }
      else if (!PL_strncasecmp(fNextToken, "BODY[", 5) && PL_strncasecmp(fNextToken, "BODY[]", 6))
      {
        fDownloadingHeaders = false;
        // A specific MIME part, or MIME part header
        mime_data();
      }
      else if (!PL_strcasecmp(fNextToken, "ENVELOPE"))
      {
        fDownloadingHeaders = true;
        bNeedEndMessageDownload = true;
        BeginMessageDownload(MESSAGE_RFC822);
        envelope_data();
      }
      else if (!PL_strcasecmp(fNextToken, "INTERNALDATE"))
      {
        fDownloadingHeaders = true; // we only request internal date while downloading headers
        if (!bNeedEndMessageDownload)
          BeginMessageDownload(MESSAGE_RFC822);
        bNeedEndMessageDownload = true;
        internal_date();
      }
      else if (!PL_strcasecmp(fNextToken, "XAOL-ENVELOPE"))
      {
        fDownloadingHeaders = true;
        if (!bNeedEndMessageDownload)
          BeginMessageDownload(MESSAGE_RFC822);
        bNeedEndMessageDownload = true;
        xaolenvelope_data();
      }
      else
      {
        nsImapAction imapAction;
        if (!fServerConnection.GetCurrentUrl())
          return;
        fServerConnection.GetCurrentUrl()->GetImapAction(&imapAction);
        nsAutoCString userDefinedFetchAttribute;
        fServerConnection.GetCurrentUrl()->GetCustomAttributeToFetch(userDefinedFetchAttribute);
        if ((imapAction == nsIImapUrl::nsImapUserDefinedFetchAttribute && !strcmp(userDefinedFetchAttribute.get(), fNextToken)) ||
            imapAction == nsIImapUrl::nsImapUserDefinedMsgCommand)
        {
          AdvanceToNextToken();
          char *fetchResult;
          if (fNextToken[0] == '(')
            // look through the tokens until we find the closing ')'
            // we can have a result like the following:
            // ((A B) (C D) (E F))
            fetchResult = CreateParenGroup();
          else {
            fetchResult = CreateAstring();
            AdvanceToNextToken();
          }
          if (imapAction == nsIImapUrl::nsImapUserDefinedFetchAttribute)
            fServerConnection.GetCurrentUrl()->SetCustomAttributeResult(nsDependentCString(fetchResult));
          if (imapAction == nsIImapUrl::nsImapUserDefinedMsgCommand)
            fServerConnection.GetCurrentUrl()->SetCustomCommandResult(nsDependentCString(fetchResult));
          PR_Free(fetchResult);
        }
        else
          SetSyntaxError(true);
      }

        }

        if (ContinueParse())
        {
          if (CurrentResponseUID() && CurrentResponseUID() != nsMsgKey_None
            && fCurrentLineContainedFlagInfo && fFlagState)
          {
            fFlagState->AddUidFlagPair(CurrentResponseUID(), fSavedFlagInfo, fFetchResponseIndex - 1);
            for (uint32_t i = 0; i < fCustomFlags.Length(); i++)
              fFlagState->AddUidCustomFlagPair(CurrentResponseUID(), fCustomFlags[i].get());
            fCustomFlags.Clear();
          }

          if (fFetchingAllFlags)
            fCurrentLineContainedFlagInfo = false;  // do not fire if in PostProcessEndOfLine

          AdvanceToNextToken(); // eat the ')' ending token
          // should be at end of line
          if (bNeedEndMessageDownload)
          {
            if (ContinueParse())
            {
              // complete the message download
              fServerConnection.NormalMessageEndDownload();
            }
            else
              fServerConnection.AbortMessageDownLoad();
          }

        }
}

typedef enum _envelopeItemType
{
	envelopeString,
	envelopeAddress
} envelopeItemType;

typedef struct
{
	const char * name;
	envelopeItemType type;
} envelopeItem;

// RFC3501:  envelope  = "(" env-date SP env-subject SP env-from SP
//                       env-sender SP env-reply-to SP env-to SP env-cc SP
//                       env-bcc SP env-in-reply-to SP env-message-id ")"
//           env-date    = nstring
//           env-subject = nstring
//           env-from    = "(" 1*address ")" / nil
//           env-sender  = "(" 1*address ")" / nil
//           env-reply-to= "(" 1*address ")" / nil
//           env-to      = "(" 1*address ")" / nil
//           env-cc      = "(" 1*address ")" / nil
//           env-bcc     = "(" 1*address ")" / nil
//           env-in-reply-to = nstring
//           env-message-id  = nstring

static const envelopeItem EnvelopeTable[] =
{
  {"Date", envelopeString},
  {"Subject", envelopeString},
  {"From", envelopeAddress},
  {"Sender", envelopeAddress},
  {"Reply-to", envelopeAddress},
  {"To", envelopeAddress},
  {"Cc", envelopeAddress},
  {"Bcc", envelopeAddress},
  {"In-reply-to", envelopeString},
  {"Message-id", envelopeString}
};

void nsImapServerResponseParser::envelope_data()
{
  AdvanceToNextToken();
  fNextToken++; // eat '('
  for (int tableIndex = 0; tableIndex < (int)(sizeof(EnvelopeTable) / sizeof(EnvelopeTable[0])); tableIndex++)
  {
    if (!ContinueParse())
      break;
    else if (*fNextToken == ')')
    {
      SetSyntaxError(true); // envelope too short
      break;
    }
    else
    {
      nsAutoCString headerLine(EnvelopeTable[tableIndex].name);
      headerLine += ": ";
      bool headerNonNil = true;
      if (EnvelopeTable[tableIndex].type == envelopeString)
      {
        nsAutoCString strValue;
        strValue.Adopt(CreateNilString());
        if (!strValue.IsEmpty())
          headerLine.Append(strValue);
        else
          headerNonNil = false;
      }
      else
      {
        nsAutoCString address;
        parse_address(address);
        headerLine += address;
        if (address.IsEmpty())
          headerNonNil = false;
      }
      if (headerNonNil)
        fServerConnection.HandleMessageDownLoadLine(headerLine.get(), false);
    }
    if (ContinueParse())
      AdvanceToNextToken();
  }
  // Now we should be at the end of the envelope and have *fToken == ')'.
  // Skip this last parenthesis.
  AdvanceToNextToken();
}

void nsImapServerResponseParser::xaolenvelope_data()
{
  // eat the opening '('
  fNextToken++;

  if (ContinueParse() && (*fNextToken != ')'))
  {
    AdvanceToNextToken();
    fNextToken++; // eat '('
    nsAutoCString subject;
    subject.Adopt(CreateNilString());
    nsAutoCString subjectLine("Subject: ");
    subjectLine += subject;
    fServerConnection.HandleMessageDownLoadLine(subjectLine.get(), false);
    fNextToken++; // eat the next '('
    if (ContinueParse())
    {
      AdvanceToNextToken();
      if (ContinueParse())
      {
        nsAutoCString fromLine;
        if (!strcmp(GetSelectedMailboxName(), "Sent Items"))
        {
          // xaol envelope switches the From with the To, so we switch them back and
          // create a fake from line From: user@aol.com
          fromLine.Append("To: ");
          nsAutoCString fakeFromLine(NS_LITERAL_CSTRING("From: "));
          fakeFromLine.Append(fServerConnection.GetImapUserName());
          fakeFromLine.Append(NS_LITERAL_CSTRING("@aol.com"));
          fServerConnection.HandleMessageDownLoadLine(fakeFromLine.get(), false);
        }
        else
        {
          fromLine.Append("From: ");
        }
        parse_address(fromLine);
        fServerConnection.HandleMessageDownLoadLine(fromLine.get(), false);
        if (ContinueParse())
        {
          AdvanceToNextToken();	// ge attachment size
          int32_t attachmentSize = atoi(fNextToken);
          if (attachmentSize != 0)
          {
            nsAutoCString attachmentLine("X-attachment-size: ");
            attachmentLine.AppendInt(attachmentSize);
            fServerConnection.HandleMessageDownLoadLine(attachmentLine.get(), false);
          }
        }
        if (ContinueParse())
        {
          AdvanceToNextToken();	// skip image size
          int32_t imageSize = atoi(fNextToken);
          if (imageSize != 0)
          {
            nsAutoCString imageLine("X-image-size: ");
            imageLine.AppendInt(imageSize);
            fServerConnection.HandleMessageDownLoadLine(imageLine.get(), false);
          }
        }
        if (ContinueParse())
          AdvanceToNextToken();	// skip )
      }
    }
  }
}

void nsImapServerResponseParser::parse_address(nsAutoCString &addressLine)
{
  if (!strcmp(fNextToken, "NIL"))
    return;
  bool firstAddress = true;
  // should really look at chars here
  NS_ASSERTION(*fNextToken == '(', "address should start with '('");
  fNextToken++; // eat the next '('
  while (ContinueParse() && *fNextToken == '(')
  {
    NS_ASSERTION(*fNextToken == '(', "address should start with '('");
    fNextToken++; // eat the next '('

    if (!firstAddress)
      addressLine += ", ";

    firstAddress = false;
    char *personalName = CreateNilString();
    AdvanceToNextToken();
    char *atDomainList = CreateNilString();
    if (ContinueParse())
    {
      AdvanceToNextToken();
      char *mailboxName = CreateNilString();
      if (ContinueParse())
      {
        AdvanceToNextToken();
        char *hostName = CreateNilString();
        AdvanceToNextToken();
        addressLine += mailboxName;
        if (hostName)
        {
          addressLine += '@';
          addressLine += hostName;
          NS_Free(hostName);
        }
        if (personalName)
        {
          addressLine += " (";
          addressLine += personalName;
          addressLine += ')';
        }
      }
    }
    PR_Free(personalName);
    PR_Free(atDomainList);

    if (*fNextToken == ')')
      fNextToken++;
    // if the next token isn't a ')' for the address term,
    // then we must have another address pair left....so get the next
    // token and continue parsing in this loop...
    if ( *fNextToken == '\0' )
      AdvanceToNextToken();

  }
  if (*fNextToken == ')')
    fNextToken++;
  //	AdvanceToNextToken();	// skip "))"
}

void nsImapServerResponseParser::internal_date()
{
  AdvanceToNextToken();
  if (ContinueParse())
  {
    nsAutoCString dateLine("Date: ");
    char *strValue = CreateNilString();
    if (strValue)
    {
      dateLine += strValue;
      NS_Free(strValue);
    }
    fServerConnection.HandleMessageDownLoadLine(dateLine.get(), false);
  }
  // advance the parser.
  AdvanceToNextToken();
}

void nsImapServerResponseParser::flags()
{
  imapMessageFlagsType messageFlags = kNoImapMsgFlag;
  fCustomFlags.Clear();

  // clear the custom flags for this message
  // otherwise the old custom flags will stay around
  // see bug #191042
  if (fFlagState && CurrentResponseUID() != nsMsgKey_None)
    fFlagState->ClearCustomFlags(CurrentResponseUID());

  // eat the opening '('
  fNextToken++;
  while (ContinueParse() && (*fNextToken != ')'))
  {
    bool knownFlag = false;
    if (*fNextToken == '\\')
    {
      switch (NS_ToUpper(fNextToken[1])) {
      case 'S':
        if (!PL_strncasecmp(fNextToken, "\\Seen",5))
        {
          messageFlags |= kImapMsgSeenFlag;
          knownFlag = true;
        }
        break;
      case 'A':
        if (!PL_strncasecmp(fNextToken, "\\Answered",9))
        {
          messageFlags |= kImapMsgAnsweredFlag;
          knownFlag = true;
        }
        break;
      case 'F':
        if (!PL_strncasecmp(fNextToken, "\\Flagged",8))
        {
          messageFlags |= kImapMsgFlaggedFlag;
          knownFlag = true;
        }
        break;
      case 'D':
        if (!PL_strncasecmp(fNextToken, "\\Deleted",8))
        {
          messageFlags |= kImapMsgDeletedFlag;
          knownFlag = true;
        }
        else if (!PL_strncasecmp(fNextToken, "\\Draft",6))
        {
          messageFlags |= kImapMsgDraftFlag;
          knownFlag = true;
        }
        break;
      case 'R':
        if (!PL_strncasecmp(fNextToken, "\\Recent",7))
        {
          messageFlags |= kImapMsgRecentFlag;
          knownFlag = true;
        }
        break;
      default:
        break;
      }
    }
    else if (*fNextToken == '$')
    {
      switch (NS_ToUpper(fNextToken[1])) {
      case 'M':
        if ((fSupportsUserDefinedFlags & (kImapMsgSupportUserFlag |
          kImapMsgSupportMDNSentFlag))
          && !PL_strncasecmp(fNextToken, "$MDNSent",8))
        {
          messageFlags |= kImapMsgMDNSentFlag;
          knownFlag = true;
        }
        break;
      case 'F':
        if ((fSupportsUserDefinedFlags & (kImapMsgSupportUserFlag |
          kImapMsgSupportForwardedFlag))
          && !PL_strncasecmp(fNextToken, "$Forwarded",10))
        {
          messageFlags |= kImapMsgForwardedFlag;
          knownFlag = true;
        }
        break;
      default:
        break;
      }
    }
    if (!knownFlag && fFlagState)
    {
      nsAutoCString flag(fNextToken);
      int32_t parenIndex = flag.FindChar(')');
      if (parenIndex > 0)
        flag.SetLength(parenIndex);
      messageFlags |= kImapMsgCustomKeywordFlag;
      if (CurrentResponseUID() != nsMsgKey_None && CurrentResponseUID() != 0)
        fFlagState->AddUidCustomFlagPair(CurrentResponseUID(), flag.get());
      else
        fCustomFlags.AppendElement(flag);
    }
    if (PL_strcasestr(fNextToken, ")"))
    {
      // eat token chars until we get the ')'
      while (*fNextToken != ')')
        fNextToken++;
    }
    else
      AdvanceToNextToken();
  }

  if (ContinueParse())
    while(*fNextToken != ')')
      fNextToken++;

    fCurrentLineContainedFlagInfo = true;	// handled in PostProcessEndOfLine
    fSavedFlagInfo = messageFlags;
}

// RFC3501:  resp-cond-state = ("OK" / "NO" / "BAD") SP resp-text
//                             ; Status condition
void nsImapServerResponseParser::resp_cond_state(bool isTagged)
{
  // According to RFC3501, Sec. 7.1, the untagged NO response "indicates a
  // warning; the command can still complete successfully."
  // However, the untagged BAD response "indicates a protocol-level error for
  // which the associated command can not be determined; it can also indicate an
  // internal server failure."
  // Thus, we flag an error for a tagged NO response and for any BAD response.
  if ((isTagged && !PL_strcasecmp(fNextToken, "NO")) ||
      !PL_strcasecmp(fNextToken, "BAD"))
    fCurrentCommandFailed = true;

  AdvanceToNextToken();
  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 nsImapServerResponseParser::resp_text()
{
  if (ContinueParse() && (*fNextToken == '['))
    resp_text_code();

  if (ContinueParse())
  {
    if (!PL_strcmp(fNextToken, "=?"))
      text_mime2();
    else
      text();
  }
}
/*
 text_mime2       ::= "=?" <charset> "?" <encoding> "?"
                               <encoded-text> "?="
                               ;; Syntax defined in [MIME-2]
*/
void nsImapServerResponseParser::text_mime2()
{
  skip_to_CRLF();
}

/*
 text            ::= 1*TEXT_CHAR

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

void nsImapServerResponseParser::parse_folder_flags()
{
  uint16_t labelFlags = 0;

  do
  {
    AdvanceToNextToken();
    if (*fNextToken == '(')
      fNextToken++;
    if (!PL_strncasecmp(fNextToken, "$MDNSent", 8))
      fSupportsUserDefinedFlags |= kImapMsgSupportMDNSentFlag;
    else if (!PL_strncasecmp(fNextToken, "$Forwarded", 10))
      fSupportsUserDefinedFlags |= kImapMsgSupportForwardedFlag;
    else if (!PL_strncasecmp(fNextToken, "\\Seen", 5))
      fSettablePermanentFlags |= kImapMsgSeenFlag;
    else if (!PL_strncasecmp(fNextToken, "\\Answered", 9))
      fSettablePermanentFlags |= kImapMsgAnsweredFlag;
    else if (!PL_strncasecmp(fNextToken, "\\Flagged", 8))
      fSettablePermanentFlags |= kImapMsgFlaggedFlag;
    else if (!PL_strncasecmp(fNextToken, "\\Deleted", 8))
      fSettablePermanentFlags |= kImapMsgDeletedFlag;
    else if (!PL_strncasecmp(fNextToken, "\\Draft", 6))
      fSettablePermanentFlags |= kImapMsgDraftFlag;
    else if (!PL_strncasecmp(fNextToken, "$Label1", 7))
      labelFlags |= 1;
    else if (!PL_strncasecmp(fNextToken, "$Label2", 7))
      labelFlags |= 2;
    else if (!PL_strncasecmp(fNextToken, "$Label3", 7))
      labelFlags |= 4;
    else if (!PL_strncasecmp(fNextToken, "$Label4", 7))
      labelFlags |= 8;
    else if (!PL_strncasecmp(fNextToken, "$Label5", 7))
      labelFlags |= 16;
    else if (!PL_strncasecmp(fNextToken, "\\*", 2))
    {
      fSupportsUserDefinedFlags |= kImapMsgSupportUserFlag;
      fSupportsUserDefinedFlags |= kImapMsgSupportForwardedFlag;
      fSupportsUserDefinedFlags |= kImapMsgSupportMDNSentFlag;
      fSupportsUserDefinedFlags |= kImapMsgLabelFlags;
    }
  } while (!fAtEndOfLine && ContinueParse());

  if (labelFlags == 31)
    fSupportsUserDefinedFlags |= kImapMsgLabelFlags;

  if (fFlagState)
    fFlagState->OrSupportedUserFlags(fSupportsUserDefinedFlags);
}
/*
  resp_text_code  ::= ("ALERT" / "PARSE" /
                              "PERMANENTFLAGS" SPACE "(" #(flag / "\*") ")" /
                              "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
                              "UIDVALIDITY" SPACE nz_number /
                              "UNSEEN" SPACE nz_number /
                              "HIGHESTMODSEQ" SPACE nz_number /
                              "NOMODSEQ" /
                              atom [SPACE 1*<any TEXT_CHAR except "]">] )
                      "]"


*/
void nsImapServerResponseParser::resp_text_code()
{
  // this is a special case way of advancing the token
  // strtok won't break up "[ALERT]" into separate tokens
  if (strlen(fNextToken) > 1)
    fNextToken++;
  else
    AdvanceToNextToken();

  if (ContinueParse())
  {
    if (!PL_strcasecmp(fNextToken,"ALERT]"))
    {
      char *alertMsg = fCurrentTokenPlaceHolder;	// advance past ALERT]
      if (alertMsg && *alertMsg && (!fLastAlert || PL_strcmp(fNextToken, fLastAlert)))
      {
        fServerConnection.AlertUserEvent(alertMsg);
        PR_Free(fLastAlert);
        fLastAlert = PL_strdup(alertMsg);
      }
      AdvanceToNextToken();
    }
    else if (!PL_strcasecmp(fNextToken,"PARSE]"))
    {
      // do nothing for now
      AdvanceToNextToken();
    }
    else if (!PL_strcasecmp(fNextToken,"NETSCAPE]"))
    {
      skip_to_CRLF();
    }
    else if (!PL_strcasecmp(fNextToken,"PERMANENTFLAGS"))
    {
      uint32_t saveSettableFlags = fSettablePermanentFlags;
      fSupportsUserDefinedFlags = 0;		// assume no unless told
      fSettablePermanentFlags = 0;            // assume none, unless told otherwise.
      parse_folder_flags();
      // if the server tells us there are no permanent flags, we're
      // just going to pretend that the FLAGS response flags, if any, are
      // permanent in case the server is broken. This will allow us
      // to store delete and seen flag changes - if they're not permanent,
      // they're not permanent, but at least we'll try to set them.
      if (!fSettablePermanentFlags)
        fSettablePermanentFlags = saveSettableFlags;
      fGotPermanentFlags = true;
    }
    else if (!PL_strcasecmp(fNextToken,"READ-ONLY]"))
    {
      fCurrentFolderReadOnly = true;
      AdvanceToNextToken();
    }
    else if (!PL_strcasecmp(fNextToken,"READ-WRITE]"))
    {
      fCurrentFolderReadOnly = false;
      AdvanceToNextToken();
    }
    else if (!PL_strcasecmp(fNextToken,"TRYCREATE]"))
    {
      // do nothing for now
      AdvanceToNextToken();
    }
    else if (!PL_strcasecmp(fNextToken,"UIDVALIDITY"))
    {
      AdvanceToNextToken();
      if (ContinueParse())
      {
        fFolderUIDValidity = strtoul(fNextToken, nullptr, 10);
        fHighestRecordedUID = 0;
        AdvanceToNextToken();
      }
    }
    else if (!PL_strcasecmp(fNextToken,"UNSEEN"))
    {
      AdvanceToNextToken();
      if (ContinueParse())
      {
        fNumberOfUnseenMessages = strtoul(fNextToken, nullptr, 10);
        AdvanceToNextToken();
      }
    }
    else if (!PL_strcasecmp(fNextToken,"UIDNEXT"))
    {
      AdvanceToNextToken();
      if (ContinueParse())
      {
        fStatusNextUID = strtoul(fNextToken, nullptr, 10);
        AdvanceToNextToken();
      }
    }
    else if (!PL_strcasecmp(fNextToken, "APPENDUID"))
    {
      AdvanceToNextToken();
      if (ContinueParse())
      {
        // ** jt -- the returned uidvalidity is the destination folder
        // uidvalidity; don't use it for current folder
        // fFolderUIDValidity = atoi(fNextToken);
        // fHighestRecordedUID = 0; ??? this should be wrong
        AdvanceToNextToken();
        if (ContinueParse())
        {
          fCurrentResponseUID = strtoul(fNextToken, nullptr, 10);
          AdvanceToNextToken();
        }
      }
    }
    else if (!PL_strcasecmp(fNextToken, "COPYUID"))
    {
      AdvanceToNextToken();
      if (ContinueParse())
      {
        // ** jt -- destination folder uidvalidity
        // fFolderUIDValidity = atoi(fNextToken);
        // original message set; ignore it
        AdvanceToNextToken();
        if (ContinueParse())
        {
          // the resulting message set; should be in the form of
          // either uid or uid1:uid2
          AdvanceToNextToken();
          // clear copy response uid
          fServerConnection.SetCopyResponseUid(fNextToken);
        }
        if (ContinueParse())
          AdvanceToNextToken();
      }
    }
    else if (!PL_strcasecmp(fNextToken, "HIGHESTMODSEQ"))
    {
      AdvanceToNextToken();
      if (ContinueParse())
      {
        fHighestModSeq = ParseUint64Str(fNextToken);
        fUseModSeq = true;
        AdvanceToNextToken();
      }
    }
    else if (!PL_strcasecmp(fNextToken, "NOMODSEQ]"))
    {
      fHighestModSeq = 0;
      fUseModSeq = false;
      skip_to_CRLF();
    }
    else if (!PL_strcasecmp(fNextToken, "CAPABILITY"))
    {
      capability_data();
    }
    else if (!PL_strcasecmp(fNextToken, "MYRIGHTS"))
    {
      myrights_data(true);
    }
    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
      {
        AdvanceToNextToken();
      } while (!PL_strcasestr(fNextToken, "]") && !fAtEndOfLine
                && ContinueParse());
    }
  }
}

// RFC3501:  response-done = response-tagged / response-fatal
 void nsImapServerResponseParser::response_done()
 {
   if (ContinueParse())
   {
     if (!PL_strcmp(fCurrentCommandTag, fNextToken))
       response_tagged();
     else
       response_fatal();
   }
 }

// RFC3501:  response-tagged = tag SP resp-cond-state CRLF
 void nsImapServerResponseParser::response_tagged()
 {
   // eat the tag
   AdvanceToNextToken();
   if (ContinueParse())
   {
     resp_cond_state(true);
     if (ContinueParse())
     {
       if (!fAtEndOfLine)
         SetSyntaxError(true);
       else if (!fCurrentCommandFailed)
         ResetLexAnalyzer();
     }
   }
 }

// RFC3501:  response-fatal = "*" SP resp-cond-bye CRLF
//                              ; Server closes connection immediately
 void nsImapServerResponseParser::response_fatal()
 {
   // eat the "*"
   AdvanceToNextToken();
   if (ContinueParse())
     resp_cond_bye();
 }

// RFC3501:  resp-cond-bye = "BYE" SP resp-text
void nsImapServerResponseParser::resp_cond_bye()
{
  SetConnected(false);
  fIMAPstate = kNonAuthenticated;
}


void nsImapServerResponseParser::msg_fetch_headers(const char *partNum)
{
  if (GetFillingInShell())
  {
    char *headerData = CreateAstring();
    AdvanceToNextToken();
    m_shell->AdoptMessageHeaders(headerData, partNum);
  }
  else
  {
    msg_fetch_content(false, 0, MESSAGE_RFC822);
  }
}


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

*/
void nsImapServerResponseParser::msg_fetch_content(bool chunk, int32_t 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)) && !GetDownloadingHeaders() &&
    (GetFillingInShell() ? m_shell->GetGeneratingWholeMessage() : true))
  {
    if (NS_FAILED(BeginMessageDownload(content_type)))
      return;
  }

  if (PL_strcasecmp(fNextToken, "NIL"))
  {
    if (*fNextToken == '"')
      fLastChunk = msg_fetch_quoted();
    else
      fLastChunk = msg_fetch_literal(chunk, origin);
  }
  else
    AdvanceToNextToken();	// eat "NIL"

  if (fLastChunk && (GetFillingInShell() ? m_shell->GetGeneratingWholeMessage() : true))
  {
    // complete the message download
    if (ContinueParse())
    {
      if (fReceivedHeaderOrSizeForUID == CurrentResponseUID())
      {
        fServerConnection.NormalMessageEndDownload();
        fReceivedHeaderOrSizeForUID = nsMsgKey_None;
      }
      else
         fReceivedHeaderOrSizeForUID = CurrentResponseUID();
    }
    else
      fServerConnection.AbortMessageDownLoad();
  }
}


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

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

    quoted_specials ::= <"> / "\"
*/

bool nsImapServerResponseParser::msg_fetch_quoted()
{
  // *Should* never get a quoted string in response to a chunked download,
  // but the RFCs don't forbid it
  char *q = CreateQuoted();
  if (q)
  {
    numberOfCharsInThisChunk = PL_strlen(q);
    fServerConnection.HandleMessageDownLoadLine(q, false, q);
    PR_Free(q);
  }
  else
    numberOfCharsInThisChunk = 0;

  AdvanceToNextToken();
  bool lastChunk = ((fServerConnection.GetCurFetchSize() == 0) ||
                    (numberOfCharsInThisChunk != fServerConnection.GetCurFetchSize()));
  return lastChunk;
}

/* msg_obsolete    ::= "COPY" / ("STORE" SPACE msg_fetch)
;; OBSOLETE untagged data responses */
void nsImapServerResponseParser::msg_obsolete()
{
  if (!PL_strcasecmp(fNextToken, "COPY"))
    AdvanceToNextToken();
  else if (!PL_strcasecmp(fNextToken, "STORE"))
  {
    AdvanceToNextToken();
    if (ContinueParse())
      msg_fetch();
  }
  else
    SetSyntaxError(true);
}

void nsImapServerResponseParser::capability_data()
{
  int32_t endToken = -1;
  fCapabilityFlag = kCapabilityDefined | kHasAuthOldLoginCapability;
  do {
    AdvanceToNextToken();
    if (fNextToken) {
      nsCString token(fNextToken);
      endToken = token.FindChar(']');
      if (endToken >= 0)
        token.SetLength(endToken);

      if(token.Equals("AUTH=LOGIN", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasAuthLoginCapability;
      else if(token.Equals("AUTH=PLAIN", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasAuthPlainCapability;
      else if (token.Equals("AUTH=CRAM-MD5", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasCRAMCapability;
      else if (token.Equals("AUTH=NTLM", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasAuthNTLMCapability;
      else if (token.Equals("AUTH=GSSAPI", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasAuthGssApiCapability;
      else if (token.Equals("AUTH=MSN", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasAuthMSNCapability;
      else if (token.Equals("AUTH=EXTERNAL", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasAuthExternalCapability;
#ifdef MOZ_MAILNEWS_OAUTH2
      else if (token.Equals("AUTH=XOAUTH2", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasXOAuth2Capability;
#endif
      else if (token.Equals("STARTTLS", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasStartTLSCapability;
      else if (token.Equals("LOGINDISABLED", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag &= ~kHasAuthOldLoginCapability; // remove flag
      else if (token.Equals("XSENDER", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasXSenderCapability;
      else if (token.Equals("IMAP4", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kIMAP4Capability;
      else if (token.Equals("IMAP4rev1", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kIMAP4rev1Capability;
      else if (Substring(token,0,5).Equals("IMAP4", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kIMAP4other;
      else if (token.Equals("X-NO-ATOMIC-RENAME", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kNoHierarchyRename;
      else if (token.Equals("X-NON-HIERARCHICAL-RENAME", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kNoHierarchyRename;
      else if (token.Equals("NAMESPACE", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kNamespaceCapability;
      else if (token.Equals("ID", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasIDCapability;
      else if (token.Equals("ACL", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kACLCapability;
      else if (token.Equals("XSERVERINFO", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kXServerInfoCapability;
      else if (token.Equals("UIDPLUS", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kUidplusCapability;
      else if (token.Equals("LITERAL+", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kLiteralPlusCapability;
      else if (token.Equals("XAOL-OPTION", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kAOLImapCapability;
      else if (token.Equals("X-GM-EXT-1", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kGmailImapCapability;
      else if (token.Equals("QUOTA", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kQuotaCapability;
      else if (token.Equals("LANGUAGE", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasLanguageCapability;
      else if (token.Equals("IDLE", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasIdleCapability;
      else if (token.Equals("CONDSTORE", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasCondStoreCapability;
      else if (token.Equals("ENABLE", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasEnableCapability;
      else if (token.Equals("LIST-EXTENDED", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasListExtendedCapability;
      else if (token.Equals("XLIST", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasXListCapability;
      else if (token.Equals("SPECIAL-USE", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasSpecialUseCapability;
      else if (token.Equals("COMPRESS=DEFLATE", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasCompressDeflateCapability;
      else if (token.Equals("MOVE", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasMoveCapability;
      else if (token.Equals("HIGHESTMODSEQ", nsCaseInsensitiveCStringComparator()))
        fCapabilityFlag |= kHasHighestModSeqCapability;
    }
  } while (fNextToken && endToken < 0 && !fAtEndOfLine && ContinueParse());

  nsImapProtocol *navCon = &fServerConnection;
  NS_ASSERTION(navCon, "null imap protocol connection while parsing capability response");	// we should always have this
  if (navCon)
    navCon->CommitCapability();
  skip_to_CRLF();
}

void nsImapServerResponseParser::xmailboxinfo_data()
{
  AdvanceToNextToken();
  if (!fNextToken)
    return;

  char *mailboxName = CreateAstring(); // PL_strdup(fNextToken);
  if (mailboxName)
  {
    do
    {
      AdvanceToNextToken();
      if (fNextToken)
      {
        if (!PL_strcmp("MANAGEURL", fNextToken))
        {
          AdvanceToNextToken();
          fFolderAdminUrl = CreateAstring();
        }
        else if (!PL_strcmp("POSTURL", fNextToken))
        {
          AdvanceToNextToken();
          // ignore this for now...
        }
      }
    } while (fNextToken && !fAtEndOfLine && ContinueParse());
  }
}

void nsImapServerResponseParser::xserverinfo_data()
{
  do
  {
    AdvanceToNextToken();
    if (!fNextToken)
      break;
    if (!PL_strcmp("MANAGEACCOUNTURL", fNextToken))
    {
      AdvanceToNextToken();
      fMailAccountUrl.Adopt(CreateNilString());
    }
    else if (!PL_strcmp("MANAGELISTSURL", fNextToken))
    {
      AdvanceToNextToken();
      fManageListsUrl.Adopt(CreateNilString());
    }
    else if (!PL_strcmp("MANAGEFILTERSURL", fNextToken))
    {
      AdvanceToNextToken();
      fManageFiltersUrl.Adopt(CreateNilString());
    }
  } while (fNextToken && !fAtEndOfLine && ContinueParse());
}

void nsImapServerResponseParser::enable_data()
{
  do
  {
    // eat each enable response;
     AdvanceToNextToken();
     if (!strcmp("CONDSTORE", fNextToken))
       fCondStoreEnabled = true;
  } while (fNextToken && !fAtEndOfLine && ContinueParse());

}

void nsImapServerResponseParser::language_data()
{
  // we may want to go out and store the language returned to us
  // by the language command in the host info session stuff.

  // for now, just eat the language....
  do
  {
    // eat each language returned to us
    AdvanceToNextToken();
  } while (fNextToken && !fAtEndOfLine && ContinueParse());
}

// cram/auth response data ::= "+" SPACE challenge CRLF
// the server expects more client data after issuing its challenge

void nsImapServerResponseParser::authChallengeResponse_data()
{
  AdvanceToNextToken();
  fAuthChallenge = strdup(fNextToken);
  fWaitingForMoreClientInput = true;

  skip_to_CRLF();
}


void nsImapServerResponseParser::namespace_data()
{
	EIMAPNamespaceType namespaceType = kPersonalNamespace;
	bool namespacesCommitted = false;
  const char* serverKey = fServerConnection.GetImapServerKey();
	while ((namespaceType != kUnknownNamespace) && ContinueParse())
	{
		AdvanceToNextToken();
		while (fAtEndOfLine && ContinueParse())
			AdvanceToNextToken();
		if (!PL_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);

					AdvanceToNextToken();
					const char *quotedDelimiter = fNextToken;
					char namespaceDelimiter = '\0';

					if (quotedDelimiter[0] == '"')
					{
						quotedDelimiter++;
						namespaceDelimiter = quotedDelimiter[0];
					}
					else if (!PL_strncasecmp(quotedDelimiter, "NIL", 3))
					{
						// NIL hierarchy delimiter.  Leave namespace delimiter nullptr.
					}
					else
					{
						// not quoted or NIL.
						SetSyntaxError(true);
					}
					if (ContinueParse())
					{
            // add code to parse the TRANSLATE attribute if it is present....
            // we'll also need to expand the name space code to take in the translated prefix name.

						nsIMAPNamespace *newNamespace = new nsIMAPNamespace(namespaceType, namespacePrefix, namespaceDelimiter, false);
						// add it to a temporary list in the host
						if (newNamespace && fHostSessionList)
							fHostSessionList->AddNewNamespaceForHost(
                                serverKey, newNamespace);

						skip_to_close_paren();	// Ignore any extension data

						bool endOfThisNamespaceType = (fNextToken[0] == ')');
						if (!endOfThisNamespaceType && fNextToken[0] != '(')	// no space between namespaces of the same type
						{
							SetSyntaxError(true);
						}
					}
					PR_Free(namespacePrefix);
				}
			}
		}
		else
		{
			SetSyntaxError(true);
		}
		switch (namespaceType)
		{
		case kPersonalNamespace:
			namespaceType = kOtherUsersNamespace;
			break;
		case kOtherUsersNamespace:
			namespaceType = kPublicNamespace;
			break;
		default:
			namespaceType = kUnknownNamespace;
			break;
		}
	}
	if (ContinueParse())
	{
		nsImapProtocol *navCon = &fServerConnection;
		NS_ASSERTION(navCon, "null protocol connection while parsing namespace");	// we should always have this
		if (navCon)
		{
			navCon->CommitNamespacesForHostEvent();
			namespacesCommitted = true;
		}
	}
	skip_to_CRLF();

	if (!namespacesCommitted && fHostSessionList)
	{
		bool success;
		fHostSessionList->FlushUncommittedNamespacesForHost(serverKey,
                                                            success);
	}
}

void nsImapServerResponseParser::myrights_data(bool unsolicited)
{
  AdvanceToNextToken();
  if (ContinueParse() && !fAtEndOfLine)
  {
    char *mailboxName;
    // an unsolicited myrights response won't have the mailbox name in
    // the response, so we use the selected mailbox name.
    if (unsolicited)
    {
      mailboxName = strdup(fSelectedMailboxName);
    }
    else
    {
      mailboxName = CreateAstring();
      if (mailboxName)
        AdvanceToNextToken();
    }
    if (mailboxName)
    {
      if (ContinueParse())
      {
        char *myrights = CreateAstring();
        if (myrights)
        {
          nsImapProtocol *navCon = &fServerConnection;
          NS_ASSERTION(navCon, "null connection parsing my rights"); // we should always have this
          if (navCon)
            navCon->AddFolderRightsForUser(mailboxName, nullptr /* means "me" */, myrights);
          PR_Free(myrights);
        }
        else
        {
          HandleMemoryFailure();
        }
        if (ContinueParse())
          AdvanceToNextToken();
      }
      PR_Free(mailboxName);
    }
    else
    {
      HandleMemoryFailure();
    }
  }
  else
  {
    SetSyntaxError(true);
  }
}

void nsImapServerResponseParser::acl_data()
{
  AdvanceToNextToken();
  if (ContinueParse() && !fAtEndOfLine)
  {
    char *mailboxName = CreateAstring();	// PL_strdup(fNextToken);
    if (mailboxName && ContinueParse())
    {
      AdvanceToNextToken();
      while (ContinueParse() && !fAtEndOfLine)
      {
        char *userName = CreateAstring(); // PL_strdup(fNextToken);
        if (userName && ContinueParse())
        {
          AdvanceToNextToken();
          if (ContinueParse())
          {
            char *rights = CreateAstring(); // PL_strdup(fNextToken);
            if (rights)
            {
              fServerConnection.AddFolderRightsForUser(mailboxName, userName, rights);
              PR_Free(rights);
            }
            else
              HandleMemoryFailure();

            if (ContinueParse())
              AdvanceToNextToken();
          }
          PR_Free(userName);
        }
        else
          HandleMemoryFailure();
      }
      PR_Free(mailboxName);
    }
    else
      HandleMemoryFailure();
  }
}


void nsImapServerResponseParser::mime_data()
{
  if (PL_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 nsIMAPBodyShell.
// 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 nsImapServerResponseParser::mime_header_data()
{
  char *partNumber = PL_strdup(fNextToken);
  if (partNumber)
  {
    char *start = partNumber + 5, *end = partNumber + 5; // 5 == strlen("BODY[")
    while (ContinueParse() && end && *end != 'M' && *end != 'm')
    {
      end++;
    }
    if (end && (*end == 'M' || *end == 'm'))
    {
      *(end-1) = 0;
      AdvanceToNextToken();
      char *mimeHeaderData = CreateAstring();	// is it really this simple?
      AdvanceToNextToken();
      if (m_shell)
      {
        m_shell->AdoptMimeHeader(start, mimeHeaderData);
      }
    }
    else
    {
      SetSyntaxError(true);
    }
    PR_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 nsImapServerResponseParser::mime_part_data()
{
  char *checkOriginToken = PL_strdup(fNextToken);
  if (checkOriginToken)
  {
    uint32_t origin = 0;
    bool originFound = false;
    char *whereStart = PL_strchr(checkOriginToken, '<');
    if (whereStart)
    {
      char *whereEnd = PL_strchr(whereStart, '>');
      if (whereEnd)
      {
        *whereEnd = 0;
        whereStart++;
        origin = atoi(whereStart);
        originFound = true;
      }
    }
    PR_Free(checkOriginToken);
    AdvanceToNextToken();
    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();
}

// parse FETCH BODYSTRUCTURE response, "a parenthesized list that describes
// the [MIME-IMB] body structure of a message" [RFC 3501].
void nsImapServerResponseParser::bodystructure_data()
{
  AdvanceToNextToken();
  if (ContinueParse() && fNextToken && *fNextToken == '(')  // It has to start with an open paren.
  {
    // Turn the BODYSTRUCTURE response into a form that the nsIMAPBodypartMessage can be constructed from.
    // FIXME: Follow up on bug 384210 to investigate why the caller has to duplicate the two in-param strings.
    nsIMAPBodypartMessage *message =
      new nsIMAPBodypartMessage(NULL, NULL, true, strdup("message"),
                                strdup("rfc822"),
                                NULL, NULL, NULL, 0,
                                fServerConnection.GetPreferPlainText());
    nsIMAPBodypart *body = bodystructure_part(PL_strdup("1"), message);
    if (body)
      message->SetBody(body);
    else
    {
      delete message;
      message = nullptr;
    }
    m_shell = new nsIMAPBodyShell(&fServerConnection, message, CurrentResponseUID(), GetSelectedMailboxName());
    // ignore syntax errors in parsing the body structure response. If there's an error
    // we'll just fall back to fetching the whole message.
    SetSyntaxError(false);
  }
  else
    SetSyntaxError(true);
}

// RFC3501:  body = "(" (body-type-1part / body-type-mpart) ")"
nsIMAPBodypart *
nsImapServerResponseParser::bodystructure_part(char *partNum, nsIMAPBodypart *parentPart)
{
  // Check to see if this buffer is a leaf or container
  // (Look at second character - if an open paren, then it is a container)
  if (*fNextToken != '(')
  {
    NS_ASSERTION(false, "bodystructure_part must begin with '('");
    return NULL;
  }

  if (fNextToken[1] == '(')
    return bodystructure_multipart(partNum, parentPart);
  else
    return bodystructure_leaf(partNum, parentPart);
}

// RFC3501: body-type-1part = (body-type-basic / body-type-msg / body-type-text)
//                            [SP body-ext-1part]
nsIMAPBodypart *
nsImapServerResponseParser::bodystructure_leaf(char *partNum, nsIMAPBodypart *parentPart)
{
  // historical note: this code was originally in nsIMAPBodypartLeaf::ParseIntoObjects()
  char *bodyType = nullptr, *bodySubType = nullptr, *bodyID = nullptr, *bodyDescription = nullptr, *bodyEncoding = nullptr;
  int32_t partLength = 0;
  bool isValid = true;

  // body type  ("application", "text", "image", etc.)
  if (ContinueParse())
  {
    fNextToken++; // eat the first '('
    bodyType = CreateNilString();
    if (ContinueParse())
      AdvanceToNextToken();
  }

  // body subtype  ("gif", "html", etc.)
  if (isValid && ContinueParse())
  {
    bodySubType = CreateNilString();
    if (ContinueParse())
      AdvanceToNextToken();
  }

  // body parameter: parenthesized list
  if (isValid && ContinueParse())
  {
    if (fNextToken[0] == '(')
    {
      fNextToken++;
      skip_to_close_paren();
    }
    else if (!PL_strcasecmp(fNextToken, "NIL"))
      AdvanceToNextToken();
  }

  // body id
  if (isValid && ContinueParse())
  {
    bodyID = CreateNilString();
    if (ContinueParse())
      AdvanceToNextToken();
  }

  // body description
  if (isValid && ContinueParse())
  {
    bodyDescription = CreateNilString();
    if (ContinueParse())
      AdvanceToNextToken();
  }

  // body encoding
  if (isValid && ContinueParse())
  {
    bodyEncoding = CreateNilString();
    if (ContinueParse())
      AdvanceToNextToken();
  }

  // body size
  if (isValid && ContinueParse())
  {
    char *bodySizeString = CreateAtom();
    if (!bodySizeString)
      isValid = false;
    else
    {
      partLength = atoi(bodySizeString);
      PR_Free(bodySizeString);
      if (ContinueParse())
        AdvanceToNextToken();
    }
  }

  if (!isValid || !ContinueParse())
  {
    PR_FREEIF(partNum);
    PR_FREEIF(bodyType);
    PR_FREEIF(bodySubType);
    PR_FREEIF(bodyID);
    PR_FREEIF(bodyDescription);
    PR_FREEIF(bodyEncoding);
  }
  else
  {
    if (PL_strcasecmp(bodyType, "message") || PL_strcasecmp(bodySubType, "rfc822"))
    {
      skip_to_close_paren();
      return new nsIMAPBodypartLeaf(partNum, parentPart, bodyType, bodySubType,
                                    bodyID, bodyDescription, bodyEncoding,
                                    partLength,
                                    fServerConnection.GetPreferPlainText());
    }

    // This part is of type "message/rfc822"  (probably a forwarded message)
    nsIMAPBodypartMessage *message =
      new nsIMAPBodypartMessage(partNum, parentPart, false,
                                bodyType, bodySubType, bodyID, bodyDescription,
                                bodyEncoding, partLength,
                                fServerConnection.GetPreferPlainText());

    // there are three additional fields: envelope structure, bodystructure, and size in lines
    // historical note: this code was originally in nsIMAPBodypartMessage::ParseIntoObjects()

    // envelope (ignored)
    if (*fNextToken == '(')
    {
      fNextToken++;
      skip_to_close_paren();
    }
    else
      isValid = false;

    // bodystructure
    if (isValid && ContinueParse())
    {
      if (*fNextToken != '(')
        isValid = false;
      else
      {
        char *bodyPartNum = PR_smprintf("%s.1", partNum);
        if (bodyPartNum)
        {
          nsIMAPBodypart *body = bodystructure_part(bodyPartNum, message);
          if (body)
            message->SetBody(body);
          else
            isValid = false;
        }
      }
    }

    // ignore "size in text lines"

    if (isValid && ContinueParse()) {
      skip_to_close_paren();
      return message;
    }
    delete message;
  }

  // parsing failed, just move to the end of the parentheses group
  if (ContinueParse())
    skip_to_close_paren();
  return nullptr;
}


// RFC3501:  body-type-mpart = 1*body SP media-subtype
//                             [SP body-ext-mpart]
nsIMAPBodypart *
nsImapServerResponseParser::bodystructure_multipart(char *partNum, nsIMAPBodypart *parentPart)
{
  nsIMAPBodypartMultipart *multipart = new nsIMAPBodypartMultipart(partNum, parentPart);
  bool isValid = multipart->GetIsValid();
  // historical note: this code was originally in nsIMAPBodypartMultipart::ParseIntoObjects()
  if (ContinueParse())
  {
    fNextToken++; // eat the first '('
    // Parse list of children
    int childCount = 0;
    while (isValid && fNextToken[0] == '(' && ContinueParse())
    {
      childCount++;
      char *childPartNum = NULL;
      // note: the multipart constructor does some magic on partNumber
      if (PL_strcmp(multipart->GetPartNumberString(), "0")) // not top-level
        childPartNum = PR_smprintf("%s.%d", multipart->GetPartNumberString(), childCount);
      else // top-level
        childPartNum = PR_smprintf("%d", childCount);
      if (!childPartNum)
        isValid = false;
      else
      {
        nsIMAPBodypart *child = bodystructure_part(childPartNum, multipart);
        if (child)
          multipart->AppendPart(child);
        else
          isValid = false;
      }
    }

    // RFC3501:  media-subtype   = string
    // (multipart subtype: mixed, alternative, etc.)
    if (isValid && ContinueParse())
    {
      char *bodySubType = CreateNilString();
      multipart->SetBodySubType(bodySubType);
      if (ContinueParse())
        AdvanceToNextToken();
    }

    // extension data:
    // RFC3501:  body-ext-mpart = body-fld-param [SP body-fld-dsp [SP body-fld-lang
    //                            [SP body-fld-loc *(SP body-extension)]]]

    // body parameter parenthesized list (optional data), includes boundary parameter
    // RFC3501:  body-fld-param  = "(" string SP string *(SP string SP string) ")" / nil
    char *boundaryData = nullptr;
    if (isValid && ContinueParse() && *fNextToken == '(')
    {
      fNextToken++;
      while (ContinueParse() && *fNextToken != ')')
      {
        char *attribute = CreateNilString();
        if (ContinueParse())
          AdvanceToNextToken();
        if (ContinueParse() && !PL_strcasecmp(attribute, "BOUNDARY"))
        {
          char *boundary = CreateNilString();
          if (boundary)
            boundaryData = PR_smprintf("--%s", boundary);
          PR_FREEIF(boundary);
        }
        else if (ContinueParse())
        {
          char *value = CreateNilString();
          PR_FREEIF(value);
        }
        PR_FREEIF(attribute);
        if (ContinueParse())
          AdvanceToNextToken();
      }
      if (ContinueParse())
        fNextToken++;  // skip closing ')'
    }
    if (boundaryData)
      multipart->SetBoundaryData(boundaryData);
    else
      isValid = false;   // Actually, we should probably generate a boundary here.
  }

  // always move to closing ')', even if part was not successfully read
  if (ContinueParse())
    skip_to_close_paren();

  if (isValid)
    return multipart;
  delete multipart;
  return nullptr;
}


// RFC2087:  quotaroot_response = "QUOTAROOT" SP astring *(SP astring)
//           quota_response = "QUOTA" SP astring SP quota_list
//           quota_list     = "(" [quota_resource *(SP quota_resource)] ")"
//           quota_resource = atom SP number SP number
// Only the STORAGE resource is considered.  The current implementation is
// slightly broken because it assumes that STORAGE is the first resource;
// a reponse   QUOTA (MESSAGE 5 100 STORAGE 10 512)   would be ignored.
void nsImapServerResponseParser::quota_data()
{
  if (!PL_strcasecmp(fNextToken, "QUOTAROOT"))
  {
    // ignore QUOTAROOT response
    nsCString quotaroot;
    AdvanceToNextToken();
    while (ContinueParse() && !fAtEndOfLine)
    {
      quotaroot.Adopt(CreateAstring());
      AdvanceToNextToken();
    }
  }
  else if(!PL_strcasecmp(fNextToken, "QUOTA"))
  {
    uint32_t used, max;
    char *parengroup;

    AdvanceToNextToken();
    if (ContinueParse())
    {
      nsCString quotaroot;
      quotaroot.Adopt(CreateAstring());

      if(ContinueParse() && !fAtEndOfLine)
      {
        AdvanceToNextToken();
        if(fNextToken)
        {
          if(!PL_strcasecmp(fNextToken, "(STORAGE"))
          {
            parengroup = CreateParenGroup();
            if(parengroup && (PR_sscanf(parengroup, "(STORAGE %lu %lu)", &used, &max) == 2) )
            {
              fServerConnection.UpdateFolderQuotaData(quotaroot, used, max);
              skip_to_CRLF();
            }
            else
              SetSyntaxError(true);

            PR_Free(parengroup);
          }
          else
            // Ignore other limits, we just check STORAGE for now
            skip_to_CRLF();
        }
        else
          SetSyntaxError(true);
      }
      else
        HandleMemoryFailure();
    }
  }
  else
    SetSyntaxError(true);
}

void nsImapServerResponseParser::id_data()
{
  AdvanceToNextToken();
  if (!PL_strcasecmp(fNextToken, "NIL"))
    AdvanceToNextToken();
  else
    fServerIdResponse.Adopt(CreateParenGroup());
  skip_to_CRLF();
}

bool nsImapServerResponseParser::GetFillingInShell()
{
  return (m_shell != nullptr);
}

bool nsImapServerResponseParser::GetDownloadingHeaders()
{
  return fDownloadingHeaders;
}

// Tells the server state parser to use a previously cached shell.
void	nsImapServerResponseParser::UseCachedShell(nsIMAPBodyShell *cachedShell)
{
  // We shouldn't already have another shell we're dealing with.
  if (m_shell && cachedShell)
  {
    MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("PARSER: Shell Collision"));
    NS_ASSERTION(false, "shell collision");
  }
  m_shell = cachedShell;
}


void nsImapServerResponseParser::ResetCapabilityFlag()
{
}

/*
   literal ::= "{" number "}" CRLF *CHAR8
   Number represents the number of CHAR8 octets
 */

// Processes a message body, header or message part fetch response. Typically
// the full message, header or part are proccessed in one call (effectively, one
// chunk), and parameter `chunk` is false and `origin` (offset into the
// response) is 0. But under some conditions and larger messages, multiple calls
// will occur to process the message in multiple chunks and parameter `chunk`
// will be true and parameter `origin` will increase by the chunk size from
// initially 0 with each call. This function returns true if this is the last or
// only chunk. This signals the caller that the stream should be closed since
// the message response has been processed.
bool nsImapServerResponseParser::msg_fetch_literal(bool chunk, int32_t origin)
{
  numberOfCharsInThisChunk = atoi(fNextToken + 1);
  // If we didn't request a specific size, or the server isn't returning exactly
  // as many octets as we requested, this must be the last or only chunk
  bool lastChunk = (!chunk ||
                    (numberOfCharsInThisChunk != fServerConnection.GetCurFetchSize()));

  if (lastChunk)
    MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
            ("PARSER: msg_fetch_literal() chunking=%s, requested=%d, receiving=%d",
             chunk ? "true":"false", fServerConnection.GetCurFetchSize(),
             numberOfCharsInThisChunk));

  charsReadSoFar = 0;

  while (ContinueParse() && !fServerConnection.DeathSignalReceived() && (charsReadSoFar < numberOfCharsInThisChunk))
  {
    AdvanceToNextLine();
    if (ContinueParse())
    {
      // When "\r\n" (CRLF) is split across two chunks, the '\n' at the beginning of
      // the next chunk might be set to an empty line consisting only of "\r\n".
      // This is observed while running unit tests with the imap "fake" server.
      // The unexpected '\r' is discarded here. However, with several real world
      // servers tested, e.g., Dovecot, Gmail, Outlook, Yahoo etc., the leading
      // '\r' is not inserted so the beginning line of the next chunk remains
      // just '\n' and no discard is required.
      // In any case, this "orphan" line is ignored and not processed below.
      if (fNextChunkStartsWithNewline && (*fCurrentLine == '\r'))
      {
        // Cause fCurrentLine to point to '\n' which discards the '\r'.
        char *usableCurrentLine = PL_strdup(fCurrentLine + 1);
        PR_Free(fCurrentLine);
        fCurrentLine = usableCurrentLine;
      }

      // strlen() *would* fail on data containing \0, but the above AdvanceToNextLine() in
      // nsMsgLineStreamBuffer::ReadNextLine() we replace '\0' with ' ' (blank) because
      // who cares about binary transparency, and anyway \0 in this context violates RFCs.
      charsReadSoFar += strlen(fCurrentLine);
      if (!fDownloadingHeaders && fCurrentCommandIsSingleMessageFetch)
      {
        fServerConnection.ProgressEventFunctionUsingName("imapDownloadingMessage");
        if (fTotalDownloadSize > 0)
          fServerConnection.PercentProgressUpdateEvent(0, charsReadSoFar + origin, fTotalDownloadSize);
      }
      if (charsReadSoFar > numberOfCharsInThisChunk)
      {
        // This is the last line of a chunk. "Literal" here means actual email data and
        // its EOLs, without imap protocol elements and their EOLs. End of line is
        // defined by two characters \r\n (i.e., CRLF, 0xd,0xa) specified by RFC822.
        // Here is an example the most typical last good line of a chunk:
        // "1s8AA5i4AAvF4QAG6+sAAD0bAPsAAAAA1OAAC)\r\n", where ")\r\n" are non-literals.
        // This an example of the last "good" line of a chunk that terminates with \r\n
        // "FxcA/wAAAALN2gADu80ACS0nAPpVVAD1wNAABF5YAPhAJgD31+QABAAAAP8oMQD+HBwA/umj\r\n"
        // followed by another line of non-literal data:
        // " UID 1004)\r\n". These two are concatenated into a single string pointed to
        // by fCurrentLine.  The extra "non-literal data" on the last chunk line makes
        // the charsReadSoFar greater than numberOfCharsInThisChunk (the configured
        // chunk size).
        // A problem occurs if the \r\n of the long line above is split between
        // chunks and \n is contained in the next chunk. For example, if last
        // lines of chunk X are:
        // "/gAOC/wA/QAAAAAAAAAA8wACCvz+AgIEAAD8/P4ABQUAAPoAAAD+AAEA/voHAAQGBQD/BAQA\r"
        // ")\r\n"
        // and the first two lines of chunk X+1 are:
        // "\n"
        // "APwAAAAAmZkA/wAAAAAREQD/AAAAAquVAAbk8QAHCBAAAPD0AAP5+wABRCoA+0BgAP0AAAAA\r\n"
        // The missing '\n' on the last line of chunk X must be added back and the
        // line consisting only of "\n" in chunk X+1 must be ignored in order to
        // produce the the correct output. This is needed to insure that the signature
        // verification of cryptographically signed emails does not fail due to missing
        // or extra EOL characters. Otherwise, the extra or missing \n or \r  doesn't
        // really matter.
        //
        // Special case observed only with the "fake" imap server used with TB
        // unit test.  When the "\r\n" at the end of a chunk is split as described
        // above, the \n at the beginning of the next chunk may actually be "\r\n"
        // like this example:
        // Last lines of chunk X
        // "/gAOC/wA/QAAAAAAAAAA8wACCvz+AgIEAAD8/P4ABQUAAPoAAAD+AAEA/voHAAQGBQD/BAQA\r"
        // ")\r\n"
        // and the first two lines of chunk X+1:
        // "\r\n"   <-- The code changes this to just "\n" like it should be.
        // "APwAAAAAmZkA/wAAAAAREQD/AAAAAquVAAbk8QAHCBAAAPD0AAP5+wABRCoA+0BgAP0AAAAA\r\n"
        //
        // Implementation:
        // Obtain pointer to last literal in chunk X, e.g., 'C' in 1st example above,
        // or to the \n or \r in the other examples.
        char *displayEndOfLine =
          (fCurrentLine + strlen(fCurrentLine) - (charsReadSoFar - numberOfCharsInThisChunk + 1));
        // Save so original unmodified fCurrentLine is restored below.
        char saveit1 = displayEndOfLine[1];
        char saveit2 = 0;  // Keep compiler happy.
        // Determine if EOL is split such that Chunk X has the \r and chunk
        // X+1 has the \n.
        fNextChunkStartsWithNewline = (displayEndOfLine[0] == '\r');
        if (fNextChunkStartsWithNewline)
        {
          saveit2 = displayEndOfLine[2];
          // Add the missing newline and terminate the string.
          displayEndOfLine[1] = '\n';
          displayEndOfLine[2] = 0;
          // This is a good thing to log.
          MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("PARSER: CR/LF split at chunk boundary"));
        }
        else
        {
          // Typical case where EOLs are not split. Terminate the string.
          displayEndOfLine[1] = 0;
        }
        // Process this modified string pointed to by fCurrentLine.
        fServerConnection.HandleMessageDownLoadLine(fCurrentLine, !lastChunk);
        // Restore fCurrentLine's original content.
        displayEndOfLine[1] = saveit1;
        if (fNextChunkStartsWithNewline)
          displayEndOfLine[2] = saveit2;
      }
      else
      {
        // Not the last line of a chunk.
        bool processTheLine = true;
        if (fNextChunkStartsWithNewline && origin > 0) {
          // A split of the \r\n between chunks was detected. Ignore orphan \n
          // on line by itself which can occur on the first line of a 2nd or
          // later chunk. Line length should be 1 and the only character should
          // be \n. Note: If previous message ended with just \r, don't expect
          // the first chunk of a message (origin == 0) to begin with \n.
          // (Typically, there is only one chunk required for a message or
          // header response unless its size exceeds the chunking threshold.)
          if (strlen(fCurrentLine) > 1 || fCurrentLine[0] != '\n') {
            // In case expected orphan \n is not really there, go ahead and
            // process the line. This should theoretically not occur but rarely,
            // and for yet to be determined reasons, it does. Logging may help.
            NS_WARNING(
                "'\\n' is not the only character in this line as expected!");
            MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
                    ("PARSER: expecting just '\\n' but line is = |%s|",
                     fCurrentLine));
          } else {
            // Discard the line containing only \n.
            processTheLine = false;
            MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
                    ("PARSER: discarding lone '\\n'"));
          }
        }
        if (processTheLine) {
          fServerConnection.HandleMessageDownLoadLine(fCurrentLine,
            !lastChunk && (charsReadSoFar == numberOfCharsInThisChunk),
            fCurrentLine);
        }
        fNextChunkStartsWithNewline = false;
      }
    }
  }

  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.
      AdvanceTokenizerStartingPoint(strlen(fCurrentLine) - (charsReadSoFar - numberOfCharsInThisChunk));
      AdvanceToNextToken();
    }
    else
    {
      skip_to_CRLF();
      AdvanceToNextToken();
    }
  } else {
    // Don't typically (maybe never?) see this.
    fNextChunkStartsWithNewline = false;
  }
  return lastChunk;
}

bool nsImapServerResponseParser::CurrentFolderReadOnly()
{
  return fCurrentFolderReadOnly;
}

int32_t	nsImapServerResponseParser::NumberOfMessages()
{
  return fNumberOfExistingMessages;
}

int32_t nsImapServerResponseParser::NumberOfRecentMessages()
{
  return fNumberOfRecentMessages;
}

int32_t nsImapServerResponseParser::NumberOfUnseenMessages()
{
  return fNumberOfUnseenMessages;
}

int32_t nsImapServerResponseParser::FolderUID()
{
  return fFolderUIDValidity;
}

void nsImapServerResponseParser::SetCurrentResponseUID(uint32_t uid)
{
  if (uid > 0)
    fCurrentResponseUID = uid;
}

uint32_t nsImapServerResponseParser::CurrentResponseUID()
{
  return fCurrentResponseUID;
}

uint32_t nsImapServerResponseParser::HighestRecordedUID()
{
  return fHighestRecordedUID;
}

bool nsImapServerResponseParser::IsNumericString(const char *string)
{
  int i;
  for(i = 0; i < (int) PL_strlen(string); i++)
  {
    if (! isdigit(string[i]))
    {
      return false;
    }
  }

  return true;
}


nsImapMailboxSpec *nsImapServerResponseParser::CreateCurrentMailboxSpec(const char *mailboxName /* = nullptr */)
{
  nsImapMailboxSpec *returnSpec = new nsImapMailboxSpec;
  if (!returnSpec)
  {
    HandleMemoryFailure();
    return  nullptr;
  }
  NS_ADDREF(returnSpec);
  const char *mailboxNameToConvert = (mailboxName) ? mailboxName : fSelectedMailboxName;
  if (mailboxNameToConvert)
  {
    const char *serverKey = fServerConnection.GetImapServerKey();
    nsIMAPNamespace *ns = nullptr;
    if (serverKey && fHostSessionList)
      fHostSessionList->GetNamespaceForMailboxForHost(serverKey, mailboxNameToConvert, ns);	// for
      // delimiter
    returnSpec->mHierarchySeparator = (ns) ? ns->GetDelimiter(): '/';

  }

  returnSpec->mFolderSelected = !mailboxName; // if mailboxName is null, we're doing a Status
  returnSpec->mFolder_UIDVALIDITY = fFolderUIDValidity;
  returnSpec->mHighestModSeq = fHighestModSeq;
  returnSpec->mNumOfMessages = (mailboxName) ? fStatusExistingMessages : fNumberOfExistingMessages;
  returnSpec->mNumOfUnseenMessages = (mailboxName) ? fStatusUnseenMessages : fNumberOfUnseenMessages;
  returnSpec->mNumOfRecentMessages = (mailboxName) ? fStatusRecentMessages : fNumberOfRecentMessages;
  returnSpec->mNextUID = fStatusNextUID;

  returnSpec->mSupportedUserFlags = fSupportsUserDefinedFlags;

  returnSpec->mBoxFlags = kNoFlags;	// stub
  returnSpec->mOnlineVerified = false;	// we're fabricating this.  The flags aren't verified.
  returnSpec->mAllocatedPathName.Assign(mailboxNameToConvert);
  returnSpec->mConnection = &fServerConnection;
  if (returnSpec->mConnection)
  {
    nsIURI * aUrl = nullptr;
    nsresult rv = NS_OK;
    returnSpec->mConnection->GetCurrentUrl()->QueryInterface(NS_GET_IID(nsIURI), (void **) &aUrl);
    if (NS_SUCCEEDED(rv) && aUrl)
      aUrl->GetHost(returnSpec->mHostName);

    NS_IF_RELEASE(aUrl);
  }
  else
    returnSpec->mHostName.Truncate();

  if (fFlagState)
    returnSpec->mFlagState = fFlagState; //copies flag state
  else
    returnSpec->mFlagState = nullptr;

  return returnSpec;

}
// Reset the flag state.
void nsImapServerResponseParser::ResetFlagInfo()
{
  if (fFlagState)
    fFlagState->Reset();
}


bool nsImapServerResponseParser::GetLastFetchChunkReceived()
{
  return fLastChunk;
}

void nsImapServerResponseParser::ClearLastFetchChunkReceived()
{
  fLastChunk = false;
}

void nsImapServerResponseParser::SetHostSessionList(nsIImapHostSessionList*
                                               aHostSessionList)
{
    NS_IF_RELEASE (fHostSessionList);
    fHostSessionList = aHostSessionList;
    NS_IF_ADDREF (fHostSessionList);
}

void nsImapServerResponseParser::SetSyntaxError(bool error, const char *msg)
{
  nsIMAPGenericParser::SetSyntaxError(error, msg);
  if (error)
  {
    if (!fCurrentLine)
    {
      HandleMemoryFailure();
      fServerConnection.Log("PARSER", ("Internal Syntax Error: %s: <no line>"), msg);
    }
    else
    {
      if (!strcmp(fCurrentLine, CRLF))
        fServerConnection.Log("PARSER", "Internal Syntax Error: %s: <CRLF>", msg);
      else
      {
        if (msg)
          fServerConnection.Log("PARSER", "Internal Syntax Error: %s:", msg);
        fServerConnection.Log("PARSER", "Internal Syntax Error on line: %s", fCurrentLine);
      }
    }
  }
}

nsresult nsImapServerResponseParser::BeginMessageDownload(const char *content_type)
{
  nsresult rv = fServerConnection.BeginMessageDownLoad(fSizeOfMostRecentMessage,
    content_type);
  if (NS_FAILED(rv))
  {
    skip_to_CRLF();
    fServerConnection.PseudoInterrupt(true);
    fServerConnection.AbortMessageDownLoad();
  }
  return rv;
}