summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/base/src/LineReader.jsm
blob: 2417457e3ca895a6201589ea37a7ae6ada7e5b40 (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
/* 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/. */

const EXPORTED_SYMBOLS = ["LineReader"];

/**
 * For a single request, mail servers may return several multi-line responses. A
 * definition of multi-line responses can be found at rfc3977#section-3.1.1.
 *
 * This class helps dealing with multi-line responses by:
 * - Break up a response to lines
 * - Join incomplete line from a previous response with the current response
 * - Remove stuffed dot (.. at the beginning of a line)
 * - Detect the end of the response (\r\n.\r\n)
 */
class LineReader {
  processingMultiLineResponse = false;
  _data = "";

  /**
   * Read a multi-line response, emit each line through a callback.
   *
   * @param {string} data - A multi-line response received from the server.
   * @param {Function} lineCallback - A line will be passed to the callback each
   *   time.
   * @param {Function} doneCallback - A function to be called when data is ended.
   */
  read(data, lineCallback, doneCallback) {
    this._data += data;
    if (this._data == ".\r\n" || this._data.endsWith("\r\n.\r\n")) {
      this.processingMultiLineResponse = false;
      this._data = this._data.slice(0, -3);
    } else {
      this.processingMultiLineResponse = true;
    }
    if (this._running) {
      // This function can be called multiple times, but this._data should only
      // be consumed once.
      return;
    }

    let i = 0;
    this._running = true;
    while (this._data) {
      let index = this._data.indexOf("\r\n");
      if (index == -1) {
        // Not enough data, save it for the next round.
        break;
      }
      let line = this._data.slice(0, index + 2);
      if (line.startsWith("..")) {
        // Remove stuffed dot.
        line = line.slice(1);
      }
      lineCallback(line);
      this._data = this._data.slice(index + 2);
      if (++i % 100 == 0) {
        // Prevent blocking main process for too long.
        Services.tm.spinEventLoopUntilEmpty();
      }
    }
    this._running = false;
    if (!this.processingMultiLineResponse && !this._data) {
      doneCallback();
    }
  }
}