UNPKG

25.8 kBJavaScriptView Raw
1'use strict';
2
3const assert = require('assert');
4const { constants } = require('fs');
5
6const {
7 fixture,
8 mustCall,
9 mustCallAtLeast,
10 mustNotCall,
11 setup: setup_,
12 setupSimple
13} = require('./common.js');
14
15const { OPEN_MODE, Stats, STATUS_CODE } = require('../lib/protocol/SFTP.js');
16
17const DEBUG = false;
18
19setup('open', mustCall((client, server) => {
20 const path_ = '/tmp/foo.txt';
21 const handle_ = Buffer.from('node.js');
22 const pflags_ = (OPEN_MODE.TRUNC | OPEN_MODE.CREAT | OPEN_MODE.WRITE);
23 server.on('OPEN', mustCall((id, path, pflags, attrs) => {
24 assert(id === 0, `Wrong request id: ${id}`);
25 assert(path === path_, `Wrong path: ${path}`);
26 assert(pflags === pflags_, `Wrong flags: ${flagsToHuman(pflags)}`);
27 server.handle(id, handle_);
28 server.end();
29 }));
30 client.open(path_, 'w', mustCall((err, handle) => {
31 assert(!err, `Unexpected open() error: ${err}`);
32 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
33 }));
34}));
35
36setup('close', mustCall((client, server) => {
37 const handle_ = Buffer.from('node.js');
38 server.on('CLOSE', mustCall((id, handle) => {
39 assert(id === 0, `Wrong request id: ${id}`);
40 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
41 server.status(id, STATUS_CODE.OK);
42 server.end();
43 }));
44 client.close(handle_, mustCall((err) => {
45 assert(!err, `Unexpected close() error: ${err}`);
46 }));
47}));
48
49setup('read', mustCall((client, server) => {
50 const expected = Buffer.from('node.jsnode.jsnode.jsnode.jsnode.jsnode.js');
51 const handle_ = Buffer.from('node.js');
52 const buf = Buffer.alloc(expected.length);
53 server.on('READ', mustCall((id, handle, offset, len) => {
54 assert(id === 0, `Wrong request id: ${id}`);
55 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
56 assert(offset === 5, `Wrong read offset: ${offset}`);
57 assert(len === buf.length, `Wrong read len: ${len}`);
58 server.data(id, expected);
59 server.end();
60 }));
61 client.read(handle_, buf, 0, buf.length, 5, mustCall((err, nb) => {
62 assert(!err, `Unexpected read() error: ${err}`);
63 assert.deepStrictEqual(buf, expected, 'read data mismatch');
64 }));
65}));
66
67setup('read (overflow)', mustCall((client, server) => {
68 const maxChunk = 34000 - 2048;
69 const expected = Buffer.alloc(3 * maxChunk, 'Q');
70 const handle_ = Buffer.from('node.js');
71 const buf = Buffer.alloc(expected.length, 0);
72 let reqs = 0;
73 server.on('READ', mustCall((id, handle, offset, len) => {
74 ++reqs;
75 assert.strictEqual(id, reqs - 1, `Wrong request id: ${id}`);
76 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
77 assert.strictEqual(offset,
78 (reqs - 1) * maxChunk,
79 `Wrong read offset: ${offset}`);
80 server.data(id, expected.slice(offset, offset + len));
81 if (reqs === 3)
82 server.end();
83 }, 3));
84 client.read(handle_, buf, 0, buf.length, 0, mustCall((err, nb) => {
85 assert(!err, `Unexpected read() error: ${err}`);
86 assert.deepStrictEqual(buf, expected);
87 assert.strictEqual(nb, buf.length, 'read nb mismatch');
88 }));
89}));
90
91setup('write', mustCall((client, server) => {
92 const handle_ = Buffer.from('node.js');
93 const buf = Buffer.from('node.jsnode.jsnode.jsnode.jsnode.jsnode.js');
94 server.on('WRITE', mustCall((id, handle, offset, data) => {
95 assert(id === 0, `Wrong request id: ${id}`);
96 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
97 assert(offset === 5, `Wrong write offset: ${offset}`);
98 assert.deepStrictEqual(data, buf, 'write data mismatch');
99 server.status(id, STATUS_CODE.OK);
100 server.end();
101 }));
102 client.write(handle_, buf, 0, buf.length, 5, mustCall((err, nb) => {
103 assert(!err, `Unexpected write() error: ${err}`);
104 assert.strictEqual(nb, buf.length, 'wrong bytes written');
105 }));
106}));
107
108setup('write (overflow)', mustCall((client, server) => {
109 const maxChunk = 34000 - 2048;
110 const handle_ = Buffer.from('node.js');
111 const buf = Buffer.allocUnsafe(3 * maxChunk);
112 let reqs = 0;
113 server.on('WRITE', mustCall((id, handle, offset, data) => {
114 ++reqs;
115 assert.strictEqual(id, reqs - 1, `Wrong request id: ${id}`);
116 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
117 assert.strictEqual(offset,
118 (reqs - 1) * maxChunk,
119 `Wrong write offset: ${offset}`);
120 assert((offset + data.length) <= buf.length, 'bad offset');
121 assert.deepStrictEqual(data,
122 buf.slice(offset, offset + data.length),
123 'write data mismatch');
124 server.status(id, STATUS_CODE.OK);
125 if (reqs === 3)
126 server.end();
127 }, 3));
128 client.write(handle_, buf, 0, buf.length, 0, mustCall((err, nb) => {
129 assert(!err, `Unexpected write() error: ${err}`);
130 assert.strictEqual(nb, buf.length, 'wrote bytes written');
131 }));
132}));
133
134setup('lstat', mustCall((client, server) => {
135 const path_ = '/foo/bar/baz';
136 const attrs_ = new Stats({
137 size: 10 * 1024,
138 uid: 9001,
139 gid: 9001,
140 atime: (Date.now() / 1000) | 0,
141 mtime: (Date.now() / 1000) | 0
142 });
143 server.on('LSTAT', mustCall((id, path) => {
144 assert(id === 0, `Wrong request id: ${id}`);
145 assert(path === path_, `Wrong path: ${path}`);
146 server.attrs(id, attrs_);
147 server.end();
148 }));
149 client.lstat(path_, mustCall((err, attrs) => {
150 assert(!err, `Unexpected lstat() error: ${err}`);
151 assert.deepStrictEqual(attrs, attrs_, 'attrs mismatch');
152 }));
153}));
154
155setup('fstat', mustCall((client, server) => {
156 const handle_ = Buffer.from('node.js');
157 const attrs_ = new Stats({
158 size: 10 * 1024,
159 uid: 9001,
160 gid: 9001,
161 atime: (Date.now() / 1000) | 0,
162 mtime: (Date.now() / 1000) | 0
163 });
164 server.on('FSTAT', mustCall((id, handle) => {
165 assert(id === 0, `Wrong request id: ${id}`);
166 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
167 server.attrs(id, attrs_);
168 server.end();
169 }));
170 client.fstat(handle_, mustCall((err, attrs) => {
171 assert(!err, `Unexpected fstat() error: ${err}`);
172 assert.deepStrictEqual(attrs, attrs_, 'attrs mismatch');
173 }));
174}));
175
176setup('setstat', mustCall((client, server) => {
177 const path_ = '/foo/bar/baz';
178 const attrs_ = new Stats({
179 uid: 9001,
180 gid: 9001,
181 atime: (Date.now() / 1000) | 0,
182 mtime: (Date.now() / 1000) | 0
183 });
184 server.on('SETSTAT', mustCall((id, path, attrs) => {
185 assert(id === 0, `Wrong request id: ${id}`);
186 assert(path === path_, `Wrong path: ${path}`);
187 assert.deepStrictEqual(attrs, attrs_, 'attrs mismatch');
188 server.status(id, STATUS_CODE.OK);
189 server.end();
190 }));
191 client.setstat(path_, attrs_, mustCall((err) => {
192 assert(!err, `Unexpected setstat() error: ${err}`);
193 }));
194}));
195
196setup('fsetstat', mustCall((client, server) => {
197 const handle_ = Buffer.from('node.js');
198 const attrs_ = new Stats({
199 uid: 9001,
200 gid: 9001,
201 atime: (Date.now() / 1000) | 0,
202 mtime: (Date.now() / 1000) | 0
203 });
204 server.on('FSETSTAT', mustCall((id, handle, attrs) => {
205 assert(id === 0, `Wrong request id: ${id}`);
206 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
207 assert.deepStrictEqual(attrs, attrs_, 'attrs mismatch');
208 server.status(id, STATUS_CODE.OK);
209 server.end();
210 }));
211 client.fsetstat(handle_, attrs_, mustCall((err) => {
212 assert(!err, `Unexpected fsetstat() error: ${err}`);
213 }));
214}));
215
216setup('opendir', mustCall((client, server) => {
217 const handle_ = Buffer.from('node.js');
218 const path_ = '/tmp';
219 server.on('OPENDIR', mustCall((id, path) => {
220 assert(id === 0, `Wrong request id: ${id}`);
221 assert(path === path_, `Wrong path: ${path}`);
222 server.handle(id, handle_);
223 server.end();
224 }));
225 client.opendir(path_, mustCall((err, handle) => {
226 assert(!err, `Unexpected opendir() error: ${err}`);
227 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
228 }));
229}));
230
231setup('readdir', mustCall((client, server) => {
232 const handle_ = Buffer.from('node.js');
233 const list_ = [
234 { filename: '.',
235 longname: 'drwxr-xr-x 56 nodejs nodejs 4096 Nov 10 01:05 .',
236 attrs: new Stats({
237 mode: 0o755 | constants.S_IFDIR,
238 size: 4096,
239 uid: 9001,
240 gid: 8001,
241 atime: 1415599549,
242 mtime: 1415599590
243 })
244 },
245 { filename: '..',
246 longname: 'drwxr-xr-x 4 root root 4096 May 16 2013 ..',
247 attrs: new Stats({
248 mode: 0o755 | constants.S_IFDIR,
249 size: 4096,
250 uid: 0,
251 gid: 0,
252 atime: 1368729954,
253 mtime: 1368729999
254 })
255 },
256 { filename: 'foo',
257 longname: 'drwxrwxrwx 2 nodejs nodejs 4096 Mar 8 2009 foo',
258 attrs: new Stats({
259 mode: 0o777 | constants.S_IFDIR,
260 size: 4096,
261 uid: 9001,
262 gid: 8001,
263 atime: 1368729954,
264 mtime: 1368729999
265 })
266 },
267 { filename: 'bar',
268 longname: '-rw-r--r-- 1 nodejs nodejs 513901992 Dec 4 2009 bar',
269 attrs: new Stats({
270 mode: 0o644 | constants.S_IFREG,
271 size: 513901992,
272 uid: 9001,
273 gid: 8001,
274 atime: 1259972199,
275 mtime: 1259972199
276 })
277 }
278 ];
279 server.on('READDIR', mustCall((id, handle) => {
280 assert(id === 0, `Wrong request id: ${id}`);
281 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
282 server.name(id, list_);
283 server.end();
284 }));
285 client.readdir(handle_, mustCall((err, list) => {
286 assert(!err, `Unexpected readdir() error: ${err}`);
287 assert.deepStrictEqual(list,
288 list_.slice(2),
289 'dir list mismatch');
290 }));
291}));
292
293setup('readdir (full)', mustCall((client, server) => {
294 const handle_ = Buffer.from('node.js');
295 const list_ = [
296 { filename: '.',
297 longname: 'drwxr-xr-x 56 nodejs nodejs 4096 Nov 10 01:05 .',
298 attrs: new Stats({
299 mode: 0o755 | constants.S_IFDIR,
300 size: 4096,
301 uid: 9001,
302 gid: 8001,
303 atime: 1415599549,
304 mtime: 1415599590
305 })
306 },
307 { filename: '..',
308 longname: 'drwxr-xr-x 4 root root 4096 May 16 2013 ..',
309 attrs: new Stats({
310 mode: 0o755 | constants.S_IFDIR,
311 size: 4096,
312 uid: 0,
313 gid: 0,
314 atime: 1368729954,
315 mtime: 1368729999
316 })
317 },
318 { filename: 'foo',
319 longname: 'drwxrwxrwx 2 nodejs nodejs 4096 Mar 8 2009 foo',
320 attrs: new Stats({
321 mode: 0o777 | constants.S_IFDIR,
322 size: 4096,
323 uid: 9001,
324 gid: 8001,
325 atime: 1368729954,
326 mtime: 1368729999
327 })
328 },
329 { filename: 'bar',
330 longname: '-rw-r--r-- 1 nodejs nodejs 513901992 Dec 4 2009 bar',
331 attrs: new Stats({
332 mode: 0o644 | constants.S_IFREG,
333 size: 513901992,
334 uid: 9001,
335 gid: 8001,
336 atime: 1259972199,
337 mtime: 1259972199
338 })
339 }
340 ];
341 server.on('READDIR', mustCall((id, handle) => {
342 assert(id === 0, `Wrong request id: ${id}`);
343 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
344 server.name(id, list_);
345 server.end();
346 }));
347 client.readdir(handle_, { full: true }, mustCall((err, list) => {
348 assert(!err, `Unexpected readdir() error: ${err}`);
349 assert.deepStrictEqual(list, list_, 'dir list mismatch');
350 }));
351}));
352
353setup('readdir (EOF)', mustCall((client, server) => {
354 const handle_ = Buffer.from('node.js');
355 server.on('READDIR', mustCall((id, handle) => {
356 assert(id === 0, `Wrong request id: ${id}`);
357 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
358 server.status(id, STATUS_CODE.EOF);
359 server.end();
360 }));
361 client.readdir(handle_, mustCall((err, list) => {
362 assert(err && err.code === STATUS_CODE.EOF,
363 `Expected EOF, got: ${err}`);
364 }));
365}));
366
367setup('unlink', mustCall((client, server) => {
368 const path_ = '/foo/bar/baz';
369 server.on('REMOVE', mustCall((id, path) => {
370 assert(id === 0, `Wrong request id: ${id}`);
371 assert(path === path_, `Wrong path: ${path}`);
372 server.status(id, STATUS_CODE.OK);
373 server.end();
374 }));
375 client.unlink(path_, mustCall((err) => {
376 assert(!err, `Unexpected unlink() error: ${err}`);
377 }));
378}));
379
380setup('mkdir', mustCall((client, server) => {
381 const path_ = '/foo/bar/baz';
382 server.on('MKDIR', mustCall((id, path) => {
383 assert(id === 0, `Wrong request id: ${id}`);
384 assert(path === path_, `Wrong path: ${path}`);
385 server.status(id, STATUS_CODE.OK);
386 server.end();
387 }));
388 client.mkdir(path_, mustCall((err) => {
389 assert(!err, `Unexpected mkdir() error: ${err}`);
390 }));
391}));
392
393setup('rmdir', mustCall((client, server) => {
394 const path_ = '/foo/bar/baz';
395 server.on('RMDIR', mustCall((id, path) => {
396 assert(id === 0, `Wrong request id: ${id}`);
397 assert(path === path_, `Wrong path: ${path}`);
398 server.status(id, STATUS_CODE.OK);
399 server.end();
400 }));
401 client.rmdir(path_, mustCall((err) => {
402 assert(!err, `Unexpected rmdir() error: ${err}`);
403 }));
404}));
405
406setup('realpath', mustCall((client, server) => {
407 const path_ = '/foo/bar/baz';
408 const name_ = { filename: '/tmp/foo' };
409 server.on('REALPATH', mustCall((id, path) => {
410 assert(id === 0, `Wrong request id: ${id}`);
411 assert(path === path_, `Wrong path: ${path}`);
412 server.name(id, name_);
413 server.end();
414 }));
415 client.realpath(path_, mustCall((err, name) => {
416 assert(!err, `Unexpected realpath() error: ${err}`);
417 assert.deepStrictEqual(name, name_.filename, 'name mismatch');
418 }));
419}));
420
421setup('stat', mustCall((client, server) => {
422 const path_ = '/foo/bar/baz';
423 const attrs_ = new Stats({
424 mode: 0o644 | constants.S_IFREG,
425 size: 10 * 1024,
426 uid: 9001,
427 gid: 9001,
428 atime: (Date.now() / 1000) | 0,
429 mtime: (Date.now() / 1000) | 0
430 });
431 server.on('STAT', mustCall((id, path) => {
432 assert(id === 0, `Wrong request id: ${id}`);
433 assert(path === path_, `Wrong path: ${path}`);
434 server.attrs(id, attrs_);
435 server.end();
436 }));
437 client.stat(path_, mustCall((err, attrs) => {
438 assert(!err, `Unexpected stat() error: ${err}`);
439 assert.deepStrictEqual(attrs, attrs_, 'attrs mismatch');
440 const expectedTypes = {
441 isDirectory: false,
442 isFile: true,
443 isBlockDevice: false,
444 isCharacterDevice: false,
445 isSymbolicLink: false,
446 isFIFO: false,
447 isSocket: false
448 };
449 for (const [fn, expect] of Object.entries(expectedTypes))
450 assert(attrs[fn]() === expect, `attrs.${fn}() failed`);
451 }));
452}));
453
454setup('rename', mustCall((client, server) => {
455 const oldPath_ = '/foo/bar/baz';
456 const newPath_ = '/tmp/foo';
457 server.on('RENAME', mustCall((id, oldPath, newPath) => {
458 assert(id === 0, `Wrong request id: ${id}`);
459 assert(oldPath === oldPath_, `Wrong old path: ${oldPath}`);
460 assert(newPath === newPath_, `Wrong new path: ${newPath}`);
461 server.status(id, STATUS_CODE.OK);
462 server.end();
463 }));
464 client.rename(oldPath_, newPath_, mustCall((err) => {
465 assert(!err, `Unexpected rename() error: ${err}`);
466 }));
467}));
468
469setup('readlink', mustCall((client, server) => {
470 const linkPath_ = '/foo/bar/baz';
471 const name = { filename: '/tmp/foo' };
472 server.on('READLINK', mustCall((id, linkPath) => {
473 assert(id === 0, `Wrong request id: ${id}`);
474 assert(linkPath === linkPath_, `Wrong link path: ${linkPath}`);
475 server.name(id, name);
476 server.end();
477 }));
478 client.readlink(linkPath_, mustCall((err, targetPath) => {
479 assert(!err, `Unexpected readlink() error: ${err}`);
480 assert(targetPath === name.filename,
481 `Wrong target path: ${targetPath}`);
482 }));
483}));
484
485setup('symlink', mustCall((client, server) => {
486 const linkPath_ = '/foo/bar/baz';
487 const targetPath_ = '/tmp/foo';
488 server.on('SYMLINK', mustCall((id, linkPath, targetPath) => {
489 assert(id === 0, `Wrong request id: ${id}`);
490 assert(linkPath === linkPath_, `Wrong link path: ${linkPath}`);
491 assert(targetPath === targetPath_, `Wrong target path: ${targetPath}`);
492 server.status(id, STATUS_CODE.OK);
493 server.end();
494 }));
495 client.symlink(targetPath_, linkPath_, mustCall((err) => {
496 assert(!err, `Unexpected symlink() error: ${err}`);
497 }));
498}));
499
500setup('readFile', mustCall((client, server) => {
501 const path_ = '/foo/bar/baz';
502 const handle_ = Buffer.from('hi mom!');
503 const data_ = Buffer.from('hello world');
504 server.on('OPEN', mustCall((id, path, pflags, attrs) => {
505 assert(id === 0, `Wrong request id: ${id}`);
506 assert(path === path_, `Wrong path: ${path}`);
507 assert(pflags === OPEN_MODE.READ, `Wrong flags: ${flagsToHuman(pflags)}`);
508 server.handle(id, handle_);
509 })).on('FSTAT', mustCall((id, handle) => {
510 assert(id === 1, `Wrong request id: ${id}`);
511 const attrs = new Stats({
512 size: data_.length,
513 uid: 9001,
514 gid: 9001,
515 atime: (Date.now() / 1000) | 0,
516 mtime: (Date.now() / 1000) | 0
517 });
518 server.attrs(id, attrs);
519 })).on('READ', mustCall((id, handle, offset, len) => {
520 assert(id === 2, `Wrong request id: ${id}`);
521 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
522 assert(offset === 0, `Wrong read offset: ${offset}`);
523 server.data(id, data_);
524 })).on('CLOSE', mustCall((id, handle) => {
525 assert(id === 3, `Wrong request id: ${id}`);
526 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
527 server.status(id, STATUS_CODE.OK);
528 server.end();
529 }));
530 client.readFile(path_, mustCall((err, buf) => {
531 assert(!err, `Unexpected error: ${err}`);
532 assert.deepStrictEqual(buf, data_, 'data mismatch');
533 }));
534}));
535
536setup('readFile (no size from fstat)', mustCall((client, server) => {
537 const path_ = '/foo/bar/baz';
538 const handle_ = Buffer.from('hi mom!');
539 const data_ = Buffer.from('hello world');
540 let reads = 0;
541 server.on('OPEN', mustCall((id, path, pflags, attrs) => {
542 assert(id === 0, `Wrong request id: ${id}`);
543 assert(path === path_, `Wrong path: ${path}`);
544 assert(pflags === OPEN_MODE.READ, `Wrong flags: ${flagsToHuman(pflags)}`);
545 server.handle(id, handle_);
546 })).on('FSTAT', mustCall((id, handle) => {
547 assert(id === 1, `Wrong request id: ${id}`);
548 const attrs = new Stats({
549 uid: 9001,
550 gid: 9001,
551 atime: (Date.now() / 1000) | 0,
552 mtime: (Date.now() / 1000) | 0
553 });
554 server.attrs(id, attrs);
555 })).on('READ', mustCall((id, handle, offset, len) => {
556 assert(++reads + 1 === id, `Wrong request id: ${id}`);
557 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
558 switch (id) {
559 case 2:
560 assert(offset === 0, `Wrong read offset for first read: ${offset}`);
561 server.data(id, data_);
562 break;
563 case 3:
564 assert(offset === data_.length,
565 `Wrong read offset for second read: ${offset}`);
566 server.status(id, STATUS_CODE.EOF);
567 break;
568 }
569 }, 2)).on('CLOSE', mustCall((id, handle) => {
570 assert(id === 4, `Wrong request id: ${id}`);
571 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
572 server.status(id, STATUS_CODE.OK);
573 server.end();
574 }));
575 client.readFile(path_, mustCall((err, buf) => {
576 assert(!err, `Unexpected error: ${err}`);
577 assert.deepStrictEqual(buf, data_, 'data mismatch');
578 }));
579}));
580
581setup('ReadStream', mustCall((client, server) => {
582 let reads = 0;
583 const path_ = '/foo/bar/baz';
584 const handle_ = Buffer.from('hi mom!');
585 const data_ = Buffer.from('hello world');
586 server.on('OPEN', mustCall((id, path, pflags, attrs) => {
587 assert(id === 0, `Wrong request id: ${id}`);
588 assert(path === path_, `Wrong path: ${path}`);
589 assert(pflags === OPEN_MODE.READ, `Wrong flags: ${flagsToHuman(pflags)}`);
590 server.handle(id, handle_);
591 })).on('READ', mustCall((id, handle, offset, len) => {
592 assert(id === ++reads, `Wrong request id: ${id}`);
593 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
594 if (reads === 1) {
595 assert(offset === 0, `Wrong read offset: ${offset}`);
596 server.data(id, data_);
597 } else {
598 server.status(id, STATUS_CODE.EOF);
599 }
600 }, 2)).on('CLOSE', mustCall((id, handle) => {
601 assert(id === 3, `Wrong request id: ${id}`);
602 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
603 server.status(id, STATUS_CODE.OK);
604 server.end();
605 }));
606 let buf = [];
607 client.createReadStream(path_).on('readable', mustCallAtLeast(function() {
608 let chunk;
609 while ((chunk = this.read()) !== null)
610 buf.push(chunk);
611 })).on('end', mustCall(() => {
612 buf = Buffer.concat(buf);
613 assert.deepStrictEqual(buf, data_, 'data mismatch');
614 }));
615}));
616
617setup('ReadStream (fewer bytes than requested)', mustCall((client, server) => {
618 const path_ = '/foo/bar/baz';
619 const handle_ = Buffer.from('hi mom!');
620 const data_ = Buffer.from('hello world');
621 server.on('OPEN', mustCall((id, path, pflags, attrs) => {
622 server.handle(id, handle_);
623 })).on('READ', mustCallAtLeast((id, handle, offset, len) => {
624 if (offset > data_.length) {
625 server.status(id, STATUS_CODE.EOF);
626 } else {
627 // Only read 4 bytes at a time
628 server.data(id, data_.slice(offset, offset + 4));
629 }
630 })).on('CLOSE', mustCall((id, handle) => {
631 server.status(id, STATUS_CODE.OK);
632 server.end();
633 }));
634 let buf = [];
635 client.createReadStream(path_).on('readable', mustCallAtLeast(function() {
636 let chunk;
637 while ((chunk = this.read()) !== null)
638 buf.push(chunk);
639 })).on('end', mustCall(() => {
640 buf = Buffer.concat(buf);
641 assert.deepStrictEqual(buf, data_, 'data mismatch');
642 }));
643}));
644
645setup('ReadStream (error)', mustCall((client, server) => {
646 const path_ = '/foo/bar/baz';
647 server.on('OPEN', mustCall((id, path, pflags, attrs) => {
648 assert(id === 0, `Wrong request id: ${id}`);
649 assert(path === path_, `Wrong path: ${path}`);
650 assert(pflags === OPEN_MODE.READ, `Wrong flags: ${flagsToHuman(pflags)}`);
651 server.status(id, STATUS_CODE.NO_SUCH_FILE);
652 server.end();
653 }));
654 client.createReadStream(path_).on('error', mustCall((err) => {
655 assert(err.code === STATUS_CODE.NO_SUCH_FILE);
656 }));
657}));
658
659setup('WriteStream', mustCall((client, server) => {
660 let writes = 0;
661 const path_ = '/foo/bar/baz';
662 const handle_ = Buffer.from('hi mom!');
663 const data_ = Buffer.from('hello world');
664 const pflags_ = OPEN_MODE.TRUNC | OPEN_MODE.CREAT | OPEN_MODE.WRITE;
665 server.on('OPEN', mustCall((id, path, pflags, attrs) => {
666 assert(id === 0, `Wrong request id: ${id}`);
667 assert(path === path_, `Wrong path: ${path}`);
668 assert(pflags === pflags_, `Wrong flags: ${flagsToHuman(pflags)}`);
669 server.handle(id, handle_);
670 })).on('FSETSTAT', mustCall((id, handle, attrs) => {
671 assert(id === 1, `Wrong request id: ${id}`);
672 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
673 assert.strictEqual(attrs.mode, 0o666, 'Wrong file mode');
674 server.status(id, STATUS_CODE.OK);
675 })).on('WRITE', mustCall((id, handle, offset, data) => {
676 assert(id === ++writes + 1, `Wrong request id: ${id}`);
677 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
678 assert(offset === ((writes - 1) * data_.length),
679 `Wrong write offset: ${offset}`);
680 assert.deepStrictEqual(data, data_, 'Wrong data');
681 server.status(id, STATUS_CODE.OK);
682 }, 3)).on('CLOSE', mustCall((id, handle) => {
683 assert(id === 5, `Wrong request id: ${id}`);
684 assert.deepStrictEqual(handle, handle_, 'handle mismatch');
685 server.status(id, STATUS_CODE.OK);
686 server.end();
687 }));
688
689 const writer = client.createWriteStream(path_);
690 writer.cork && writer.cork();
691 writer.write(data_);
692 writer.write(data_);
693 writer.write(data_);
694 writer.uncork && writer.uncork();
695 writer.end();
696}));
697
698{
699 const { client, server } = setup_(
700 'SFTP server aborts with exit-status',
701 {
702 client: { username: 'foo', password: 'bar' },
703 server: { hostKeys: [ fixture('ssh_host_rsa_key') ] },
704 },
705 );
706
707 server.on('connection', mustCall((conn) => {
708 conn.on('authentication', mustCall((ctx) => {
709 ctx.accept();
710 })).on('ready', mustCall(() => {
711 conn.on('session', mustCall((accept, reject) => {
712 accept().on('sftp', mustCall((accept, reject) => {
713 const sftp = accept();
714
715 // XXX: hack
716 sftp._protocol.exitStatus(sftp.outgoing.id, 127);
717 sftp._protocol.channelClose(sftp.outgoing.id);
718 }));
719 }));
720 }));
721 }));
722
723 client.on('ready', mustCall(() => {
724 const timeout = setTimeout(mustNotCall(), 1000);
725 client.sftp(mustCall((err, sftp) => {
726 clearTimeout(timeout);
727 assert(err, 'Expected error');
728 assert(err.code === 127, `Expected exit code 127, saw: ${err.code}`);
729 client.end();
730 }));
731 }));
732}
733
734
735// =============================================================================
736function setup(title, cb) {
737 const { client, server } = setupSimple(DEBUG, title);
738 let clientSFTP;
739 let serverSFTP;
740
741 const onSFTP = mustCall(() => {
742 if (clientSFTP && serverSFTP)
743 cb(clientSFTP, serverSFTP);
744 }, 2);
745
746 client.on('ready', mustCall(() => {
747 client.sftp(mustCall((err, sftp) => {
748 assert(!err, `[${title}] Unexpected client sftp start error: ${err}`);
749 sftp.on('close', mustCall(() => {
750 client.end();
751 }));
752 clientSFTP = sftp;
753 onSFTP();
754 }));
755 }));
756
757 server.on('connection', mustCall((conn) => {
758 conn.on('ready', mustCall(() => {
759 conn.on('session', mustCall((accept, reject) => {
760 accept().on('sftp', mustCall((accept, reject) => {
761 const sftp = accept();
762 sftp.on('close', mustCall(() => {
763 conn.end();
764 }));
765 serverSFTP = sftp;
766 onSFTP();
767 }));
768 }));
769 }));
770 }));
771}
772
773function flagsToHuman(flags) {
774 const ret = [];
775
776 for (const [name, value] of Object.entries(OPEN_MODE)) {
777 if (flags & value)
778 ret.push(name);
779 }
780
781 return ret.join(' | ');
782}