1 | 'use strict';
|
2 |
|
3 | const assert = require('assert');
|
4 | const { constants } = require('fs');
|
5 |
|
6 | const {
|
7 | fixture,
|
8 | mustCall,
|
9 | mustCallAtLeast,
|
10 | mustNotCall,
|
11 | setup: setup_,
|
12 | setupSimple
|
13 | } = require('./common.js');
|
14 |
|
15 | const { OPEN_MODE, Stats, STATUS_CODE } = require('../lib/protocol/SFTP.js');
|
16 |
|
17 | const DEBUG = false;
|
18 |
|
19 | setup('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 |
|
36 | setup('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 |
|
49 | setup('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 |
|
67 | setup('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 |
|
91 | setup('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 |
|
108 | setup('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 |
|
134 | setup('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 |
|
155 | setup('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 |
|
176 | setup('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 |
|
196 | setup('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 |
|
216 | setup('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 |
|
231 | setup('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 |
|
293 | setup('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 |
|
353 | setup('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 |
|
367 | setup('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 |
|
380 | setup('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 |
|
393 | setup('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 |
|
406 | setup('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 |
|
421 | setup('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 |
|
454 | setup('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 |
|
469 | setup('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 |
|
485 | setup('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 |
|
500 | setup('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 |
|
536 | setup('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 |
|
581 | setup('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 |
|
617 | setup('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 |
|
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 |
|
645 | setup('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 |
|
659 | setup('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 |
|
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 |
|
736 | function 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 |
|
773 | function 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 | }
|