summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/imap/src/nsImapGenericParser.cpp
blob: 009c7c1e5a6cdc479158a66ccfd0500d45baa8e0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "msgCore.h"  // for pre-compiled headers

#include "nsImapGenericParser.h"
#include "nsString.h"

////////////////// nsImapGenericParser /////////////////////////

nsImapGenericParser::nsImapGenericParser()
    : fNextToken(nullptr),
      fCurrentLine(nullptr),
      fLineOfTokens(nullptr),
      fStartOfLineOfTokens(nullptr),
      fCurrentTokenPlaceHolder(nullptr),
      fAtEndOfLine(false),
      fParserState(stateOK) {}

nsImapGenericParser::~nsImapGenericParser() {
  PR_FREEIF(fCurrentLine);
  PR_FREEIF(fStartOfLineOfTokens);
}

void nsImapGenericParser::HandleMemoryFailure() { SetConnected(false); }

void nsImapGenericParser::ResetLexAnalyzer() {
  PR_FREEIF(fCurrentLine);
  PR_FREEIF(fStartOfLineOfTokens);

  fNextToken = fCurrentLine = fLineOfTokens = fStartOfLineOfTokens =
      fCurrentTokenPlaceHolder = nullptr;
  fAtEndOfLine = false;
}

bool nsImapGenericParser::LastCommandSuccessful() {
  return fParserState == stateOK;
}

void nsImapGenericParser::SetSyntaxError(bool error, const char* msg) {
  if (error)
    fParserState |= stateSyntaxErrorFlag;
  else
    fParserState &= ~stateSyntaxErrorFlag;
  NS_ASSERTION(!error, "syntax error in generic parser");
}

void nsImapGenericParser::SetConnected(bool connected) {
  if (connected)
    fParserState &= ~stateDisconnectedFlag;
  else
    fParserState |= stateDisconnectedFlag;
}

void nsImapGenericParser::skip_to_CRLF() {
  while (Connected() && !fAtEndOfLine) AdvanceToNextToken();
}

// fNextToken initially should point to
// a string after the initial open paren ("(")
// After this call, fNextToken points to the
// first character after the matching close
// paren.  Only call AdvanceToNextToken() to get the NEXT
// token after the one returned in fNextToken.
void nsImapGenericParser::skip_to_close_paren() {
  int numberOfCloseParensNeeded = 1;
  while (ContinueParse()) {
    // go through fNextToken, account for nested parens
    const char* loc;
    for (loc = fNextToken; loc && *loc; loc++) {
      if (*loc == '(')
        numberOfCloseParensNeeded++;
      else if (*loc == ')') {
        numberOfCloseParensNeeded--;
        if (numberOfCloseParensNeeded == 0) {
          fNextToken = loc + 1;
          if (!fNextToken || !*fNextToken) AdvanceToNextToken();
          return;
        }
      } else if (*loc == '{' || *loc == '"') {
        // quoted or literal
        fNextToken = loc;
        char* a = CreateString();
        PR_FREEIF(a);
        break;  // move to next token
      }
    }
    if (ContinueParse()) AdvanceToNextToken();
  }
}

void nsImapGenericParser::AdvanceToNextToken() {
  if (!fCurrentLine || fAtEndOfLine) AdvanceToNextLine();
  if (Connected()) {
    if (!fStartOfLineOfTokens) {
      // this is the first token of the line; setup tokenizer now
      fStartOfLineOfTokens = PL_strdup(fCurrentLine);
      if (!fStartOfLineOfTokens) {
        HandleMemoryFailure();
        return;
      }
      fLineOfTokens = fStartOfLineOfTokens;
      fCurrentTokenPlaceHolder = fStartOfLineOfTokens;
    }
    fNextToken = NS_strtok(WHITESPACE, &fCurrentTokenPlaceHolder);
    if (!fNextToken) {
      fAtEndOfLine = true;
      fNextToken = CRLF;
    }
  }
}

void nsImapGenericParser::AdvanceToNextLine() {
  PR_FREEIF(fCurrentLine);
  PR_FREEIF(fStartOfLineOfTokens);

  bool ok = GetNextLineForParser(&fCurrentLine);
  if (!ok) {
    SetConnected(false);
    fStartOfLineOfTokens = nullptr;
    fLineOfTokens = nullptr;
    fCurrentTokenPlaceHolder = nullptr;
    fAtEndOfLine = true;
    fNextToken = CRLF;
  } else if (!fCurrentLine) {
    HandleMemoryFailure();
  } else {
    fNextToken = nullptr;
    // determine if there are any tokens (without calling AdvanceToNextToken);
    // otherwise we are already at end of line
    NS_ASSERTION(strlen(WHITESPACE) == 3, "assume 3 chars of whitespace");
    char* firstToken = fCurrentLine;
    while (*firstToken &&
           (*firstToken == WHITESPACE[0] || *firstToken == WHITESPACE[1] ||
            *firstToken == WHITESPACE[2]))
      firstToken++;
    fAtEndOfLine = (*firstToken == '\0');
  }
}

// advances |fLineOfTokens| by |bytesToAdvance| bytes
void nsImapGenericParser::AdvanceTokenizerStartingPoint(
    int32_t bytesToAdvance) {
  NS_ASSERTION(bytesToAdvance >= 0, "bytesToAdvance must not be negative");
  if (!fStartOfLineOfTokens) {
    AdvanceToNextToken();  // the tokenizer was not yet initialized, do it now
    if (!fStartOfLineOfTokens) return;
  }

  if (!fStartOfLineOfTokens) return;
  // The last call to AdvanceToNextToken() cleared the token separator to '\0'
  // iff |fCurrentTokenPlaceHolder|.  We must recover this token separator now.
  if (fCurrentTokenPlaceHolder) {
    int endTokenOffset = fCurrentTokenPlaceHolder - fStartOfLineOfTokens - 1;
    if (endTokenOffset >= 0)
      fStartOfLineOfTokens[endTokenOffset] = fCurrentLine[endTokenOffset];
  }

  NS_ASSERTION(bytesToAdvance + (fLineOfTokens - fStartOfLineOfTokens) <=
                   (int32_t)strlen(fCurrentLine),
               "cannot advance beyond end of fLineOfTokens");
  fLineOfTokens += bytesToAdvance;
  fCurrentTokenPlaceHolder = fLineOfTokens;
}

// RFC3501:  astring = 1*ASTRING-CHAR / string
//           string  = quoted / literal
// This function leaves us off with fCurrentTokenPlaceHolder immediately after
// the end of the Astring.  Call AdvanceToNextToken() to get the token after it.
char* nsImapGenericParser::CreateAstring() {
  if (*fNextToken == '{') return CreateLiteral();  // literal
  if (*fNextToken == '"') return CreateQuoted();   // quoted
  return CreateAtom(true);                         // atom
}

// Create an atom
// This function does not advance the parser.
// Call AdvanceToNextToken() to get the next token after the atom.
// RFC3501:  atom            = 1*ATOM-CHAR
//           ASTRING-CHAR    = ATOM-CHAR / resp-specials
//           ATOM-CHAR       = <any CHAR except atom-specials>
//           atom-specials   = "(" / ")" / "{" / SP / CTL / list-wildcards /
//                             quoted-specials / resp-specials
//           list-wildcards  = "%" / "*"
//           quoted-specials = DQUOTE / "\"
//           resp-specials   = "]"
// "Characters are 7-bit US-ASCII unless otherwise specified." [RFC3501, 1.2.]
char* nsImapGenericParser::CreateAtom(bool isAstring) {
  char* rv = PL_strdup(fNextToken);
  if (!rv) {
    HandleMemoryFailure();
    return nullptr;
  }
  // We wish to stop at the following characters (in decimal ascii)
  // 1-31 (CTL), 32 (SP), 34 '"', 37 '%', 40-42 "()*", 92 '\\', 123 '{'
  // also, ']' is only allowed in astrings
  char* last = rv;
  char c = *last;
  while ((c > 42 || c == 33 || c == 35 || c == 36 || c == 38 || c == 39) &&
         c != '\\' && c != '{' && (isAstring || c != ']'))
    c = *++last;
  if (rv == last) {
    SetSyntaxError(true, "no atom characters found");
    PL_strfree(rv);
    return nullptr;
  }
  if (*last) {
    // not the whole token was consumed
    *last = '\0';
    AdvanceTokenizerStartingPoint((fNextToken - fLineOfTokens) + (last - rv));
  }
  return rv;
}

// CreateNilString return either NULL (for "NIL") or a string
// Call with fNextToken pointing to the thing which we think is the nilstring.
// This function leaves us off with fCurrentTokenPlaceHolder immediately after
// the end of the string.
// Regardless of type, call AdvanceToNextToken() to get the token after it.
// RFC3501:   nstring  = string / nil
//            nil      = "NIL"
char* nsImapGenericParser::CreateNilString() {
  if (!PL_strncasecmp(fNextToken, "NIL", 3)) {
    // check if there is text after "NIL" in fNextToken,
    // equivalent handling as in CreateQuoted
    if (fNextToken[3])
      AdvanceTokenizerStartingPoint((fNextToken - fLineOfTokens) + 3);
    return NULL;
  }
  return CreateString();
}

// Create a string, which can either be quoted or literal,
// but not an atom.
// This function leaves us off with fCurrentTokenPlaceHolder immediately after
// the end of the String.  Call AdvanceToNextToken() to get the token after it.
char* nsImapGenericParser::CreateString() {
  if (*fNextToken == '{') {
    char* rv = CreateLiteral();  // literal
    return (rv);
  }
  if (*fNextToken == '"') {
    char* rv = CreateQuoted();  // quoted
    return (rv);
  }
  SetSyntaxError(true, "string does not start with '{' or '\"'");
  return NULL;
}

// This function sets fCurrentTokenPlaceHolder immediately after the end of the
// closing quote.  Call AdvanceToNextToken() to get the token after it.
// QUOTED_CHAR     ::= <any TEXT_CHAR except quoted_specials> /
//                     "\" quoted_specials
// TEXT_CHAR       ::= <any CHAR except CR and LF>
// quoted_specials ::= <"> / "\"
// Note that according to RFC 1064 and RFC 2060, CRs and LFs are not allowed
// inside a quoted string.  It is sufficient to read from the current line only.
char* nsImapGenericParser::CreateQuoted(bool /*skipToEnd*/) {
  // one char past opening '"'
  char* currentChar = fCurrentLine + (fNextToken - fStartOfLineOfTokens) + 1;

  int escapeCharsCut = 0;
  nsCString returnString(currentChar);
  int charIndex;
  for (charIndex = 0; returnString.CharAt(charIndex) != '"'; charIndex++) {
    if (!returnString.CharAt(charIndex)) {
      SetSyntaxError(true, "no closing '\"' found in quoted");
      return nullptr;
    }
    if (returnString.CharAt(charIndex) == '\\') {
      // eat the escape character, but keep the escaped character
      returnString.Cut(charIndex, 1);
      escapeCharsCut++;
    }
  }
  // +2 because of the start and end quotes
  AdvanceTokenizerStartingPoint((fNextToken - fLineOfTokens) + charIndex +
                                escapeCharsCut + 2);

  returnString.SetLength(charIndex);
  return ToNewCString(returnString);
}

// This function leaves us off with fCurrentTokenPlaceHolder immediately after
// the end of the literal string.  Call AdvanceToNextToken() to get the token
// after the literal string.
// RFC3501:  literal = "{" number "}" CRLF *CHAR8
//                       ; Number represents the number of CHAR8s
//           CHAR8   = %x01-ff
//                       ; any OCTET except NUL, %x00
char* nsImapGenericParser::CreateLiteral() {
  int32_t numberOfCharsInMessage = atoi(fNextToken + 1);
  uint32_t numBytes = numberOfCharsInMessage + 1;
  NS_ASSERTION(numBytes, "overflow!");
  if (!numBytes) return nullptr;
  char* returnString = (char*)PR_Malloc(numBytes);
  if (!returnString) {
    HandleMemoryFailure();
    return nullptr;
  }

  int32_t currentLineLength = 0;
  int32_t charsReadSoFar = 0;
  int32_t bytesToCopy = 0;
  while (charsReadSoFar < numberOfCharsInMessage) {
    AdvanceToNextLine();
    if (!ContinueParse()) break;

    currentLineLength = strlen(fCurrentLine);
    bytesToCopy = (currentLineLength > numberOfCharsInMessage - charsReadSoFar
                       ? numberOfCharsInMessage - charsReadSoFar
                       : currentLineLength);
    NS_ASSERTION(bytesToCopy, "zero-length line?");
    memcpy(returnString + charsReadSoFar, fCurrentLine, bytesToCopy);
    charsReadSoFar += bytesToCopy;
  }

  if (ContinueParse()) {
    if (currentLineLength == bytesToCopy) {
      // We have consumed the entire line.
      // Consider the input  "{4}\r\n"  "L1\r\n"  " A2\r\n"  which is read
      // line-by-line.  Reading an Astring, this should result in "L1\r\n".
      // Note that the second line is "L1\r\n", where the "\r\n" is part of
      // the literal.  Hence, we now read the next line to ensure that the
      // next call to AdvanceToNextToken() leads to fNextToken=="A2" in our
      // example.
      AdvanceToNextLine();
    } else
      AdvanceTokenizerStartingPoint(bytesToCopy);
  }

  returnString[charsReadSoFar] = 0;
  return returnString;
}

// Call this to create a buffer containing all characters within
// a given set of parentheses.
// Call this with fNextToken[0]=='(', that is, the open paren
// of the group.
// It will allocate and return all characters up to and including the
// corresponding closing paren, and leave the parser in the right place
// afterwards.
char* nsImapGenericParser::CreateParenGroup() {
  NS_ASSERTION(fNextToken[0] == '(', "we don't have a paren group!");

  int numOpenParens = 0;
  AdvanceTokenizerStartingPoint(fNextToken - fLineOfTokens);

  // Build up a buffer containing the paren group.
  nsCString returnString;
  char* parenGroupStart = fCurrentTokenPlaceHolder;
  NS_ASSERTION(parenGroupStart[0] == '(', "we don't have a paren group (2)!");
  while (*fCurrentTokenPlaceHolder) {
    if (*fCurrentTokenPlaceHolder == '{')  // literal
    {
      // Ensure it is a properly formatted literal.
      NS_ASSERTION(!strcmp("}\r\n", fCurrentTokenPlaceHolder +
                                        strlen(fCurrentTokenPlaceHolder) - 3),
                   "not a literal");

      // Append previous characters and the "{xx}\r\n" to buffer.
      returnString.Append(parenGroupStart);

      // Append literal itself.
      AdvanceToNextToken();
      if (!ContinueParse()) break;
      char* lit = CreateLiteral();
      NS_ASSERTION(lit, "syntax error or out of memory");
      if (!lit) break;
      returnString.Append(lit);
      PR_Free(lit);
      if (!ContinueParse()) break;
      parenGroupStart = fCurrentTokenPlaceHolder;
    } else if (*fCurrentTokenPlaceHolder == '"')  // quoted
    {
      // Append the _escaped_ version of the quoted string:
      // just skip it (because the quoted string must be on the same line).
      AdvanceToNextToken();
      if (!ContinueParse()) break;
      char* q = CreateQuoted();
      if (!q) break;
      PR_Free(q);
      if (!ContinueParse()) break;
    } else {
      // Append this character to the buffer.
      char c = *fCurrentTokenPlaceHolder++;
      if (c == '(')
        numOpenParens++;
      else if (c == ')') {
        numOpenParens--;
        if (numOpenParens == 0) break;
      }
    }
  }

  if (numOpenParens != 0 || !ContinueParse()) {
    SetSyntaxError(true, "closing ')' not found in paren group");
    return nullptr;
  }

  returnString.Append(parenGroupStart,
                      fCurrentTokenPlaceHolder - parenGroupStart);
  AdvanceToNextToken();
  return ToNewCString(returnString);
}