807 lines
26 KiB
JavaScript
807 lines
26 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const { constants } = require('fs');
|
|
|
|
const {
|
|
fixture,
|
|
mustCall,
|
|
mustCallAtLeast,
|
|
mustNotCall,
|
|
setup: setup_,
|
|
setupSimple
|
|
} = require('./common.js');
|
|
|
|
const { OPEN_MODE, Stats, STATUS_CODE } = require('../lib/protocol/SFTP.js');
|
|
|
|
const DEBUG = false;
|
|
|
|
setup('open', mustCall((client, server) => {
|
|
const path_ = '/tmp/foo.txt';
|
|
const handle_ = Buffer.from('node.js');
|
|
const pflags_ = (OPEN_MODE.TRUNC | OPEN_MODE.CREAT | OPEN_MODE.WRITE);
|
|
server.on('OPEN', mustCall((id, path, pflags, attrs) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert(path === path_, `Wrong path: ${path}`);
|
|
assert(pflags === pflags_, `Wrong flags: ${flagsToHuman(pflags)}`);
|
|
server.handle(id, handle_);
|
|
server.end();
|
|
}));
|
|
client.open(path_, 'w', mustCall((err, handle) => {
|
|
assert(!err, `Unexpected open() error: ${err}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
}));
|
|
}));
|
|
|
|
setup('close', mustCall((client, server) => {
|
|
const handle_ = Buffer.from('node.js');
|
|
server.on('CLOSE', mustCall((id, handle) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
server.status(id, STATUS_CODE.OK);
|
|
server.end();
|
|
}));
|
|
client.close(handle_, mustCall((err) => {
|
|
assert(!err, `Unexpected close() error: ${err}`);
|
|
}));
|
|
}));
|
|
|
|
setup('read', mustCall((client, server) => {
|
|
const expected = Buffer.from('node.jsnode.jsnode.jsnode.jsnode.jsnode.js');
|
|
const handle_ = Buffer.from('node.js');
|
|
const buf = Buffer.alloc(expected.length);
|
|
server.on('READ', mustCall((id, handle, offset, len) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
assert(offset === 5, `Wrong read offset: ${offset}`);
|
|
assert(len === buf.length, `Wrong read len: ${len}`);
|
|
server.data(id, expected);
|
|
server.end();
|
|
}));
|
|
client.read(handle_, buf, 0, buf.length, 5, mustCall((err, nb) => {
|
|
assert(!err, `Unexpected read() error: ${err}`);
|
|
assert.deepStrictEqual(buf, expected, 'read data mismatch');
|
|
}));
|
|
}));
|
|
|
|
setup('read (partial)', mustCall((client, server) => {
|
|
const expected = Buffer.from('blargh');
|
|
const handle_ = Buffer.from('node.js');
|
|
const buf = Buffer.alloc(256);
|
|
server.on('READ', mustCall((id, handle, offset, len) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
assert(offset === 0, `Wrong read offset: ${offset}`);
|
|
assert(len === buf.length, `Wrong read len: ${len}`);
|
|
server.data(id, expected);
|
|
server.end();
|
|
}));
|
|
client.read(handle_, buf, 0, buf.length, 0, mustCall((err, nb, data) => {
|
|
assert(!err, `Unexpected read() error: ${err}`);
|
|
assert.strictEqual(nb, expected.length, 'nb count mismatch');
|
|
assert.deepStrictEqual(
|
|
buf.slice(0, expected.length),
|
|
expected,
|
|
'read data mismatch'
|
|
);
|
|
assert.deepStrictEqual(data, expected, 'read data mismatch');
|
|
}));
|
|
}));
|
|
|
|
setup('read (overflow)', mustCall((client, server) => {
|
|
const maxChunk = client._maxReadLen;
|
|
const expected = Buffer.alloc(3 * maxChunk, 'Q');
|
|
const handle_ = Buffer.from('node.js');
|
|
const buf = Buffer.alloc(expected.length, 0);
|
|
let reqs = 0;
|
|
server.on('READ', mustCall((id, handle, offset, len) => {
|
|
++reqs;
|
|
assert.strictEqual(id, reqs - 1, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
assert.strictEqual(offset,
|
|
(reqs - 1) * maxChunk,
|
|
`Wrong read offset: ${offset}`);
|
|
server.data(id, expected.slice(offset, offset + len));
|
|
if (reqs === 3)
|
|
server.end();
|
|
}, 3));
|
|
client.read(handle_, buf, 0, buf.length, 0, mustCall((err, nb) => {
|
|
assert(!err, `Unexpected read() error: ${err}`);
|
|
assert.deepStrictEqual(buf, expected);
|
|
assert.strictEqual(nb, buf.length, 'read nb mismatch');
|
|
}));
|
|
}));
|
|
|
|
setup('write', mustCall((client, server) => {
|
|
const handle_ = Buffer.from('node.js');
|
|
const buf = Buffer.from('node.jsnode.jsnode.jsnode.jsnode.jsnode.js');
|
|
server.on('WRITE', mustCall((id, handle, offset, data) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
assert(offset === 5, `Wrong write offset: ${offset}`);
|
|
assert.deepStrictEqual(data, buf, 'write data mismatch');
|
|
server.status(id, STATUS_CODE.OK);
|
|
server.end();
|
|
}));
|
|
client.write(handle_, buf, 0, buf.length, 5, mustCall((err, nb) => {
|
|
assert(!err, `Unexpected write() error: ${err}`);
|
|
assert.strictEqual(nb, buf.length, 'wrong bytes written');
|
|
}));
|
|
}));
|
|
|
|
setup('write (overflow)', mustCall((client, server) => {
|
|
const maxChunk = client._maxWriteLen;
|
|
const handle_ = Buffer.from('node.js');
|
|
const buf = Buffer.allocUnsafe(3 * maxChunk);
|
|
let reqs = 0;
|
|
server.on('WRITE', mustCall((id, handle, offset, data) => {
|
|
++reqs;
|
|
assert.strictEqual(id, reqs - 1, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
assert.strictEqual(offset,
|
|
(reqs - 1) * maxChunk,
|
|
`Wrong write offset: ${offset}`);
|
|
assert((offset + data.length) <= buf.length, 'bad offset');
|
|
assert.deepStrictEqual(data,
|
|
buf.slice(offset, offset + data.length),
|
|
'write data mismatch');
|
|
server.status(id, STATUS_CODE.OK);
|
|
if (reqs === 3)
|
|
server.end();
|
|
}, 3));
|
|
client.write(handle_, buf, 0, buf.length, 0, mustCall((err, nb) => {
|
|
assert(!err, `Unexpected write() error: ${err}`);
|
|
assert.strictEqual(nb, buf.length, 'wrote bytes written');
|
|
}));
|
|
}));
|
|
|
|
setup('lstat', mustCall((client, server) => {
|
|
const path_ = '/foo/bar/baz';
|
|
const attrs_ = new Stats({
|
|
size: 10 * 1024,
|
|
uid: 9001,
|
|
gid: 9001,
|
|
atime: (Date.now() / 1000) | 0,
|
|
mtime: (Date.now() / 1000) | 0
|
|
});
|
|
server.on('LSTAT', mustCall((id, path) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert(path === path_, `Wrong path: ${path}`);
|
|
server.attrs(id, attrs_);
|
|
server.end();
|
|
}));
|
|
client.lstat(path_, mustCall((err, attrs) => {
|
|
assert(!err, `Unexpected lstat() error: ${err}`);
|
|
assert.deepStrictEqual(attrs, attrs_, 'attrs mismatch');
|
|
}));
|
|
}));
|
|
|
|
setup('fstat', mustCall((client, server) => {
|
|
const handle_ = Buffer.from('node.js');
|
|
const attrs_ = new Stats({
|
|
size: 10 * 1024,
|
|
uid: 9001,
|
|
gid: 9001,
|
|
atime: (Date.now() / 1000) | 0,
|
|
mtime: (Date.now() / 1000) | 0
|
|
});
|
|
server.on('FSTAT', mustCall((id, handle) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
server.attrs(id, attrs_);
|
|
server.end();
|
|
}));
|
|
client.fstat(handle_, mustCall((err, attrs) => {
|
|
assert(!err, `Unexpected fstat() error: ${err}`);
|
|
assert.deepStrictEqual(attrs, attrs_, 'attrs mismatch');
|
|
}));
|
|
}));
|
|
|
|
setup('setstat', mustCall((client, server) => {
|
|
const path_ = '/foo/bar/baz';
|
|
const attrs_ = new Stats({
|
|
uid: 9001,
|
|
gid: 9001,
|
|
atime: (Date.now() / 1000) | 0,
|
|
mtime: (Date.now() / 1000) | 0
|
|
});
|
|
server.on('SETSTAT', mustCall((id, path, attrs) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert(path === path_, `Wrong path: ${path}`);
|
|
assert.deepStrictEqual(attrs, attrs_, 'attrs mismatch');
|
|
server.status(id, STATUS_CODE.OK);
|
|
server.end();
|
|
}));
|
|
client.setstat(path_, attrs_, mustCall((err) => {
|
|
assert(!err, `Unexpected setstat() error: ${err}`);
|
|
}));
|
|
}));
|
|
|
|
setup('fsetstat', mustCall((client, server) => {
|
|
const handle_ = Buffer.from('node.js');
|
|
const attrs_ = new Stats({
|
|
uid: 9001,
|
|
gid: 9001,
|
|
atime: (Date.now() / 1000) | 0,
|
|
mtime: (Date.now() / 1000) | 0
|
|
});
|
|
server.on('FSETSTAT', mustCall((id, handle, attrs) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
assert.deepStrictEqual(attrs, attrs_, 'attrs mismatch');
|
|
server.status(id, STATUS_CODE.OK);
|
|
server.end();
|
|
}));
|
|
client.fsetstat(handle_, attrs_, mustCall((err) => {
|
|
assert(!err, `Unexpected fsetstat() error: ${err}`);
|
|
}));
|
|
}));
|
|
|
|
setup('opendir', mustCall((client, server) => {
|
|
const handle_ = Buffer.from('node.js');
|
|
const path_ = '/tmp';
|
|
server.on('OPENDIR', mustCall((id, path) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert(path === path_, `Wrong path: ${path}`);
|
|
server.handle(id, handle_);
|
|
server.end();
|
|
}));
|
|
client.opendir(path_, mustCall((err, handle) => {
|
|
assert(!err, `Unexpected opendir() error: ${err}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
}));
|
|
}));
|
|
|
|
setup('readdir', mustCall((client, server) => {
|
|
const handle_ = Buffer.from('node.js');
|
|
const list_ = [
|
|
{ filename: '.',
|
|
longname: 'drwxr-xr-x 56 nodejs nodejs 4096 Nov 10 01:05 .',
|
|
attrs: new Stats({
|
|
mode: 0o755 | constants.S_IFDIR,
|
|
size: 4096,
|
|
uid: 9001,
|
|
gid: 8001,
|
|
atime: 1415599549,
|
|
mtime: 1415599590
|
|
})
|
|
},
|
|
{ filename: '..',
|
|
longname: 'drwxr-xr-x 4 root root 4096 May 16 2013 ..',
|
|
attrs: new Stats({
|
|
mode: 0o755 | constants.S_IFDIR,
|
|
size: 4096,
|
|
uid: 0,
|
|
gid: 0,
|
|
atime: 1368729954,
|
|
mtime: 1368729999
|
|
})
|
|
},
|
|
{ filename: 'foo',
|
|
longname: 'drwxrwxrwx 2 nodejs nodejs 4096 Mar 8 2009 foo',
|
|
attrs: new Stats({
|
|
mode: 0o777 | constants.S_IFDIR,
|
|
size: 4096,
|
|
uid: 9001,
|
|
gid: 8001,
|
|
atime: 1368729954,
|
|
mtime: 1368729999
|
|
})
|
|
},
|
|
{ filename: 'bar',
|
|
longname: '-rw-r--r-- 1 nodejs nodejs 513901992 Dec 4 2009 bar',
|
|
attrs: new Stats({
|
|
mode: 0o644 | constants.S_IFREG,
|
|
size: 513901992,
|
|
uid: 9001,
|
|
gid: 8001,
|
|
atime: 1259972199,
|
|
mtime: 1259972199
|
|
})
|
|
}
|
|
];
|
|
server.on('READDIR', mustCall((id, handle) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
server.name(id, list_);
|
|
server.end();
|
|
}));
|
|
client.readdir(handle_, mustCall((err, list) => {
|
|
assert(!err, `Unexpected readdir() error: ${err}`);
|
|
assert.deepStrictEqual(list,
|
|
list_.slice(2),
|
|
'dir list mismatch');
|
|
}));
|
|
}));
|
|
|
|
setup('readdir (full)', mustCall((client, server) => {
|
|
const handle_ = Buffer.from('node.js');
|
|
const list_ = [
|
|
{ filename: '.',
|
|
longname: 'drwxr-xr-x 56 nodejs nodejs 4096 Nov 10 01:05 .',
|
|
attrs: new Stats({
|
|
mode: 0o755 | constants.S_IFDIR,
|
|
size: 4096,
|
|
uid: 9001,
|
|
gid: 8001,
|
|
atime: 1415599549,
|
|
mtime: 1415599590
|
|
})
|
|
},
|
|
{ filename: '..',
|
|
longname: 'drwxr-xr-x 4 root root 4096 May 16 2013 ..',
|
|
attrs: new Stats({
|
|
mode: 0o755 | constants.S_IFDIR,
|
|
size: 4096,
|
|
uid: 0,
|
|
gid: 0,
|
|
atime: 1368729954,
|
|
mtime: 1368729999
|
|
})
|
|
},
|
|
{ filename: 'foo',
|
|
longname: 'drwxrwxrwx 2 nodejs nodejs 4096 Mar 8 2009 foo',
|
|
attrs: new Stats({
|
|
mode: 0o777 | constants.S_IFDIR,
|
|
size: 4096,
|
|
uid: 9001,
|
|
gid: 8001,
|
|
atime: 1368729954,
|
|
mtime: 1368729999
|
|
})
|
|
},
|
|
{ filename: 'bar',
|
|
longname: '-rw-r--r-- 1 nodejs nodejs 513901992 Dec 4 2009 bar',
|
|
attrs: new Stats({
|
|
mode: 0o644 | constants.S_IFREG,
|
|
size: 513901992,
|
|
uid: 9001,
|
|
gid: 8001,
|
|
atime: 1259972199,
|
|
mtime: 1259972199
|
|
})
|
|
}
|
|
];
|
|
server.on('READDIR', mustCall((id, handle) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
server.name(id, list_);
|
|
server.end();
|
|
}));
|
|
client.readdir(handle_, { full: true }, mustCall((err, list) => {
|
|
assert(!err, `Unexpected readdir() error: ${err}`);
|
|
assert.deepStrictEqual(list, list_, 'dir list mismatch');
|
|
}));
|
|
}));
|
|
|
|
setup('readdir (EOF)', mustCall((client, server) => {
|
|
const handle_ = Buffer.from('node.js');
|
|
server.on('READDIR', mustCall((id, handle) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
server.status(id, STATUS_CODE.EOF);
|
|
server.end();
|
|
}));
|
|
client.readdir(handle_, mustCall((err, list) => {
|
|
assert(err && err.code === STATUS_CODE.EOF,
|
|
`Expected EOF, got: ${err}`);
|
|
}));
|
|
}));
|
|
|
|
setup('unlink', mustCall((client, server) => {
|
|
const path_ = '/foo/bar/baz';
|
|
server.on('REMOVE', mustCall((id, path) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert(path === path_, `Wrong path: ${path}`);
|
|
server.status(id, STATUS_CODE.OK);
|
|
server.end();
|
|
}));
|
|
client.unlink(path_, mustCall((err) => {
|
|
assert(!err, `Unexpected unlink() error: ${err}`);
|
|
}));
|
|
}));
|
|
|
|
setup('mkdir', mustCall((client, server) => {
|
|
const path_ = '/foo/bar/baz';
|
|
server.on('MKDIR', mustCall((id, path) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert(path === path_, `Wrong path: ${path}`);
|
|
server.status(id, STATUS_CODE.OK);
|
|
server.end();
|
|
}));
|
|
client.mkdir(path_, mustCall((err) => {
|
|
assert(!err, `Unexpected mkdir() error: ${err}`);
|
|
}));
|
|
}));
|
|
|
|
setup('rmdir', mustCall((client, server) => {
|
|
const path_ = '/foo/bar/baz';
|
|
server.on('RMDIR', mustCall((id, path) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert(path === path_, `Wrong path: ${path}`);
|
|
server.status(id, STATUS_CODE.OK);
|
|
server.end();
|
|
}));
|
|
client.rmdir(path_, mustCall((err) => {
|
|
assert(!err, `Unexpected rmdir() error: ${err}`);
|
|
}));
|
|
}));
|
|
|
|
setup('realpath', mustCall((client, server) => {
|
|
const path_ = '/foo/bar/baz';
|
|
const name_ = { filename: '/tmp/foo' };
|
|
server.on('REALPATH', mustCall((id, path) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert(path === path_, `Wrong path: ${path}`);
|
|
server.name(id, name_);
|
|
server.end();
|
|
}));
|
|
client.realpath(path_, mustCall((err, name) => {
|
|
assert(!err, `Unexpected realpath() error: ${err}`);
|
|
assert.deepStrictEqual(name, name_.filename, 'name mismatch');
|
|
}));
|
|
}));
|
|
|
|
setup('stat', mustCall((client, server) => {
|
|
const path_ = '/foo/bar/baz';
|
|
const attrs_ = new Stats({
|
|
mode: 0o644 | constants.S_IFREG,
|
|
size: 10 * 1024,
|
|
uid: 9001,
|
|
gid: 9001,
|
|
atime: (Date.now() / 1000) | 0,
|
|
mtime: (Date.now() / 1000) | 0
|
|
});
|
|
server.on('STAT', mustCall((id, path) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert(path === path_, `Wrong path: ${path}`);
|
|
server.attrs(id, attrs_);
|
|
server.end();
|
|
}));
|
|
client.stat(path_, mustCall((err, attrs) => {
|
|
assert(!err, `Unexpected stat() error: ${err}`);
|
|
assert.deepStrictEqual(attrs, attrs_, 'attrs mismatch');
|
|
const expectedTypes = {
|
|
isDirectory: false,
|
|
isFile: true,
|
|
isBlockDevice: false,
|
|
isCharacterDevice: false,
|
|
isSymbolicLink: false,
|
|
isFIFO: false,
|
|
isSocket: false
|
|
};
|
|
for (const [fn, expect] of Object.entries(expectedTypes))
|
|
assert(attrs[fn]() === expect, `attrs.${fn}() failed`);
|
|
}));
|
|
}));
|
|
|
|
setup('rename', mustCall((client, server) => {
|
|
const oldPath_ = '/foo/bar/baz';
|
|
const newPath_ = '/tmp/foo';
|
|
server.on('RENAME', mustCall((id, oldPath, newPath) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert(oldPath === oldPath_, `Wrong old path: ${oldPath}`);
|
|
assert(newPath === newPath_, `Wrong new path: ${newPath}`);
|
|
server.status(id, STATUS_CODE.OK);
|
|
server.end();
|
|
}));
|
|
client.rename(oldPath_, newPath_, mustCall((err) => {
|
|
assert(!err, `Unexpected rename() error: ${err}`);
|
|
}));
|
|
}));
|
|
|
|
setup('readlink', mustCall((client, server) => {
|
|
const linkPath_ = '/foo/bar/baz';
|
|
const name = { filename: '/tmp/foo' };
|
|
server.on('READLINK', mustCall((id, linkPath) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert(linkPath === linkPath_, `Wrong link path: ${linkPath}`);
|
|
server.name(id, name);
|
|
server.end();
|
|
}));
|
|
client.readlink(linkPath_, mustCall((err, targetPath) => {
|
|
assert(!err, `Unexpected readlink() error: ${err}`);
|
|
assert(targetPath === name.filename,
|
|
`Wrong target path: ${targetPath}`);
|
|
}));
|
|
}));
|
|
|
|
setup('symlink', mustCall((client, server) => {
|
|
const linkPath_ = '/foo/bar/baz';
|
|
const targetPath_ = '/tmp/foo';
|
|
server.on('SYMLINK', mustCall((id, linkPath, targetPath) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert(linkPath === linkPath_, `Wrong link path: ${linkPath}`);
|
|
assert(targetPath === targetPath_, `Wrong target path: ${targetPath}`);
|
|
server.status(id, STATUS_CODE.OK);
|
|
server.end();
|
|
}));
|
|
client.symlink(targetPath_, linkPath_, mustCall((err) => {
|
|
assert(!err, `Unexpected symlink() error: ${err}`);
|
|
}));
|
|
}));
|
|
|
|
setup('readFile', mustCall((client, server) => {
|
|
const path_ = '/foo/bar/baz';
|
|
const handle_ = Buffer.from('hi mom!');
|
|
const data_ = Buffer.from('hello world');
|
|
server.on('OPEN', mustCall((id, path, pflags, attrs) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert(path === path_, `Wrong path: ${path}`);
|
|
assert(pflags === OPEN_MODE.READ, `Wrong flags: ${flagsToHuman(pflags)}`);
|
|
server.handle(id, handle_);
|
|
})).on('FSTAT', mustCall((id, handle) => {
|
|
assert(id === 1, `Wrong request id: ${id}`);
|
|
const attrs = new Stats({
|
|
size: data_.length,
|
|
uid: 9001,
|
|
gid: 9001,
|
|
atime: (Date.now() / 1000) | 0,
|
|
mtime: (Date.now() / 1000) | 0
|
|
});
|
|
server.attrs(id, attrs);
|
|
})).on('READ', mustCall((id, handle, offset, len) => {
|
|
assert(id === 2, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
assert(offset === 0, `Wrong read offset: ${offset}`);
|
|
server.data(id, data_);
|
|
})).on('CLOSE', mustCall((id, handle) => {
|
|
assert(id === 3, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
server.status(id, STATUS_CODE.OK);
|
|
server.end();
|
|
}));
|
|
client.readFile(path_, mustCall((err, buf) => {
|
|
assert(!err, `Unexpected error: ${err}`);
|
|
assert.deepStrictEqual(buf, data_, 'data mismatch');
|
|
}));
|
|
}));
|
|
|
|
setup('readFile (no size from fstat)', mustCall((client, server) => {
|
|
const path_ = '/foo/bar/baz';
|
|
const handle_ = Buffer.from('hi mom!');
|
|
const data_ = Buffer.from('hello world');
|
|
let reads = 0;
|
|
server.on('OPEN', mustCall((id, path, pflags, attrs) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert(path === path_, `Wrong path: ${path}`);
|
|
assert(pflags === OPEN_MODE.READ, `Wrong flags: ${flagsToHuman(pflags)}`);
|
|
server.handle(id, handle_);
|
|
})).on('FSTAT', mustCall((id, handle) => {
|
|
assert(id === 1, `Wrong request id: ${id}`);
|
|
const attrs = new Stats({
|
|
uid: 9001,
|
|
gid: 9001,
|
|
atime: (Date.now() / 1000) | 0,
|
|
mtime: (Date.now() / 1000) | 0
|
|
});
|
|
server.attrs(id, attrs);
|
|
})).on('READ', mustCall((id, handle, offset, len) => {
|
|
assert(++reads + 1 === id, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
switch (id) {
|
|
case 2:
|
|
assert(offset === 0, `Wrong read offset for first read: ${offset}`);
|
|
server.data(id, data_);
|
|
break;
|
|
case 3:
|
|
assert(offset === data_.length,
|
|
`Wrong read offset for second read: ${offset}`);
|
|
server.status(id, STATUS_CODE.EOF);
|
|
break;
|
|
}
|
|
}, 2)).on('CLOSE', mustCall((id, handle) => {
|
|
assert(id === 4, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
server.status(id, STATUS_CODE.OK);
|
|
server.end();
|
|
}));
|
|
client.readFile(path_, mustCall((err, buf) => {
|
|
assert(!err, `Unexpected error: ${err}`);
|
|
assert.deepStrictEqual(buf, data_, 'data mismatch');
|
|
}));
|
|
}));
|
|
|
|
setup('ReadStream', mustCall((client, server) => {
|
|
let reads = 0;
|
|
const path_ = '/foo/bar/baz';
|
|
const handle_ = Buffer.from('hi mom!');
|
|
const data_ = Buffer.from('hello world');
|
|
server.on('OPEN', mustCall((id, path, pflags, attrs) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert(path === path_, `Wrong path: ${path}`);
|
|
assert(pflags === OPEN_MODE.READ, `Wrong flags: ${flagsToHuman(pflags)}`);
|
|
server.handle(id, handle_);
|
|
})).on('READ', mustCall((id, handle, offset, len) => {
|
|
assert(id === ++reads, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
if (reads === 1) {
|
|
assert(offset === 0, `Wrong read offset: ${offset}`);
|
|
server.data(id, data_);
|
|
} else {
|
|
server.status(id, STATUS_CODE.EOF);
|
|
}
|
|
}, 2)).on('CLOSE', mustCall((id, handle) => {
|
|
assert(id === 3, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
server.status(id, STATUS_CODE.OK);
|
|
server.end();
|
|
}));
|
|
let buf = [];
|
|
client.createReadStream(path_).on('readable', mustCallAtLeast(function() {
|
|
let chunk;
|
|
while ((chunk = this.read()) !== null)
|
|
buf.push(chunk);
|
|
})).on('end', mustCall(() => {
|
|
buf = Buffer.concat(buf);
|
|
assert.deepStrictEqual(buf, data_, 'data mismatch');
|
|
}));
|
|
}));
|
|
|
|
setup('ReadStream (fewer bytes than requested)', mustCall((client, server) => {
|
|
const path_ = '/foo/bar/baz';
|
|
const handle_ = Buffer.from('hi mom!');
|
|
const data_ = Buffer.from('hello world');
|
|
server.on('OPEN', mustCall((id, path, pflags, attrs) => {
|
|
server.handle(id, handle_);
|
|
})).on('READ', mustCallAtLeast((id, handle, offset, len) => {
|
|
if (offset > data_.length) {
|
|
server.status(id, STATUS_CODE.EOF);
|
|
} else {
|
|
// Only read 4 bytes at a time
|
|
server.data(id, data_.slice(offset, offset + 4));
|
|
}
|
|
})).on('CLOSE', mustCall((id, handle) => {
|
|
server.status(id, STATUS_CODE.OK);
|
|
server.end();
|
|
}));
|
|
let buf = [];
|
|
client.createReadStream(path_).on('readable', mustCallAtLeast(function() {
|
|
let chunk;
|
|
while ((chunk = this.read()) !== null)
|
|
buf.push(chunk);
|
|
})).on('end', mustCall(() => {
|
|
buf = Buffer.concat(buf);
|
|
assert.deepStrictEqual(buf, data_, 'data mismatch');
|
|
}));
|
|
}));
|
|
|
|
setup('ReadStream (error)', mustCall((client, server) => {
|
|
const path_ = '/foo/bar/baz';
|
|
server.on('OPEN', mustCall((id, path, pflags, attrs) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert(path === path_, `Wrong path: ${path}`);
|
|
assert(pflags === OPEN_MODE.READ, `Wrong flags: ${flagsToHuman(pflags)}`);
|
|
server.status(id, STATUS_CODE.NO_SUCH_FILE);
|
|
server.end();
|
|
}));
|
|
client.createReadStream(path_).on('error', mustCall((err) => {
|
|
assert(err.code === STATUS_CODE.NO_SUCH_FILE);
|
|
}));
|
|
}));
|
|
|
|
setup('WriteStream', mustCall((client, server) => {
|
|
let writes = 0;
|
|
const path_ = '/foo/bar/baz';
|
|
const handle_ = Buffer.from('hi mom!');
|
|
const data_ = Buffer.from('hello world');
|
|
const pflags_ = OPEN_MODE.TRUNC | OPEN_MODE.CREAT | OPEN_MODE.WRITE;
|
|
server.on('OPEN', mustCall((id, path, pflags, attrs) => {
|
|
assert(id === 0, `Wrong request id: ${id}`);
|
|
assert(path === path_, `Wrong path: ${path}`);
|
|
assert(pflags === pflags_, `Wrong flags: ${flagsToHuman(pflags)}`);
|
|
server.handle(id, handle_);
|
|
})).on('FSETSTAT', mustCall((id, handle, attrs) => {
|
|
assert(id === 1, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
assert.strictEqual(attrs.mode, 0o666, 'Wrong file mode');
|
|
server.status(id, STATUS_CODE.OK);
|
|
})).on('WRITE', mustCall((id, handle, offset, data) => {
|
|
assert(id === ++writes + 1, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
assert(offset === ((writes - 1) * data_.length),
|
|
`Wrong write offset: ${offset}`);
|
|
assert.deepStrictEqual(data, data_, 'Wrong data');
|
|
server.status(id, STATUS_CODE.OK);
|
|
}, 3)).on('CLOSE', mustCall((id, handle) => {
|
|
assert(id === 5, `Wrong request id: ${id}`);
|
|
assert.deepStrictEqual(handle, handle_, 'handle mismatch');
|
|
server.status(id, STATUS_CODE.OK);
|
|
server.end();
|
|
}));
|
|
|
|
const writer = client.createWriteStream(path_);
|
|
writer.cork && writer.cork();
|
|
writer.write(data_);
|
|
writer.write(data_);
|
|
writer.write(data_);
|
|
writer.uncork && writer.uncork();
|
|
writer.end();
|
|
}));
|
|
|
|
{
|
|
const { client, server } = setup_(
|
|
'SFTP server aborts with exit-status',
|
|
{
|
|
client: { username: 'foo', password: 'bar' },
|
|
server: { hostKeys: [ fixture('ssh_host_rsa_key') ] },
|
|
},
|
|
);
|
|
|
|
server.on('connection', mustCall((conn) => {
|
|
conn.on('authentication', mustCall((ctx) => {
|
|
ctx.accept();
|
|
})).on('ready', mustCall(() => {
|
|
conn.on('session', mustCall((accept, reject) => {
|
|
accept().on('sftp', mustCall((accept, reject) => {
|
|
const sftp = accept();
|
|
|
|
// XXX: hack
|
|
sftp._protocol.exitStatus(sftp.outgoing.id, 127);
|
|
sftp._protocol.channelClose(sftp.outgoing.id);
|
|
}));
|
|
}));
|
|
}));
|
|
}));
|
|
|
|
client.on('ready', mustCall(() => {
|
|
const timeout = setTimeout(mustNotCall(), 1000);
|
|
client.sftp(mustCall((err, sftp) => {
|
|
clearTimeout(timeout);
|
|
assert(err, 'Expected error');
|
|
assert(err.code === 127, `Expected exit code 127, saw: ${err.code}`);
|
|
client.end();
|
|
}));
|
|
}));
|
|
}
|
|
|
|
|
|
// =============================================================================
|
|
function setup(title, cb) {
|
|
const { client, server } = setupSimple(DEBUG, title);
|
|
let clientSFTP;
|
|
let serverSFTP;
|
|
|
|
const onSFTP = mustCall(() => {
|
|
if (clientSFTP && serverSFTP)
|
|
cb(clientSFTP, serverSFTP);
|
|
}, 2);
|
|
|
|
client.on('ready', mustCall(() => {
|
|
client.sftp(mustCall((err, sftp) => {
|
|
assert(!err, `[${title}] Unexpected client sftp start error: ${err}`);
|
|
sftp.on('close', mustCall(() => {
|
|
client.end();
|
|
}));
|
|
clientSFTP = sftp;
|
|
onSFTP();
|
|
}));
|
|
}));
|
|
|
|
server.on('connection', mustCall((conn) => {
|
|
conn.on('ready', mustCall(() => {
|
|
conn.on('session', mustCall((accept, reject) => {
|
|
accept().on('sftp', mustCall((accept, reject) => {
|
|
const sftp = accept();
|
|
sftp.on('close', mustCall(() => {
|
|
conn.end();
|
|
}));
|
|
serverSFTP = sftp;
|
|
onSFTP();
|
|
}));
|
|
}));
|
|
}));
|
|
}));
|
|
}
|
|
|
|
function flagsToHuman(flags) {
|
|
const ret = [];
|
|
|
|
for (const [name, value] of Object.entries(OPEN_MODE)) {
|
|
if (flags & value)
|
|
ret.push(name);
|
|
}
|
|
|
|
return ret.join(' | ');
|
|
}
|