From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../imap/src/nsImapServerResponseParser.cpp | 2640 ++++++++++++++++++++ 1 file changed, 2640 insertions(+) create mode 100644 comm/mailnews/imap/src/nsImapServerResponseParser.cpp (limited to 'comm/mailnews/imap/src/nsImapServerResponseParser.cpp') 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 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 ::= "=?" "?" "?" + "?=" + ;; 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*] ) + "]" + + +*/ +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 ::= / + "\" 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 +nsImapServerResponseParser::CreateCurrentMailboxSpec( + const char* mailboxName /* = nullptr */) { + RefPtr 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: "), + msg); + } else { + if (!strcmp(fCurrentLine, CRLF)) + fServerConnection.Log("PARSER", "Internal Syntax Error: %s: ", + 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; +} -- cgit v1.2.3