test-sftp.js 25 KB

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