1249 lines
32 KiB
JavaScript
1249 lines
32 KiB
JavaScript
'use strict'
|
|
|
|
const assert = require('./assert-compat.js')
|
|
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()
|
|
})
|
|
)
|
|
})
|
|
)
|
|
}
|
|
|
|
{
|
|
const { client, server } = setup_('SFTP client sets environment', {
|
|
client: { username: 'foo', password: 'bar' },
|
|
server: { hostKeys: [fixture('ssh_host_rsa_key')] }
|
|
})
|
|
|
|
const env = { SSH2NODETEST: 'foo' }
|
|
|
|
server.on(
|
|
'connection',
|
|
mustCall((conn) => {
|
|
conn
|
|
.on(
|
|
'authentication',
|
|
mustCall((ctx) => {
|
|
ctx.accept()
|
|
})
|
|
)
|
|
.on(
|
|
'ready',
|
|
mustCall(() => {
|
|
conn.on(
|
|
'session',
|
|
mustCall((accept, reject) => {
|
|
accept()
|
|
.on(
|
|
'env',
|
|
mustCall((accept, reject, info) => {
|
|
accept && accept()
|
|
assert(info.key === Object.keys(env)[0], 'Wrong env key')
|
|
assert(info.val === Object.values(env)[0], 'Wrong env value')
|
|
})
|
|
)
|
|
.on(
|
|
'sftp',
|
|
mustCall((accept, reject) => {
|
|
accept()
|
|
})
|
|
)
|
|
})
|
|
)
|
|
})
|
|
)
|
|
})
|
|
)
|
|
|
|
client.on(
|
|
'ready',
|
|
mustCall(() => {
|
|
const timeout = setTimeout(mustNotCall(), 1000)
|
|
client.sftp(
|
|
env,
|
|
mustCall((err, sftp) => {
|
|
clearTimeout(timeout)
|
|
assert(!err, `Unexpected exec error: ${err}`)
|
|
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(' | ')
|
|
}
|