|
26 | 26 |
|
27 | 27 | var ASCII_PLUS = 43;
|
28 | 28 |
|
| 29 | + // State tracking when constructing an IMAP command from buffers. |
| 30 | + var BUFFER_STATE_LITERAL = 'literal'; |
| 31 | + var BUFFER_STATE_POSSIBLY_LITERAL_LENGTH_1 = 'literal_length_1'; |
| 32 | + var BUFFER_STATE_POSSIBLY_LITERAL_LENGTH_2 = 'literal_length_2'; |
| 33 | + var BUFFER_STATE_DEFAULT = 'default'; |
| 34 | + |
29 | 35 | /**
|
30 | 36 | * Creates a connection object to an IMAP server. Call `connect` method to inititate
|
31 | 37 | * the actual connection, the constructor only defines the properties but does not actually connect.
|
|
71 | 77 |
|
72 | 78 | // As the server sends data in chunks, it needs to be split into separate lines. Helps parsing the input.
|
73 | 79 | this._incomingBuffers = [];
|
| 80 | + this._bufferState = BUFFER_STATE_DEFAULT; |
74 | 81 | this._literalRemaining = 0;
|
75 | 82 |
|
76 | 83 | //
|
|
408 | 415 | };
|
409 | 416 |
|
410 | 417 | Imap.prototype._iterateIncomingBuffer = function*() {
|
411 |
| - let buf; |
412 |
| - if (this._concatLastTwoBuffers) { |
413 |
| - // allocate new buffer for the sake of simpler parsing |
414 |
| - delete this._concatLastTwoBuffers; |
415 |
| - const latest = this._incomingBuffers.pop(); |
416 |
| - const prevBuf = this._incomingBuffers[this._incomingBuffers.length-1]; |
417 |
| - buf = new Uint8Array(prevBuf.length + latest.length); |
418 |
| - buf.set(prevBuf); |
419 |
| - buf.set(latest, prevBuf.length); |
420 |
| - this._incomingBuffers[this._incomingBuffers.length-1] = buf; |
421 |
| - } else { |
422 |
| - buf = this._incomingBuffers[this._incomingBuffers.length-1]; |
423 |
| - } |
| 418 | + let buf = this._incomingBuffers[this._incomingBuffers.length-1]; |
424 | 419 | let i = 0;
|
425 | 420 |
|
426 | 421 | // loop invariant:
|
427 | 422 | // this._incomingBuffers starts with the beginning of incoming command.
|
428 | 423 | // buf is shorthand for last element of this._incomingBuffers.
|
429 | 424 | // buf[0..i-1] is part of incoming command.
|
430 | 425 | while (i < buf.length) {
|
431 |
| - if (this._literalRemaining === 0) { |
432 |
| - const leftIdx = buf.indexOf(LEFT_CURLY_BRACKET, i); |
433 |
| - if (leftIdx > -1) { |
434 |
| - const leftOfLeftCurly = new Uint8Array(buf.buffer, i, leftIdx-i); |
435 |
| - if (leftOfLeftCurly.indexOf(LINE_FEED) === -1) { |
436 |
| - let j = leftIdx + 1; |
437 |
| - while (buf[j] >= 48 && buf[j] <= 57) { // digits |
438 |
| - j++; |
439 |
| - } |
440 |
| - if (j+3 >= buf.length) { |
441 |
| - // not enough info to determine if this is literal length |
442 |
| - this._concatLastTwoBuffers = true; |
443 |
| - return; |
444 |
| - } |
445 |
| - if (j > leftIdx + 1 && |
446 |
| - buf[j] === RIGHT_CURLY_BRACKET && |
447 |
| - buf[j+1] === CARRIAGE_RETURN && |
448 |
| - buf[j+2] === LINE_FEED) { |
449 |
| - const numBuf = buf.subarray(leftIdx+1, j); |
450 |
| - this._literalRemaining = Number(mimecodec.fromTypedArray(numBuf)); |
451 |
| - i = j + 3; |
452 |
| - } else { |
453 |
| - i = j; |
454 |
| - continue; // not a literal but there might still be one |
455 |
| - } |
456 |
| - } |
457 |
| - } |
458 |
| - } |
459 |
| - |
460 |
| - const diff = Math.min(buf.length-i, this._literalRemaining); |
461 |
| - if (diff) { |
462 |
| - this._literalRemaining -= diff; |
463 |
| - i += diff; |
464 |
| - if (this._literalRemaining === 0) { |
465 |
| - continue; // find another literal |
466 |
| - } |
467 |
| - } |
468 |
| - |
469 |
| - if (this._literalRemaining === 0 && i < buf.length) { |
470 |
| - const LFidx = buf.indexOf(LINE_FEED, i); |
471 |
| - if (LFidx > -1) { |
472 |
| - if (LFidx < buf.length-1) { |
473 |
| - this._incomingBuffers[this._incomingBuffers.length-1] = new Uint8Array(buf.buffer, 0, LFidx+1); |
474 |
| - } |
475 |
| - const commandLength = this._incomingBuffers.reduce((prev, curr) => prev + curr.length, 0) - 2; // 2 for CRLF |
476 |
| - const command = new Uint8Array(commandLength); |
477 |
| - let index = 0; |
478 |
| - while (this._incomingBuffers.length > 0) { |
479 |
| - let uint8Array = this._incomingBuffers.shift(); |
480 |
| - |
481 |
| - const remainingLength = commandLength - index; |
482 |
| - if (uint8Array.length > remainingLength) { |
483 |
| - const excessLength = uint8Array.length - remainingLength; |
484 |
| - uint8Array = uint8Array.subarray(0, -excessLength); |
485 |
| - |
486 |
| - if (this._incomingBuffers.length > 0) { |
487 |
| - this._incomingBuffers = []; |
488 |
| - } |
489 |
| - } |
490 |
| - command.set(uint8Array, index); |
491 |
| - index += uint8Array.length; |
492 |
| - } |
493 |
| - yield command; |
494 |
| - if (LFidx < buf.length-1) { |
495 |
| - buf = new Uint8Array(buf.subarray(LFidx+1)); |
496 |
| - this._incomingBuffers.push(buf); |
497 |
| - i = 0; |
498 |
| - } else { |
499 |
| - // clear the timeout when an entire command has arrived |
500 |
| - // and not waiting on more data for next command |
501 |
| - clearTimeout(this._socketTimeoutTimer); |
502 |
| - this._socketTimeoutTimer = null; |
| 426 | + switch (this._bufferState) { |
| 427 | + case BUFFER_STATE_LITERAL: |
| 428 | + const diff = Math.min(buf.length-i, this._literalRemaining); |
| 429 | + this._literalRemaining -= diff; |
| 430 | + i += diff; |
| 431 | + if (this._literalRemaining === 0) { |
| 432 | + this._bufferState = BUFFER_STATE_DEFAULT; |
| 433 | + } |
| 434 | + continue; |
| 435 | + |
| 436 | + case BUFFER_STATE_POSSIBLY_LITERAL_LENGTH_2: |
| 437 | + if (i < buf.length) { |
| 438 | + if (buf[i] === CARRIAGE_RETURN) { |
| 439 | + this._literalRemaining = Number(mimecodec.fromTypedArray(this._lengthBuffer)) + 2; // for CRLF |
| 440 | + this._bufferState = BUFFER_STATE_LITERAL; |
| 441 | + } else { |
| 442 | + this._bufferState = BUFFER_STATE_DEFAULT; |
| 443 | + } |
| 444 | + delete this._lengthBuffer; |
| 445 | + } |
| 446 | + continue; |
| 447 | + |
| 448 | + case BUFFER_STATE_POSSIBLY_LITERAL_LENGTH_1: |
| 449 | + const start = i; |
| 450 | + while (i < buf.length && buf[i] >= 48 && buf[i] <= 57) { // digits |
| 451 | + i++; |
| 452 | + } |
| 453 | + if (start !== i) { |
| 454 | + const latest = buf.subarray(start, i); |
| 455 | + const prevBuf = this._lengthBuffer; |
| 456 | + this._lengthBuffer = new Uint8Array(prevBuf.length + latest.length); |
| 457 | + this._lengthBuffer.set(prevBuf); |
| 458 | + this._lengthBuffer.set(latest, prevBuf.length); |
| 459 | + } |
| 460 | + if (i < buf.length) { |
| 461 | + if (this._lengthBuffer.length > 0 && buf[i] === RIGHT_CURLY_BRACKET) { |
| 462 | + this._bufferState = BUFFER_STATE_POSSIBLY_LITERAL_LENGTH_2; |
| 463 | + } else { |
| 464 | + delete this._lengthBuffer; |
| 465 | + this._bufferState = BUFFER_STATE_DEFAULT; |
| 466 | + } |
| 467 | + i++; |
| 468 | + } |
| 469 | + continue; |
| 470 | + |
| 471 | + default: |
| 472 | + // find literal length |
| 473 | + const leftIdx = buf.indexOf(LEFT_CURLY_BRACKET, i); |
| 474 | + if (leftIdx > -1) { |
| 475 | + const leftOfLeftCurly = new Uint8Array(buf.buffer, i, leftIdx-i); |
| 476 | + if (leftOfLeftCurly.indexOf(LINE_FEED) === -1) { |
| 477 | + i = leftIdx + 1; |
| 478 | + this._lengthBuffer = new Uint8Array(0); |
| 479 | + this._bufferState = BUFFER_STATE_POSSIBLY_LITERAL_LENGTH_1; |
| 480 | + continue; |
| 481 | + } |
| 482 | + } |
| 483 | + |
| 484 | + // find end of command |
| 485 | + const LFidx = buf.indexOf(LINE_FEED, i); |
| 486 | + if (LFidx > -1) { |
| 487 | + if (LFidx < buf.length-1) { |
| 488 | + this._incomingBuffers[this._incomingBuffers.length-1] = new Uint8Array(buf.buffer, 0, LFidx+1); |
| 489 | + } |
| 490 | + const commandLength = this._incomingBuffers.reduce((prev, curr) => prev + curr.length, 0) - 2; // 2 for CRLF |
| 491 | + const command = new Uint8Array(commandLength); |
| 492 | + let index = 0; |
| 493 | + while (this._incomingBuffers.length > 0) { |
| 494 | + let uint8Array = this._incomingBuffers.shift(); |
| 495 | + |
| 496 | + const remainingLength = commandLength - index; |
| 497 | + if (uint8Array.length > remainingLength) { |
| 498 | + const excessLength = uint8Array.length - remainingLength; |
| 499 | + uint8Array = uint8Array.subarray(0, -excessLength); |
| 500 | + |
| 501 | + if (this._incomingBuffers.length > 0) { |
| 502 | + this._incomingBuffers = []; |
| 503 | + } |
| 504 | + } |
| 505 | + command.set(uint8Array, index); |
| 506 | + index += uint8Array.length; |
| 507 | + } |
| 508 | + yield command; |
| 509 | + if (LFidx < buf.length-1) { |
| 510 | + buf = new Uint8Array(buf.subarray(LFidx+1)); |
| 511 | + this._incomingBuffers.push(buf); |
| 512 | + i = 0; |
| 513 | + } else { |
| 514 | + // clear the timeout when an entire command has arrived |
| 515 | + // and not waiting on more data for next command |
| 516 | + clearTimeout(this._socketTimeoutTimer); |
| 517 | + this._socketTimeoutTimer = null; |
| 518 | + return; |
| 519 | + } |
| 520 | + } else { |
503 | 521 | return;
|
504 |
| - } |
505 |
| - } else { |
506 |
| - return; |
507 |
| - } |
508 |
| - } |
| 522 | + } |
| 523 | + } |
509 | 524 | }
|
510 | 525 | };
|
511 | 526 |
|
|
0 commit comments