summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/imap/src/nsImapServerResponseParser.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/imap/src/nsImapServerResponseParser.cpp')
-rw-r--r--comm/mailnews/imap/src/nsImapServerResponseParser.cpp2640
1 files changed, 2640 insertions, 0 deletions
diff --git a/comm/mailnews/imap/src/nsImapServerResponseParser.cpp b/comm/mailnews/imap/src/nsImapServerResponseParser.cpp
new file mode 100644
index 0000000000..a52279b6c9
--- /dev/null
+++ b/comm/mailnews/imap/src/nsImapServerResponseParser.cpp
@@ -0,0 +1,2640 @@
+/* -*- 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 "nsIImapFlagAndUidState.h"
+#include "nsImapNamespace.h"
+#include "nsImapUtils.h"
+#include "nsCRT.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Logging.h"
+
+////////////////// nsImapServerResponseParser /////////////////////////
+
+extern mozilla::LazyLogModule IMAP; // defined in nsImapProtocol.cpp
+
+nsImapServerResponseParser::nsImapServerResponseParser(
+ nsImapProtocol& imapProtocolConnection)
+ : nsImapGenericParser(),
+ fReportingErrors(true),
+ fCurrentFolderReadOnly(false),
+ fCurrentLineContainedFlagInfo(false),
+ fServerIsNetscape3xServer(false),
+ fSeqNumOfFirstUnseenMsg(0),
+ fNumberOfExistingMessages(0),
+ fNumberOfRecentMessages(0),
+ fSizeOfMostRecentMessage(0),
+ fTotalDownloadSize(0),
+ fCurrentCommandTag(nullptr),
+ fSelectedMailboxName(nullptr),
+ fIMAPstate(kNonAuthenticated),
+ fLastChunk(false),
+ fNextChunkStartsWithNewline(false),
+ fServerConnection(imapProtocolConnection),
+ fHostSessionList(nullptr) {
+ fSearchResults = nsImapSearchResultSequence::CreateSearchResultSequence();
+ fFolderAdminUrl = nullptr;
+ fNetscapeServerVersionString = nullptr;
+ fXSenderInfo = nullptr;
+ fSupportsUserDefinedFlags = 0;
+ fSettablePermanentFlags = 0;
+ fCapabilityFlag = kCapabilityUndefined;
+ fLastAlert = nullptr;
+ fDownloadingHeaders = false;
+ fGotPermanentFlags = false;
+ fFolderUIDValidity = 0;
+ fHighestModSeq = 0;
+ fAuthChallenge = nullptr;
+ fStatusUnseenMessages = 0;
+ fStatusRecentMessages = 0;
+ fStatusNextUID = nsMsgKey_None;
+ fNextUID = nsMsgKey_None;
+ fStatusExistingMessages = 0;
+ fReceivedHeaderOrSizeForUID = nsMsgKey_None;
+ fUtf8AcceptEnabled = false;
+ fStdJunkNotJunkUseOk = 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);
+}
+
+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;
+}
+
+bool nsImapServerResponseParser::UntaggedResponse() {
+ return fUntaggedResponse;
+}
+
+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;
+ fUntaggedResponse = false;
+}
+
+// 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 != ' ',
+ "Invalid 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;
+ }
+
+ // When inIdle, only one pass through "do" and "while" below occurs.
+ 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();
+ }
+ // For checking expected response to IDLE command below and
+ // for checking if an untagged response occurred by the caller.
+ fUntaggedResponse = true;
+ }
+
+ // command continuation request [RFC3501, Sec. 7.5]
+ if (ContinueParse() && fNextToken &&
+ *fNextToken == '+') // never pipeline APPEND or AUTHENTICATE
+ {
+ NS_ASSERTION(
+ (fNumberOfTaggedResponsesExpected -
+ numberOfTaggedResponsesReceived) == 1,
+ " didn't get the number of tagged responses we expected");
+ numberOfTaggedResponsesReceived = fNumberOfTaggedResponsesExpected;
+ if (commandToken && !PL_strcasecmp(commandToken, "authenticate") &&
+ placeInTokenString &&
+ (!PL_strncasecmp(placeInTokenString, "CRAM-MD5",
+ strlen("CRAM-MD5")) ||
+ !PL_strncasecmp(placeInTokenString, "NTLM", strlen("NTLM")) ||
+ !PL_strncasecmp(placeInTokenString, "GSSAPI",
+ strlen("GSSAPI")) ||
+ !PL_strncasecmp(placeInTokenString, "MSN", strlen("MSN")))) {
+ // we need to store the challenge from the server if we are using
+ // CRAM-MD5 or NTLM.
+ authChallengeResponse_data();
+ }
+ } else
+ numberOfTaggedResponsesReceived++;
+
+ if (numberOfTaggedResponsesReceived < fNumberOfTaggedResponsesExpected)
+ response_tagged();
+
+ } while (
+ ContinueParse() && !inIdle &&
+ (numberOfTaggedResponsesReceived < fNumberOfTaggedResponsesExpected));
+
+ // check and see if the server is waiting for more input
+ // it's possible that we ate this + while parsing certain responses (like
+ // cram data), in these cases, the parsing routine for that specific
+ // command will manually set fWaitingForMoreClientInput so we don't lose
+ // that information....
+ if ((fNextToken && *fNextToken == '+') || inIdle) {
+ if (inIdle &&
+ !((fNextToken && *fNextToken == '+') || fUntaggedResponse)) {
+ // IDLE "response" + will not be "eaten" as described above since it
+ // is not an authentication response. So if IDLE response does not
+ // begin with '+' (continuation) or '*' (untagged and probably useful
+ // response) then something is wrong and it is probably a tagged
+ // NO or BAD due to transient error or bad configuration of the
+ // server.
+ if (!PL_strcmp(fCurrentCommandTag, fNextToken)) {
+ response_tagged();
+ } else {
+ // Expected tag doesn't match the received tag. Not good, start
+ // over.
+ response_fatal();
+ }
+ // Show an alert notification containing the server response to bad
+ // IDLE.
+ fServerConnection.AlertUserEventFromServer(fCurrentLine, true);
+ } else {
+ fWaitingForMoreClientInput = true;
+ }
+ }
+ // if we aren't still waiting for more input....
+ else if (!fWaitingForMoreClientInput && !aGreetingWithCapability) {
+ if (ContinueParse()) response_done();
+
+ if (ContinueParse() && !CommandFailed()) {
+ // a successful command may change the eIMAPstate
+ ProcessOkCommand(commandToken);
+ } else if (CommandFailed()) {
+ // a failed command may change the eIMAPstate
+ ProcessBadCommand(commandToken);
+ if (fReportingErrors && !aIgnoreBadAndNOResponses)
+ fServerConnection.AlertUserEventFromServer(fCurrentLine, false);
+ }
+ }
+ }
+ } else
+ SetConnected(false);
+}
+
+void nsImapServerResponseParser::HandleMemoryFailure() {
+ fServerConnection.AlertUserEventUsingName("imapOutOfMemory");
+ nsImapGenericParser::HandleMemoryFailure();
+}
+
+// SEARCH is the only command that requires pre-processing for now.
+// others will be added here.
+void nsImapServerResponseParser::PreProcessCommandToken(
+ const char* commandToken, const char* currentCommand) {
+ fCurrentCommandIsSingleMessageFetch = false;
+ fWaitingForMoreClientInput = false;
+
+ if (!PL_strcasecmp(commandToken, "SEARCH"))
+ fSearchResults->ResetSequence();
+ else if (!PL_strcasecmp(commandToken, "SELECT") && currentCommand) {
+ // the mailbox name must be quoted, so strip the quotes
+ const char* openQuote = PL_strchr(currentCommand, '"');
+ NS_ASSERTION(openQuote, "expected open quote in imap server response");
+ if (!openQuote) { // ill formed select command
+ openQuote = PL_strchr(currentCommand, ' ');
+ }
+ PR_Free(fSelectedMailboxName);
+ fSelectedMailboxName = PL_strdup(openQuote + 1);
+ if (fSelectedMailboxName) {
+ // strip the escape chars and the ending quote
+ char* currentChar = fSelectedMailboxName;
+ while (*currentChar) {
+ if (*currentChar == '\\') {
+ PL_strcpy(currentChar, currentChar + 1);
+ currentChar++; // skip what we are escaping
+ } else if (*currentChar == '\"')
+ *currentChar = 0; // end quote
+ else
+ currentChar++;
+ }
+ } else
+ HandleMemoryFailure();
+
+ // we don't want bogus info for this new box
+ // delete fFlagState; // not our object
+ // fFlagState = nullptr;
+ } else if (!PL_strcasecmp(commandToken, "CLOSE")) {
+ return; // just for debugging
+ // we don't want bogus info outside the selected state
+ // delete fFlagState; // not our object
+ // fFlagState = nullptr;
+ } else if (!PL_strcasecmp(commandToken, "UID")) {
+ nsCString copyCurrentCommand(currentCommand);
+ if (!fServerConnection.DeathSignalReceived()) {
+ char* placeInTokenString = copyCurrentCommand.BeginWriting();
+ (void)NS_strtok(WHITESPACE, &placeInTokenString); // skip tag token
+ (void)NS_strtok(WHITESPACE, &placeInTokenString); // skip uid token
+ char* fetchToken = NS_strtok(WHITESPACE, &placeInTokenString);
+ if (!PL_strcasecmp(fetchToken, "FETCH")) {
+ char* uidStringToken = NS_strtok(WHITESPACE, &placeInTokenString);
+ // , and : are uid delimiters
+ if (!PL_strchr(uidStringToken, ',') && !PL_strchr(uidStringToken, ':'))
+ fCurrentCommandIsSingleMessageFetch = true;
+ }
+ }
+ }
+}
+
+const char* nsImapServerResponseParser::GetSelectedMailboxName() {
+ return fSelectedMailboxName;
+}
+
+nsImapSearchResultIterator*
+nsImapServerResponseParser::CreateSearchResultIterator() {
+ return new nsImapSearchResultIterator(*fSearchResults);
+}
+
+nsImapServerResponseParser::eIMAPstate
+nsImapServerResponseParser::GetIMAPstate() {
+ return fIMAPstate;
+}
+
+void nsImapServerResponseParser::PreauthSetAuthenticatedState() {
+ fIMAPstate = kAuthenticated;
+}
+
+void nsImapServerResponseParser::ProcessOkCommand(const char* commandToken) {
+ if (!PL_strcasecmp(commandToken, "LOGIN") ||
+ !PL_strcasecmp(commandToken, "AUTHENTICATE"))
+ fIMAPstate = kAuthenticated;
+ else if (!PL_strcasecmp(commandToken, "LOGOUT"))
+ fIMAPstate = kNonAuthenticated;
+ else if (!PL_strcasecmp(commandToken, "SELECT") ||
+ !PL_strcasecmp(commandToken, "EXAMINE"))
+ fIMAPstate = kFolderSelected;
+ else if (!PL_strcasecmp(commandToken, "CLOSE")) {
+ fIMAPstate = kAuthenticated;
+ // we no longer have a selected mailbox.
+ PR_FREEIF(fSelectedMailboxName);
+ } else if ((!PL_strcasecmp(commandToken, "LIST")) ||
+ (!PL_strcasecmp(commandToken, "LSUB")) ||
+ (!PL_strcasecmp(commandToken, "XLIST"))) {
+ // fServerConnection.MailboxDiscoveryFinished();
+ // This used to be reporting that we were finished
+ // discovering folders for each time we issued a
+ // LIST or LSUB. So if we explicitly listed the
+ // INBOX, or Trash, or namespaces, we would get multiple
+ // "done" states, even though we hadn't finished.
+ // Move this to be called from the connection object
+ // itself.
+ } else if (!PL_strcasecmp(commandToken, "FETCH")) {
+ if (!fZeroLengthMessageUidString.IsEmpty()) {
+ // "Deleting zero length message");
+ fServerConnection.Store(fZeroLengthMessageUidString, "+Flags (\\Deleted)",
+ true);
+ if (LastCommandSuccessful()) fServerConnection.Expunge();
+
+ fZeroLengthMessageUidString.Truncate();
+ }
+ } else if (!PL_strcasecmp(commandToken, "GETQUOTAROOT")) {
+ if (LastCommandSuccessful()) {
+ nsCString str;
+ fServerConnection.UpdateFolderQuotaData(kValidateQuota, str, 0, 0);
+ }
+ }
+}
+
+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
+}
+
+// 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 accommodate 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 these flags.
+ if (fGotPermanentFlags)
+ skip_to_CRLF();
+ else
+ parse_folder_flags(true);
+ } 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) {
+ RefPtr<nsImapMailboxSpec> boxSpec = new nsImapMailboxSpec;
+ 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()) mailbox(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) {
+ 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;
+ }
+ }
+
+ 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);
+ // 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);
+ }
+
+ if (boxname) PL_strfree(boxname);
+}
+
+/*
+ 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, "X-GM-MSGID"_ns,
+ 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, "X-GM-THRID"_ns,
+ 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, "X-GM-LABELS"_ns,
+ 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_strncasecmp(fNextToken, "BODY[TEXT", 9)) {
+ // This parses the "preview" response (first 2048 bytes of body text).
+ mime_part_data(); // Note: TEXT is not an actual mime part.
+ } 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;
+ if (*fNextToken == ')') {
+ SetSyntaxError(true); // envelope too short
+ break;
+ }
+
+ 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.AppendLiteral("To: ");
+ nsAutoCString fakeFromLine("From: "_ns);
+ fakeFromLine.Append(fServerConnection.GetImapUserName());
+ fakeFromLine.AppendLiteral("@aol.com");
+ fServerConnection.HandleMessageDownLoadLine(fakeFromLine.get(),
+ false);
+ } else {
+ fromLine.AppendLiteral("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) {
+ // NOTE: Not sure this function correctly handling group address syntax.
+ // See Bug 1609846.
+ 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();
+ if (mailboxName) {
+ addressLine += mailboxName;
+ }
+ if (hostName) {
+ addressLine += '@';
+ addressLine += hostName;
+ PR_Free(hostName);
+ }
+ if (personalName) {
+ addressLine += " (";
+ addressLine += personalName;
+ addressLine += ')';
+ }
+ }
+ PR_Free(mailboxName);
+ }
+ 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;
+ 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(bool calledForFlags) {
+ uint16_t junkNotJunkFlags = 0;
+
+ do {
+ AdvanceToNextToken();
+ if (*fNextToken == '(') fNextToken++;
+ 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, "\\*", 2)) {
+ // User defined and special keywords (tags) can be defined and set for
+ // mailbox. Should only occur in PERMANENTFLAGS response.
+ fSupportsUserDefinedFlags |= kImapMsgSupportUserFlag;
+ fSupportsUserDefinedFlags |= kImapMsgSupportForwardedFlag;
+ fSupportsUserDefinedFlags |= kImapMsgSupportMDNSentFlag;
+ }
+ // Treat special and built-in $LabelX's as user defined and include
+ // $Junk/$NotJunk too.
+ else if (!PL_strncasecmp(fNextToken, "$MDNSent", 8))
+ fSupportsUserDefinedFlags |= kImapMsgSupportMDNSentFlag;
+ else if (!PL_strncasecmp(fNextToken, "$Forwarded", 10))
+ fSupportsUserDefinedFlags |= kImapMsgSupportForwardedFlag;
+ else if (!PL_strncasecmp(fNextToken, "$Junk", 5))
+ junkNotJunkFlags |= 1;
+ else if (!PL_strncasecmp(fNextToken, "$NotJunk", 8))
+ junkNotJunkFlags |= 2;
+ } while (!fAtEndOfLine && ContinueParse());
+
+ if (fFlagState) fFlagState->OrSupportedUserFlags(fSupportsUserDefinedFlags);
+
+ if (calledForFlags) {
+ // Set true if both "$Junk" and "$NotJunk" appear in FLAGS.
+ fStdJunkNotJunkUseOk = (junkNotJunkFlags == 3);
+ }
+}
+/*
+ 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(false);
+ // 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()) {
+ // Note: As a response code, "UNSEEN" is NOT the number of
+ // unseen/unread messages. It is the lowest sequence number of the first
+ // unseen/unread message in the mailbox.
+ fSeqNumOfFirstUnseenMsg = strtoul(fNextToken, nullptr, 10);
+ AdvanceToNextToken();
+ }
+ } else if (!PL_strcasecmp(fNextToken, "UIDNEXT")) {
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ fNextUID = 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) {
+ 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.
+ // Note: no longer concerned with body shell issues since now we only fetch
+ // full message.
+ if ((!chunk || (origin == 0)) && !GetDownloadingHeaders()) {
+ 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) {
+ // complete the message download
+ if (ContinueParse()) {
+ if (fReceivedHeaderOrSizeForUID == CurrentResponseUID()) {
+ fServerConnection.NormalMessageEndDownload();
+ fReceivedHeaderOrSizeForUID = nsMsgKey_None;
+ } else
+ fReceivedHeaderOrSizeForUID = CurrentResponseUID();
+ } else
+ fServerConnection.AbortMessageDownLoad();
+ }
+}
+
+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();
+}
+
+/*
+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;
+ else if (token.Equals("CLIENTID", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasClientIDCapability;
+ else if (token.Equals("UTF8=ACCEPT",
+ nsCaseInsensitiveCStringComparator) ||
+ token.Equals("UTF8=ONLY", nsCaseInsensitiveCStringComparator))
+ fCapabilityFlag |= kHasUTF8AcceptCapability;
+ }
+ } 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();
+ fServerConnection.SetCapabilityResponseOccurred();
+ }
+ 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 (!PL_strcasecmp("UTF8=ACCEPT", fNextToken)) fUtf8AcceptEnabled = 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.
+
+ // Add it to a temporary list in the host.
+ if (fHostSessionList) {
+ nsImapNamespace* newNamespace = new nsImapNamespace(
+ namespaceType, namespacePrefix, namespaceDelimiter, false);
+ 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();
+ }
+}
+
+// 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
+// draft-melnikov-extra-quota-00 proposes some additions to RFC2087 and
+// improves the documentation. We still only support RFC2087 capability QUOTA
+// and command GETQUOTAROOT and its untagged QUOTAROOT and QUOTA responses.
+void nsImapServerResponseParser::quota_data() {
+ if (!PL_strcasecmp(fNextToken, "QUOTAROOT")) {
+ // Ignore QUOTAROOT response (except to invalidate previously stored data).
+ nsCString quotaroot;
+ AdvanceToNextToken();
+ while (ContinueParse() && !fAtEndOfLine) {
+ quotaroot.Adopt(CreateAstring());
+ AdvanceToNextToken();
+ }
+ // Invalidate any previously stored quota data. Updated QUOTA data follows.
+ fServerConnection.UpdateFolderQuotaData(kInvalidateQuota, quotaroot, 0, 0);
+ } else if (!PL_strcasecmp(fNextToken, "QUOTA")) {
+ // Should have one QUOTA response per QUOTAROOT.
+ uint64_t usage, limit;
+ AdvanceToNextToken();
+ if (ContinueParse()) {
+ nsCString quotaroot;
+ quotaroot.Adopt(CreateAstring());
+ nsCString resource;
+ AdvanceToNextToken();
+ if (fNextToken) {
+ if (fNextToken[0] == '(') fNextToken++;
+ // Should have zero or more "resource|usage|limit" triplet per quotaroot
+ // name. See draft-melnikov-extra-quota-00 for specific examples. Well
+ // known resources are STORAGE (in Kbytes), number of MESSAGEs and
+ // number of MAILBOXes. However, servers typically only set a quota on
+ // STORAGE in KBytes. A mailbox can have multiple quotaroots but
+ // typically only one and with a single resource.
+ while (ContinueParse() && !fAtEndOfLine) {
+ resource.Adopt(CreateAstring());
+ AdvanceToNextToken();
+ usage = atoll(fNextToken);
+ AdvanceToNextToken();
+ nsAutoCString limitToken(fNextToken);
+ if (fNextToken[strlen(fNextToken) - 1] == ')')
+ limitToken.SetLength(strlen(fNextToken) - 1);
+ limit = atoll(limitToken.get());
+ // Some servers don't define a quotaroot name which we displays as
+ // blank.
+ nsCString quotaRootResource(quotaroot);
+ if (!quotaRootResource.IsEmpty()) {
+ quotaRootResource.AppendLiteral(" / ");
+ }
+ quotaRootResource.Append(resource);
+ fServerConnection.UpdateFolderQuotaData(
+ kStoreQuota, quotaRootResource, usage, limit);
+ AdvanceToNextToken();
+ }
+ }
+ }
+ } else {
+ SetSyntaxError(true);
+ }
+}
+
+void nsImapServerResponseParser::id_data() {
+ AdvanceToNextToken();
+ if (!PL_strcasecmp(fNextToken, "NIL"))
+ AdvanceToNextToken();
+ else
+ fServerIdResponse.Adopt(CreateParenGroup());
+ skip_to_CRLF();
+}
+
+bool nsImapServerResponseParser::GetDownloadingHeaders() {
+ return fDownloadingHeaders;
+}
+
+void nsImapServerResponseParser::ResetCapabilityFlag() {}
+
+/*
+ literal ::= "{" number "}" CRLF *CHAR8
+ Number represents the number of CHAR8 octets
+ */
+
+// Processes a message body, header or message part fetch response. Typically
+// the full message, header or part are processed in one call (effectively, one
+// chunk), and parameter `chunk` is false and `origin` (offset into the
+// response) is 0. But under some conditions and larger messages, multiple calls
+// will occur to process the message in multiple chunks and parameter `chunk`
+// will be true and parameter `origin` will increase by the chunk size from
+// initially 0 with each call. This function returns true if this is the last or
+// only chunk. This signals the caller that the stream should be closed since
+// the message response has been processed.
+bool nsImapServerResponseParser::msg_fetch_literal(bool chunk, int32_t origin) {
+ numberOfCharsInThisChunk = atoi(fNextToken + 1);
+ // If we didn't request a specific size, or the server isn't returning exactly
+ // as many octets as we requested, this must be the last or only chunk
+ bool lastChunk = (!chunk || (numberOfCharsInThisChunk !=
+ fServerConnection.GetCurFetchSize()));
+
+ // clang-format off
+ if (lastChunk)
+ MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
+ ("PARSER: msg_fetch_literal() chunking=%s, requested=%d, receiving=%d",
+ chunk ? "true":"false", fServerConnection.GetCurFetchSize(),
+ numberOfCharsInThisChunk));
+ // clang-format on
+
+ charsReadSoFar = 0;
+
+ while (ContinueParse() && !fServerConnection.DeathSignalReceived() &&
+ (charsReadSoFar < numberOfCharsInThisChunk)) {
+ AdvanceToNextLine();
+ if (ContinueParse()) {
+ // When "\r\n" (CRLF) is split across two chunks, the '\n' at the
+ // beginning of the next chunk might be set to an empty line consisting
+ // only of "\r\n". This is observed while running unit tests with the imap
+ // "fake" server. The unexpected '\r' is discarded here. However, with
+ // several real world servers tested, e.g., Dovecot, Gmail, Outlook, Yahoo
+ // etc., the leading
+ // '\r' is not inserted so the beginning line of the next chunk remains
+ // just '\n' and no discard is required.
+ // In any case, this "orphan" line is ignored and not processed below.
+ if (fNextChunkStartsWithNewline && (*fCurrentLine == '\r')) {
+ // Cause fCurrentLine to point to '\n' which discards the '\r'.
+ char* usableCurrentLine = PL_strdup(fCurrentLine + 1);
+ PR_Free(fCurrentLine);
+ fCurrentLine = usableCurrentLine;
+ }
+
+ // strlen() *would* fail on data containing \0, but the above
+ // AdvanceToNextLine() in nsMsgLineStreamBuffer::ReadNextLine() we replace
+ // '\0' with ' ' (blank) because who cares about binary transparency, and
+ // anyway \0 in this context violates RFCs.
+ charsReadSoFar += strlen(fCurrentLine);
+ if (!fDownloadingHeaders && fCurrentCommandIsSingleMessageFetch) {
+ fServerConnection.ProgressEventFunctionUsingName(
+ "imapDownloadingMessage");
+ if (fTotalDownloadSize > 0)
+ fServerConnection.PercentProgressUpdateEvent(
+ ""_ns, u""_ns, charsReadSoFar + origin, fTotalDownloadSize);
+ }
+ if (charsReadSoFar > numberOfCharsInThisChunk) {
+ // This is the last line of a chunk. "Literal" here means actual email
+ // data and its EOLs, without imap protocol elements and their EOLs. End
+ // of line is defined by two characters \r\n (i.e., CRLF, 0xd,0xa)
+ // specified by RFC822. Here is an example the most typical last good
+ // line of a chunk: "1s8AA5i4AAvF4QAG6+sAAD0bAPsAAAAA1OAAC)\r\n", where
+ // ")\r\n" are non-literals. This an example of the last "good" line of
+ // a chunk that terminates with \r\n
+ // "FxcA/wAAAALN2gADu80ACS0nAPpVVAD1wNAABF5YAPhAJgD31+QABAAAAP8oMQD+HBwA/umj\r\n"
+ // followed by another line of non-literal data:
+ // " UID 1004)\r\n". These two are concatenated into a single string
+ // pointed to by fCurrentLine. The extra "non-literal data" on the last
+ // chunk line makes the charsReadSoFar greater than
+ // numberOfCharsInThisChunk (the configured chunk size). A problem
+ // occurs if the \r\n of the long line above is split between chunks and
+ // \n is contained in the next chunk. For example, if last lines of
+ // chunk X are:
+ // "/gAOC/wA/QAAAAAAAAAA8wACCvz+AgIEAAD8/P4ABQUAAPoAAAD+AAEA/voHAAQGBQD/BAQA\r"
+ // ")\r\n"
+ // and the first two lines of chunk X+1 are:
+ // "\n"
+ // "APwAAAAAmZkA/wAAAAAREQD/AAAAAquVAAbk8QAHCBAAAPD0AAP5+wABRCoA+0BgAP0AAAAA\r\n"
+ // The missing '\n' on the last line of chunk X must be added back and
+ // the line consisting only of "\n" in chunk X+1 must be ignored in
+ // order to produce the the correct output. This is needed to insure
+ // that the signature verification of cryptographically signed emails
+ // does not fail due to missing or extra EOL characters. Otherwise, the
+ // extra or missing \n or \r doesn't really matter.
+ //
+ // Special case observed only with the "fake" imap server used with TB
+ // unit test. When the "\r\n" at the end of a chunk is split as
+ // described above, the \n at the beginning of the next chunk may
+ // actually be "\r\n" like this example: Last lines of chunk X
+ // "/gAOC/wA/QAAAAAAAAAA8wACCvz+AgIEAAD8/P4ABQUAAPoAAAD+AAEA/voHAAQGBQD/BAQA\r"
+ // ")\r\n"
+ // and the first two lines of chunk X+1:
+ // "\r\n" <-- The code changes this to just "\n" like it should be.
+ // "APwAAAAAmZkA/wAAAAAREQD/AAAAAquVAAbk8QAHCBAAAPD0AAP5+wABRCoA+0BgAP0AAAAA\r\n"
+ //
+ // Implementation:
+ // Obtain pointer to last literal in chunk X, e.g., 'C' in 1st example
+ // above, or to the \n or \r in the other examples.
+ char* displayEndOfLine =
+ (fCurrentLine + strlen(fCurrentLine) -
+ (charsReadSoFar - numberOfCharsInThisChunk + 1));
+ // Save so original unmodified fCurrentLine is restored below.
+ char saveit1 = displayEndOfLine[1];
+ char saveit2 = 0; // Keep compiler happy.
+ // Determine if EOL is split such that Chunk X has the \r and chunk
+ // X+1 has the \n.
+ fNextChunkStartsWithNewline = (displayEndOfLine[0] == '\r');
+ if (fNextChunkStartsWithNewline) {
+ saveit2 = displayEndOfLine[2];
+ // Add the missing newline and terminate the string.
+ displayEndOfLine[1] = '\n';
+ displayEndOfLine[2] = 0;
+ // This is a good thing to log.
+ MOZ_LOG(IMAP, mozilla::LogLevel::Info,
+ ("PARSER: CR/LF split at chunk boundary"));
+ } else {
+ // Typical case where EOLs are not split. Terminate the string.
+ displayEndOfLine[1] = 0;
+ }
+ // Process this modified string pointed to by fCurrentLine.
+ fServerConnection.HandleMessageDownLoadLine(fCurrentLine, !lastChunk);
+ // Restore fCurrentLine's original content.
+ displayEndOfLine[1] = saveit1;
+ if (fNextChunkStartsWithNewline) displayEndOfLine[2] = saveit2;
+ } else {
+ // Not the last line of a chunk.
+ bool processTheLine = true;
+ if (fNextChunkStartsWithNewline && origin > 0) {
+ // A split of the \r\n between chunks was detected. Ignore orphan \n
+ // on line by itself which can occur on the first line of a 2nd or
+ // later chunk. Line length should be 1 and the only character should
+ // be \n. Note: If previous message ended with just \r, don't expect
+ // the first chunk of a message (origin == 0) to begin with \n.
+ // (Typically, there is only one chunk required for a message or
+ // header response unless its size exceeds the chunking threshold.)
+ if (strlen(fCurrentLine) > 1 || fCurrentLine[0] != '\n') {
+ // In case expected orphan \n is not really there, go ahead and
+ // process the line. This should theoretically not occur but rarely,
+ // and for yet to be determined reasons, it does. Logging may help.
+ NS_WARNING(
+ "'\\n' is not the only character in this line as expected!");
+ MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
+ ("PARSER: expecting just '\\n' but line is = |%s|",
+ fCurrentLine));
+ } else {
+ // Discard the line containing only \n.
+ processTheLine = false;
+ MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
+ ("PARSER: discarding lone '\\n'"));
+ }
+ }
+ if (processTheLine) {
+ fServerConnection.HandleMessageDownLoadLine(
+ fCurrentLine,
+ !lastChunk && (charsReadSoFar == numberOfCharsInThisChunk),
+ fCurrentLine);
+ }
+ fNextChunkStartsWithNewline = false;
+ }
+ }
+ }
+
+ if (ContinueParse()) {
+ if (charsReadSoFar > numberOfCharsInThisChunk) {
+ // move the lexical analyzer state to the end of this message because this
+ // message fetch ends in the middle of this line.
+ AdvanceTokenizerStartingPoint(
+ strlen(fCurrentLine) - (charsReadSoFar - numberOfCharsInThisChunk));
+ AdvanceToNextToken();
+ } else {
+ skip_to_CRLF();
+ AdvanceToNextToken();
+ }
+ } else {
+ // Don't typically (maybe never?) see this.
+ fNextChunkStartsWithNewline = false;
+ }
+ return lastChunk;
+}
+
+bool nsImapServerResponseParser::CurrentFolderReadOnly() {
+ return fCurrentFolderReadOnly;
+}
+
+int32_t nsImapServerResponseParser::NumberOfMessages() {
+ return fNumberOfExistingMessages;
+}
+
+int32_t nsImapServerResponseParser::NumberOfRecentMessages() {
+ return fNumberOfRecentMessages;
+}
+
+int32_t nsImapServerResponseParser::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;
+}
+
+void nsImapServerResponseParser::ResetHighestRecordedUID() {
+ fHighestRecordedUID = 0;
+}
+
+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;
+}
+
+// Capture the mailbox state for folder select/update and for status.
+// If mailboxName is null, we've done imap SELECT; otherwise STATUS.
+already_AddRefed<nsImapMailboxSpec>
+nsImapServerResponseParser::CreateCurrentMailboxSpec(
+ const char* mailboxName /* = nullptr */) {
+ RefPtr<nsImapMailboxSpec> returnSpec = new nsImapMailboxSpec;
+ 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;
+ returnSpec->mFolder_UIDVALIDITY = fFolderUIDValidity;
+ returnSpec->mHighestModSeq = fHighestModSeq;
+ // clang-format off
+ returnSpec->mNumOfMessages =
+ (mailboxName) ? fStatusExistingMessages : fNumberOfExistingMessages;
+ returnSpec->mNumOfUnseenMessages =
+ (mailboxName) ? fStatusUnseenMessages : -1;
+ returnSpec->mNumOfRecentMessages =
+ (mailboxName) ? fStatusRecentMessages : fNumberOfRecentMessages;
+ returnSpec->mNextUID =
+ (mailboxName) ? fStatusNextUID : fNextUID;
+ // clang-format on
+
+ returnSpec->mSupportedUserFlags = fSupportsUserDefinedFlags;
+
+ returnSpec->mBoxFlags = kNoFlags; // stub
+ returnSpec->mOnlineVerified = false; // Fabricated. 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.forget();
+}
+// Reset the flag state.
+void nsImapServerResponseParser::ResetFlagInfo() {
+ if (fFlagState) fFlagState->Reset();
+}
+
+bool nsImapServerResponseParser::GetLastFetchChunkReceived() {
+ return fLastChunk;
+}
+
+void nsImapServerResponseParser::ClearLastFetchChunkReceived() {
+ fLastChunk = false;
+}
+
+int32_t nsImapServerResponseParser::GetNumBytesFetched() {
+ return numberOfCharsInThisChunk;
+}
+
+void nsImapServerResponseParser::ClearNumBytesFetched() {
+ numberOfCharsInThisChunk = 0;
+}
+
+void nsImapServerResponseParser::SetHostSessionList(
+ nsIImapHostSessionList* aHostSessionList) {
+ fHostSessionList = aHostSessionList;
+}
+
+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;
+}