1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "msgCore.h"  // for pre-compiled headers
7 #include "nsMimeTypes.h"
8 #include "nsImapCore.h"
9 #include "nsImapProtocol.h"
10 #include "nsImapServerResponseParser.h"
11 #include "nsImapBodyShell.h"
12 #include "nsIImapFlagAndUidState.h"
13 #include "nsImapNamespace.h"
14 #include "nsImapUtils.h"
15 #include "nsCRT.h"
16 #include "nsMsgUtils.h"
17 #include "mozilla/Logging.h"
18 
19 ////////////////// nsImapServerResponseParser /////////////////////////
20 
21 extern mozilla::LazyLogModule IMAP;  // defined in nsImapProtocol.cpp
22 
nsImapServerResponseParser(nsImapProtocol & imapProtocolConnection)23 nsImapServerResponseParser::nsImapServerResponseParser(
24     nsImapProtocol& imapProtocolConnection)
25     : nsImapGenericParser(),
26       fReportingErrors(true),
27       fCurrentFolderReadOnly(false),
28       fCurrentLineContainedFlagInfo(false),
29       fServerIsNetscape3xServer(false),
30       fNumberOfUnseenMessages(0),
31       fNumberOfExistingMessages(0),
32       fNumberOfRecentMessages(0),
33       fSizeOfMostRecentMessage(0),
34       fTotalDownloadSize(0),
35       fCurrentCommandTag(nullptr),
36       fSelectedMailboxName(nullptr),
37       fIMAPstate(kNonAuthenticated),
38       fLastChunk(false),
39       fNextChunkStartsWithNewline(false),
40       fServerConnection(imapProtocolConnection),
41       fHostSessionList(nullptr) {
42   fSearchResults = nsImapSearchResultSequence::CreateSearchResultSequence();
43   fFolderAdminUrl = nullptr;
44   fNetscapeServerVersionString = nullptr;
45   fXSenderInfo = nullptr;
46   fSupportsUserDefinedFlags = 0;
47   fSettablePermanentFlags = 0;
48   fCapabilityFlag = kCapabilityUndefined;
49   fLastAlert = nullptr;
50   fDownloadingHeaders = false;
51   fGotPermanentFlags = false;
52   fFolderUIDValidity = 0;
53   fHighestModSeq = 0;
54   fAuthChallenge = nullptr;
55   fStatusUnseenMessages = 0;
56   fStatusRecentMessages = 0;
57   fStatusNextUID = nsMsgKey_None;
58   fStatusExistingMessages = 0;
59   fReceivedHeaderOrSizeForUID = nsMsgKey_None;
60   fCondStoreEnabled = false;  // Seems to be unused!
61   fUtf8AcceptEnabled = false;
62   fStdJunkNotJunkUseOk = false;
63 }
64 
~nsImapServerResponseParser()65 nsImapServerResponseParser::~nsImapServerResponseParser() {
66   PR_Free(fCurrentCommandTag);
67   delete fSearchResults;
68   PR_Free(fFolderAdminUrl);
69   PR_Free(fNetscapeServerVersionString);
70   PR_Free(fXSenderInfo);
71   PR_Free(fLastAlert);
72   PR_Free(fSelectedMailboxName);
73   PR_Free(fAuthChallenge);
74 }
75 
LastCommandSuccessful()76 bool nsImapServerResponseParser::LastCommandSuccessful() {
77   return (!CommandFailed() && !fServerConnection.DeathSignalReceived() &&
78           nsImapGenericParser::LastCommandSuccessful());
79 }
80 
81 // returns true if things look ok to continue
GetNextLineForParser(char ** nextLine)82 bool nsImapServerResponseParser::GetNextLineForParser(char** nextLine) {
83   bool rv = true;
84   *nextLine = fServerConnection.CreateNewLineFromSocket();
85   if (fServerConnection.DeathSignalReceived() ||
86       NS_FAILED(fServerConnection.GetConnectionStatus()))
87     rv = false;
88   // we'd really like to try to silently reconnect, but we shouldn't put this
89   // message up just in the interrupt case
90   if (NS_FAILED(fServerConnection.GetConnectionStatus()) &&
91       !fServerConnection.DeathSignalReceived())
92     fServerConnection.AlertUserEventUsingName("imapServerDisconnected");
93   return rv;
94 }
95 
CommandFailed()96 bool nsImapServerResponseParser::CommandFailed() {
97   return fCurrentCommandFailed;
98 }
99 
SetCommandFailed(bool failed)100 void nsImapServerResponseParser::SetCommandFailed(bool failed) {
101   fCurrentCommandFailed = failed;
102 }
103 
SetFlagState(nsIImapFlagAndUidState * state)104 void nsImapServerResponseParser::SetFlagState(nsIImapFlagAndUidState* state) {
105   fFlagState = state;
106 }
107 
SizeOfMostRecentMessage()108 uint32_t nsImapServerResponseParser::SizeOfMostRecentMessage() {
109   return fSizeOfMostRecentMessage;
110 }
111 
112 // Call this when adding a pipelined command to the session
IncrementNumberOfTaggedResponsesExpected(const char * newExpectedTag)113 void nsImapServerResponseParser::IncrementNumberOfTaggedResponsesExpected(
114     const char* newExpectedTag) {
115   fNumberOfTaggedResponsesExpected++;
116   PR_Free(fCurrentCommandTag);
117   fCurrentCommandTag = PL_strdup(newExpectedTag);
118   if (!fCurrentCommandTag) HandleMemoryFailure();
119 }
120 
InitializeState()121 void nsImapServerResponseParser::InitializeState() {
122   fCurrentCommandFailed = false;
123   fNumberOfRecentMessages = 0;
124   fReceivedHeaderOrSizeForUID = nsMsgKey_None;
125 }
126 
127 // RFC3501:  response = *(continue-req / response-data) response-done
128 //           response-data = "*" SP (resp-cond-state / resp-cond-bye /
129 //                           mailbox-data / message-data / capability-data) CRLF
130 //           continue-req    = "+" SP (resp-text / base64) CRLF
ParseIMAPServerResponse(const char * aCurrentCommand,bool aIgnoreBadAndNOResponses,char * aGreetingWithCapability)131 void nsImapServerResponseParser::ParseIMAPServerResponse(
132     const char* aCurrentCommand, bool aIgnoreBadAndNOResponses,
133     char* aGreetingWithCapability) {
134   NS_ASSERTION(aCurrentCommand && *aCurrentCommand != '\r' &&
135                    *aCurrentCommand != '\n' && *aCurrentCommand != ' ',
136                "Invalid command string");
137   bool sendingIdleDone = !strcmp(aCurrentCommand, "DONE" CRLF);
138   if (sendingIdleDone) fWaitingForMoreClientInput = false;
139 
140   // Reinitialize the parser
141   SetConnected(true);
142   SetSyntaxError(false);
143 
144   // Reinitialize our state
145   InitializeState();
146 
147   // the default is to not pipeline
148   fNumberOfTaggedResponsesExpected = 1;
149   int numberOfTaggedResponsesReceived = 0;
150 
151   nsCString copyCurrentCommand(aCurrentCommand);
152   if (!fServerConnection.DeathSignalReceived()) {
153     char* placeInTokenString = nullptr;
154     char* tagToken = nullptr;
155     const char* commandToken = nullptr;
156     bool inIdle = false;
157     if (!sendingIdleDone) {
158       placeInTokenString = copyCurrentCommand.BeginWriting();
159       tagToken = NS_strtok(WHITESPACE, &placeInTokenString);
160       commandToken = NS_strtok(WHITESPACE, &placeInTokenString);
161     } else
162       commandToken = "DONE";
163     if (tagToken) {
164       PR_Free(fCurrentCommandTag);
165       fCurrentCommandTag = PL_strdup(tagToken);
166       if (!fCurrentCommandTag) HandleMemoryFailure();
167       inIdle = commandToken && !strcmp(commandToken, "IDLE");
168     }
169 
170     if (commandToken && ContinueParse())
171       PreProcessCommandToken(commandToken, aCurrentCommand);
172 
173     // For checking expected response to IDLE command below.
174     bool untagged = false;
175 
176     if (ContinueParse()) {
177       ResetLexAnalyzer();
178 
179       if (aGreetingWithCapability) {
180         PR_FREEIF(fCurrentLine);
181         fCurrentLine = aGreetingWithCapability;
182       }
183 
184       do {
185         AdvanceToNextToken();
186 
187         // untagged responses [RFC3501, Sec. 2.2.2]
188         while (ContinueParse() && fNextToken && *fNextToken == '*') {
189           response_data();
190           if (ContinueParse()) {
191             if (!fAtEndOfLine)
192               SetSyntaxError(true);
193             else if (!inIdle && !fCurrentCommandFailed &&
194                      !aGreetingWithCapability)
195               AdvanceToNextToken();
196           }
197           untagged = true;
198         }
199 
200         // command continuation request [RFC3501, Sec. 7.5]
201         if (ContinueParse() && fNextToken &&
202             *fNextToken == '+')  // never pipeline APPEND or AUTHENTICATE
203         {
204           NS_ASSERTION(
205               (fNumberOfTaggedResponsesExpected -
206                numberOfTaggedResponsesReceived) == 1,
207               " didn't get the number of tagged responses we expected");
208           numberOfTaggedResponsesReceived = fNumberOfTaggedResponsesExpected;
209           if (commandToken && !PL_strcasecmp(commandToken, "authenticate") &&
210               placeInTokenString &&
211               (!PL_strncasecmp(placeInTokenString, "CRAM-MD5",
212                                strlen("CRAM-MD5")) ||
213                !PL_strncasecmp(placeInTokenString, "NTLM", strlen("NTLM")) ||
214                !PL_strncasecmp(placeInTokenString, "GSSAPI",
215                                strlen("GSSAPI")) ||
216                !PL_strncasecmp(placeInTokenString, "MSN", strlen("MSN")))) {
217             // we need to store the challenge from the server if we are using
218             // CRAM-MD5 or NTLM.
219             authChallengeResponse_data();
220           }
221         } else
222           numberOfTaggedResponsesReceived++;
223 
224         if (numberOfTaggedResponsesReceived < fNumberOfTaggedResponsesExpected)
225           response_tagged();
226 
227       } while (
228           ContinueParse() && !inIdle &&
229           (numberOfTaggedResponsesReceived < fNumberOfTaggedResponsesExpected));
230 
231       // check and see if the server is waiting for more input
232       // it's possible that we ate this + while parsing certain responses (like
233       // cram data), in these cases, the parsing routine for that specific
234       // command will manually set fWaitingForMoreClientInput so we don't lose
235       // that information....
236       if ((fNextToken && *fNextToken == '+') || inIdle) {
237         if (inIdle && !((fNextToken && *fNextToken == '+') || untagged)) {
238           // IDLE "response" + will not be "eaten" as described above since it
239           // is not an authentication response. So if IDLE response does not
240           // begin with '+' (continuation) or '*' (untagged and probably useful
241           // response) then something is wrong and it is probably a tagged
242           // NO or BAD due to transient error or bad configuration of the
243           // server.
244           if (!PL_strcmp(fCurrentCommandTag, fNextToken)) {
245             response_tagged();
246           } else {
247             // Expected tag doesn't match the received tag. Not good, start
248             // over.
249             response_fatal();
250           }
251           // Show an alert notification containing the server response to bad
252           // IDLE.
253           fServerConnection.AlertUserEventFromServer(fCurrentLine, true);
254         } else {
255           fWaitingForMoreClientInput = true;
256         }
257       }
258       // if we aren't still waiting for more input....
259       else if (!fWaitingForMoreClientInput && !aGreetingWithCapability) {
260         if (ContinueParse()) response_done();
261 
262         if (ContinueParse() && !CommandFailed()) {
263           // a successful command may change the eIMAPstate
264           ProcessOkCommand(commandToken);
265         } else if (CommandFailed()) {
266           // a failed command may change the eIMAPstate
267           ProcessBadCommand(commandToken);
268           if (fReportingErrors && !aIgnoreBadAndNOResponses)
269             fServerConnection.AlertUserEventFromServer(fCurrentLine, false);
270         }
271       }
272     }
273   } else
274     SetConnected(false);
275 }
276 
HandleMemoryFailure()277 void nsImapServerResponseParser::HandleMemoryFailure() {
278   fServerConnection.AlertUserEventUsingName("imapOutOfMemory");
279   nsImapGenericParser::HandleMemoryFailure();
280 }
281 
282 // SEARCH is the only command that requires pre-processing for now.
283 // others will be added here.
PreProcessCommandToken(const char * commandToken,const char * currentCommand)284 void nsImapServerResponseParser::PreProcessCommandToken(
285     const char* commandToken, const char* currentCommand) {
286   fCurrentCommandIsSingleMessageFetch = false;
287   fWaitingForMoreClientInput = false;
288 
289   if (!PL_strcasecmp(commandToken, "SEARCH"))
290     fSearchResults->ResetSequence();
291   else if (!PL_strcasecmp(commandToken, "SELECT") && currentCommand) {
292     // the mailbox name must be quoted, so strip the quotes
293     const char* openQuote = PL_strchr(currentCommand, '"');
294     NS_ASSERTION(openQuote, "expected open quote in imap server response");
295     if (!openQuote) {  // ill formed select command
296       openQuote = PL_strchr(currentCommand, ' ');
297     }
298     PR_Free(fSelectedMailboxName);
299     fSelectedMailboxName = PL_strdup(openQuote + 1);
300     if (fSelectedMailboxName) {
301       // strip the escape chars and the ending quote
302       char* currentChar = fSelectedMailboxName;
303       while (*currentChar) {
304         if (*currentChar == '\\') {
305           PL_strcpy(currentChar, currentChar + 1);
306           currentChar++;  // skip what we are escaping
307         } else if (*currentChar == '\"')
308           *currentChar = 0;  // end quote
309         else
310           currentChar++;
311       }
312     } else
313       HandleMemoryFailure();
314 
315     // we don't want bogus info for this new box
316     // delete fFlagState;  // not our object
317     // fFlagState = nullptr;
318   } else if (!PL_strcasecmp(commandToken, "CLOSE")) {
319     return;  // just for debugging
320     // we don't want bogus info outside the selected state
321     // delete fFlagState;  // not our object
322     // fFlagState = nullptr;
323   } else if (!PL_strcasecmp(commandToken, "UID")) {
324     nsCString copyCurrentCommand(currentCommand);
325     if (!fServerConnection.DeathSignalReceived()) {
326       char* placeInTokenString = copyCurrentCommand.BeginWriting();
327       (void)NS_strtok(WHITESPACE, &placeInTokenString);  // skip tag token
328       (void)NS_strtok(WHITESPACE, &placeInTokenString);  // skip uid token
329       char* fetchToken = NS_strtok(WHITESPACE, &placeInTokenString);
330       if (!PL_strcasecmp(fetchToken, "FETCH")) {
331         char* uidStringToken = NS_strtok(WHITESPACE, &placeInTokenString);
332         // , and : are uid delimiters
333         if (!PL_strchr(uidStringToken, ',') && !PL_strchr(uidStringToken, ':'))
334           fCurrentCommandIsSingleMessageFetch = true;
335       }
336     }
337   }
338 }
339 
GetSelectedMailboxName()340 const char* nsImapServerResponseParser::GetSelectedMailboxName() {
341   return fSelectedMailboxName;
342 }
343 
344 nsImapSearchResultIterator*
CreateSearchResultIterator()345 nsImapServerResponseParser::CreateSearchResultIterator() {
346   return new nsImapSearchResultIterator(*fSearchResults);
347 }
348 
349 nsImapServerResponseParser::eIMAPstate
GetIMAPstate()350 nsImapServerResponseParser::GetIMAPstate() {
351   return fIMAPstate;
352 }
353 
PreauthSetAuthenticatedState()354 void nsImapServerResponseParser::PreauthSetAuthenticatedState() {
355   fIMAPstate = kAuthenticated;
356 }
357 
ProcessOkCommand(const char * commandToken)358 void nsImapServerResponseParser::ProcessOkCommand(const char* commandToken) {
359   if (!PL_strcasecmp(commandToken, "LOGIN") ||
360       !PL_strcasecmp(commandToken, "AUTHENTICATE"))
361     fIMAPstate = kAuthenticated;
362   else if (!PL_strcasecmp(commandToken, "LOGOUT"))
363     fIMAPstate = kNonAuthenticated;
364   else if (!PL_strcasecmp(commandToken, "SELECT") ||
365            !PL_strcasecmp(commandToken, "EXAMINE"))
366     fIMAPstate = kFolderSelected;
367   else if (!PL_strcasecmp(commandToken, "CLOSE")) {
368     fIMAPstate = kAuthenticated;
369     // we no longer have a selected mailbox.
370     PR_FREEIF(fSelectedMailboxName);
371   } else if ((!PL_strcasecmp(commandToken, "LIST")) ||
372              (!PL_strcasecmp(commandToken, "LSUB")) ||
373              (!PL_strcasecmp(commandToken, "XLIST"))) {
374     // fServerConnection.MailboxDiscoveryFinished();
375     // This used to be reporting that we were finished
376     // discovering folders for each time we issued a
377     // LIST or LSUB.  So if we explicitly listed the
378     // INBOX, or Trash, or namespaces, we would get multiple
379     // "done" states, even though we hadn't finished.
380     // Move this to be called from the connection object
381     // itself.
382   } else if (!PL_strcasecmp(commandToken, "FETCH")) {
383     if (!fZeroLengthMessageUidString.IsEmpty()) {
384       // "Deleting zero length message");
385       fServerConnection.Store(fZeroLengthMessageUidString, "+Flags (\\Deleted)",
386                               true);
387       if (LastCommandSuccessful()) fServerConnection.Expunge();
388 
389       fZeroLengthMessageUidString.Truncate();
390     }
391   } else if (!PL_strcasecmp(commandToken, "GETQUOTAROOT")) {
392     if (LastCommandSuccessful()) {
393       nsCString str;
394       fServerConnection.UpdateFolderQuotaData(kValidateQuota, str, 0, 0);
395     }
396   }
397   if (GetFillingInShell()) {
398     // There is a BODYSTRUCTURE response.  Now let's generate the stream...
399     // that is, if we're not doing it already
400     if (!m_shell->IsBeingGenerated()) {
401       nsImapProtocol* navCon = &fServerConnection;
402 
403       char* imapPart = nullptr;
404 
405       fServerConnection.GetCurrentUrl()->GetImapPartToFetch(&imapPart);
406       m_shell->Generate(imapPart);
407       PR_Free(imapPart);
408 
409       if ((navCon && navCon->GetPseudoInterrupted()) ||
410           fServerConnection.DeathSignalReceived()) {
411         // we were pseudointerrupted or interrupted
412         // if it's not in the cache, then we were (pseudo)interrupted while
413         // generating for the first time. Release it.
414         if (!m_shell->IsShellCached()) m_shell = nullptr;
415         navCon->PseudoInterrupt(false);
416       } else if (m_shell->GetIsValid()) {
417         // If we have a valid shell that has not already been cached, then cache
418         // it.
419         if (!m_shell->IsShellCached() &&
420             fHostSessionList)  // cache is responsible for destroying it
421         {
422           MOZ_LOG(IMAP, mozilla::LogLevel::Info,
423                   ("BODYSHELL:  Adding shell to cache."));
424           const char* serverKey = fServerConnection.GetImapServerKey();
425           fHostSessionList->AddShellToCacheForHost(serverKey, m_shell);
426         }
427       }
428       m_shell = nullptr;
429     }
430   }
431 }
432 
ProcessBadCommand(const char * commandToken)433 void nsImapServerResponseParser::ProcessBadCommand(const char* commandToken) {
434   if (!PL_strcasecmp(commandToken, "LOGIN") ||
435       !PL_strcasecmp(commandToken, "AUTHENTICATE"))
436     fIMAPstate = kNonAuthenticated;
437   else if (!PL_strcasecmp(commandToken, "LOGOUT"))
438     fIMAPstate = kNonAuthenticated;  // ??
439   else if (!PL_strcasecmp(commandToken, "SELECT") ||
440            !PL_strcasecmp(commandToken, "EXAMINE"))
441     fIMAPstate = kAuthenticated;  // nothing selected
442   else if (!PL_strcasecmp(commandToken, "CLOSE"))
443     fIMAPstate = kAuthenticated;  // nothing selected
444   if (GetFillingInShell() && !m_shell->IsBeingGenerated()) m_shell = nullptr;
445 }
446 
447 // RFC3501:  response-data = "*" SP (resp-cond-state / resp-cond-bye /
448 //                           mailbox-data / message-data / capability-data) CRLF
449 // These are ``untagged'' responses [RFC3501, Sec. 2.2.2]
450 /*
451  The RFC1730 grammar spec did not allow one symbol look ahead to determine
452  between mailbox_data / message_data so I combined the numeric possibilities
453  of mailbox_data and all of message_data into numeric_mailbox_data.
454 
455  It is assumed that the initial "*" is already consumed before calling this
456  method. The production implemented here is
457          response_data   ::= (resp_cond_state / resp_cond_bye /
458                               mailbox_data / numeric_mailbox_data /
459                               capability_data)
460                               CRLF
461 */
response_data()462 void nsImapServerResponseParser::response_data() {
463   AdvanceToNextToken();
464 
465   if (ContinueParse()) {
466     // Instead of comparing lots of strings and make function calls, try to
467     // pre-flight the possibilities based on the first letter of the token.
468     switch (NS_ToUpper(fNextToken[0])) {
469       case 'O':  // OK
470         if (NS_ToUpper(fNextToken[1]) == 'K')
471           resp_cond_state(false);
472         else
473           SetSyntaxError(true);
474         break;
475       case 'N':  // NO
476         if (NS_ToUpper(fNextToken[1]) == 'O')
477           resp_cond_state(false);
478         else if (!PL_strcasecmp(fNextToken, "NAMESPACE"))
479           namespace_data();
480         else
481           SetSyntaxError(true);
482         break;
483       case 'B':  // BAD
484         if (!PL_strcasecmp(fNextToken, "BAD"))
485           resp_cond_state(false);
486         else if (!PL_strcasecmp(fNextToken, "BYE"))
487           resp_cond_bye();
488         else
489           SetSyntaxError(true);
490         break;
491       case 'F':
492         if (!PL_strcasecmp(fNextToken, "FLAGS"))
493           mailbox_data();
494         else
495           SetSyntaxError(true);
496         break;
497       case 'P':
498         if (PL_strcasecmp(fNextToken, "PERMANENTFLAGS"))
499           mailbox_data();
500         else
501           SetSyntaxError(true);
502         break;
503       case 'L':
504         if (!PL_strcasecmp(fNextToken, "LIST") ||
505             !PL_strcasecmp(fNextToken, "LSUB"))
506           mailbox_data();
507         else if (!PL_strcasecmp(fNextToken, "LANGUAGE"))
508           language_data();
509         else
510           SetSyntaxError(true);
511         break;
512       case 'M':
513         if (!PL_strcasecmp(fNextToken, "MAILBOX"))
514           mailbox_data();
515         else if (!PL_strcasecmp(fNextToken, "MYRIGHTS"))
516           myrights_data(false);
517         else
518           SetSyntaxError(true);
519         break;
520       case 'S':
521         if (!PL_strcasecmp(fNextToken, "SEARCH"))
522           mailbox_data();
523         else if (!PL_strcasecmp(fNextToken, "STATUS")) {
524           AdvanceToNextToken();
525           if (fNextToken) {
526             char* mailboxName = CreateAstring();
527             PL_strfree(mailboxName);
528           }
529           while (ContinueParse() && !fAtEndOfLine) {
530             AdvanceToNextToken();
531             if (!fNextToken) break;
532 
533             if (*fNextToken == '(') fNextToken++;
534             if (!PL_strcasecmp(fNextToken, "UIDNEXT")) {
535               AdvanceToNextToken();
536               if (fNextToken) {
537                 fStatusNextUID = strtoul(fNextToken, nullptr, 10);
538                 // if this token ends in ')', then it is the last token
539                 // else we advance
540                 if (*(fNextToken + strlen(fNextToken) - 1) == ')')
541                   fNextToken += strlen(fNextToken) - 1;
542               }
543             } else if (!PL_strcasecmp(fNextToken, "MESSAGES")) {
544               AdvanceToNextToken();
545               if (fNextToken) {
546                 fStatusExistingMessages = strtoul(fNextToken, nullptr, 10);
547                 // if this token ends in ')', then it is the last token
548                 // else we advance
549                 if (*(fNextToken + strlen(fNextToken) - 1) == ')')
550                   fNextToken += strlen(fNextToken) - 1;
551               }
552             } else if (!PL_strcasecmp(fNextToken, "UNSEEN")) {
553               AdvanceToNextToken();
554               if (fNextToken) {
555                 fStatusUnseenMessages = strtoul(fNextToken, nullptr, 10);
556                 // if this token ends in ')', then it is the last token
557                 // else we advance
558                 if (*(fNextToken + strlen(fNextToken) - 1) == ')')
559                   fNextToken += strlen(fNextToken) - 1;
560               }
561             } else if (!PL_strcasecmp(fNextToken, "RECENT")) {
562               AdvanceToNextToken();
563               if (fNextToken) {
564                 fStatusRecentMessages = strtoul(fNextToken, nullptr, 10);
565                 // if this token ends in ')', then it is the last token
566                 // else we advance
567                 if (*(fNextToken + strlen(fNextToken) - 1) == ')')
568                   fNextToken += strlen(fNextToken) - 1;
569               }
570             } else if (*fNextToken == ')')
571               break;
572             else if (!fAtEndOfLine)
573               SetSyntaxError(true);
574           }
575         } else
576           SetSyntaxError(true);
577         break;
578       case 'C':
579         if (!PL_strcasecmp(fNextToken, "CAPABILITY"))
580           capability_data();
581         else
582           SetSyntaxError(true);
583         break;
584       case 'V':
585         if (!PL_strcasecmp(fNextToken, "VERSION")) {
586           // figure out the version of the Netscape server here
587           PR_FREEIF(fNetscapeServerVersionString);
588           AdvanceToNextToken();
589           if (!fNextToken)
590             SetSyntaxError(true);
591           else {
592             fNetscapeServerVersionString = CreateAstring();
593             AdvanceToNextToken();
594             if (fNetscapeServerVersionString) {
595               fServerIsNetscape3xServer =
596                   (*fNetscapeServerVersionString == '3');
597             }
598           }
599           skip_to_CRLF();
600         } else
601           SetSyntaxError(true);
602         break;
603       case 'A':
604         if (!PL_strcasecmp(fNextToken, "ACL")) {
605           acl_data();
606         } else if (!PL_strcasecmp(fNextToken, "ACCOUNT-URL")) {
607           fMailAccountUrl.Truncate();
608           AdvanceToNextToken();
609           if (!fNextToken)
610             SetSyntaxError(true);
611           else {
612             fMailAccountUrl.Adopt(CreateAstring());
613             AdvanceToNextToken();
614           }
615         } else
616           SetSyntaxError(true);
617         break;
618       case 'E':
619         if (!PL_strcasecmp(fNextToken, "ENABLED")) enable_data();
620         break;
621       case 'X':
622         if (!PL_strcasecmp(fNextToken, "XSERVERINFO"))
623           xserverinfo_data();
624         else if (!PL_strcasecmp(fNextToken, "XMAILBOXINFO"))
625           xmailboxinfo_data();
626         else if (!PL_strcasecmp(fNextToken, "XAOL-OPTION"))
627           skip_to_CRLF();
628         else if (!PL_strcasecmp(fNextToken, "XLIST"))
629           mailbox_data();
630         else {
631           // check if custom command
632           nsAutoCString customCommand;
633           fServerConnection.GetCurrentUrl()->GetCommand(customCommand);
634           if (customCommand.Equals(fNextToken)) {
635             nsAutoCString customCommandResponse;
636             while (Connected() && !fAtEndOfLine) {
637               AdvanceToNextToken();
638               customCommandResponse.Append(fNextToken);
639               customCommandResponse.Append(' ');
640             }
641             fServerConnection.GetCurrentUrl()->SetCustomCommandResult(
642                 customCommandResponse);
643           } else
644             SetSyntaxError(true);
645         }
646         break;
647       case 'Q':
648         if (!PL_strcasecmp(fNextToken, "QUOTAROOT") ||
649             !PL_strcasecmp(fNextToken, "QUOTA"))
650           quota_data();
651         else
652           SetSyntaxError(true);
653         break;
654       case 'I':
655         id_data();
656         break;
657       default:
658         if (IsNumericString(fNextToken))
659           numeric_mailbox_data();
660         else
661           SetSyntaxError(true);
662         break;
663     }
664 
665     if (ContinueParse()) PostProcessEndOfLine();
666   }
667 }
668 
PostProcessEndOfLine()669 void nsImapServerResponseParser::PostProcessEndOfLine() {
670   // for now we only have to do one thing here
671   // a fetch response to a 'uid store' command might return the flags
672   // before it returns the uid of the message.  So we need both before
673   // we report the new flag info to the front end
674 
675   // also check and be sure that there was a UID in the current response
676   if (fCurrentLineContainedFlagInfo && CurrentResponseUID()) {
677     fCurrentLineContainedFlagInfo = false;
678     nsCString customFlags;
679     fFlagState->GetCustomFlags(CurrentResponseUID(),
680                                getter_Copies(customFlags));
681     fServerConnection.NotifyMessageFlags(fSavedFlagInfo, customFlags,
682                                          CurrentResponseUID(), fHighestModSeq);
683   }
684 }
685 
686 /*
687  mailbox_data    ::=  "FLAGS" SPACE flag_list /
688                                "LIST" SPACE mailbox_list /
689                                "LSUB" SPACE mailbox_list /
690                                "XLIST" SPACE mailbox_list /
691                                "MAILBOX" SPACE text /
692                                "SEARCH" [SPACE 1#nz_number] /
693                                number SPACE "EXISTS" / number SPACE "RECENT"
694 
695 This production was changed to accommodate predictive parsing
696 
697  mailbox_data    ::=  "FLAGS" SPACE flag_list /
698                                "LIST" SPACE mailbox_list /
699                                "LSUB" SPACE mailbox_list /
700                                "XLIST" SPACE mailbox_list /
701                                "MAILBOX" SPACE text /
702                                "SEARCH" [SPACE 1#nz_number]
703 */
mailbox_data()704 void nsImapServerResponseParser::mailbox_data() {
705   if (!PL_strcasecmp(fNextToken, "FLAGS")) {
706     // this handles the case where we got the permanent flags response
707     // before the flags response, in which case, we want to ignore these flags.
708     if (fGotPermanentFlags)
709       skip_to_CRLF();
710     else
711       parse_folder_flags(true);
712   } else if (!PL_strcasecmp(fNextToken, "LIST") ||
713              !PL_strcasecmp(fNextToken, "XLIST")) {
714     AdvanceToNextToken();
715     if (ContinueParse()) mailbox_list(false);
716   } else if (!PL_strcasecmp(fNextToken, "LSUB")) {
717     AdvanceToNextToken();
718     if (ContinueParse()) mailbox_list(true);
719   } else if (!PL_strcasecmp(fNextToken, "MAILBOX"))
720     skip_to_CRLF();
721   else if (!PL_strcasecmp(fNextToken, "SEARCH")) {
722     fSearchResults->AddSearchResultLine(fCurrentLine);
723     fServerConnection.NotifySearchHit(fCurrentLine);
724     skip_to_CRLF();
725   }
726 }
727 
728 /*
729  mailbox_list    ::= "(" #("\Marked" / "\Noinferiors" /
730                               "\Noselect" / "\Unmarked" / flag_extension) ")"
731                               SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox
732 */
733 
mailbox_list(bool discoveredFromLsub)734 void nsImapServerResponseParser::mailbox_list(bool discoveredFromLsub) {
735   RefPtr<nsImapMailboxSpec> boxSpec = new nsImapMailboxSpec;
736   boxSpec->mFolderSelected = false;
737   boxSpec->mBoxFlags = kNoFlags;
738   boxSpec->mAllocatedPathName.Truncate();
739   boxSpec->mHostName.Truncate();
740   boxSpec->mConnection = &fServerConnection;
741   boxSpec->mFlagState = nullptr;
742   boxSpec->mDiscoveredFromLsub = discoveredFromLsub;
743   boxSpec->mOnlineVerified = true;
744   boxSpec->mBoxFlags &= ~kNameSpace;
745 
746   bool endOfFlags = false;
747   fNextToken++;  // eat the first "("
748   do {
749     if (!PL_strncasecmp(fNextToken, "\\Marked", 7))
750       boxSpec->mBoxFlags |= kMarked;
751     else if (!PL_strncasecmp(fNextToken, "\\Unmarked", 9))
752       boxSpec->mBoxFlags |= kUnmarked;
753     else if (!PL_strncasecmp(fNextToken, "\\Noinferiors", 12)) {
754       boxSpec->mBoxFlags |= kNoinferiors;
755       // RFC 5258 \Noinferiors implies \HasNoChildren
756       if (fCapabilityFlag & kHasListExtendedCapability)
757         boxSpec->mBoxFlags |= kHasNoChildren;
758     } else if (!PL_strncasecmp(fNextToken, "\\Noselect", 9))
759       boxSpec->mBoxFlags |= kNoselect;
760     else if (!PL_strncasecmp(fNextToken, "\\Drafts", 7))
761       boxSpec->mBoxFlags |= kImapDrafts;
762     else if (!PL_strncasecmp(fNextToken, "\\Trash", 6))
763       boxSpec->mBoxFlags |= kImapXListTrash;
764     else if (!PL_strncasecmp(fNextToken, "\\Sent", 5))
765       boxSpec->mBoxFlags |= kImapSent;
766     else if (!PL_strncasecmp(fNextToken, "\\Spam", 5) ||
767              !PL_strncasecmp(fNextToken, "\\Junk", 5))
768       boxSpec->mBoxFlags |= kImapSpam;
769     else if (!PL_strncasecmp(fNextToken, "\\Archive", 8))
770       boxSpec->mBoxFlags |= kImapArchive;
771     else if (!PL_strncasecmp(fNextToken, "\\All", 4) ||
772              !PL_strncasecmp(fNextToken, "\\AllMail", 8))
773       boxSpec->mBoxFlags |= kImapAllMail;
774     else if (!PL_strncasecmp(fNextToken, "\\Inbox", 6))
775       boxSpec->mBoxFlags |= kImapInbox;
776     else if (!PL_strncasecmp(fNextToken, "\\NonExistent", 11)) {
777       boxSpec->mBoxFlags |= kNonExistent;
778       // RFC 5258 \NonExistent implies \Noselect
779       boxSpec->mBoxFlags |= kNoselect;
780     } else if (!PL_strncasecmp(fNextToken, "\\Subscribed", 10))
781       boxSpec->mBoxFlags |= kSubscribed;
782     else if (!PL_strncasecmp(fNextToken, "\\Remote", 6))
783       boxSpec->mBoxFlags |= kRemote;
784     else if (!PL_strncasecmp(fNextToken, "\\HasChildren", 11))
785       boxSpec->mBoxFlags |= kHasChildren;
786     else if (!PL_strncasecmp(fNextToken, "\\HasNoChildren", 13))
787       boxSpec->mBoxFlags |= kHasNoChildren;
788     // we ignore flag other extensions
789 
790     endOfFlags = *(fNextToken + strlen(fNextToken) - 1) == ')';
791     AdvanceToNextToken();
792   } while (!endOfFlags && ContinueParse());
793 
794   if (ContinueParse()) {
795     if (*fNextToken == '"') {
796       fNextToken++;
797       if (*fNextToken == '\\')  // handle escaped char
798         boxSpec->mHierarchySeparator = *(fNextToken + 1);
799       else
800         boxSpec->mHierarchySeparator = *fNextToken;
801     } else  // likely NIL.  Discovered late in 4.02 that we do not handle
802             // literals here (e.g. {10} <10 chars>), although this is almost
803             // impossibly unlikely
804       boxSpec->mHierarchySeparator = kOnlineHierarchySeparatorNil;
805     AdvanceToNextToken();
806     if (ContinueParse()) mailbox(boxSpec);
807   }
808 }
809 
810 /* mailbox         ::= "INBOX" / astring
811  */
mailbox(nsImapMailboxSpec * boxSpec)812 void nsImapServerResponseParser::mailbox(nsImapMailboxSpec* boxSpec) {
813   char* boxname = nullptr;
814   const char* serverKey = fServerConnection.GetImapServerKey();
815   bool xlistInbox = boxSpec->mBoxFlags & kImapInbox;
816 
817   if (!PL_strcasecmp(fNextToken, "INBOX") || xlistInbox) {
818     boxname = PL_strdup("INBOX");
819     if (xlistInbox) PR_Free(CreateAstring());
820     AdvanceToNextToken();
821   } else {
822     boxname = CreateAstring();
823     AdvanceToNextToken();
824   }
825 
826   if (boxname && fHostSessionList) {
827     fHostSessionList->SetNamespaceHierarchyDelimiterFromMailboxForHost(
828         serverKey, boxname, boxSpec->mHierarchySeparator);
829 
830     nsImapNamespace* ns = nullptr;
831     fHostSessionList->GetNamespaceForMailboxForHost(serverKey, boxname, ns);
832     if (ns) {
833       switch (ns->GetType()) {
834         case kPersonalNamespace:
835           boxSpec->mBoxFlags |= kPersonalMailbox;
836           break;
837         case kPublicNamespace:
838           boxSpec->mBoxFlags |= kPublicMailbox;
839           break;
840         case kOtherUsersNamespace:
841           boxSpec->mBoxFlags |= kOtherUsersMailbox;
842           break;
843         default:  // (kUnknownNamespace)
844           break;
845       }
846       boxSpec->mNamespaceForFolder = ns;
847     }
848   }
849 
850   if (!boxname) {
851     if (!fServerConnection.DeathSignalReceived()) HandleMemoryFailure();
852   } else if (boxSpec->mConnection && boxSpec->mConnection->GetCurrentUrl()) {
853     boxSpec->mConnection->GetCurrentUrl()->AllocateCanonicalPath(
854         boxname, boxSpec->mHierarchySeparator,
855         getter_Copies(boxSpec->mAllocatedPathName));
856     nsIURI* aURL = nullptr;
857     boxSpec->mConnection->GetCurrentUrl()->QueryInterface(NS_GET_IID(nsIURI),
858                                                           (void**)&aURL);
859     if (aURL) aURL->GetHost(boxSpec->mHostName);
860 
861     NS_IF_RELEASE(aURL);
862     // storage for the boxSpec is now owned by server connection
863     fServerConnection.DiscoverMailboxSpec(boxSpec);
864 
865     // if this was cancelled by the user,then we sure don't want to
866     // send more mailboxes their way
867     if (NS_FAILED(fServerConnection.GetConnectionStatus())) SetConnected(false);
868   }
869 
870   if (boxname) PL_strfree(boxname);
871 }
872 
873 /*
874  message_data    ::= nz_number SPACE ("EXPUNGE" /
875                               ("FETCH" SPACE msg_fetch) / msg_obsolete)
876 
877 was changed to
878 
879 numeric_mailbox_data ::=  number SPACE "EXISTS" / number SPACE "RECENT"
880  / nz_number SPACE ("EXPUNGE" /
881                               ("FETCH" SPACE msg_fetch) / msg_obsolete)
882 
883 */
numeric_mailbox_data()884 void nsImapServerResponseParser::numeric_mailbox_data() {
885   int32_t tokenNumber = atoi(fNextToken);
886   AdvanceToNextToken();
887 
888   if (ContinueParse()) {
889     if (!PL_strcasecmp(fNextToken, "FETCH")) {
890       fFetchResponseIndex = tokenNumber;
891       AdvanceToNextToken();
892       if (ContinueParse()) msg_fetch();
893     } else if (!PL_strcasecmp(fNextToken, "EXISTS")) {
894       fNumberOfExistingMessages = tokenNumber;
895       AdvanceToNextToken();
896     } else if (!PL_strcasecmp(fNextToken, "RECENT")) {
897       fNumberOfRecentMessages = tokenNumber;
898       AdvanceToNextToken();
899     } else if (!PL_strcasecmp(fNextToken, "EXPUNGE")) {
900       if (!fServerConnection.GetIgnoreExpunges())
901         fFlagState->ExpungeByIndex((uint32_t)tokenNumber);
902       skip_to_CRLF();
903     } else
904       msg_obsolete();
905   }
906 }
907 
908 /*
909 msg_fetch       ::= "(" 1#("BODY" SPACE body /
910 "BODYSTRUCTURE" SPACE body /
911 "BODY[" section "]" SPACE nstring /
912 "ENVELOPE" SPACE envelope /
913 "FLAGS" SPACE "(" #(flag / "\Recent") ")" /
914 "INTERNALDATE" SPACE date_time /
915 "MODSEQ" SPACE "(" nz_number ")" /
916 "RFC822" [".HEADER" / ".TEXT"] SPACE nstring /
917 "RFC822.SIZE" SPACE number /
918 "UID" SPACE uniqueid) ")"
919 
920 */
921 
msg_fetch()922 void nsImapServerResponseParser::msg_fetch() {
923   bool bNeedEndMessageDownload = false;
924 
925   // we have not seen a uid response or flags for this fetch, yet
926   fCurrentResponseUID = 0;
927   fCurrentLineContainedFlagInfo = false;
928   fSizeOfMostRecentMessage = 0;
929   // show any incremental progress, for instance, for header downloading
930   fServerConnection.ShowProgress();
931 
932   fNextToken++;  // eat the '(' character
933 
934   // some of these productions are ignored for now
935   while (ContinueParse() && (*fNextToken != ')')) {
936     if (!PL_strcasecmp(fNextToken, "FLAGS")) {
937       if (fCurrentResponseUID == 0)
938         fFlagState->GetUidOfMessage(fFetchResponseIndex - 1,
939                                     &fCurrentResponseUID);
940 
941       AdvanceToNextToken();
942       if (ContinueParse()) flags();
943 
944       if (ContinueParse()) {  // eat the closing ')'
945         fNextToken++;
946         // there may be another ')' to close out
947         // msg_fetch.  If there is then don't advance
948         if (*fNextToken != ')') AdvanceToNextToken();
949       }
950     } else if (!PL_strcasecmp(fNextToken, "UID")) {
951       AdvanceToNextToken();
952       if (ContinueParse()) {
953         fCurrentResponseUID = strtoul(fNextToken, nullptr, 10);
954         if (fCurrentResponseUID > fHighestRecordedUID)
955           fHighestRecordedUID = fCurrentResponseUID;
956         // size came before UID
957         if (fSizeOfMostRecentMessage)
958           fReceivedHeaderOrSizeForUID = CurrentResponseUID();
959         // if this token ends in ')', then it is the last token
960         // else we advance
961         char lastTokenChar = *(fNextToken + strlen(fNextToken) - 1);
962         if (lastTokenChar == ')')
963           fNextToken += strlen(fNextToken) - 1;
964         else if (lastTokenChar < '0' || lastTokenChar > '9') {
965           // GIANT HACK
966           // this is a corrupt uid - see if it's pre 5.08 Zimbra omitting
967           // a space between the UID and MODSEQ
968           if (strlen(fNextToken) > 6 &&
969               !strcmp("MODSEQ", fNextToken + strlen(fNextToken) - 6))
970             fNextToken += strlen(fNextToken) - 6;
971         } else
972           AdvanceToNextToken();
973       }
974     } else if (!PL_strcasecmp(fNextToken, "MODSEQ")) {
975       AdvanceToNextToken();
976       if (ContinueParse()) {
977         fNextToken++;  // eat '('
978         uint64_t modSeq = ParseUint64Str(fNextToken);
979         if (modSeq > fHighestModSeq) fHighestModSeq = modSeq;
980 
981         if (PL_strcasestr(fNextToken, ")")) {
982           // eat token chars until we get the ')'
983           fNextToken = strchr(fNextToken, ')');
984           if (fNextToken) {
985             fNextToken++;
986             if (*fNextToken != ')') AdvanceToNextToken();
987           } else
988             SetSyntaxError(true);
989         } else {
990           SetSyntaxError(true);
991         }
992       }
993     } else if (!PL_strcasecmp(fNextToken, "RFC822") ||
994                !PL_strcasecmp(fNextToken, "RFC822.HEADER") ||
995                !PL_strncasecmp(fNextToken, "BODY[HEADER", 11) ||
996                !PL_strncasecmp(fNextToken, "BODY[]", 6) ||
997                !PL_strcasecmp(fNextToken, "RFC822.TEXT") ||
998                (!PL_strncasecmp(fNextToken, "BODY[", 5) &&
999                 PL_strstr(fNextToken, "HEADER"))) {
1000       if (fCurrentResponseUID == 0)
1001         fFlagState->GetUidOfMessage(fFetchResponseIndex - 1,
1002                                     &fCurrentResponseUID);
1003 
1004       if (!PL_strcasecmp(fNextToken, "RFC822.HEADER") ||
1005           !PL_strcasecmp(fNextToken, "BODY[HEADER]")) {
1006         // all of this message's headers
1007         AdvanceToNextToken();
1008         fDownloadingHeaders = true;
1009         BeginMessageDownload(MESSAGE_RFC822);  // initialize header parser
1010         bNeedEndMessageDownload = false;
1011         if (ContinueParse()) msg_fetch_headers(nullptr);
1012       } else if (!PL_strncasecmp(fNextToken, "BODY[HEADER.FIELDS", 19)) {
1013         fDownloadingHeaders = true;
1014         BeginMessageDownload(MESSAGE_RFC822);  // initialize header parser
1015         // specific message header fields
1016         while (ContinueParse() && fNextToken[strlen(fNextToken) - 1] != ']')
1017           AdvanceToNextToken();
1018         if (ContinueParse()) {
1019           bNeedEndMessageDownload = false;
1020           AdvanceToNextToken();
1021           if (ContinueParse()) msg_fetch_headers(nullptr);
1022         }
1023       } else {
1024         char* whereHeader = PL_strstr(fNextToken, "HEADER");
1025         if (whereHeader) {
1026           const char* startPartNum = fNextToken + 5;
1027           if (whereHeader > startPartNum) {
1028             int32_t partLength =
1029                 whereHeader - startPartNum - 1;  //-1 for the dot!
1030             char* partNum = (char*)PR_CALLOC((partLength + 1) * sizeof(char));
1031             if (partNum) {
1032               PL_strncpy(partNum, startPartNum, partLength);
1033               if (ContinueParse()) {
1034                 if (PL_strstr(fNextToken, "FIELDS")) {
1035                   while (ContinueParse() &&
1036                          fNextToken[strlen(fNextToken) - 1] != ']')
1037                     AdvanceToNextToken();
1038                 }
1039                 if (ContinueParse()) {
1040                   AdvanceToNextToken();
1041                   if (ContinueParse()) msg_fetch_headers(partNum);
1042                 }
1043               }
1044               PR_Free(partNum);
1045             }
1046           } else
1047             SetSyntaxError(true);
1048         } else {
1049           fDownloadingHeaders = false;
1050 
1051           bool chunk = false;
1052           int32_t origin = 0;
1053           if (!PL_strncasecmp(fNextToken, "BODY[]<", 7)) {
1054             char* tokenCopy = 0;
1055             tokenCopy = PL_strdup(fNextToken);
1056             if (tokenCopy) {
1057               char* originString =
1058                   tokenCopy + 7;  // where the byte number starts
1059               char* closeBracket = PL_strchr(tokenCopy, '>');
1060               if (closeBracket && originString && *originString) {
1061                 *closeBracket = 0;
1062                 origin = atoi(originString);
1063                 chunk = true;
1064               }
1065               PR_Free(tokenCopy);
1066             }
1067           }
1068 
1069           AdvanceToNextToken();
1070           if (ContinueParse()) {
1071             msg_fetch_content(chunk, origin, MESSAGE_RFC822);
1072           }
1073         }
1074       }
1075     } else if (!PL_strcasecmp(fNextToken, "RFC822.SIZE") ||
1076                !PL_strcasecmp(fNextToken, "XAOL.SIZE")) {
1077       AdvanceToNextToken();
1078       if (ContinueParse()) {
1079         bool sendEndMsgDownload =
1080             (GetDownloadingHeaders() &&
1081              fReceivedHeaderOrSizeForUID == CurrentResponseUID());
1082         fSizeOfMostRecentMessage = strtoul(fNextToken, nullptr, 10);
1083         fReceivedHeaderOrSizeForUID = CurrentResponseUID();
1084         if (sendEndMsgDownload) {
1085           fServerConnection.NormalMessageEndDownload();
1086           fReceivedHeaderOrSizeForUID = nsMsgKey_None;
1087         }
1088 
1089         if (fSizeOfMostRecentMessage == 0 && CurrentResponseUID()) {
1090           // on no, bogus Netscape 2.0 mail server bug
1091           char uidString[100];
1092           sprintf(uidString, "%ld", (long)CurrentResponseUID());
1093 
1094           if (!fZeroLengthMessageUidString.IsEmpty())
1095             fZeroLengthMessageUidString += ",";
1096 
1097           fZeroLengthMessageUidString += uidString;
1098         }
1099 
1100         // if this token ends in ')', then it is the last token
1101         // else we advance
1102         if (*(fNextToken + strlen(fNextToken) - 1) == ')')
1103           fNextToken += strlen(fNextToken) - 1;
1104         else
1105           AdvanceToNextToken();
1106       }
1107     } else if (!PL_strcasecmp(fNextToken, "XSENDER")) {
1108       PR_FREEIF(fXSenderInfo);
1109       AdvanceToNextToken();
1110       if (!fNextToken)
1111         SetSyntaxError(true);
1112       else {
1113         fXSenderInfo = CreateAstring();
1114         AdvanceToNextToken();
1115       }
1116     } else if (!PL_strcasecmp(fNextToken, "X-GM-MSGID")) {
1117       AdvanceToNextToken();
1118       if (!fNextToken)
1119         SetSyntaxError(true);
1120       else {
1121         fMsgID = CreateAtom();
1122         AdvanceToNextToken();
1123         nsCString msgIDValue;
1124         msgIDValue.Assign(fMsgID);
1125         if (fCurrentResponseUID == 0)
1126           fFlagState->GetUidOfMessage(fFetchResponseIndex - 1,
1127                                       &fCurrentResponseUID);
1128         fFlagState->SetCustomAttribute(fCurrentResponseUID, "X-GM-MSGID"_ns,
1129                                        msgIDValue);
1130         PR_FREEIF(fMsgID);
1131       }
1132     } else if (!PL_strcasecmp(fNextToken, "X-GM-THRID")) {
1133       AdvanceToNextToken();
1134       if (!fNextToken)
1135         SetSyntaxError(true);
1136       else {
1137         fThreadID = CreateAtom();
1138         AdvanceToNextToken();
1139         nsCString threadIDValue;
1140         threadIDValue.Assign(fThreadID);
1141         if (fCurrentResponseUID == 0)
1142           fFlagState->GetUidOfMessage(fFetchResponseIndex - 1,
1143                                       &fCurrentResponseUID);
1144         fFlagState->SetCustomAttribute(fCurrentResponseUID, "X-GM-THRID"_ns,
1145                                        threadIDValue);
1146         PR_FREEIF(fThreadID);
1147       }
1148     } else if (!PL_strcasecmp(fNextToken, "X-GM-LABELS")) {
1149       AdvanceToNextToken();
1150       if (!fNextToken)
1151         SetSyntaxError(true);
1152       else {
1153         fLabels = CreateParenGroup();
1154         nsCString labelsValue;
1155         labelsValue.Assign(fLabels);
1156         labelsValue.Cut(0, 1);
1157         labelsValue.Cut(labelsValue.Length() - 1, 1);
1158         if (fCurrentResponseUID == 0)
1159           fFlagState->GetUidOfMessage(fFetchResponseIndex - 1,
1160                                       &fCurrentResponseUID);
1161         fFlagState->SetCustomAttribute(fCurrentResponseUID, "X-GM-LABELS"_ns,
1162                                        labelsValue);
1163         PR_FREEIF(fLabels);
1164       }
1165     }
1166 
1167     // I only fetch RFC822 so I should never see these BODY responses
1168     else if (!PL_strcasecmp(fNextToken, "BODY"))
1169       skip_to_CRLF();  // I never ask for this
1170     else if (!PL_strcasecmp(fNextToken, "BODYSTRUCTURE")) {
1171       if (fCurrentResponseUID == 0)
1172         fFlagState->GetUidOfMessage(fFetchResponseIndex - 1,
1173                                     &fCurrentResponseUID);
1174       bodystructure_data();
1175     } else if (!PL_strncasecmp(fNextToken, "BODY[TEXT", 9)) {
1176       mime_data();
1177     } else if (!PL_strncasecmp(fNextToken, "BODY[", 5) &&
1178                PL_strncasecmp(fNextToken, "BODY[]", 6)) {
1179       fDownloadingHeaders = false;
1180       // A specific MIME part, or MIME part header
1181       mime_data();
1182     } else if (!PL_strcasecmp(fNextToken, "ENVELOPE")) {
1183       fDownloadingHeaders = true;
1184       bNeedEndMessageDownload = true;
1185       BeginMessageDownload(MESSAGE_RFC822);
1186       envelope_data();
1187     } else if (!PL_strcasecmp(fNextToken, "INTERNALDATE")) {
1188       fDownloadingHeaders =
1189           true;  // we only request internal date while downloading headers
1190       if (!bNeedEndMessageDownload) BeginMessageDownload(MESSAGE_RFC822);
1191       bNeedEndMessageDownload = true;
1192       internal_date();
1193     } else if (!PL_strcasecmp(fNextToken, "XAOL-ENVELOPE")) {
1194       fDownloadingHeaders = true;
1195       if (!bNeedEndMessageDownload) BeginMessageDownload(MESSAGE_RFC822);
1196       bNeedEndMessageDownload = true;
1197       xaolenvelope_data();
1198     } else {
1199       nsImapAction imapAction;
1200       if (!fServerConnection.GetCurrentUrl()) return;
1201       fServerConnection.GetCurrentUrl()->GetImapAction(&imapAction);
1202       nsAutoCString userDefinedFetchAttribute;
1203       fServerConnection.GetCurrentUrl()->GetCustomAttributeToFetch(
1204           userDefinedFetchAttribute);
1205       if ((imapAction == nsIImapUrl::nsImapUserDefinedFetchAttribute &&
1206            !strcmp(userDefinedFetchAttribute.get(), fNextToken)) ||
1207           imapAction == nsIImapUrl::nsImapUserDefinedMsgCommand) {
1208         AdvanceToNextToken();
1209         char* fetchResult;
1210         if (fNextToken[0] == '(')
1211           // look through the tokens until we find the closing ')'
1212           // we can have a result like the following:
1213           // ((A B) (C D) (E F))
1214           fetchResult = CreateParenGroup();
1215         else {
1216           fetchResult = CreateAstring();
1217           AdvanceToNextToken();
1218         }
1219         if (imapAction == nsIImapUrl::nsImapUserDefinedFetchAttribute)
1220           fServerConnection.GetCurrentUrl()->SetCustomAttributeResult(
1221               nsDependentCString(fetchResult));
1222         if (imapAction == nsIImapUrl::nsImapUserDefinedMsgCommand)
1223           fServerConnection.GetCurrentUrl()->SetCustomCommandResult(
1224               nsDependentCString(fetchResult));
1225         PR_Free(fetchResult);
1226       } else
1227         SetSyntaxError(true);
1228     }
1229   }
1230 
1231   if (ContinueParse()) {
1232     if (CurrentResponseUID() && CurrentResponseUID() != nsMsgKey_None &&
1233         fCurrentLineContainedFlagInfo && fFlagState) {
1234       fFlagState->AddUidFlagPair(CurrentResponseUID(), fSavedFlagInfo,
1235                                  fFetchResponseIndex - 1);
1236       for (uint32_t i = 0; i < fCustomFlags.Length(); i++)
1237         fFlagState->AddUidCustomFlagPair(CurrentResponseUID(),
1238                                          fCustomFlags[i].get());
1239       fCustomFlags.Clear();
1240     }
1241 
1242     if (fFetchingAllFlags)
1243       fCurrentLineContainedFlagInfo =
1244           false;  // do not fire if in PostProcessEndOfLine
1245 
1246     AdvanceToNextToken();  // eat the ')' ending token
1247     // should be at end of line
1248     if (bNeedEndMessageDownload) {
1249       if (ContinueParse()) {
1250         // complete the message download
1251         fServerConnection.NormalMessageEndDownload();
1252       } else
1253         fServerConnection.AbortMessageDownLoad();
1254     }
1255   }
1256 }
1257 
1258 typedef enum _envelopeItemType {
1259   envelopeString,
1260   envelopeAddress
1261 } envelopeItemType;
1262 
1263 typedef struct {
1264   const char* name;
1265   envelopeItemType type;
1266 } envelopeItem;
1267 
1268 // RFC3501:  envelope  = "(" env-date SP env-subject SP env-from SP
1269 //                       env-sender SP env-reply-to SP env-to SP env-cc SP
1270 //                       env-bcc SP env-in-reply-to SP env-message-id ")"
1271 //           env-date    = nstring
1272 //           env-subject = nstring
1273 //           env-from    = "(" 1*address ")" / nil
1274 //           env-sender  = "(" 1*address ")" / nil
1275 //           env-reply-to= "(" 1*address ")" / nil
1276 //           env-to      = "(" 1*address ")" / nil
1277 //           env-cc      = "(" 1*address ")" / nil
1278 //           env-bcc     = "(" 1*address ")" / nil
1279 //           env-in-reply-to = nstring
1280 //           env-message-id  = nstring
1281 
1282 static const envelopeItem EnvelopeTable[] = {
1283     {"Date", envelopeString},        {"Subject", envelopeString},
1284     {"From", envelopeAddress},       {"Sender", envelopeAddress},
1285     {"Reply-to", envelopeAddress},   {"To", envelopeAddress},
1286     {"Cc", envelopeAddress},         {"Bcc", envelopeAddress},
1287     {"In-reply-to", envelopeString}, {"Message-id", envelopeString}};
1288 
envelope_data()1289 void nsImapServerResponseParser::envelope_data() {
1290   AdvanceToNextToken();
1291   fNextToken++;  // eat '('
1292   for (int tableIndex = 0;
1293        tableIndex < (int)(sizeof(EnvelopeTable) / sizeof(EnvelopeTable[0]));
1294        tableIndex++) {
1295     if (!ContinueParse()) break;
1296     if (*fNextToken == ')') {
1297       SetSyntaxError(true);  // envelope too short
1298       break;
1299     }
1300 
1301     nsAutoCString headerLine(EnvelopeTable[tableIndex].name);
1302     headerLine += ": ";
1303     bool headerNonNil = true;
1304     if (EnvelopeTable[tableIndex].type == envelopeString) {
1305       nsAutoCString strValue;
1306       strValue.Adopt(CreateNilString());
1307       if (!strValue.IsEmpty())
1308         headerLine.Append(strValue);
1309       else
1310         headerNonNil = false;
1311     } else {
1312       nsAutoCString address;
1313       parse_address(address);
1314       headerLine += address;
1315       if (address.IsEmpty()) headerNonNil = false;
1316     }
1317     if (headerNonNil)
1318       fServerConnection.HandleMessageDownLoadLine(headerLine.get(), false);
1319 
1320     if (ContinueParse()) AdvanceToNextToken();
1321   }
1322   // Now we should be at the end of the envelope and have *fToken == ')'.
1323   // Skip this last parenthesis.
1324   AdvanceToNextToken();
1325 }
1326 
xaolenvelope_data()1327 void nsImapServerResponseParser::xaolenvelope_data() {
1328   // eat the opening '('
1329   fNextToken++;
1330 
1331   if (ContinueParse() && (*fNextToken != ')')) {
1332     AdvanceToNextToken();
1333     fNextToken++;  // eat '('
1334     nsAutoCString subject;
1335     subject.Adopt(CreateNilString());
1336     nsAutoCString subjectLine("Subject: ");
1337     subjectLine += subject;
1338     fServerConnection.HandleMessageDownLoadLine(subjectLine.get(), false);
1339     fNextToken++;  // eat the next '('
1340     if (ContinueParse()) {
1341       AdvanceToNextToken();
1342       if (ContinueParse()) {
1343         nsAutoCString fromLine;
1344         if (!strcmp(GetSelectedMailboxName(), "Sent Items")) {
1345           // xaol envelope switches the From with the To, so we switch them back
1346           // and create a fake from line From: user@aol.com
1347           fromLine.AppendLiteral("To: ");
1348           nsAutoCString fakeFromLine("From: "_ns);
1349           fakeFromLine.Append(fServerConnection.GetImapUserName());
1350           fakeFromLine.AppendLiteral("@aol.com");
1351           fServerConnection.HandleMessageDownLoadLine(fakeFromLine.get(),
1352                                                       false);
1353         } else {
1354           fromLine.AppendLiteral("From: ");
1355         }
1356         parse_address(fromLine);
1357         fServerConnection.HandleMessageDownLoadLine(fromLine.get(), false);
1358         if (ContinueParse()) {
1359           AdvanceToNextToken();  // ge attachment size
1360           int32_t attachmentSize = atoi(fNextToken);
1361           if (attachmentSize != 0) {
1362             nsAutoCString attachmentLine("X-attachment-size: ");
1363             attachmentLine.AppendInt(attachmentSize);
1364             fServerConnection.HandleMessageDownLoadLine(attachmentLine.get(),
1365                                                         false);
1366           }
1367         }
1368         if (ContinueParse()) {
1369           AdvanceToNextToken();  // skip image size
1370           int32_t imageSize = atoi(fNextToken);
1371           if (imageSize != 0) {
1372             nsAutoCString imageLine("X-image-size: ");
1373             imageLine.AppendInt(imageSize);
1374             fServerConnection.HandleMessageDownLoadLine(imageLine.get(), false);
1375           }
1376         }
1377         if (ContinueParse()) AdvanceToNextToken();  // skip )
1378       }
1379     }
1380   }
1381 }
1382 
parse_address(nsAutoCString & addressLine)1383 void nsImapServerResponseParser::parse_address(nsAutoCString& addressLine) {
1384   // NOTE: Not sure this function correctly handling group address syntax.
1385   // See Bug 1609846.
1386   if (!strcmp(fNextToken, "NIL")) return;
1387   bool firstAddress = true;
1388   // should really look at chars here
1389   NS_ASSERTION(*fNextToken == '(', "address should start with '('");
1390   fNextToken++;  // eat the next '('
1391   while (ContinueParse() && *fNextToken == '(') {
1392     NS_ASSERTION(*fNextToken == '(', "address should start with '('");
1393     fNextToken++;  // eat the next '('
1394 
1395     if (!firstAddress) addressLine += ", ";
1396 
1397     firstAddress = false;
1398     char* personalName = CreateNilString();
1399     AdvanceToNextToken();
1400     char* atDomainList = CreateNilString();
1401     if (ContinueParse()) {
1402       AdvanceToNextToken();
1403       char* mailboxName = CreateNilString();
1404       if (ContinueParse()) {
1405         AdvanceToNextToken();
1406         char* hostName = CreateNilString();
1407         AdvanceToNextToken();
1408         if (mailboxName) {
1409           addressLine += mailboxName;
1410         }
1411         if (hostName) {
1412           addressLine += '@';
1413           addressLine += hostName;
1414           PR_Free(hostName);
1415         }
1416         if (personalName) {
1417           addressLine += " (";
1418           addressLine += personalName;
1419           addressLine += ')';
1420         }
1421       }
1422       PR_Free(mailboxName);
1423     }
1424     PR_Free(personalName);
1425     PR_Free(atDomainList);
1426 
1427     if (*fNextToken == ')') fNextToken++;
1428     // if the next token isn't a ')' for the address term,
1429     // then we must have another address pair left....so get the next
1430     // token and continue parsing in this loop...
1431     if (*fNextToken == '\0') AdvanceToNextToken();
1432   }
1433   if (*fNextToken == ')') fNextToken++;
1434   // AdvanceToNextToken();  // skip "))"
1435 }
1436 
internal_date()1437 void nsImapServerResponseParser::internal_date() {
1438   AdvanceToNextToken();
1439   if (ContinueParse()) {
1440     nsAutoCString dateLine("Date: ");
1441     char* strValue = CreateNilString();
1442     if (strValue) {
1443       dateLine += strValue;
1444       free(strValue);
1445     }
1446     fServerConnection.HandleMessageDownLoadLine(dateLine.get(), false);
1447   }
1448   // advance the parser.
1449   AdvanceToNextToken();
1450 }
1451 
flags()1452 void nsImapServerResponseParser::flags() {
1453   imapMessageFlagsType messageFlags = kNoImapMsgFlag;
1454   fCustomFlags.Clear();
1455 
1456   // clear the custom flags for this message
1457   // otherwise the old custom flags will stay around
1458   // see bug #191042
1459   if (fFlagState && CurrentResponseUID() != nsMsgKey_None)
1460     fFlagState->ClearCustomFlags(CurrentResponseUID());
1461 
1462   // eat the opening '('
1463   fNextToken++;
1464   while (ContinueParse() && (*fNextToken != ')')) {
1465     bool knownFlag = false;
1466     if (*fNextToken == '\\') {
1467       switch (NS_ToUpper(fNextToken[1])) {
1468         case 'S':
1469           if (!PL_strncasecmp(fNextToken, "\\Seen", 5)) {
1470             messageFlags |= kImapMsgSeenFlag;
1471             knownFlag = true;
1472           }
1473           break;
1474         case 'A':
1475           if (!PL_strncasecmp(fNextToken, "\\Answered", 9)) {
1476             messageFlags |= kImapMsgAnsweredFlag;
1477             knownFlag = true;
1478           }
1479           break;
1480         case 'F':
1481           if (!PL_strncasecmp(fNextToken, "\\Flagged", 8)) {
1482             messageFlags |= kImapMsgFlaggedFlag;
1483             knownFlag = true;
1484           }
1485           break;
1486         case 'D':
1487           if (!PL_strncasecmp(fNextToken, "\\Deleted", 8)) {
1488             messageFlags |= kImapMsgDeletedFlag;
1489             knownFlag = true;
1490           } else if (!PL_strncasecmp(fNextToken, "\\Draft", 6)) {
1491             messageFlags |= kImapMsgDraftFlag;
1492             knownFlag = true;
1493           }
1494           break;
1495         case 'R':
1496           if (!PL_strncasecmp(fNextToken, "\\Recent", 7)) {
1497             messageFlags |= kImapMsgRecentFlag;
1498             knownFlag = true;
1499           }
1500           break;
1501         default:
1502           break;
1503       }
1504     } else if (*fNextToken == '$') {
1505       switch (NS_ToUpper(fNextToken[1])) {
1506         case 'M':
1507           if ((fSupportsUserDefinedFlags &
1508                (kImapMsgSupportUserFlag | kImapMsgSupportMDNSentFlag)) &&
1509               !PL_strncasecmp(fNextToken, "$MDNSent", 8)) {
1510             messageFlags |= kImapMsgMDNSentFlag;
1511             knownFlag = true;
1512           }
1513           break;
1514         case 'F':
1515           if ((fSupportsUserDefinedFlags &
1516                (kImapMsgSupportUserFlag | kImapMsgSupportForwardedFlag)) &&
1517               !PL_strncasecmp(fNextToken, "$Forwarded", 10)) {
1518             messageFlags |= kImapMsgForwardedFlag;
1519             knownFlag = true;
1520           }
1521           break;
1522         default:
1523           break;
1524       }
1525     }
1526     if (!knownFlag && fFlagState) {
1527       nsAutoCString flag(fNextToken);
1528       int32_t parenIndex = flag.FindChar(')');
1529       if (parenIndex > 0) flag.SetLength(parenIndex);
1530       messageFlags |= kImapMsgCustomKeywordFlag;
1531       if (CurrentResponseUID() != nsMsgKey_None && CurrentResponseUID() != 0)
1532         fFlagState->AddUidCustomFlagPair(CurrentResponseUID(), flag.get());
1533       else
1534         fCustomFlags.AppendElement(flag);
1535     }
1536     if (PL_strcasestr(fNextToken, ")")) {
1537       // eat token chars until we get the ')'
1538       while (*fNextToken != ')') fNextToken++;
1539     } else
1540       AdvanceToNextToken();
1541   }
1542 
1543   if (ContinueParse())
1544     while (*fNextToken != ')') fNextToken++;
1545 
1546   fCurrentLineContainedFlagInfo = true;  // handled in PostProcessEndOfLine
1547   fSavedFlagInfo = messageFlags;
1548 }
1549 
1550 // RFC3501:  resp-cond-state = ("OK" / "NO" / "BAD") SP resp-text
1551 //                             ; Status condition
resp_cond_state(bool isTagged)1552 void nsImapServerResponseParser::resp_cond_state(bool isTagged) {
1553   // According to RFC3501, Sec. 7.1, the untagged NO response "indicates a
1554   // warning; the command can still complete successfully."
1555   // However, the untagged BAD response "indicates a protocol-level error for
1556   // which the associated command can not be determined; it can also indicate an
1557   // internal server failure."
1558   // Thus, we flag an error for a tagged NO response and for any BAD response.
1559   if ((isTagged && !PL_strcasecmp(fNextToken, "NO")) ||
1560       !PL_strcasecmp(fNextToken, "BAD"))
1561     fCurrentCommandFailed = true;
1562 
1563   AdvanceToNextToken();
1564   if (ContinueParse()) resp_text();
1565 }
1566 
1567 /*
1568 resp_text       ::= ["[" resp_text_code "]" SPACE] (text_mime2 / text)
1569 
1570   was changed to in order to enable a one symbol look ahead predictive
1571   parser.
1572 
1573     resp_text       ::= ["[" resp_text_code  SPACE] (text_mime2 / text)
1574 */
resp_text()1575 void nsImapServerResponseParser::resp_text() {
1576   if (ContinueParse() && (*fNextToken == '[')) resp_text_code();
1577 
1578   if (ContinueParse()) {
1579     if (!PL_strcmp(fNextToken, "=?"))
1580       text_mime2();
1581     else
1582       text();
1583   }
1584 }
1585 /*
1586  text_mime2       ::= "=?" <charset> "?" <encoding> "?"
1587                                <encoded-text> "?="
1588                                ;; Syntax defined in [MIME-2]
1589 */
text_mime2()1590 void nsImapServerResponseParser::text_mime2() { skip_to_CRLF(); }
1591 
1592 /*
1593  text            ::= 1*TEXT_CHAR
1594 
1595 */
text()1596 void nsImapServerResponseParser::text() { skip_to_CRLF(); }
1597 
parse_folder_flags(bool calledForFlags)1598 void nsImapServerResponseParser::parse_folder_flags(bool calledForFlags) {
1599   uint16_t labelFlags = 0;
1600   uint16_t junkNotJunkFlags = 0;
1601 
1602   do {
1603     AdvanceToNextToken();
1604     if (*fNextToken == '(') fNextToken++;
1605     if (!PL_strncasecmp(fNextToken, "\\Seen", 5))
1606       fSettablePermanentFlags |= kImapMsgSeenFlag;
1607     else if (!PL_strncasecmp(fNextToken, "\\Answered", 9))
1608       fSettablePermanentFlags |= kImapMsgAnsweredFlag;
1609     else if (!PL_strncasecmp(fNextToken, "\\Flagged", 8))
1610       fSettablePermanentFlags |= kImapMsgFlaggedFlag;
1611     else if (!PL_strncasecmp(fNextToken, "\\Deleted", 8))
1612       fSettablePermanentFlags |= kImapMsgDeletedFlag;
1613     else if (!PL_strncasecmp(fNextToken, "\\Draft", 6))
1614       fSettablePermanentFlags |= kImapMsgDraftFlag;
1615     else if (!PL_strncasecmp(fNextToken, "\\*", 2)) {
1616       // User defined and special keywords (tags) can be defined and set for
1617       // mailbox. Should only occur in PERMANENTFLAGS response.
1618       fSupportsUserDefinedFlags |= kImapMsgSupportUserFlag;
1619       fSupportsUserDefinedFlags |= kImapMsgSupportForwardedFlag;
1620       fSupportsUserDefinedFlags |= kImapMsgSupportMDNSentFlag;
1621       fSupportsUserDefinedFlags |= kImapMsgLabelFlags;
1622     }
1623     // Treat special and built-in $LabelX's as user defined and include
1624     // $Junk/$NotJunk too.
1625     else if (!PL_strncasecmp(fNextToken, "$MDNSent", 8))
1626       fSupportsUserDefinedFlags |= kImapMsgSupportMDNSentFlag;
1627     else if (!PL_strncasecmp(fNextToken, "$Forwarded", 10))
1628       fSupportsUserDefinedFlags |= kImapMsgSupportForwardedFlag;
1629     else if (!PL_strncasecmp(fNextToken, "$Label1", 7))
1630       labelFlags |= 1;
1631     else if (!PL_strncasecmp(fNextToken, "$Label2", 7))
1632       labelFlags |= 2;
1633     else if (!PL_strncasecmp(fNextToken, "$Label3", 7))
1634       labelFlags |= 4;
1635     else if (!PL_strncasecmp(fNextToken, "$Label4", 7))
1636       labelFlags |= 8;
1637     else if (!PL_strncasecmp(fNextToken, "$Label5", 7))
1638       labelFlags |= 16;
1639     else if (!PL_strncasecmp(fNextToken, "$Junk", 5))
1640       junkNotJunkFlags |= 1;
1641     else if (!PL_strncasecmp(fNextToken, "$NotJunk", 8))
1642       junkNotJunkFlags |= 2;
1643   } while (!fAtEndOfLine && ContinueParse());
1644 
1645   if (labelFlags == 31) fSupportsUserDefinedFlags |= kImapMsgLabelFlags;
1646 
1647   if (fFlagState) fFlagState->OrSupportedUserFlags(fSupportsUserDefinedFlags);
1648 
1649   if (calledForFlags) {
1650     // Set true if both "$Junk" and "$NotJunk" appear in FLAGS.
1651     fStdJunkNotJunkUseOk = (junkNotJunkFlags == 3);
1652   }
1653 }
1654 /*
1655   resp_text_code  ::= ("ALERT" / "PARSE" /
1656                               "PERMANENTFLAGS" SPACE "(" #(flag / "\*") ")" /
1657                               "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
1658                               "UIDVALIDITY" SPACE nz_number /
1659                               "UNSEEN" SPACE nz_number /
1660                               "HIGHESTMODSEQ" SPACE nz_number /
1661                               "NOMODSEQ" /
1662                               atom [SPACE 1*<any TEXT_CHAR except "]">] )
1663                       "]"
1664 
1665 
1666 */
resp_text_code()1667 void nsImapServerResponseParser::resp_text_code() {
1668   // this is a special case way of advancing the token
1669   // strtok won't break up "[ALERT]" into separate tokens
1670   if (strlen(fNextToken) > 1)
1671     fNextToken++;
1672   else
1673     AdvanceToNextToken();
1674 
1675   if (ContinueParse()) {
1676     if (!PL_strcasecmp(fNextToken, "ALERT]")) {
1677       char* alertMsg = fCurrentTokenPlaceHolder;  // advance past ALERT]
1678       if (alertMsg && *alertMsg &&
1679           (!fLastAlert || PL_strcmp(fNextToken, fLastAlert))) {
1680         fServerConnection.AlertUserEvent(alertMsg);
1681         PR_Free(fLastAlert);
1682         fLastAlert = PL_strdup(alertMsg);
1683       }
1684       AdvanceToNextToken();
1685     } else if (!PL_strcasecmp(fNextToken, "PARSE]")) {
1686       // do nothing for now
1687       AdvanceToNextToken();
1688     } else if (!PL_strcasecmp(fNextToken, "NETSCAPE]")) {
1689       skip_to_CRLF();
1690     } else if (!PL_strcasecmp(fNextToken, "PERMANENTFLAGS")) {
1691       uint32_t saveSettableFlags = fSettablePermanentFlags;
1692       fSupportsUserDefinedFlags = 0;  // assume no unless told
1693       fSettablePermanentFlags = 0;    // assume none, unless told otherwise.
1694       parse_folder_flags(false);
1695       // if the server tells us there are no permanent flags, we're
1696       // just going to pretend that the FLAGS response flags, if any, are
1697       // permanent in case the server is broken. This will allow us
1698       // to store delete and seen flag changes - if they're not permanent,
1699       // they're not permanent, but at least we'll try to set them.
1700       if (!fSettablePermanentFlags) fSettablePermanentFlags = saveSettableFlags;
1701       fGotPermanentFlags = true;
1702     } else if (!PL_strcasecmp(fNextToken, "READ-ONLY]")) {
1703       fCurrentFolderReadOnly = true;
1704       AdvanceToNextToken();
1705     } else if (!PL_strcasecmp(fNextToken, "READ-WRITE]")) {
1706       fCurrentFolderReadOnly = false;
1707       AdvanceToNextToken();
1708     } else if (!PL_strcasecmp(fNextToken, "TRYCREATE]")) {
1709       // do nothing for now
1710       AdvanceToNextToken();
1711     } else if (!PL_strcasecmp(fNextToken, "UIDVALIDITY")) {
1712       AdvanceToNextToken();
1713       if (ContinueParse()) {
1714         fFolderUIDValidity = strtoul(fNextToken, nullptr, 10);
1715         fHighestRecordedUID = 0;
1716         AdvanceToNextToken();
1717       }
1718     } else if (!PL_strcasecmp(fNextToken, "UNSEEN")) {
1719       AdvanceToNextToken();
1720       if (ContinueParse()) {
1721         fNumberOfUnseenMessages = strtoul(fNextToken, nullptr, 10);
1722         AdvanceToNextToken();
1723       }
1724     } else if (!PL_strcasecmp(fNextToken, "UIDNEXT")) {
1725       AdvanceToNextToken();
1726       if (ContinueParse()) {
1727         fStatusNextUID = strtoul(fNextToken, nullptr, 10);
1728         AdvanceToNextToken();
1729       }
1730     } else if (!PL_strcasecmp(fNextToken, "APPENDUID")) {
1731       AdvanceToNextToken();
1732       if (ContinueParse()) {
1733         // ** jt -- the returned uidvalidity is the destination folder
1734         // uidvalidity; don't use it for current folder
1735         // fFolderUIDValidity = atoi(fNextToken);
1736         // fHighestRecordedUID = 0; ??? this should be wrong
1737         AdvanceToNextToken();
1738         if (ContinueParse()) {
1739           fCurrentResponseUID = strtoul(fNextToken, nullptr, 10);
1740           AdvanceToNextToken();
1741         }
1742       }
1743     } else if (!PL_strcasecmp(fNextToken, "COPYUID")) {
1744       AdvanceToNextToken();
1745       if (ContinueParse()) {
1746         // ** jt -- destination folder uidvalidity
1747         // fFolderUIDValidity = atoi(fNextToken);
1748         // original message set; ignore it
1749         AdvanceToNextToken();
1750         if (ContinueParse()) {
1751           // the resulting message set; should be in the form of
1752           // either uid or uid1:uid2
1753           AdvanceToNextToken();
1754           // clear copy response uid
1755           fServerConnection.SetCopyResponseUid(fNextToken);
1756         }
1757         if (ContinueParse()) AdvanceToNextToken();
1758       }
1759     } else if (!PL_strcasecmp(fNextToken, "HIGHESTMODSEQ")) {
1760       AdvanceToNextToken();
1761       if (ContinueParse()) {
1762         fHighestModSeq = ParseUint64Str(fNextToken);
1763         fUseModSeq = true;
1764         AdvanceToNextToken();
1765       }
1766     } else if (!PL_strcasecmp(fNextToken, "NOMODSEQ]")) {
1767       fHighestModSeq = 0;
1768       fUseModSeq = false;
1769       skip_to_CRLF();
1770     } else if (!PL_strcasecmp(fNextToken, "CAPABILITY")) {
1771       capability_data();
1772     } else if (!PL_strcasecmp(fNextToken, "MYRIGHTS")) {
1773       myrights_data(true);
1774     } else  // just text
1775     {
1776       // do nothing but eat tokens until we see the ] or CRLF
1777       // we should see the ] but we don't want to go into an
1778       // endless loop if the CRLF is not there
1779       do {
1780         AdvanceToNextToken();
1781       } while (!PL_strcasestr(fNextToken, "]") && !fAtEndOfLine &&
1782                ContinueParse());
1783     }
1784   }
1785 }
1786 
1787 // RFC3501:  response-done = response-tagged / response-fatal
response_done()1788 void nsImapServerResponseParser::response_done() {
1789   if (ContinueParse()) {
1790     if (!PL_strcmp(fCurrentCommandTag, fNextToken))
1791       response_tagged();
1792     else
1793       response_fatal();
1794   }
1795 }
1796 
1797 // RFC3501:  response-tagged = tag SP resp-cond-state CRLF
response_tagged()1798 void nsImapServerResponseParser::response_tagged() {
1799   // eat the tag
1800   AdvanceToNextToken();
1801   if (ContinueParse()) {
1802     resp_cond_state(true);
1803     if (ContinueParse()) {
1804       if (!fAtEndOfLine)
1805         SetSyntaxError(true);
1806       else if (!fCurrentCommandFailed)
1807         ResetLexAnalyzer();
1808     }
1809   }
1810 }
1811 
1812 // RFC3501:  response-fatal = "*" SP resp-cond-bye CRLF
1813 //                              ; Server closes connection immediately
response_fatal()1814 void nsImapServerResponseParser::response_fatal() {
1815   // eat the "*"
1816   AdvanceToNextToken();
1817   if (ContinueParse()) resp_cond_bye();
1818 }
1819 
1820 // RFC3501:  resp-cond-bye = "BYE" SP resp-text
resp_cond_bye()1821 void nsImapServerResponseParser::resp_cond_bye() {
1822   SetConnected(false);
1823   fIMAPstate = kNonAuthenticated;
1824 }
1825 
msg_fetch_headers(const char * partNum)1826 void nsImapServerResponseParser::msg_fetch_headers(const char* partNum) {
1827   if (GetFillingInShell()) {
1828     char* headerData = CreateAstring();
1829     AdvanceToNextToken();
1830     m_shell->AdoptMessageHeaders(headerData, partNum);
1831   } else {
1832     msg_fetch_content(false, 0, MESSAGE_RFC822);
1833   }
1834 }
1835 
1836 /* nstring         ::= string / nil
1837 string          ::= quoted / literal
1838 nil             ::= "NIL"
1839 
1840 */
msg_fetch_content(bool chunk,int32_t origin,const char * content_type)1841 void nsImapServerResponseParser::msg_fetch_content(bool chunk, int32_t origin,
1842                                                    const char* content_type) {
1843   // setup the stream for downloading this message.
1844   // Don't do it if we are filling in a shell or downloading a part.
1845   // DO do it if we are downloading a whole message as a result of
1846   // an invalid shell trying to generate.
1847   if ((!chunk || (origin == 0)) && !GetDownloadingHeaders() &&
1848       (GetFillingInShell() ? m_shell->GetGeneratingWholeMessage() : true)) {
1849     if (NS_FAILED(BeginMessageDownload(content_type))) return;
1850   }
1851 
1852   if (PL_strcasecmp(fNextToken, "NIL")) {
1853     if (*fNextToken == '"')
1854       fLastChunk = msg_fetch_quoted();
1855     else
1856       fLastChunk = msg_fetch_literal(chunk, origin);
1857   } else
1858     AdvanceToNextToken();  // eat "NIL"
1859 
1860   if (fLastChunk &&
1861       (GetFillingInShell() ? m_shell->GetGeneratingWholeMessage() : true)) {
1862     // complete the message download
1863     if (ContinueParse()) {
1864       if (fReceivedHeaderOrSizeForUID == CurrentResponseUID()) {
1865         fServerConnection.NormalMessageEndDownload();
1866         fReceivedHeaderOrSizeForUID = nsMsgKey_None;
1867       } else
1868         fReceivedHeaderOrSizeForUID = CurrentResponseUID();
1869     } else
1870       fServerConnection.AbortMessageDownLoad();
1871   }
1872 }
1873 
1874 /*
1875 quoted          ::= <"> *QUOTED_CHAR <">
1876 
1877   QUOTED_CHAR     ::= <any TEXT_CHAR except quoted_specials> /
1878   "\" quoted_specials
1879 
1880     quoted_specials ::= <"> / "\"
1881 */
1882 
msg_fetch_quoted()1883 bool nsImapServerResponseParser::msg_fetch_quoted() {
1884   // *Should* never get a quoted string in response to a chunked download,
1885   // but the RFCs don't forbid it
1886   char* q = CreateQuoted();
1887   if (q) {
1888     numberOfCharsInThisChunk = PL_strlen(q);
1889     fServerConnection.HandleMessageDownLoadLine(q, false, q);
1890     PR_Free(q);
1891   } else
1892     numberOfCharsInThisChunk = 0;
1893 
1894   AdvanceToNextToken();
1895   bool lastChunk =
1896       ((fServerConnection.GetCurFetchSize() == 0) ||
1897        (numberOfCharsInThisChunk != fServerConnection.GetCurFetchSize()));
1898   return lastChunk;
1899 }
1900 
1901 /* msg_obsolete    ::= "COPY" / ("STORE" SPACE msg_fetch)
1902 ;; OBSOLETE untagged data responses */
msg_obsolete()1903 void nsImapServerResponseParser::msg_obsolete() {
1904   if (!PL_strcasecmp(fNextToken, "COPY"))
1905     AdvanceToNextToken();
1906   else if (!PL_strcasecmp(fNextToken, "STORE")) {
1907     AdvanceToNextToken();
1908     if (ContinueParse()) msg_fetch();
1909   } else
1910     SetSyntaxError(true);
1911 }
1912 
capability_data()1913 void nsImapServerResponseParser::capability_data() {
1914   int32_t endToken = -1;
1915   fCapabilityFlag = kCapabilityDefined | kHasAuthOldLoginCapability;
1916   do {
1917     AdvanceToNextToken();
1918     if (fNextToken) {
1919       nsCString token(fNextToken);
1920       endToken = token.FindChar(']');
1921       if (endToken >= 0) token.SetLength(endToken);
1922 
1923       if (token.Equals("AUTH=LOGIN", nsCaseInsensitiveCStringComparator))
1924         fCapabilityFlag |= kHasAuthLoginCapability;
1925       else if (token.Equals("AUTH=PLAIN", nsCaseInsensitiveCStringComparator))
1926         fCapabilityFlag |= kHasAuthPlainCapability;
1927       else if (token.Equals("AUTH=CRAM-MD5",
1928                             nsCaseInsensitiveCStringComparator))
1929         fCapabilityFlag |= kHasCRAMCapability;
1930       else if (token.Equals("AUTH=NTLM", nsCaseInsensitiveCStringComparator))
1931         fCapabilityFlag |= kHasAuthNTLMCapability;
1932       else if (token.Equals("AUTH=GSSAPI", nsCaseInsensitiveCStringComparator))
1933         fCapabilityFlag |= kHasAuthGssApiCapability;
1934       else if (token.Equals("AUTH=MSN", nsCaseInsensitiveCStringComparator))
1935         fCapabilityFlag |= kHasAuthMSNCapability;
1936       else if (token.Equals("AUTH=EXTERNAL",
1937                             nsCaseInsensitiveCStringComparator))
1938         fCapabilityFlag |= kHasAuthExternalCapability;
1939       else if (token.Equals("AUTH=XOAUTH2", nsCaseInsensitiveCStringComparator))
1940         fCapabilityFlag |= kHasXOAuth2Capability;
1941       else if (token.Equals("STARTTLS", nsCaseInsensitiveCStringComparator))
1942         fCapabilityFlag |= kHasStartTLSCapability;
1943       else if (token.Equals("LOGINDISABLED",
1944                             nsCaseInsensitiveCStringComparator))
1945         fCapabilityFlag &= ~kHasAuthOldLoginCapability;  // remove flag
1946       else if (token.Equals("XSENDER", nsCaseInsensitiveCStringComparator))
1947         fCapabilityFlag |= kHasXSenderCapability;
1948       else if (token.Equals("IMAP4", nsCaseInsensitiveCStringComparator))
1949         fCapabilityFlag |= kIMAP4Capability;
1950       else if (token.Equals("IMAP4rev1", nsCaseInsensitiveCStringComparator))
1951         fCapabilityFlag |= kIMAP4rev1Capability;
1952       else if (Substring(token, 0, 5)
1953                    .Equals("IMAP4", nsCaseInsensitiveCStringComparator))
1954         fCapabilityFlag |= kIMAP4other;
1955       else if (token.Equals("X-NO-ATOMIC-RENAME",
1956                             nsCaseInsensitiveCStringComparator))
1957         fCapabilityFlag |= kNoHierarchyRename;
1958       else if (token.Equals("X-NON-HIERARCHICAL-RENAME",
1959                             nsCaseInsensitiveCStringComparator))
1960         fCapabilityFlag |= kNoHierarchyRename;
1961       else if (token.Equals("NAMESPACE", nsCaseInsensitiveCStringComparator))
1962         fCapabilityFlag |= kNamespaceCapability;
1963       else if (token.Equals("ID", nsCaseInsensitiveCStringComparator))
1964         fCapabilityFlag |= kHasIDCapability;
1965       else if (token.Equals("ACL", nsCaseInsensitiveCStringComparator))
1966         fCapabilityFlag |= kACLCapability;
1967       else if (token.Equals("XSERVERINFO", nsCaseInsensitiveCStringComparator))
1968         fCapabilityFlag |= kXServerInfoCapability;
1969       else if (token.Equals("UIDPLUS", nsCaseInsensitiveCStringComparator))
1970         fCapabilityFlag |= kUidplusCapability;
1971       else if (token.Equals("LITERAL+", nsCaseInsensitiveCStringComparator))
1972         fCapabilityFlag |= kLiteralPlusCapability;
1973       else if (token.Equals("XAOL-OPTION", nsCaseInsensitiveCStringComparator))
1974         fCapabilityFlag |= kAOLImapCapability;
1975       else if (token.Equals("X-GM-EXT-1", nsCaseInsensitiveCStringComparator))
1976         fCapabilityFlag |= kGmailImapCapability;
1977       else if (token.Equals("QUOTA", nsCaseInsensitiveCStringComparator))
1978         fCapabilityFlag |= kQuotaCapability;
1979       else if (token.Equals("LANGUAGE", nsCaseInsensitiveCStringComparator))
1980         fCapabilityFlag |= kHasLanguageCapability;
1981       else if (token.Equals("IDLE", nsCaseInsensitiveCStringComparator))
1982         fCapabilityFlag |= kHasIdleCapability;
1983       else if (token.Equals("CONDSTORE", nsCaseInsensitiveCStringComparator))
1984         fCapabilityFlag |= kHasCondStoreCapability;
1985       else if (token.Equals("ENABLE", nsCaseInsensitiveCStringComparator))
1986         fCapabilityFlag |= kHasEnableCapability;
1987       else if (token.Equals("LIST-EXTENDED",
1988                             nsCaseInsensitiveCStringComparator))
1989         fCapabilityFlag |= kHasListExtendedCapability;
1990       else if (token.Equals("XLIST", nsCaseInsensitiveCStringComparator))
1991         fCapabilityFlag |= kHasXListCapability;
1992       else if (token.Equals("SPECIAL-USE", nsCaseInsensitiveCStringComparator))
1993         fCapabilityFlag |= kHasSpecialUseCapability;
1994       else if (token.Equals("COMPRESS=DEFLATE",
1995                             nsCaseInsensitiveCStringComparator))
1996         fCapabilityFlag |= kHasCompressDeflateCapability;
1997       else if (token.Equals("MOVE", nsCaseInsensitiveCStringComparator))
1998         fCapabilityFlag |= kHasMoveCapability;
1999       else if (token.Equals("HIGHESTMODSEQ",
2000                             nsCaseInsensitiveCStringComparator))
2001         fCapabilityFlag |= kHasHighestModSeqCapability;
2002       else if (token.Equals("CLIENTID", nsCaseInsensitiveCStringComparator))
2003         fCapabilityFlag |= kHasClientIDCapability;
2004       else if (token.Equals("UTF8=ACCEPT",
2005                             nsCaseInsensitiveCStringComparator) ||
2006                token.Equals("UTF8=ONLY", nsCaseInsensitiveCStringComparator))
2007         fCapabilityFlag |= kHasUTF8AcceptCapability;
2008     }
2009   } while (fNextToken && endToken < 0 && !fAtEndOfLine && ContinueParse());
2010 
2011   nsImapProtocol* navCon = &fServerConnection;
2012   NS_ASSERTION(navCon,
2013                "null imap protocol connection while parsing capability "
2014                "response");  // we should always have this
2015   if (navCon) {
2016     navCon->CommitCapability();
2017     fServerConnection.SetCapabilityResponseOccurred();
2018   }
2019   skip_to_CRLF();
2020 }
2021 
xmailboxinfo_data()2022 void nsImapServerResponseParser::xmailboxinfo_data() {
2023   AdvanceToNextToken();
2024   if (!fNextToken) return;
2025 
2026   char* mailboxName = CreateAstring();  // PL_strdup(fNextToken);
2027   if (mailboxName) {
2028     do {
2029       AdvanceToNextToken();
2030       if (fNextToken) {
2031         if (!PL_strcmp("MANAGEURL", fNextToken)) {
2032           AdvanceToNextToken();
2033           fFolderAdminUrl = CreateAstring();
2034         } else if (!PL_strcmp("POSTURL", fNextToken)) {
2035           AdvanceToNextToken();
2036           // ignore this for now...
2037         }
2038       }
2039     } while (fNextToken && !fAtEndOfLine && ContinueParse());
2040   }
2041 }
2042 
xserverinfo_data()2043 void nsImapServerResponseParser::xserverinfo_data() {
2044   do {
2045     AdvanceToNextToken();
2046     if (!fNextToken) break;
2047     if (!PL_strcmp("MANAGEACCOUNTURL", fNextToken)) {
2048       AdvanceToNextToken();
2049       fMailAccountUrl.Adopt(CreateNilString());
2050     } else if (!PL_strcmp("MANAGELISTSURL", fNextToken)) {
2051       AdvanceToNextToken();
2052       fManageListsUrl.Adopt(CreateNilString());
2053     } else if (!PL_strcmp("MANAGEFILTERSURL", fNextToken)) {
2054       AdvanceToNextToken();
2055       fManageFiltersUrl.Adopt(CreateNilString());
2056     }
2057   } while (fNextToken && !fAtEndOfLine && ContinueParse());
2058 }
2059 
enable_data()2060 void nsImapServerResponseParser::enable_data() {
2061   do {
2062     // eat each enable response;
2063     AdvanceToNextToken();
2064     if (!strcmp("CONDSTORE", fNextToken)) fCondStoreEnabled = true;
2065     if (!PL_strcasecmp("UTF8=ACCEPT", fNextToken)) fUtf8AcceptEnabled = true;
2066   } while (fNextToken && !fAtEndOfLine && ContinueParse());
2067   // fCondStoreEnabled is not used. Also, an imap extension is not truly enabled
2068   // unless there is an untagged response detected here.
2069 }
2070 
language_data()2071 void nsImapServerResponseParser::language_data() {
2072   // we may want to go out and store the language returned to us
2073   // by the language command in the host info session stuff.
2074 
2075   // for now, just eat the language....
2076   do {
2077     // eat each language returned to us
2078     AdvanceToNextToken();
2079   } while (fNextToken && !fAtEndOfLine && ContinueParse());
2080 }
2081 
2082 // cram/auth response data ::= "+" SPACE challenge CRLF
2083 // the server expects more client data after issuing its challenge
2084 
authChallengeResponse_data()2085 void nsImapServerResponseParser::authChallengeResponse_data() {
2086   AdvanceToNextToken();
2087   fAuthChallenge = strdup(fNextToken);
2088   fWaitingForMoreClientInput = true;
2089 
2090   skip_to_CRLF();
2091 }
2092 
namespace_data()2093 void nsImapServerResponseParser::namespace_data() {
2094   EIMAPNamespaceType namespaceType = kPersonalNamespace;
2095   bool namespacesCommitted = false;
2096   const char* serverKey = fServerConnection.GetImapServerKey();
2097   while ((namespaceType != kUnknownNamespace) && ContinueParse()) {
2098     AdvanceToNextToken();
2099     while (fAtEndOfLine && ContinueParse()) AdvanceToNextToken();
2100     if (!PL_strcasecmp(fNextToken, "NIL")) {
2101       // No namespace for this type.
2102       // Don't add anything to the Namespace object.
2103     } else if (fNextToken[0] == '(') {
2104       // There may be multiple namespaces of the same type.
2105       // Go through each of them and add them to our Namespace object.
2106 
2107       fNextToken++;
2108       while (fNextToken[0] == '(' && ContinueParse()) {
2109         // we have another namespace for this namespace type
2110         fNextToken++;
2111         if (fNextToken[0] != '"') {
2112           SetSyntaxError(true);
2113         } else {
2114           char* namespacePrefix = CreateQuoted(false);
2115 
2116           AdvanceToNextToken();
2117           const char* quotedDelimiter = fNextToken;
2118           char namespaceDelimiter = '\0';
2119 
2120           if (quotedDelimiter[0] == '"') {
2121             quotedDelimiter++;
2122             namespaceDelimiter = quotedDelimiter[0];
2123           } else if (!PL_strncasecmp(quotedDelimiter, "NIL", 3)) {
2124             // NIL hierarchy delimiter.  Leave namespace delimiter nullptr.
2125           } else {
2126             // not quoted or NIL.
2127             SetSyntaxError(true);
2128           }
2129           if (ContinueParse()) {
2130             // add code to parse the TRANSLATE attribute if it is present....
2131             // we'll also need to expand the name space code to take in the
2132             // translated prefix name.
2133 
2134             nsImapNamespace* newNamespace = new nsImapNamespace(
2135                 namespaceType, namespacePrefix, namespaceDelimiter, false);
2136             // add it to a temporary list in the host
2137             if (newNamespace && fHostSessionList)
2138               fHostSessionList->AddNewNamespaceForHost(serverKey, newNamespace);
2139 
2140             skip_to_close_paren();  // Ignore any extension data
2141 
2142             bool endOfThisNamespaceType = (fNextToken[0] == ')');
2143             if (!endOfThisNamespaceType &&
2144                 fNextToken[0] !=
2145                     '(')  // no space between namespaces of the same type
2146             {
2147               SetSyntaxError(true);
2148             }
2149           }
2150           PR_Free(namespacePrefix);
2151         }
2152       }
2153     } else {
2154       SetSyntaxError(true);
2155     }
2156     switch (namespaceType) {
2157       case kPersonalNamespace:
2158         namespaceType = kOtherUsersNamespace;
2159         break;
2160       case kOtherUsersNamespace:
2161         namespaceType = kPublicNamespace;
2162         break;
2163       default:
2164         namespaceType = kUnknownNamespace;
2165         break;
2166     }
2167   }
2168   if (ContinueParse()) {
2169     nsImapProtocol* navCon = &fServerConnection;
2170     NS_ASSERTION(
2171         navCon,
2172         "null protocol connection while parsing namespace");  // we should
2173                                                               // always have
2174                                                               // this
2175     if (navCon) {
2176       navCon->CommitNamespacesForHostEvent();
2177       namespacesCommitted = true;
2178     }
2179   }
2180   skip_to_CRLF();
2181 
2182   if (!namespacesCommitted && fHostSessionList) {
2183     bool success;
2184     fHostSessionList->FlushUncommittedNamespacesForHost(serverKey, success);
2185   }
2186 }
2187 
myrights_data(bool unsolicited)2188 void nsImapServerResponseParser::myrights_data(bool unsolicited) {
2189   AdvanceToNextToken();
2190   if (ContinueParse() && !fAtEndOfLine) {
2191     char* mailboxName;
2192     // an unsolicited myrights response won't have the mailbox name in
2193     // the response, so we use the selected mailbox name.
2194     if (unsolicited) {
2195       mailboxName = strdup(fSelectedMailboxName);
2196     } else {
2197       mailboxName = CreateAstring();
2198       if (mailboxName) AdvanceToNextToken();
2199     }
2200     if (mailboxName) {
2201       if (ContinueParse()) {
2202         char* myrights = CreateAstring();
2203         if (myrights) {
2204           nsImapProtocol* navCon = &fServerConnection;
2205           NS_ASSERTION(
2206               navCon, "null connection parsing my rights");  // we should always
2207                                                              // have this
2208           if (navCon)
2209             navCon->AddFolderRightsForUser(mailboxName,
2210                                            nullptr /* means "me" */, myrights);
2211           PR_Free(myrights);
2212         } else {
2213           HandleMemoryFailure();
2214         }
2215         if (ContinueParse()) AdvanceToNextToken();
2216       }
2217       PR_Free(mailboxName);
2218     } else {
2219       HandleMemoryFailure();
2220     }
2221   } else {
2222     SetSyntaxError(true);
2223   }
2224 }
2225 
acl_data()2226 void nsImapServerResponseParser::acl_data() {
2227   AdvanceToNextToken();
2228   if (ContinueParse() && !fAtEndOfLine) {
2229     char* mailboxName = CreateAstring();  // PL_strdup(fNextToken);
2230     if (mailboxName && ContinueParse()) {
2231       AdvanceToNextToken();
2232       while (ContinueParse() && !fAtEndOfLine) {
2233         char* userName = CreateAstring();  // PL_strdup(fNextToken);
2234         if (userName && ContinueParse()) {
2235           AdvanceToNextToken();
2236           if (ContinueParse()) {
2237             char* rights = CreateAstring();  // PL_strdup(fNextToken);
2238             if (rights) {
2239               fServerConnection.AddFolderRightsForUser(mailboxName, userName,
2240                                                        rights);
2241               PR_Free(rights);
2242             } else
2243               HandleMemoryFailure();
2244 
2245             if (ContinueParse()) AdvanceToNextToken();
2246           }
2247           PR_Free(userName);
2248         } else
2249           HandleMemoryFailure();
2250       }
2251       PR_Free(mailboxName);
2252     } else
2253       HandleMemoryFailure();
2254   }
2255 }
2256 
mime_data()2257 void nsImapServerResponseParser::mime_data() {
2258   if (PL_strstr(fNextToken, "MIME"))
2259     mime_header_data();
2260   else
2261     mime_part_data();
2262 }
2263 
2264 // mime_header_data should not be streamed out;  rather, it should be
2265 // buffered in the nsImapBodyShell.
2266 // This is because we are still in the process of generating enough
2267 // information from the server (such as the MIME header's size) so that
2268 // we can construct the final output stream.
mime_header_data()2269 void nsImapServerResponseParser::mime_header_data() {
2270   char* partNumber = PL_strdup(fNextToken);
2271   if (partNumber) {
2272     char *start = partNumber + 5,
2273          *end = partNumber + 5;  // 5 == strlen("BODY[")
2274     while (ContinueParse() && end && *end != 'M' && *end != 'm') {
2275       end++;
2276     }
2277     if (end && (*end == 'M' || *end == 'm')) {
2278       *(end - 1) = 0;
2279       AdvanceToNextToken();
2280       char* mimeHeaderData = CreateAstring();  // is it really this simple?
2281       AdvanceToNextToken();
2282       if (m_shell) {
2283         m_shell->AdoptMimeHeader(start, mimeHeaderData);
2284       }
2285     } else {
2286       SetSyntaxError(true);
2287     }
2288     PR_Free(partNumber);  // partNumber is not adopted by the body shell.
2289   } else {
2290     HandleMemoryFailure();
2291   }
2292 }
2293 
2294 // Actual mime parts are filled in on demand (either from shell generation
2295 // or from explicit user download), so we need to stream these out.
mime_part_data()2296 void nsImapServerResponseParser::mime_part_data() {
2297   char* checkOriginToken = PL_strdup(fNextToken);
2298   if (checkOriginToken) {
2299     uint32_t origin = 0;
2300     bool originFound = false;
2301     char* whereStart = PL_strchr(checkOriginToken, '<');
2302     if (whereStart) {
2303       char* whereEnd = PL_strchr(whereStart, '>');
2304       if (whereEnd) {
2305         *whereEnd = 0;
2306         whereStart++;
2307         origin = atoi(whereStart);
2308         originFound = true;
2309       }
2310     }
2311     PR_Free(checkOriginToken);
2312     AdvanceToNextToken();
2313     msg_fetch_content(originFound, origin,
2314                       MESSAGE_RFC822);  // keep content type as message/rfc822,
2315                                         // even though the
2316     // MIME part might not be, because then libmime will
2317     // still handle and decode it.
2318   } else
2319     HandleMemoryFailure();
2320 }
2321 
2322 // parse FETCH BODYSTRUCTURE response, "a parenthesized list that describes
2323 // the [MIME-IMB] body structure of a message" [RFC 3501].
bodystructure_data()2324 void nsImapServerResponseParser::bodystructure_data() {
2325   AdvanceToNextToken();
2326   if (ContinueParse() && fNextToken &&
2327       *fNextToken == '(')  // It has to start with an open paren.
2328   {
2329     // Turn the BODYSTRUCTURE response into a form that the
2330     // nsIMAPBodypartMessage can be constructed from.
2331     // FIXME: Follow up on bug 384210 to investigate why the caller has to
2332     // duplicate the two in-param strings.
2333     nsIMAPBodypartMessage* message = new nsIMAPBodypartMessage(
2334         NULL, NULL, true, strdup("message"), strdup("rfc822"), NULL, NULL, NULL,
2335         0, fServerConnection.GetPreferPlainText());
2336     nsIMAPBodypart* body = bodystructure_part(PL_strdup("1"), message);
2337     if (body)
2338       message->SetBody(body);
2339     else {
2340       delete message;
2341       message = nullptr;
2342     }
2343     m_shell =
2344         new nsImapBodyShell(&fServerConnection, message, CurrentResponseUID(),
2345                             FolderUID(), GetSelectedMailboxName());
2346     // ignore syntax errors in parsing the body structure response. If there's
2347     // an error we'll just fall back to fetching the whole message.
2348     SetSyntaxError(false);
2349   } else
2350     SetSyntaxError(true);
2351 }
2352 
2353 // RFC3501:  body = "(" (body-type-1part / body-type-mpart) ")"
bodystructure_part(char * partNum,nsIMAPBodypart * parentPart)2354 nsIMAPBodypart* nsImapServerResponseParser::bodystructure_part(
2355     char* partNum, nsIMAPBodypart* parentPart) {
2356   // Check to see if this buffer is a leaf or container
2357   // (Look at second character - if an open paren, then it is a container)
2358   if (*fNextToken != '(') {
2359     NS_ASSERTION(false, "bodystructure_part must begin with '('");
2360     return NULL;
2361   }
2362 
2363   if (fNextToken[1] == '(') return bodystructure_multipart(partNum, parentPart);
2364   return bodystructure_leaf(partNum, parentPart);
2365 }
2366 
2367 // RFC3501: body-type-1part = (body-type-basic / body-type-msg / body-type-text)
2368 //                            [SP body-ext-1part]
bodystructure_leaf(char * partNum,nsIMAPBodypart * parentPart)2369 nsIMAPBodypart* nsImapServerResponseParser::bodystructure_leaf(
2370     char* partNum, nsIMAPBodypart* parentPart) {
2371   // historical note: this code was originally in
2372   // nsIMAPBodypartLeaf::ParseIntoObjects()
2373   char *bodyType = nullptr, *bodySubType = nullptr, *bodyID = nullptr,
2374        *bodyDescription = nullptr, *bodyEncoding = nullptr;
2375   int32_t partLength = 0;
2376   bool isValid = true;
2377 
2378   // body type  ("application", "text", "image", etc.)
2379   if (ContinueParse()) {
2380     fNextToken++;  // eat the first '('
2381     bodyType = CreateNilString();
2382     if (ContinueParse()) AdvanceToNextToken();
2383   }
2384 
2385   // body subtype  ("gif", "html", etc.)
2386   if (isValid && ContinueParse()) {
2387     bodySubType = CreateNilString();
2388     if (ContinueParse()) AdvanceToNextToken();
2389   }
2390 
2391   // body parameter: parenthesized list
2392   if (isValid && ContinueParse()) {
2393     if (fNextToken[0] == '(') {
2394       fNextToken++;
2395       skip_to_close_paren();
2396     } else if (!PL_strcasecmp(fNextToken, "NIL"))
2397       AdvanceToNextToken();
2398   }
2399 
2400   // body id
2401   if (isValid && ContinueParse()) {
2402     bodyID = CreateNilString();
2403     if (ContinueParse()) AdvanceToNextToken();
2404   }
2405 
2406   // body description
2407   if (isValid && ContinueParse()) {
2408     bodyDescription = CreateNilString();
2409     if (ContinueParse()) AdvanceToNextToken();
2410   }
2411 
2412   // body encoding
2413   if (isValid && ContinueParse()) {
2414     bodyEncoding = CreateNilString();
2415     if (ContinueParse()) AdvanceToNextToken();
2416   }
2417 
2418   // body size
2419   if (isValid && ContinueParse()) {
2420     char* bodySizeString = CreateAtom();
2421     if (!bodySizeString)
2422       isValid = false;
2423     else {
2424       partLength = atoi(bodySizeString);
2425       PR_Free(bodySizeString);
2426       if (ContinueParse()) AdvanceToNextToken();
2427     }
2428   }
2429 
2430   if (!isValid || !ContinueParse()) {
2431     PR_FREEIF(partNum);
2432     PR_FREEIF(bodyType);
2433     PR_FREEIF(bodySubType);
2434     PR_FREEIF(bodyID);
2435     PR_FREEIF(bodyDescription);
2436     PR_FREEIF(bodyEncoding);
2437   } else {
2438     if (PL_strcasecmp(bodyType, "message") ||
2439         PL_strcasecmp(bodySubType, "rfc822")) {
2440       skip_to_close_paren();
2441       return new nsIMAPBodypartLeaf(
2442           partNum, parentPart, bodyType, bodySubType, bodyID, bodyDescription,
2443           bodyEncoding, partLength, fServerConnection.GetPreferPlainText());
2444     }
2445 
2446     // This part is of type "message/rfc822"  (probably a forwarded message)
2447     nsIMAPBodypartMessage* message = new nsIMAPBodypartMessage(
2448         partNum, parentPart, false, bodyType, bodySubType, bodyID,
2449         bodyDescription, bodyEncoding, partLength,
2450         fServerConnection.GetPreferPlainText());
2451 
2452     // there are three additional fields: envelope structure, bodystructure, and
2453     // size in lines historical note: this code was originally in
2454     // nsIMAPBodypartMessage::ParseIntoObjects()
2455 
2456     // envelope (ignored)
2457     if (*fNextToken == '(') {
2458       fNextToken++;
2459       skip_to_close_paren();
2460     } else
2461       isValid = false;
2462 
2463     // bodystructure
2464     if (isValid && ContinueParse()) {
2465       if (*fNextToken != '(')
2466         isValid = false;
2467       else {
2468         char* bodyPartNum = PR_smprintf("%s.1", partNum);
2469         if (bodyPartNum) {
2470           nsIMAPBodypart* body = bodystructure_part(bodyPartNum, message);
2471           if (body)
2472             message->SetBody(body);
2473           else
2474             isValid = false;
2475         }
2476       }
2477     }
2478 
2479     // ignore "size in text lines"
2480 
2481     if (isValid && ContinueParse()) {
2482       skip_to_close_paren();
2483       return message;
2484     }
2485     delete message;
2486   }
2487 
2488   // parsing failed, just move to the end of the parentheses group
2489   if (ContinueParse()) skip_to_close_paren();
2490   return nullptr;
2491 }
2492 
2493 // RFC3501:  body-type-mpart = 1*body SP media-subtype
2494 //                             [SP body-ext-mpart]
bodystructure_multipart(char * partNum,nsIMAPBodypart * parentPart)2495 nsIMAPBodypart* nsImapServerResponseParser::bodystructure_multipart(
2496     char* partNum, nsIMAPBodypart* parentPart) {
2497   nsIMAPBodypartMultipart* multipart =
2498       new nsIMAPBodypartMultipart(partNum, parentPart);
2499   bool isValid = multipart->GetIsValid();
2500   // historical note: this code was originally in
2501   // nsIMAPBodypartMultipart::ParseIntoObjects()
2502   if (ContinueParse()) {
2503     fNextToken++;  // eat the first '('
2504     // Parse list of children
2505     int childCount = 0;
2506     while (isValid && fNextToken[0] == '(' && ContinueParse()) {
2507       childCount++;
2508       char* childPartNum = NULL;
2509       // note: the multipart constructor does some magic on partNumber
2510       if (PL_strcmp(multipart->GetPartNumberString(), "0"))  // not top-level
2511         childPartNum =
2512             PR_smprintf("%s.%d", multipart->GetPartNumberString(), childCount);
2513       else  // top-level
2514         childPartNum = PR_smprintf("%d", childCount);
2515       if (!childPartNum)
2516         isValid = false;
2517       else {
2518         nsIMAPBodypart* child = bodystructure_part(childPartNum, multipart);
2519         if (child)
2520           multipart->AppendPart(child);
2521         else
2522           isValid = false;
2523       }
2524     }
2525 
2526     // RFC3501:  media-subtype   = string
2527     // (multipart subtype: mixed, alternative, etc.)
2528     if (isValid && ContinueParse()) {
2529       char* bodySubType = CreateNilString();
2530       multipart->SetBodySubType(bodySubType);
2531       if (ContinueParse()) AdvanceToNextToken();
2532     }
2533 
2534     // clang-format off
2535     // extension data:
2536     // RFC3501:  body-ext-mpart = body-fld-param [SP body-fld-dsp [SP body-fld-lang
2537     //                            [SP body-fld-loc *(SP body-extension)]]]
2538 
2539     // body parameter parenthesized list (optional data), includes boundary parameter
2540     // RFC3501:  body-fld-param  = "(" string SP string *(SP string SP string) ")" / nil
2541     // clang-format on
2542     char* boundaryData = nullptr;
2543     if (isValid && ContinueParse() && *fNextToken == '(') {
2544       fNextToken++;
2545       while (ContinueParse() && *fNextToken != ')') {
2546         char* attribute = CreateNilString();
2547         if (ContinueParse()) AdvanceToNextToken();
2548         if (ContinueParse() && !PL_strcasecmp(attribute, "BOUNDARY")) {
2549           char* boundary = CreateNilString();
2550           if (boundary) boundaryData = PR_smprintf("--%s", boundary);
2551           PR_FREEIF(boundary);
2552         } else if (ContinueParse()) {
2553           char* value = CreateNilString();
2554           PR_FREEIF(value);
2555         }
2556         PR_FREEIF(attribute);
2557         if (ContinueParse()) AdvanceToNextToken();
2558       }
2559       if (ContinueParse()) fNextToken++;  // skip closing ')'
2560     }
2561     if (boundaryData)
2562       multipart->SetBoundaryData(boundaryData);
2563     else
2564       isValid =
2565           false;  // Actually, we should probably generate a boundary here.
2566   }
2567 
2568   // always move to closing ')', even if part was not successfully read
2569   if (ContinueParse()) skip_to_close_paren();
2570 
2571   if (isValid) return multipart;
2572   delete multipart;
2573   return nullptr;
2574 }
2575 
2576 // RFC2087:  quotaroot_response = "QUOTAROOT" SP astring *(SP astring)
2577 //           quota_response = "QUOTA" SP astring SP quota_list
2578 //           quota_list     = "(" [quota_resource *(SP quota_resource)] ")"
2579 //           quota_resource = atom SP number SP number
2580 // draft-melnikov-extra-quota-00 proposes some additions to RFC2087 and
2581 // improves the documentation. We still only support RFC2087 capability QUOTA
2582 // and command GETQUOTAROOT and its untagged QUOTAROOT and QUOTA responses.
quota_data()2583 void nsImapServerResponseParser::quota_data() {
2584   if (!PL_strcasecmp(fNextToken, "QUOTAROOT")) {
2585     // Ignore QUOTAROOT response (except to invalidate previously stored data).
2586     nsCString quotaroot;
2587     AdvanceToNextToken();
2588     while (ContinueParse() && !fAtEndOfLine) {
2589       quotaroot.Adopt(CreateAstring());
2590       AdvanceToNextToken();
2591     }
2592     // Invalidate any previously stored quota data. Updated QUOTA data follows.
2593     fServerConnection.UpdateFolderQuotaData(kInvalidateQuota, quotaroot, 0, 0);
2594   } else if (!PL_strcasecmp(fNextToken, "QUOTA")) {
2595     // Should have one QUOTA response per QUOTAROOT.
2596     uint64_t usage, limit;
2597     AdvanceToNextToken();
2598     if (ContinueParse()) {
2599       nsCString quotaroot;
2600       quotaroot.Adopt(CreateAstring());
2601       nsCString resource;
2602       AdvanceToNextToken();
2603       if (fNextToken) {
2604         if (fNextToken[0] == '(') fNextToken++;
2605         // Should have zero or more "resource|usage|limit" triplet per quotaroot
2606         // name. See draft-melnikov-extra-quota-00 for specific examples. Well
2607         // known resources are STORAGE (in Kbytes), number of MESSAGEs and
2608         // number of MAILBOXes. However, servers typically only set a quota on
2609         // STORAGE in KBytes. A mailbox can have multiple quotaroots but
2610         // typically only one and with a single resource.
2611         while (ContinueParse() && !fAtEndOfLine) {
2612           resource.Adopt(CreateAstring());
2613           AdvanceToNextToken();
2614           usage = atoll(fNextToken);
2615           AdvanceToNextToken();
2616           nsAutoCString limitToken(fNextToken);
2617           if (fNextToken[strlen(fNextToken) - 1] == ')')
2618             limitToken.SetLength(strlen(fNextToken) - 1);
2619           limit = atoll(limitToken.get());
2620           // Some servers don't define a quotaroot name which we displays as
2621           // blank.
2622           nsCString quotaRootResource(quotaroot);
2623           if (!quotaRootResource.IsEmpty()) {
2624             quotaRootResource.AppendLiteral(" / ");
2625           }
2626           quotaRootResource.Append(resource);
2627           fServerConnection.UpdateFolderQuotaData(
2628               kStoreQuota, quotaRootResource, usage, limit);
2629           AdvanceToNextToken();
2630         }
2631       }
2632     }
2633   } else {
2634     SetSyntaxError(true);
2635   }
2636 }
2637 
id_data()2638 void nsImapServerResponseParser::id_data() {
2639   AdvanceToNextToken();
2640   if (!PL_strcasecmp(fNextToken, "NIL"))
2641     AdvanceToNextToken();
2642   else
2643     fServerIdResponse.Adopt(CreateParenGroup());
2644   skip_to_CRLF();
2645 }
2646 
GetFillingInShell()2647 bool nsImapServerResponseParser::GetFillingInShell() {
2648   return (m_shell != nullptr);
2649 }
2650 
GetDownloadingHeaders()2651 bool nsImapServerResponseParser::GetDownloadingHeaders() {
2652   return fDownloadingHeaders;
2653 }
2654 
2655 // Tells the server state parser to use a previously cached shell.
UseCachedShell(nsImapBodyShell * cachedShell)2656 void nsImapServerResponseParser::UseCachedShell(nsImapBodyShell* cachedShell) {
2657   // We shouldn't already have another shell we're dealing with.
2658   if (m_shell && cachedShell) {
2659     MOZ_LOG(IMAP, mozilla::LogLevel::Info, ("PARSER: Shell Collision"));
2660     NS_ASSERTION(false, "shell collision");
2661   }
2662   m_shell = cachedShell;
2663 }
2664 
ResetCapabilityFlag()2665 void nsImapServerResponseParser::ResetCapabilityFlag() {}
2666 
2667 /*
2668    literal ::= "{" number "}" CRLF *CHAR8
2669    Number represents the number of CHAR8 octets
2670  */
2671 
2672 // Processes a message body, header or message part fetch response. Typically
2673 // the full message, header or part are processed in one call (effectively, one
2674 // chunk), and parameter `chunk` is false and `origin` (offset into the
2675 // response) is 0. But under some conditions and larger messages, multiple calls
2676 // will occur to process the message in multiple chunks and parameter `chunk`
2677 // will be true and parameter `origin` will increase by the chunk size from
2678 // initially 0 with each call. This function returns true if this is the last or
2679 // only chunk. This signals the caller that the stream should be closed since
2680 // the message response has been processed.
msg_fetch_literal(bool chunk,int32_t origin)2681 bool nsImapServerResponseParser::msg_fetch_literal(bool chunk, int32_t origin) {
2682   numberOfCharsInThisChunk = atoi(fNextToken + 1);
2683   // If we didn't request a specific size, or the server isn't returning exactly
2684   // as many octets as we requested, this must be the last or only chunk
2685   bool lastChunk = (!chunk || (numberOfCharsInThisChunk !=
2686                                fServerConnection.GetCurFetchSize()));
2687 
2688   // clang-format off
2689   if (lastChunk)
2690     MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
2691             ("PARSER: msg_fetch_literal() chunking=%s, requested=%d, receiving=%d",
2692              chunk ? "true":"false", fServerConnection.GetCurFetchSize(),
2693              numberOfCharsInThisChunk));
2694   // clang-format on
2695 
2696   charsReadSoFar = 0;
2697 
2698   while (ContinueParse() && !fServerConnection.DeathSignalReceived() &&
2699          (charsReadSoFar < numberOfCharsInThisChunk)) {
2700     AdvanceToNextLine();
2701     if (ContinueParse()) {
2702       // When "\r\n" (CRLF) is split across two chunks, the '\n' at the
2703       // beginning of the next chunk might be set to an empty line consisting
2704       // only of "\r\n". This is observed while running unit tests with the imap
2705       // "fake" server. The unexpected '\r' is discarded here. However, with
2706       // several real world servers tested, e.g., Dovecot, Gmail, Outlook, Yahoo
2707       // etc., the leading
2708       // '\r' is not inserted so the beginning line of the next chunk remains
2709       // just '\n' and no discard is required.
2710       // In any case, this "orphan" line is ignored and not processed below.
2711       if (fNextChunkStartsWithNewline && (*fCurrentLine == '\r')) {
2712         // Cause fCurrentLine to point to '\n' which discards the '\r'.
2713         char* usableCurrentLine = PL_strdup(fCurrentLine + 1);
2714         PR_Free(fCurrentLine);
2715         fCurrentLine = usableCurrentLine;
2716       }
2717 
2718       // strlen() *would* fail on data containing \0, but the above
2719       // AdvanceToNextLine() in nsMsgLineStreamBuffer::ReadNextLine() we replace
2720       // '\0' with ' ' (blank) because who cares about binary transparency, and
2721       // anyway \0 in this context violates RFCs.
2722       charsReadSoFar += strlen(fCurrentLine);
2723       if (!fDownloadingHeaders && fCurrentCommandIsSingleMessageFetch) {
2724         fServerConnection.ProgressEventFunctionUsingName(
2725             "imapDownloadingMessage");
2726         if (fTotalDownloadSize > 0)
2727           fServerConnection.PercentProgressUpdateEvent(
2728               ""_ns, u""_ns, charsReadSoFar + origin, fTotalDownloadSize);
2729       }
2730       if (charsReadSoFar > numberOfCharsInThisChunk) {
2731         // This is the last line of a chunk. "Literal" here means actual email
2732         // data and its EOLs, without imap protocol elements and their EOLs. End
2733         // of line is defined by two characters \r\n (i.e., CRLF, 0xd,0xa)
2734         // specified by RFC822. Here is an example the most typical last good
2735         // line of a chunk: "1s8AA5i4AAvF4QAG6+sAAD0bAPsAAAAA1OAAC)\r\n", where
2736         // ")\r\n" are non-literals. This an example of the last "good" line of
2737         // a chunk that terminates with \r\n
2738         // "FxcA/wAAAALN2gADu80ACS0nAPpVVAD1wNAABF5YAPhAJgD31+QABAAAAP8oMQD+HBwA/umj\r\n"
2739         // followed by another line of non-literal data:
2740         // " UID 1004)\r\n". These two are concatenated into a single string
2741         // pointed to by fCurrentLine.  The extra "non-literal data" on the last
2742         // chunk line makes the charsReadSoFar greater than
2743         // numberOfCharsInThisChunk (the configured chunk size). A problem
2744         // occurs if the \r\n of the long line above is split between chunks and
2745         // \n is contained in the next chunk. For example, if last lines of
2746         // chunk X are:
2747         // "/gAOC/wA/QAAAAAAAAAA8wACCvz+AgIEAAD8/P4ABQUAAPoAAAD+AAEA/voHAAQGBQD/BAQA\r"
2748         // ")\r\n"
2749         // and the first two lines of chunk X+1 are:
2750         // "\n"
2751         // "APwAAAAAmZkA/wAAAAAREQD/AAAAAquVAAbk8QAHCBAAAPD0AAP5+wABRCoA+0BgAP0AAAAA\r\n"
2752         // The missing '\n' on the last line of chunk X must be added back and
2753         // the line consisting only of "\n" in chunk X+1 must be ignored in
2754         // order to produce the the correct output. This is needed to insure
2755         // that the signature verification of cryptographically signed emails
2756         // does not fail due to missing or extra EOL characters. Otherwise, the
2757         // extra or missing \n or \r  doesn't really matter.
2758         //
2759         // Special case observed only with the "fake" imap server used with TB
2760         // unit test.  When the "\r\n" at the end of a chunk is split as
2761         // described above, the \n at the beginning of the next chunk may
2762         // actually be "\r\n" like this example: Last lines of chunk X
2763         // "/gAOC/wA/QAAAAAAAAAA8wACCvz+AgIEAAD8/P4ABQUAAPoAAAD+AAEA/voHAAQGBQD/BAQA\r"
2764         // ")\r\n"
2765         // and the first two lines of chunk X+1:
2766         // "\r\n"   <-- The code changes this to just "\n" like it should be.
2767         // "APwAAAAAmZkA/wAAAAAREQD/AAAAAquVAAbk8QAHCBAAAPD0AAP5+wABRCoA+0BgAP0AAAAA\r\n"
2768         //
2769         // Implementation:
2770         // Obtain pointer to last literal in chunk X, e.g., 'C' in 1st example
2771         // above, or to the \n or \r in the other examples.
2772         char* displayEndOfLine =
2773             (fCurrentLine + strlen(fCurrentLine) -
2774              (charsReadSoFar - numberOfCharsInThisChunk + 1));
2775         // Save so original unmodified fCurrentLine is restored below.
2776         char saveit1 = displayEndOfLine[1];
2777         char saveit2 = 0;  // Keep compiler happy.
2778         // Determine if EOL is split such that Chunk X has the \r and chunk
2779         // X+1 has the \n.
2780         fNextChunkStartsWithNewline = (displayEndOfLine[0] == '\r');
2781         if (fNextChunkStartsWithNewline) {
2782           saveit2 = displayEndOfLine[2];
2783           // Add the missing newline and terminate the string.
2784           displayEndOfLine[1] = '\n';
2785           displayEndOfLine[2] = 0;
2786           // This is a good thing to log.
2787           MOZ_LOG(IMAP, mozilla::LogLevel::Info,
2788                   ("PARSER: CR/LF split at chunk boundary"));
2789         } else {
2790           // Typical case where EOLs are not split. Terminate the string.
2791           displayEndOfLine[1] = 0;
2792         }
2793         // Process this modified string pointed to by fCurrentLine.
2794         fServerConnection.HandleMessageDownLoadLine(fCurrentLine, !lastChunk);
2795         // Restore fCurrentLine's original content.
2796         displayEndOfLine[1] = saveit1;
2797         if (fNextChunkStartsWithNewline) displayEndOfLine[2] = saveit2;
2798       } else {
2799         // Not the last line of a chunk.
2800         bool processTheLine = true;
2801         if (fNextChunkStartsWithNewline && origin > 0) {
2802           // A split of the \r\n between chunks was detected. Ignore orphan \n
2803           // on line by itself which can occur on the first line of a 2nd or
2804           // later chunk. Line length should be 1 and the only character should
2805           // be \n. Note: If previous message ended with just \r, don't expect
2806           // the first chunk of a message (origin == 0) to begin with \n.
2807           // (Typically, there is only one chunk required for a message or
2808           // header response unless its size exceeds the chunking threshold.)
2809           if (strlen(fCurrentLine) > 1 || fCurrentLine[0] != '\n') {
2810             // In case expected orphan \n is not really there, go ahead and
2811             // process the line. This should theoretically not occur but rarely,
2812             // and for yet to be determined reasons, it does. Logging may help.
2813             NS_WARNING(
2814                 "'\\n' is not the only character in this line as expected!");
2815             MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
2816                     ("PARSER: expecting just '\\n' but line is = |%s|",
2817                      fCurrentLine));
2818           } else {
2819             // Discard the line containing only \n.
2820             processTheLine = false;
2821             MOZ_LOG(IMAP, mozilla::LogLevel::Debug,
2822                     ("PARSER: discarding lone '\\n'"));
2823           }
2824         }
2825         if (processTheLine) {
2826           fServerConnection.HandleMessageDownLoadLine(
2827               fCurrentLine,
2828               !lastChunk && (charsReadSoFar == numberOfCharsInThisChunk),
2829               fCurrentLine);
2830         }
2831         fNextChunkStartsWithNewline = false;
2832       }
2833     }
2834   }
2835 
2836   if (ContinueParse()) {
2837     if (charsReadSoFar > numberOfCharsInThisChunk) {
2838       // move the lexical analyzer state to the end of this message because this
2839       // message fetch ends in the middle of this line.
2840       AdvanceTokenizerStartingPoint(
2841           strlen(fCurrentLine) - (charsReadSoFar - numberOfCharsInThisChunk));
2842       AdvanceToNextToken();
2843     } else {
2844       skip_to_CRLF();
2845       AdvanceToNextToken();
2846     }
2847   } else {
2848     // Don't typically (maybe never?) see this.
2849     fNextChunkStartsWithNewline = false;
2850   }
2851   return lastChunk;
2852 }
2853 
CurrentFolderReadOnly()2854 bool nsImapServerResponseParser::CurrentFolderReadOnly() {
2855   return fCurrentFolderReadOnly;
2856 }
2857 
NumberOfMessages()2858 int32_t nsImapServerResponseParser::NumberOfMessages() {
2859   return fNumberOfExistingMessages;
2860 }
2861 
NumberOfRecentMessages()2862 int32_t nsImapServerResponseParser::NumberOfRecentMessages() {
2863   return fNumberOfRecentMessages;
2864 }
2865 
NumberOfUnseenMessages()2866 int32_t nsImapServerResponseParser::NumberOfUnseenMessages() {
2867   return fNumberOfUnseenMessages;
2868 }
2869 
FolderUID()2870 int32_t nsImapServerResponseParser::FolderUID() { return fFolderUIDValidity; }
2871 
SetCurrentResponseUID(uint32_t uid)2872 void nsImapServerResponseParser::SetCurrentResponseUID(uint32_t uid) {
2873   if (uid > 0) fCurrentResponseUID = uid;
2874 }
2875 
CurrentResponseUID()2876 uint32_t nsImapServerResponseParser::CurrentResponseUID() {
2877   return fCurrentResponseUID;
2878 }
2879 
HighestRecordedUID()2880 uint32_t nsImapServerResponseParser::HighestRecordedUID() {
2881   return fHighestRecordedUID;
2882 }
2883 
ResetHighestRecordedUID()2884 void nsImapServerResponseParser::ResetHighestRecordedUID() {
2885   fHighestRecordedUID = 0;
2886 }
2887 
IsNumericString(const char * string)2888 bool nsImapServerResponseParser::IsNumericString(const char* string) {
2889   int i;
2890   for (i = 0; i < (int)PL_strlen(string); i++) {
2891     if (!isdigit(string[i])) {
2892       return false;
2893     }
2894   }
2895 
2896   return true;
2897 }
2898 
2899 already_AddRefed<nsImapMailboxSpec>
CreateCurrentMailboxSpec(const char * mailboxName)2900 nsImapServerResponseParser::CreateCurrentMailboxSpec(
2901     const char* mailboxName /* = nullptr */) {
2902   RefPtr<nsImapMailboxSpec> returnSpec = new nsImapMailboxSpec;
2903   const char* mailboxNameToConvert =
2904       (mailboxName) ? mailboxName : fSelectedMailboxName;
2905   if (mailboxNameToConvert) {
2906     const char* serverKey = fServerConnection.GetImapServerKey();
2907     nsImapNamespace* ns = nullptr;
2908     if (serverKey && fHostSessionList)
2909       fHostSessionList->GetNamespaceForMailboxForHost(
2910           serverKey, mailboxNameToConvert, ns);  // for
2911     // delimiter
2912     returnSpec->mHierarchySeparator = (ns) ? ns->GetDelimiter() : '/';
2913   }
2914 
2915   returnSpec->mFolderSelected =
2916       !mailboxName;  // if mailboxName is null, we're doing a Status
2917   returnSpec->mFolder_UIDVALIDITY = fFolderUIDValidity;
2918   returnSpec->mHighestModSeq = fHighestModSeq;
2919   returnSpec->mNumOfMessages =
2920       (mailboxName) ? fStatusExistingMessages : fNumberOfExistingMessages;
2921   returnSpec->mNumOfUnseenMessages =
2922       (mailboxName) ? fStatusUnseenMessages : fNumberOfUnseenMessages;
2923   returnSpec->mNumOfRecentMessages =
2924       (mailboxName) ? fStatusRecentMessages : fNumberOfRecentMessages;
2925   returnSpec->mNextUID = fStatusNextUID;
2926 
2927   returnSpec->mSupportedUserFlags = fSupportsUserDefinedFlags;
2928 
2929   returnSpec->mBoxFlags = kNoFlags;  // stub
2930   returnSpec->mOnlineVerified =
2931       false;  // we're fabricating this.  The flags aren't verified.
2932   returnSpec->mAllocatedPathName.Assign(mailboxNameToConvert);
2933   returnSpec->mConnection = &fServerConnection;
2934   if (returnSpec->mConnection) {
2935     nsIURI* aUrl = nullptr;
2936     nsresult rv = NS_OK;
2937     returnSpec->mConnection->GetCurrentUrl()->QueryInterface(NS_GET_IID(nsIURI),
2938                                                              (void**)&aUrl);
2939     if (NS_SUCCEEDED(rv) && aUrl) aUrl->GetHost(returnSpec->mHostName);
2940 
2941     NS_IF_RELEASE(aUrl);
2942   } else
2943     returnSpec->mHostName.Truncate();
2944 
2945   if (fFlagState)
2946     returnSpec->mFlagState = fFlagState;  // copies flag state
2947   else
2948     returnSpec->mFlagState = nullptr;
2949 
2950   return returnSpec.forget();
2951 }
2952 // Reset the flag state.
ResetFlagInfo()2953 void nsImapServerResponseParser::ResetFlagInfo() {
2954   if (fFlagState) fFlagState->Reset();
2955 }
2956 
GetLastFetchChunkReceived()2957 bool nsImapServerResponseParser::GetLastFetchChunkReceived() {
2958   return fLastChunk;
2959 }
2960 
ClearLastFetchChunkReceived()2961 void nsImapServerResponseParser::ClearLastFetchChunkReceived() {
2962   fLastChunk = false;
2963 }
2964 
SetHostSessionList(nsIImapHostSessionList * aHostSessionList)2965 void nsImapServerResponseParser::SetHostSessionList(
2966     nsIImapHostSessionList* aHostSessionList) {
2967   fHostSessionList = aHostSessionList;
2968 }
2969 
SetSyntaxError(bool error,const char * msg)2970 void nsImapServerResponseParser::SetSyntaxError(bool error, const char* msg) {
2971   nsImapGenericParser::SetSyntaxError(error, msg);
2972   if (error) {
2973     if (!fCurrentLine) {
2974       HandleMemoryFailure();
2975       fServerConnection.Log("PARSER", ("Internal Syntax Error: %s: <no line>"),
2976                             msg);
2977     } else {
2978       if (!strcmp(fCurrentLine, CRLF))
2979         fServerConnection.Log("PARSER", "Internal Syntax Error: %s: <CRLF>",
2980                               msg);
2981       else {
2982         if (msg)
2983           fServerConnection.Log("PARSER", "Internal Syntax Error: %s:", msg);
2984         fServerConnection.Log("PARSER", "Internal Syntax Error on line: %s",
2985                               fCurrentLine);
2986       }
2987     }
2988   }
2989 }
2990 
BeginMessageDownload(const char * content_type)2991 nsresult nsImapServerResponseParser::BeginMessageDownload(
2992     const char* content_type) {
2993   nsresult rv = fServerConnection.BeginMessageDownLoad(fSizeOfMostRecentMessage,
2994                                                        content_type);
2995   if (NS_FAILED(rv)) {
2996     skip_to_CRLF();
2997     fServerConnection.PseudoInterrupt(true);
2998     fServerConnection.AbortMessageDownLoad();
2999   }
3000   return rv;
3001 }
3002