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