imapflow 1.4.7 → 1.4.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +16 -0
- package/CLAUDE.md +10 -1
- package/Gruntfile.js +3 -1
- package/lib/commands/authenticate.js +13 -1
- package/lib/commands/enable.js +13 -4
- package/lib/commands/expunge.js +2 -2
- package/lib/commands/idle.js +6 -3
- package/lib/commands/list.js +237 -60
- package/lib/commands/move.js +2 -2
- package/lib/commands/namespace.js +3 -1
- package/lib/commands/search.js +88 -13
- package/lib/commands/status.js +13 -25
- package/lib/imap-flow.d.ts +5 -3
- package/lib/imap-flow.js +43 -9
- package/lib/search-compiler.js +15 -1
- package/lib/tools.js +141 -9
- package/package.json +7 -6
- package/test/commands-branches-test.js +11 -4
- package/test/commands-integration-test.js +1270 -124
- package/test/connection-edge-cases-test.js +2 -2
- package/test/connection-test.js +1 -1
- package/test/fixtures/test-tls.js +2 -2
- package/test/imap-flow-coverage-test.js +8 -1
- package/test/imap-flow-fetch-download-test.js +1 -4
- package/test/imap-flow-internals-test.js +2 -2
- package/test/imap-flow-methods-test.js +65 -6
- package/test/imap-parser-test.js +1 -2
- package/test/integration/README.md +40 -0
- package/test/integration/dovecot-test.conf +27 -0
- package/test/integration/rev2-live-test.js +242 -0
- package/test/integration/run-rev2-tests.sh +61 -0
- package/test/reliability-improvements-test.js +4 -1
- package/test/search-compiler-test.js +19 -0
- package/test/search-test.js +52 -54
- package/test/tools-test.js +134 -15
- package/.github/codeql/codeql-config.yml +0 -12
- package/.github/workflows/codeql.yml +0 -102
package/test/search-test.js
CHANGED
|
@@ -14,6 +14,7 @@ function makeConnection({ hasEsearch = true } = {}) {
|
|
|
14
14
|
state: 'SELECTED',
|
|
15
15
|
states: { SELECTED: 'SELECTED' },
|
|
16
16
|
capabilities: caps,
|
|
17
|
+
enabled: new Set(),
|
|
17
18
|
exec: async () => ({ next: () => {} }),
|
|
18
19
|
log: { warn: () => {} }
|
|
19
20
|
};
|
|
@@ -72,23 +73,6 @@ module.exports['ESEARCH: parseEsearchResponse PARTIAL (Array form)'] = test => {
|
|
|
72
73
|
test.done();
|
|
73
74
|
};
|
|
74
75
|
|
|
75
|
-
module.exports['ESEARCH: parseEsearchResponse PARTIAL (LIST object form)'] = test => {
|
|
76
|
-
// Also handle {type: 'LIST', attributes: [...]} form for robustness
|
|
77
|
-
const attrs = [
|
|
78
|
-
{ type: 'ATOM', value: 'PARTIAL' },
|
|
79
|
-
{
|
|
80
|
-
type: 'LIST',
|
|
81
|
-
attributes: [
|
|
82
|
-
{ type: 'ATOM', value: '1:100' },
|
|
83
|
-
{ type: 'ATOM', value: '1001,1003:1010,1015' }
|
|
84
|
-
]
|
|
85
|
-
}
|
|
86
|
-
];
|
|
87
|
-
const result = parseEsearchResponse(attrs);
|
|
88
|
-
test.deepEqual(result.partial, { range: '1:100', messages: '1001,1003:1010,1015' });
|
|
89
|
-
test.done();
|
|
90
|
-
};
|
|
91
|
-
|
|
92
76
|
module.exports['ESEARCH: parseEsearchResponse COUNT + PARTIAL combined'] = test => {
|
|
93
77
|
const attrs = [
|
|
94
78
|
{ type: 'ATOM', value: 'COUNT' },
|
|
@@ -116,12 +100,14 @@ module.exports['ESEARCH: emits RETURN clause when returnOptions present and serv
|
|
|
116
100
|
capturedAttributes = JSON.stringify(attributes);
|
|
117
101
|
return { next: () => {} };
|
|
118
102
|
};
|
|
119
|
-
searchCmd(conn, { seen: false }, { uid: true, returnOptions: ['COUNT'] })
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
103
|
+
searchCmd(conn, { seen: false }, { uid: true, returnOptions: ['COUNT'] })
|
|
104
|
+
.then(() => {
|
|
105
|
+
test.equal(capturedCommand, 'UID SEARCH');
|
|
106
|
+
test.ok(capturedAttributes.includes('"RETURN"'), 'should include RETURN atom');
|
|
107
|
+
test.ok(capturedAttributes.includes('"COUNT"'), 'should include COUNT in return list');
|
|
108
|
+
test.done();
|
|
109
|
+
})
|
|
110
|
+
.catch(err => test.done(err));
|
|
125
111
|
};
|
|
126
112
|
|
|
127
113
|
module.exports['ESEARCH: RETURN clause includes PARTIAL range atom'] = test => {
|
|
@@ -131,11 +117,13 @@ module.exports['ESEARCH: RETURN clause includes PARTIAL range atom'] = test => {
|
|
|
131
117
|
capturedAttributes = JSON.stringify(attributes);
|
|
132
118
|
return { next: () => {} };
|
|
133
119
|
};
|
|
134
|
-
searchCmd(conn, { seen: false }, { uid: true, returnOptions: [{ partial: '1:100' }] })
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
120
|
+
searchCmd(conn, { seen: false }, { uid: true, returnOptions: [{ partial: '1:100' }] })
|
|
121
|
+
.then(() => {
|
|
122
|
+
test.ok(capturedAttributes.includes('"PARTIAL"'), 'should include PARTIAL atom');
|
|
123
|
+
test.ok(capturedAttributes.includes('"1:100"'), 'should include range string');
|
|
124
|
+
test.done();
|
|
125
|
+
})
|
|
126
|
+
.catch(err => test.done(err));
|
|
139
127
|
};
|
|
140
128
|
|
|
141
129
|
module.exports['ESEARCH: no RETURN clause when server lacks ESEARCH capability'] = test => {
|
|
@@ -152,12 +140,14 @@ module.exports['ESEARCH: no RETURN clause when server lacks ESEARCH capability']
|
|
|
152
140
|
}
|
|
153
141
|
return { next: () => {} };
|
|
154
142
|
};
|
|
155
|
-
searchCmd(conn, { seen: false }, { uid: true, returnOptions: ['COUNT', 'ALL'] })
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
143
|
+
searchCmd(conn, { seen: false }, { uid: true, returnOptions: ['COUNT', 'ALL'] })
|
|
144
|
+
.then(result => {
|
|
145
|
+
test.ok(!capturedAttributes.includes('"RETURN"'), 'should NOT include RETURN when no ESEARCH');
|
|
146
|
+
test.ok(Array.isArray(result), 'should return number[] when ESEARCH unavailable');
|
|
147
|
+
test.deepEqual(result, [1, 2, 3]);
|
|
148
|
+
test.done();
|
|
149
|
+
})
|
|
150
|
+
.catch(err => test.done(err));
|
|
161
151
|
};
|
|
162
152
|
|
|
163
153
|
module.exports['ESEARCH: parseEsearchResponse skips non-ATOM tokens'] = test => {
|
|
@@ -247,11 +237,13 @@ module.exports['ESEARCH: backward compat — no returnOptions returns number[]']
|
|
|
247
237
|
return { next: () => {} };
|
|
248
238
|
};
|
|
249
239
|
// No returnOptions — must return number[] even if server has ESEARCH
|
|
250
|
-
searchCmd(conn, { seen: true }, { uid: true })
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
240
|
+
searchCmd(conn, { seen: true }, { uid: true })
|
|
241
|
+
.then(result => {
|
|
242
|
+
test.ok(Array.isArray(result));
|
|
243
|
+
test.deepEqual(result, [10, 20]);
|
|
244
|
+
test.done();
|
|
245
|
+
})
|
|
246
|
+
.catch(err => test.done(err));
|
|
255
247
|
};
|
|
256
248
|
|
|
257
249
|
// ── imap-flow.js public API fallback test ─────────────────────────────────
|
|
@@ -271,16 +263,19 @@ module.exports['imap-flow: search() derives ESearchResult when server has no ESE
|
|
|
271
263
|
// Stub run() to return a sorted number[]
|
|
272
264
|
client.run = async () => [10, 20, 30, 40, 50];
|
|
273
265
|
|
|
274
|
-
client
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
266
|
+
client
|
|
267
|
+
.search({ seen: false }, { uid: true, returnOptions: ['COUNT', 'MIN', 'MAX', 'ALL'] })
|
|
268
|
+
.then(result => {
|
|
269
|
+
test.equal(typeof result, 'object', 'should return object, not array');
|
|
270
|
+
test.ok(!Array.isArray(result), 'should not be an array');
|
|
271
|
+
test.equal(result.count, 5);
|
|
272
|
+
test.equal(result.min, 10);
|
|
273
|
+
test.equal(result.max, 50);
|
|
274
|
+
// packMessageRange([10,20,30,40,50]) → "10,20,30,40,50" (non-contiguous)
|
|
275
|
+
test.ok(typeof result.all === 'string' && result.all.length > 0, 'all should be non-empty compact string');
|
|
276
|
+
test.done();
|
|
277
|
+
})
|
|
278
|
+
.catch(err => test.done(err));
|
|
284
279
|
};
|
|
285
280
|
|
|
286
281
|
module.exports['imap-flow: search() fallback with empty result set'] = test => {
|
|
@@ -294,9 +289,12 @@ module.exports['imap-flow: search() fallback with empty result set'] = test => {
|
|
|
294
289
|
client.state = client.states.SELECTED;
|
|
295
290
|
client.capabilities = new Map();
|
|
296
291
|
client.run = async () => [];
|
|
297
|
-
client
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
292
|
+
client
|
|
293
|
+
.search({}, { uid: true, returnOptions: ['COUNT', 'ALL'] })
|
|
294
|
+
.then(result => {
|
|
295
|
+
test.equal(result.count, 0);
|
|
296
|
+
test.equal(result.all, undefined, 'all should be absent for empty result');
|
|
297
|
+
test.done();
|
|
298
|
+
})
|
|
299
|
+
.catch(err => test.done(err));
|
|
302
300
|
};
|
package/test/tools-test.js
CHANGED
|
@@ -7,7 +7,8 @@ const iconv = require('iconv-lite');
|
|
|
7
7
|
|
|
8
8
|
// Mock connection for testing
|
|
9
9
|
let createMockConnection = (options = {}) => ({
|
|
10
|
-
enabled: new Set(options.utf8 ? ['UTF8=ACCEPT'] : []),
|
|
10
|
+
enabled: new Set(options.enabled || (options.utf8 ? ['UTF8=ACCEPT'] : [])),
|
|
11
|
+
capabilities: new Map(options.capabilities || []),
|
|
11
12
|
namespace: options.namespace || null
|
|
12
13
|
});
|
|
13
14
|
|
|
@@ -162,6 +163,102 @@ module.exports['Tools: updateCapabilities with valid list'] = test => {
|
|
|
162
163
|
test.done();
|
|
163
164
|
};
|
|
164
165
|
|
|
166
|
+
module.exports['Tools: updateCapabilities normalizes IMAP4rev2 casing'] = test => {
|
|
167
|
+
let list = [{ value: 'IMAP4rev2' }, { value: 'imap4rev1' }];
|
|
168
|
+
let result = tools.updateCapabilities(list);
|
|
169
|
+
// Wire tokens are uppercased, but the rev1/rev2 keys use the RFC spelling
|
|
170
|
+
test.equal(result.get('IMAP4rev2'), true);
|
|
171
|
+
test.equal(result.get('IMAP4rev1'), true);
|
|
172
|
+
test.equal(result.has('IMAP4REV2'), false);
|
|
173
|
+
test.done();
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
module.exports['Tools: isRev2Active for rev2-only server'] = test => {
|
|
177
|
+
// rev2 without rev1 means rev2 is the base protocol, no ENABLE needed
|
|
178
|
+
let connection = createMockConnection({ capabilities: [['IMAP4rev2', true]] });
|
|
179
|
+
test.equal(tools.isRev2Active(connection), true);
|
|
180
|
+
test.done();
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
module.exports['Tools: isRev2Active for dual server without ENABLE'] = test => {
|
|
184
|
+
// Advertising both keeps the session in rev1 mode until ENABLE IMAP4rev2
|
|
185
|
+
let connection = createMockConnection({
|
|
186
|
+
capabilities: [
|
|
187
|
+
['IMAP4rev1', true],
|
|
188
|
+
['IMAP4rev2', true]
|
|
189
|
+
]
|
|
190
|
+
});
|
|
191
|
+
test.equal(tools.isRev2Active(connection), false);
|
|
192
|
+
test.done();
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
module.exports['Tools: isRev2Active for dual server with ENABLE'] = test => {
|
|
196
|
+
let connection = createMockConnection({
|
|
197
|
+
capabilities: [
|
|
198
|
+
['IMAP4rev1', true],
|
|
199
|
+
['IMAP4rev2', true]
|
|
200
|
+
],
|
|
201
|
+
enabled: ['IMAP4REV2']
|
|
202
|
+
});
|
|
203
|
+
test.equal(tools.isRev2Active(connection), true);
|
|
204
|
+
test.done();
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
module.exports['Tools: isRev2Active for rev1-only server'] = test => {
|
|
208
|
+
let connection = createMockConnection({ capabilities: [['IMAP4rev1', true]] });
|
|
209
|
+
test.equal(tools.isRev2Active(connection), false);
|
|
210
|
+
test.done();
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
module.exports['Tools: hasCapability with advertised token'] = test => {
|
|
214
|
+
let connection = createMockConnection({
|
|
215
|
+
capabilities: [
|
|
216
|
+
['IMAP4rev1', true],
|
|
217
|
+
['UIDPLUS', true]
|
|
218
|
+
]
|
|
219
|
+
});
|
|
220
|
+
test.equal(tools.hasCapability(connection, 'UIDPLUS'), true);
|
|
221
|
+
test.equal(tools.hasCapability(connection, 'MOVE'), false);
|
|
222
|
+
test.done();
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
module.exports['Tools: hasCapability folds extensions into active rev2'] = test => {
|
|
226
|
+
let connection = createMockConnection({ capabilities: [['IMAP4rev2', true]] });
|
|
227
|
+
// RFC 9051 Appendix E folds these into base IMAP4rev2
|
|
228
|
+
for (let capability of ['UIDPLUS', 'MOVE', 'NAMESPACE', 'ESEARCH', 'LITERAL-', 'LIST-EXTENDED', 'LIST-STATUS', 'SPECIAL-USE', 'ENABLE']) {
|
|
229
|
+
test.equal(tools.hasCapability(connection, capability), true, `${capability} should be folded into rev2`);
|
|
230
|
+
}
|
|
231
|
+
// BINARY is intentionally not folded
|
|
232
|
+
test.equal(tools.hasCapability(connection, 'BINARY'), false);
|
|
233
|
+
test.done();
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
module.exports['Tools: hasCapability does not fold on unenabled dual server'] = test => {
|
|
237
|
+
let connection = createMockConnection({
|
|
238
|
+
capabilities: [
|
|
239
|
+
['IMAP4rev1', true],
|
|
240
|
+
['IMAP4rev2', true]
|
|
241
|
+
]
|
|
242
|
+
});
|
|
243
|
+
// Session is in rev1 mode - only explicitly advertised tokens count
|
|
244
|
+
test.equal(tools.hasCapability(connection, 'UIDPLUS'), false);
|
|
245
|
+
test.done();
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
module.exports['Tools: encodePath keeps UTF-8 when rev2 is active'] = test => {
|
|
249
|
+
let connection = createMockConnection({ capabilities: [['IMAP4rev2', true]] });
|
|
250
|
+
// rev2 mailbox names are native UTF-8, modified UTF-7 must not be applied
|
|
251
|
+
test.equal(tools.encodePath(connection, 'T\u00f5rva'), 'T\u00f5rva');
|
|
252
|
+
test.done();
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
module.exports['Tools: decodePath keeps ampersand sequences when rev2 is active'] = test => {
|
|
256
|
+
let connection = createMockConnection({ capabilities: [['IMAP4rev2', true]] });
|
|
257
|
+
// Under rev2 an "&"-sequence is a literal name, not modified UTF-7
|
|
258
|
+
test.equal(tools.decodePath(connection, 'A&AOQ-B'), 'A&AOQ-B');
|
|
259
|
+
test.done();
|
|
260
|
+
};
|
|
261
|
+
|
|
165
262
|
module.exports['Tools: updateCapabilities with APPENDLIMIT'] = test => {
|
|
166
263
|
let list = [{ value: 'APPENDLIMIT=52428800' }];
|
|
167
264
|
let result = tools.updateCapabilities(list);
|
|
@@ -493,6 +590,30 @@ module.exports['Tools: expandRange with same start/end'] = test => {
|
|
|
493
590
|
test.done();
|
|
494
591
|
};
|
|
495
592
|
|
|
593
|
+
module.exports['Tools: expandRange skips entries that are not valid nz-numbers'] = test => {
|
|
594
|
+
// Server-supplied garbage must not produce bogus ids or endless loops
|
|
595
|
+
test.deepEqual(tools.expandRange('Infinity:5'), []);
|
|
596
|
+
test.deepEqual(tools.expandRange('0:3'), []);
|
|
597
|
+
test.deepEqual(tools.expandRange('abc,4,1:x'), [4]);
|
|
598
|
+
test.deepEqual(tools.expandRange('*'), []);
|
|
599
|
+
test.deepEqual(tools.expandRange('4294967296'), []);
|
|
600
|
+
test.done();
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
module.exports['Tools: expandRange caps hostile range spans'] = test => {
|
|
604
|
+
// A hostile range like 1:4294967295 is cut off at the expansion limit
|
|
605
|
+
// instead of exhausting memory
|
|
606
|
+
let result = tools.expandRange('1:4294967295');
|
|
607
|
+
test.equal(result.length, 0x1000000);
|
|
608
|
+
test.equal(result[0], 1);
|
|
609
|
+
test.equal(result[result.length - 1], 0x1000000);
|
|
610
|
+
|
|
611
|
+
let reverse = tools.expandRange('4294967295:4278190080');
|
|
612
|
+
test.equal(reverse.length, 0x1000000);
|
|
613
|
+
test.equal(reverse[0], 4294967295);
|
|
614
|
+
test.done();
|
|
615
|
+
};
|
|
616
|
+
|
|
496
617
|
// ============================================
|
|
497
618
|
// packMessageRange tests
|
|
498
619
|
// ============================================
|
|
@@ -1303,9 +1424,7 @@ module.exports['Tools: parseBodystructure parses message/rfc822 with envelope an
|
|
|
1303
1424
|
};
|
|
1304
1425
|
|
|
1305
1426
|
module.exports['Tools: parseBodystructure parses multipart with params and disposition'] = async test => {
|
|
1306
|
-
let bs =
|
|
1307
|
-
'(("TEXT" "PLAIN" NIL NIL NIL "7BIT" 10 1)("TEXT" "HTML" NIL NIL NIL "7BIT" 20 2) ' +
|
|
1308
|
-
'"ALTERNATIVE" ("BOUNDARY" "xyz") ("inline" NIL) ("en"))';
|
|
1427
|
+
let bs = '(("TEXT" "PLAIN" NIL NIL NIL "7BIT" 10 1)("TEXT" "HTML" NIL NIL NIL "7BIT" 20 2) "ALTERNATIVE" ("BOUNDARY" "xyz") ("inline" NIL) ("en"))';
|
|
1309
1428
|
let untagged = await parser('* 1 FETCH (BODYSTRUCTURE ' + bs + ')');
|
|
1310
1429
|
let node = tools.parseBodystructure(untagged.attributes[1][1]);
|
|
1311
1430
|
test.equal(node.type, 'multipart/alternative');
|
|
@@ -1348,7 +1467,7 @@ module.exports['Tools: parseBodystructure handles minimal NIL fields'] = async t
|
|
|
1348
1467
|
};
|
|
1349
1468
|
|
|
1350
1469
|
module.exports['Tools: parseBodystructure decodes RFC 2231 charset continuation params'] = async test => {
|
|
1351
|
-
let bs =
|
|
1470
|
+
let bs = '("TEXT" "PLAIN" ("name*0*" "utf-8\'\'%E2%82%AC abc" "name*1" "def") NIL NIL "7BIT" 10 1)';
|
|
1352
1471
|
let untagged = await parser('* 1 FETCH (BODYSTRUCTURE ' + bs + ')');
|
|
1353
1472
|
let node = tools.parseBodystructure(untagged.attributes[1][1]);
|
|
1354
1473
|
test.equal(node.parameters.name, '€ abcdef');
|
|
@@ -1453,9 +1572,7 @@ module.exports['Tools: parseBodystructure empty-string size/linecount/language/l
|
|
|
1453
1572
|
};
|
|
1454
1573
|
|
|
1455
1574
|
module.exports['Tools: parseBodystructure message/rfc822 empty-string linecount'] = async test => {
|
|
1456
|
-
let bs =
|
|
1457
|
-
'("MESSAGE" "RFC822" NIL NIL NIL "7BIT" 100 ("d" "s" NIL NIL NIL NIL NIL NIL NIL NIL) ' +
|
|
1458
|
-
'("TEXT" "PLAIN" NIL NIL NIL "7BIT" 1 1) "")';
|
|
1575
|
+
let bs = '("MESSAGE" "RFC822" NIL NIL NIL "7BIT" 100 ("d" "s" NIL NIL NIL NIL NIL NIL NIL NIL) ("TEXT" "PLAIN" NIL NIL NIL "7BIT" 1 1) "")';
|
|
1459
1576
|
let untagged = await parser('* 1 FETCH (BODYSTRUCTURE ' + bs + ')');
|
|
1460
1577
|
let node = tools.parseBodystructure(untagged.attributes[1][1]);
|
|
1461
1578
|
test.equal(node.type, 'message/rfc822');
|
|
@@ -1468,7 +1585,7 @@ module.exports['Tools: parseBodystructure decodes RFC 2231 with empty charset an
|
|
|
1468
1585
|
// exercise the 2-char hex escape branch; the empty *1* continuation segment too.
|
|
1469
1586
|
// value includes '=' / '?' (2-char hex escapes), a space (-> '_') and a TAB
|
|
1470
1587
|
// (a <0x10 control char -> single-hex-digit escape needing a '0' pad).
|
|
1471
|
-
let bs =
|
|
1588
|
+
let bs = '("TEXT" "PLAIN" ("name*0*" "\'\'a=b?c d\te" "name*1*" "") NIL NIL "7BIT" 1 1)';
|
|
1472
1589
|
let untagged = await parser('* 1 FETCH (BODYSTRUCTURE ' + bs + ')');
|
|
1473
1590
|
let node = tools.parseBodystructure(untagged.attributes[1][1]);
|
|
1474
1591
|
test.ok(node.parameters.name.includes('a=b?c'));
|
|
@@ -1485,13 +1602,15 @@ module.exports['Tools: parseBodystructure parses empty-string body location'] =
|
|
|
1485
1602
|
test.done();
|
|
1486
1603
|
};
|
|
1487
1604
|
|
|
1488
|
-
module.exports['Tools: expandRange descending range to non-numeric second bound'] = test => {
|
|
1489
|
-
|
|
1605
|
+
module.exports['Tools: expandRange skips descending range to non-numeric second bound'] = test => {
|
|
1606
|
+
// Endpoints that are not valid nz-numbers make the whole entry invalid -
|
|
1607
|
+
// the pre-hardening behavior expanded '5:abc' down to 0
|
|
1608
|
+
test.deepEqual(tools.expandRange('5:abc'), []);
|
|
1490
1609
|
test.done();
|
|
1491
1610
|
};
|
|
1492
1611
|
|
|
1493
|
-
module.exports['Tools: expandRange
|
|
1494
|
-
test.deepEqual(tools.expandRange('abc,:5,1:3'), [
|
|
1612
|
+
module.exports['Tools: expandRange skips non-numeric entries but keeps valid ones'] = test => {
|
|
1613
|
+
test.deepEqual(tools.expandRange('abc,:5,1:3'), [1, 2, 3]);
|
|
1495
1614
|
test.done();
|
|
1496
1615
|
};
|
|
1497
1616
|
|
|
@@ -1508,7 +1627,7 @@ module.exports['Tools: encodePath keeps name as-is when iconv encode throws'] =
|
|
|
1508
1627
|
throw new Error('encode boom');
|
|
1509
1628
|
};
|
|
1510
1629
|
try {
|
|
1511
|
-
let connection = { enabled: new Set() };
|
|
1630
|
+
let connection = { enabled: new Set(), capabilities: new Map() };
|
|
1512
1631
|
let result = tools.encodePath(connection, 'Tärä');
|
|
1513
1632
|
test.equal(result, 'Tärä', 'falls back to the raw path');
|
|
1514
1633
|
} finally {
|
|
@@ -1523,7 +1642,7 @@ module.exports['Tools: decodePath keeps name as-is when iconv decode throws'] =
|
|
|
1523
1642
|
throw new Error('decode boom');
|
|
1524
1643
|
};
|
|
1525
1644
|
try {
|
|
1526
|
-
let connection = { enabled: new Set() };
|
|
1645
|
+
let connection = { enabled: new Set(), capabilities: new Map() };
|
|
1527
1646
|
let result = tools.decodePath(connection, 'Inbox&AOk-');
|
|
1528
1647
|
test.equal(result, 'Inbox&AOk-');
|
|
1529
1648
|
} finally {
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
name: "ImapFlow CodeQL config"
|
|
2
|
-
|
|
3
|
-
# Exclude code that is not part of the published library runtime from analysis.
|
|
4
|
-
# These paths generate only false-positive noise:
|
|
5
|
-
# - test fixtures intentionally feed malformed/hostile input and exercise
|
|
6
|
-
# edge cases that are not representative of production usage
|
|
7
|
-
# - the example scripts hardcode demo credentials and connection details for
|
|
8
|
-
# local experimentation and are not maintained as production code
|
|
9
|
-
paths-ignore:
|
|
10
|
-
- test
|
|
11
|
-
- '**/test/**'
|
|
12
|
-
- examples
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
# For most projects, this workflow file will not need changing; you simply need
|
|
2
|
-
# to commit it to your repository.
|
|
3
|
-
#
|
|
4
|
-
# You may wish to alter this file to override the set of languages analyzed,
|
|
5
|
-
# or to provide custom queries or build logic.
|
|
6
|
-
#
|
|
7
|
-
# ******** NOTE ********
|
|
8
|
-
# We have attempted to detect the languages in your repository. Please check
|
|
9
|
-
# the `language` matrix defined below to confirm you have the correct set of
|
|
10
|
-
# supported CodeQL languages.
|
|
11
|
-
#
|
|
12
|
-
name: "CodeQL Advanced"
|
|
13
|
-
|
|
14
|
-
on:
|
|
15
|
-
push:
|
|
16
|
-
branches: [ "master" ]
|
|
17
|
-
pull_request:
|
|
18
|
-
branches: [ "master" ]
|
|
19
|
-
schedule:
|
|
20
|
-
- cron: '40 17 * * 6'
|
|
21
|
-
|
|
22
|
-
jobs:
|
|
23
|
-
analyze:
|
|
24
|
-
name: Analyze (${{ matrix.language }})
|
|
25
|
-
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
|
26
|
-
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
|
27
|
-
# - https://gh.io/supported-runners-and-hardware-resources
|
|
28
|
-
# - https://gh.io/using-larger-runners (GitHub.com only)
|
|
29
|
-
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
|
30
|
-
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
|
31
|
-
permissions:
|
|
32
|
-
# required for all workflows
|
|
33
|
-
security-events: write
|
|
34
|
-
|
|
35
|
-
# required to fetch internal or private CodeQL packs
|
|
36
|
-
packages: read
|
|
37
|
-
|
|
38
|
-
# only required for workflows in private repositories
|
|
39
|
-
actions: read
|
|
40
|
-
contents: read
|
|
41
|
-
|
|
42
|
-
strategy:
|
|
43
|
-
fail-fast: false
|
|
44
|
-
matrix:
|
|
45
|
-
include:
|
|
46
|
-
- language: actions
|
|
47
|
-
build-mode: none
|
|
48
|
-
- language: javascript-typescript
|
|
49
|
-
build-mode: none
|
|
50
|
-
# CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
|
|
51
|
-
# Use `c-cpp` to analyze code written in C, C++ or both
|
|
52
|
-
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
|
53
|
-
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
|
54
|
-
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
|
55
|
-
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
|
56
|
-
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
|
57
|
-
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
|
58
|
-
steps:
|
|
59
|
-
- name: Checkout repository
|
|
60
|
-
uses: actions/checkout@v6
|
|
61
|
-
|
|
62
|
-
# Add any setup steps before running the `github/codeql-action/init` action.
|
|
63
|
-
# This includes steps like installing compilers or runtimes (`actions/setup-node`
|
|
64
|
-
# or others). This is typically only required for manual builds.
|
|
65
|
-
# - name: Setup runtime (example)
|
|
66
|
-
# uses: actions/setup-example@v1
|
|
67
|
-
|
|
68
|
-
# Initializes the CodeQL tools for scanning.
|
|
69
|
-
- name: Initialize CodeQL
|
|
70
|
-
uses: github/codeql-action/init@v4
|
|
71
|
-
with:
|
|
72
|
-
languages: ${{ matrix.language }}
|
|
73
|
-
build-mode: ${{ matrix.build-mode }}
|
|
74
|
-
config-file: ./.github/codeql/codeql-config.yml
|
|
75
|
-
# If you wish to specify custom queries, you can do so here or in a config file.
|
|
76
|
-
# By default, queries listed here will override any specified in a config file.
|
|
77
|
-
# Prefix the list here with "+" to use these queries and those in the config file.
|
|
78
|
-
|
|
79
|
-
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
|
80
|
-
# queries: security-extended,security-and-quality
|
|
81
|
-
|
|
82
|
-
# If the analyze step fails for one of the languages you are analyzing with
|
|
83
|
-
# "We were unable to automatically build your code", modify the matrix above
|
|
84
|
-
# to set the build mode to "manual" for that language. Then modify this step
|
|
85
|
-
# to build your code.
|
|
86
|
-
# ℹ️ Command-line programs to run using the OS shell.
|
|
87
|
-
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
|
88
|
-
- name: Run manual build steps
|
|
89
|
-
if: matrix.build-mode == 'manual'
|
|
90
|
-
shell: bash
|
|
91
|
-
run: |
|
|
92
|
-
echo 'If you are using a "manual" build mode for one or more of the' \
|
|
93
|
-
'languages you are analyzing, replace this with the commands to build' \
|
|
94
|
-
'your code, for example:'
|
|
95
|
-
echo ' make bootstrap'
|
|
96
|
-
echo ' make release'
|
|
97
|
-
exit 1
|
|
98
|
-
|
|
99
|
-
- name: Perform CodeQL Analysis
|
|
100
|
-
uses: github/codeql-action/analyze@v4
|
|
101
|
-
with:
|
|
102
|
-
category: "/language:${{matrix.language}}"
|