summaryrefslogtreecommitdiffstats
path: root/mailnews/imap/src/nsImapServerResponseParser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mailnews/imap/src/nsImapServerResponseParser.cpp')
-rw-r--r--mailnews/imap/src/nsImapServerResponseParser.cpp3360
1 files changed, 3360 insertions, 0 deletions
diff --git a/mailnews/imap/src/nsImapServerResponseParser.cpp b/mailnews/imap/src/nsImapServerResponseParser.cpp
new file mode 100644
index 000000000..faa37cc45
--- /dev/null
+++ b/mailnews/imap/src/nsImapServerResponseParser.cpp
@@ -0,0 +1,3360 @@
+/* -*- 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),
+ 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);
+
+ 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();
+ }
+ }
+
+ // 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)
+ {
+ 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);
+ }
+ }
+ }
+ }
+ 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;
+ else if (token.Equals("AUTH=XOAUTH2", nsCaseInsensitiveCStringComparator()))
+ fCapabilityFlag |= kHasXOAuth2Capability;
+ 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
+*/
+// returns true if this is the last chunk and we should close the stream
+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()));
+
+#ifdef DEBUG
+ if (lastChunk)
+ MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
+ ("PARSER: fetch_literal chunk = %d, requested %d, receiving %d",
+ chunk, fServerConnection.GetCurFetchSize(),
+ numberOfCharsInThisChunk));
+#endif
+
+ charsReadSoFar = 0;
+ static bool lastCRLFwasCRCRLF = false;
+
+ while (ContinueParse() && !fServerConnection.DeathSignalReceived() && (charsReadSoFar < numberOfCharsInThisChunk))
+ {
+ AdvanceToNextLine();
+ if (ContinueParse())
+ {
+ // When we split CRLF across two chunks, AdvanceToNextLine() turns the LF at the
+ // beginning of the next chunk into an empty line ending with CRLF, so discard
+ // that leading CR
+ bool specialLineEnding = false;
+ if (lastCRLFwasCRCRLF && (*fCurrentLine == '\r'))
+ {
+ char *usableCurrentLine = PL_strdup(fCurrentLine + 1);
+ PR_Free(fCurrentLine);
+ fCurrentLine = usableCurrentLine;
+ specialLineEnding = true;
+ }
+
+ // This *would* fail on data containing \0, but down below AdvanceToNextLine() in
+ // nsMsgLineStreamBuffer::ReadNextLine() we replace '\0' with ' ' (blank) because
+ // who cares about binary transparency, and anyways \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)
+ {
+ // The chunk we are receiving doesn't end in CRLF, so the last line includes
+ // the CRLF that comes after the literal
+ char *displayEndOfLine = (fCurrentLine + strlen(fCurrentLine) - (charsReadSoFar - numberOfCharsInThisChunk));
+ char saveit = *displayEndOfLine;
+ *displayEndOfLine = 0;
+ fServerConnection.HandleMessageDownLoadLine(fCurrentLine, specialLineEnding || !lastChunk);
+ *displayEndOfLine = saveit;
+ lastCRLFwasCRCRLF = (*(displayEndOfLine - 1) == '\r');
+ }
+ else
+ {
+ lastCRLFwasCRCRLF = (*(fCurrentLine + strlen(fCurrentLine) - 1) == '\r');
+ fServerConnection.HandleMessageDownLoadLine(fCurrentLine,
+ specialLineEnding || (!lastChunk && (charsReadSoFar == numberOfCharsInThisChunk)),
+ fCurrentLine);
+ }
+ }
+ }
+
+ // This would be a good thing to log.
+ if (lastCRLFwasCRCRLF)
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("PARSER: CR/LF fell on chunk boundary."));
+
+ if (ContinueParse())
+ {
+ if (charsReadSoFar > numberOfCharsInThisChunk)
+ {
+ // move the lexical analyzer state to the end of this message because this message
+ // fetch ends in the middle of this line.
+ AdvanceTokenizerStartingPoint(strlen(fCurrentLine) - (charsReadSoFar - numberOfCharsInThisChunk));
+ AdvanceToNextToken();
+ }
+ else
+ {
+ skip_to_CRLF();
+ AdvanceToNextToken();
+ }
+ }
+ else
+ {
+ lastCRLFwasCRCRLF = 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;
+}