Sven Slootweg 5 months ago
parent
commit
7344b45be0
  1. 3
      .eslintrc
  2. 3726
      lib/Connection.js
  3. 12
      lib/Parser.js
  4. 55
      lib/util/command.js
  5. 112
      lib/util/fetch-task.js
  6. 32
      lib/util/p-interval.js
  7. 13
      lib/util/pick-object.js
  8. 7
      package.json
  9. 2
      test/test-connection-fetch-dup.js
  10. 2
      test/test-connection-idle-normal.js
  11. 3
      test/test.js
  12. 49
      yarn.lock

3
.eslintrc

@ -1,3 +1,4 @@
{
"extends": "@joepie91/eslint-config"
"extends": "@joepie91/eslint-config",
"ignorePatterns": "test/**"
}

3726
lib/Connection.js
File diff suppressed because it is too large
View File

12
lib/Parser.js

@ -440,7 +440,7 @@ function parseFetch(text, literals, seqno) {
val = parseBodyStructure(val);
else if (m = RE_BODYINLINEKEY.exec(list[i])) {
// a body was sent as a non-literal
val = new Buffer(''+val);
val = Buffer.from(''+val);
body = new ReadableStream();
body._readableState.sync = false;
body._read = EMPTY_READCB;
@ -764,7 +764,7 @@ function decodeBytes(buf, encoding, offset, mlen, pendoffset, state, nextBuf) {
if (state.encoding === encoding && state.consecutive) {
// concatenate buffer + current bytes in hopes of finally having
// something that's decodable
var newbuf = new Buffer(state.buffer.length + buf.length);
var newbuf = Buffer.alloc(state.buffer.length + buf.length);
state.buffer.copy(newbuf, 0);
buf.copy(newbuf, state.buffer.length);
buf = newbuf;
@ -795,7 +795,7 @@ function decodeBytes(buf, encoding, offset, mlen, pendoffset, state, nextBuf) {
// try to decode a lookahead buffer (current buffer + next buffer)
// and see if it starts with the decoded value of the current buffer.
// if not, the current buffer is partial
var lookahead, lookaheadBuf = new Buffer(buf.length + nextBuf.length);
var lookahead, lookaheadBuf = Buffer.alloc(buf.length + nextBuf.length);
buf.copy(lookaheadBuf);
nextBuf.copy(lookaheadBuf, buf.length);
try {
@ -924,17 +924,17 @@ function decodeWords(str, state) {
state.consecutive = m.consecutive;
if (m.encoding === 'q') {
// q-encoding, similar to quoted-printable
bytes = new Buffer(m.chunk.replace(RE_QENC, qEncReplacer), 'binary');
bytes = Buffer.from(m.chunk.replace(RE_QENC, qEncReplacer), 'binary');
next = undefined;
} else {
// base64
bytes = m.buf || new Buffer(m.chunk, 'base64');
bytes = m.buf || Buffer.from(m.chunk, 'base64');
next = replaces[i + 1];
if (next && next.consecutive && next.encoding === m.encoding
&& next.charset === m.charset) {
// we use the next base64 chunk, if any, to determine the integrity
// of the current chunk
next.buf = new Buffer(next.chunk, 'base64');
next.buf = Buffer.from(next.chunk, 'base64');
}
}
decodeBytes(bytes, m.charset, m.index, m.length, m.pendoffset, state,

55
lib/util/command.js

@ -0,0 +1,55 @@
"use strict";
const utf7 = require('utf7').imap;
const RE_BACKSLASH = /\\/g;
const RE_DBLQUOTE = /"/g;
function escape(str) {
return str.replace(RE_BACKSLASH, '\\\\').replace(RE_DBLQUOTE, '\\"');
}
module.exports = {
command: function (strings, ... interpolations) {
return {
toCommandString: function () {
let processedInterpolations = interpolations.map((interpolation) => {
let isObject = interpolation != null && typeof interpolation === "object";
if (isObject && interpolation._isRaw) {
return interpolation.string;
} else if (isObject && interpolation._is7Bit) {
return escape(interpolation.string);
} else if (typeof interpolation === "string") {
return escape(utf7.encode(interpolation));
} else {
throw new Error(`Invalid input into command string: ${interpolation}`);
}
});
let combined = "";
strings.slice(0, -1).forEach((string, i) => {
combined += string;
combined += processedInterpolations[i];
});
combined += strings[strings.length - 1];
return combined;
}
};
},
unsafeRaw: function (string) {
return {
_isRaw: true,
string: string
};
},
already7Bit: function (string) {
return {
_is7Bit: true,
string: string
};
}
};

112
lib/util/fetch-task.js

@ -0,0 +1,112 @@
"use strict";
const events = require("events");
const syncpipe = require("syncpipe");
const defaultValue = require("default-value");
const splitFilter = require("split-filter");
const mapObject = require("map-obj");
const pickObject = require("./pick-object");
const RE_BODYPART = /^BODY\[/;
const RE_ESCAPE = /\\\\/g;
const FETCH_ATTR_MAP = {
'RFC822.SIZE': 'size',
'BODY': 'struct',
'BODYSTRUCTURE': 'struct',
'ENVELOPE': 'envelope',
'INTERNALDATE': 'date'
};
function mapIncomingAttributeKey(key) {
return defaultValue(FETCH_ATTR_MAP[key], key.toLowerCase());
}
// FIXME: Get rid of the separate-emitter hackery here, and make the task itself an emitter instead, or find some other better way to wire things up
module.exports = function createFetchTaskTracker() {
let tasks = new Map();
return {
get: function (id) {
// TODO: Eventually make this fail hard, after the calling code has been fully refactored
return tasks.get(id);
},
create: function (id, keysToFetch) {
let emitter = new events.EventEmitter();
let task = createFetchTask({ emitter, keysToFetch });
// FIXME: Delete after completion
tasks.set(id, task);
return task;
},
getOrCreate: function (id, keysToFetch) {
if (tasks.has(id)) {
return this.get(id);
} else {
return this.create(id, keysToFetch);
}
}
};
};
function createFetchTask({ emitter, keysToFetch }) {
let attributes = {};
let endHandled = false;
return {
emitter: emitter,
getRemainingKeys: function () {
return keysToFetch;
},
processFetchResponse: function (payload) {
if (keysToFetch.length > 0) {
let caseMappedPayload = mapObject(payload, (key, value) => {
return [ key.toUpperCase(), value ];
});
let [ existingKeys, missingKeys ] = splitFilter(keysToFetch, (key) => caseMappedPayload[key] != null);
let relevantKeys = existingKeys.filter((key) => RE_BODYPART.test(key) === false);
let newAttributes = syncpipe(caseMappedPayload, [
(_) => pickObject(_, relevantKeys),
(_) => mapObject(_, (key, value) => {
return [
/* key */ mapIncomingAttributeKey(key),
/* value */ (key === 'X-GM-LABELS')
// TODO: Why is this special case needed?
? value.map((label) => String(label).replace(RE_ESCAPE, '\\'))
: value
];
})
]);
Object.assign(attributes, newAttributes);
keysToFetch = missingKeys;
let isDone = (missingKeys.length === 0);
if (isDone === true) {
endHandled = true;
// FIXME: Why nextTick?
process.nextTick(function() {
emitter.emit("attributes", attributes);
emitter.emit("end");
});
}
} else {
if (endHandled === false) {
endHandled = true;
// FIXME: Why nextTick?
process.nextTick(function() {
emitter.emit("end");
});
}
}
}
};
};

32
lib/util/p-interval.js

@ -0,0 +1,32 @@
"use strict";
// TODO: Make separate package
const pTry = require("p-try");
module.exports = function pInterval(interval, handler) {
let timer = setTimeout(doHandler, interval);
function doHandler() {
let startTime = Date.now();
pTry(() => {
return handler();
}).then(() => {
let timeElapsed = Date.now() - startTime();
let timeout = (timeElapsed < interval)
? interval - timeElapsed
: 1; // Be consistently async!
timer = setTimeout(doHandler, timeout);
});
}
return function cancelInterval() {
if (timer != null) {
clearTimeout(timer);
timer = null;
}
};
};

13
lib/util/pick-object.js

@ -0,0 +1,13 @@
"use strict";
module.exports = function pickObject(object, keys) {
let newObject = {};
for (let key of keys) {
if (key in object) {
newObject[key] = object[key];
}
}
return newObject;
};

7
package.json

@ -5,7 +5,14 @@
"description": "An IMAP module for node.js that makes communicating with IMAP servers easy",
"main": "./lib/Connection",
"dependencies": {
"bluebird": "^3.7.2",
"default-value": "^1.0.0",
"map-obj": "^4.2.1",
"p-defer": "^3.0.0",
"p-try": "^2.2.0",
"readable-stream": "1.1.x",
"split-filter": "^1.1.3",
"syncpipe": "^1.0.0",
"utf7": ">=1.0.2"
},
"scripts": {

2
test/test-connection-fetch-dup.js

@ -89,4 +89,4 @@ process.once('exit', function() {
date: new Date('05-Sep-2004 00:38:03 +0000'),
flags: [ '\\Seen' ]
});
});
});

2
test/test-connection-idle-normal.js

@ -141,4 +141,4 @@ process.once('exit', function() {
which: 'TEXT',
size: 16
});
});
});

3
test/test.js

@ -1,4 +1,5 @@
require('fs').readdirSync(__dirname).forEach(function(f) {
// if (f === "test-connection-fetch-dup.js")
if (f.substr(0, 5).toLowerCase() === 'test-')
require('./' + f);
});
});

49
yarn.lock

@ -105,6 +105,11 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
assure-array@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/assure-array/-/assure-array-1.0.0.tgz#4f4ad16a87659d6200a4fb7103462033d216ec1f"
integrity sha1-T0rRaodlnWIApPtxA0YgM9IW7B8=
astral-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
@ -115,6 +120,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
bluebird@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@ -200,6 +210,13 @@ deep-is@^0.1.3:
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
default-value@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/default-value/-/default-value-1.0.0.tgz#8c6f52a5a1193fe78fdc9f86eb71d16c9757c83a"
integrity sha1-jG9SpaEZP+eP3J+G63HRbJdXyDo=
dependencies:
es6-promise-try "0.0.1"
doctrine@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
@ -219,6 +236,11 @@ enquirer@^2.3.5:
dependencies:
ansi-colors "^4.1.1"
es6-promise-try@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/es6-promise-try/-/es6-promise-try-0.0.1.tgz#10f140dad27459cef949973e5d21a087f7274b20"
integrity sha1-EPFA2tJ0Wc75SZc+XSGgh/cnSyA=
escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
@ -522,6 +544,11 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
map-obj@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.2.1.tgz#e4ea399dbc979ae735c83c863dd31bdf364277b7"
integrity sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==
minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
@ -558,6 +585,16 @@ optionator@^0.9.1:
type-check "^0.4.0"
word-wrap "^1.2.3"
p-defer@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-3.0.0.tgz#d1dceb4ee9b2b604b1d94ffec83760175d4e6f83"
integrity sha512-ugZxsxmtTln604yeYd29EGrNhazN2lywetzpKhfmQjW/VJmhpDmWbiX+h0zL8V91R0UXkhb3KtPmyq9PZw3aYw==
p-try@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
@ -655,6 +692,11 @@ slice-ansi@^4.0.0:
astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0"
split-filter@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/split-filter/-/split-filter-1.1.3.tgz#c68cc598783d88f60d16e7b452dacfe95ba60539"
integrity sha512-2xXwhWeJUFrYE8CL+qoy9mCohu5/E+uglvpqL1FVXz1XbvTwivafVC6oTDeg/9ksOAxg6DvyCF44Dvf5crFU0w==
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
@ -700,6 +742,13 @@ supports-color@^7.1.0:
dependencies:
has-flag "^4.0.0"
syncpipe@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/syncpipe/-/syncpipe-1.0.0.tgz#170340f813150bc8fcb8878b1b9c71ea0ccd3727"
integrity sha512-cdiAFTnFJRvUaNPDc2n9CqoFvtIL3+JUMJZrC3kA3FzpugHOqu0TvkgNwmnxPZ5/WjAzMcfMS3xm+AO7rg/j/w==
dependencies:
assure-array "^1.0.0"
table@^6.0.4:
version "6.0.7"
resolved "https://registry.yarnpkg.com/table/-/table-6.0.7.tgz#e45897ffbcc1bcf9e8a87bf420f2c9e5a7a52a34"

Loading…
Cancel
Save