Compare commits
13 Commits
improve-lo
...
v2.12.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e272802525 | ||
|
|
7589322abc | ||
|
|
fae5564041 | ||
|
|
e9b4a6385d | ||
|
|
71621aae4f | ||
|
|
0eaa0f8743 | ||
|
|
8828a4ea09 | ||
|
|
b33659320f | ||
|
|
6a6b949d3b | ||
|
|
283be85db3 | ||
|
|
e555ce9230 | ||
|
|
e6575808f1 | ||
|
|
a5e58a106e |
@@ -1,7 +1,7 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "6"
|
||||
- "node"
|
||||
# - "node"
|
||||
|
||||
env:
|
||||
FTP_URL: ftp://127.0.0.1:8880
|
||||
|
||||
55
ftp-srv.d.ts
vendored
55
ftp-srv.d.ts
vendored
@@ -1,4 +1,7 @@
|
||||
declare class FileSystem {
|
||||
import * as tls from 'tls'
|
||||
import { Stats } from 'fs'
|
||||
|
||||
export interface FileSystem {
|
||||
constructor(connection: any, {root, cwd}?: {
|
||||
root: any;
|
||||
cwd: any;
|
||||
@@ -32,8 +35,36 @@ declare class FileSystem {
|
||||
getUniqueName(): string;
|
||||
}
|
||||
|
||||
declare class FtpServer {
|
||||
constructor(url: string, options?: {});
|
||||
export interface FtpConnection {
|
||||
server: FtpServer;
|
||||
id: string;
|
||||
log: any;
|
||||
transferType: string;
|
||||
encoding: string;
|
||||
bufferSize: boolean;
|
||||
readonly ip: string;
|
||||
restByteCount: number | undefined;
|
||||
secure: boolean
|
||||
|
||||
close (code: number, message: number): Promise<any>
|
||||
login (username: string, password: string): Promise<any>
|
||||
reply (options: number | Object, ...letters: Array<any>): Promise<any>
|
||||
|
||||
}
|
||||
|
||||
export interface FtpServerOptions {
|
||||
pasv_range?: number | string,
|
||||
greeting?: string,
|
||||
tls?: tls.SecureContext | false,
|
||||
anonymous?: boolean,
|
||||
blacklist?: Array<string>,
|
||||
whitelist?: Array<string>,
|
||||
file_format?: (stat: Stats) => string | Promise<string> | "ls" | "ep",
|
||||
log: any
|
||||
}
|
||||
|
||||
export interface FtpServer {
|
||||
constructor(url: string, options?: FtpServerOptions);
|
||||
|
||||
readonly isTLS: boolean;
|
||||
|
||||
@@ -56,6 +87,24 @@ declare class FtpServer {
|
||||
disconnectClient(id: string): Promise<any>;
|
||||
|
||||
close(): any;
|
||||
|
||||
on(event: "login", listener: (
|
||||
data: {
|
||||
connection: FtpConnection,
|
||||
username: string,
|
||||
password: string
|
||||
},
|
||||
resolve: (fs?: FileSystem, root?: string, cwd?: string, blacklist?: Array<string>, whitelist?: Array<string>) => void,
|
||||
reject: (err?: Error) => void
|
||||
) => void)
|
||||
|
||||
on(event: "client-error", listener: (
|
||||
data: {
|
||||
connection: FtpConnection,
|
||||
context: string,
|
||||
error: Error,
|
||||
}
|
||||
) => void)
|
||||
}
|
||||
|
||||
declare const FtpSrv: FtpServer;
|
||||
|
||||
2348
package-lock.json
generated
2348
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -14,8 +14,10 @@
|
||||
"license": "MIT",
|
||||
"main": "ftp-srv.js",
|
||||
"files": [
|
||||
"src"
|
||||
"src",
|
||||
"ftp-srv.d.ts"
|
||||
],
|
||||
"types": "./ftp-srv.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/trs/ftp-srv"
|
||||
@@ -41,7 +43,6 @@
|
||||
"verify:js:watch": "chokidar 'src/**/*.js' 'test/**/*.js' 'config/**/*.js' -c 'npm run verify:js:fix' --initial --silent",
|
||||
"verify:watch": "npm run verify:js:watch --silent"
|
||||
},
|
||||
"types": "./ftp-srv.d.ts",
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "node_modules/cz-customizable"
|
||||
|
||||
@@ -11,15 +11,21 @@ module.exports = {
|
||||
.then(() => when.try(this.fs.read.bind(this.fs), command.arg, {start: this.restByteCount}))
|
||||
.then(stream => {
|
||||
this.restByteCount = 0;
|
||||
return when.promise((resolve, reject) => {
|
||||
this.connector.socket.on('error', err => stream.emit('error', err));
|
||||
|
||||
stream.on('data', data => this.connector.socket.write(data, this.transferType));
|
||||
stream.on('end', () => resolve(this.reply(226)));
|
||||
stream.on('error', err => reject(err));
|
||||
this.reply(150).then(() => this.connector.socket.resume());
|
||||
const eventsPromise = when.promise((resolve, reject) => {
|
||||
this.connector.socket.once('error', err => reject(err));
|
||||
|
||||
stream.on('data', data => this.connector.socket
|
||||
&& this.connector.socket.write(data, this.transferType));
|
||||
stream.once('error', err => reject(err));
|
||||
stream.once('end', () => resolve());
|
||||
});
|
||||
|
||||
return this.reply(150).then(() => this.connector.socket.resume())
|
||||
.then(() => eventsPromise)
|
||||
.finally(() => stream.destroy ? stream.destroy() : null);
|
||||
})
|
||||
.then(() => this.reply(226))
|
||||
.catch(when.TimeoutError, err => {
|
||||
log.error(err);
|
||||
return this.reply(425, 'No connection established');
|
||||
|
||||
@@ -14,20 +14,27 @@ module.exports = {
|
||||
.then(() => when.try(this.fs.write.bind(this.fs), fileName, {append, start: this.restByteCount}))
|
||||
.then(stream => {
|
||||
this.restByteCount = 0;
|
||||
return when.promise((resolve, reject) => {
|
||||
stream.once('error', err => this.connector.socket.emit('error', err));
|
||||
stream.once('finish', () => resolve(this.reply(226, fileName)));
|
||||
|
||||
// Emit `close` if stream has a close listener, otherwise emit `finish` with the end() method
|
||||
// It is assumed that the `close` handler will call the end() method
|
||||
this.connector.socket.once('end', () => stream.listenerCount('close') ? stream.emit('close') : stream.end());
|
||||
this.connector.socket.once('error', err => reject(err));
|
||||
const streamPromise = when.promise((resolve, reject) => {
|
||||
stream.once('error', err => reject(err));
|
||||
stream.once('finish', () => resolve());
|
||||
});
|
||||
|
||||
const socketPromise = when.promise((resolve, reject) => {
|
||||
this.connector.socket.on('data', data => stream.write(data, this.transferType));
|
||||
this.connector.socket.once('end', () => {
|
||||
if (stream.listenerCount('close')) stream.emit('close');
|
||||
else stream.end();
|
||||
resolve();
|
||||
});
|
||||
this.connector.socket.once('error', err => reject(err));
|
||||
});
|
||||
|
||||
this.reply(150).then(() => this.connector.socket.resume());
|
||||
})
|
||||
.finally(() => stream.destroy ? when.try(stream.destroy.bind(stream)) : null);
|
||||
return this.reply(150).then(() => this.connector.socket.resume())
|
||||
.then(() => when.join(streamPromise, socketPromise))
|
||||
.finally(() => stream.destroy ? stream.destroy() : null);
|
||||
})
|
||||
.then(() => this.reply(226, fileName))
|
||||
.catch(when.TimeoutError, err => {
|
||||
log.error(err);
|
||||
return this.reply(425, 'No connection established');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint no-unused-expressions: 0 */
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
const bunyan = require('bunyan');
|
||||
const fs = require('fs');
|
||||
|
||||
@@ -10,10 +11,13 @@ before(() => require('dotenv').load());
|
||||
|
||||
describe('FtpServer', function () {
|
||||
this.timeout(2000);
|
||||
let sandbox;
|
||||
let log = bunyan.createLogger({name: 'test'});
|
||||
let server;
|
||||
let client;
|
||||
|
||||
let connection;
|
||||
|
||||
before(() => {
|
||||
server = new FtpServer(process.env.FTP_URL, {
|
||||
log,
|
||||
@@ -26,11 +30,18 @@ describe('FtpServer', function () {
|
||||
greeting: ['hello', 'world']
|
||||
});
|
||||
server.on('login', (data, resolve) => {
|
||||
connection = data.connection;
|
||||
resolve({root: process.cwd()});
|
||||
});
|
||||
|
||||
return server.listen();
|
||||
});
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
});
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
after(() => {
|
||||
server.close();
|
||||
});
|
||||
@@ -109,6 +120,22 @@ describe('FtpServer', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('STOR fail.txt', done => {
|
||||
sandbox.stub(connection.fs, 'write').callsFake(function () {
|
||||
const fsPath = './test/fail.txt';
|
||||
const stream = require('fs').createWriteStream(fsPath, {flags: 'w+'});
|
||||
stream.once('error', () => fs.unlink(fsPath));
|
||||
setTimeout(() => stream.emit('error', new Error('STOR fail test'), 1));
|
||||
return stream;
|
||||
});
|
||||
const buffer = Buffer.from('test text file');
|
||||
client.put(buffer, 'fail.txt', err => {
|
||||
expect(err).to.exist;
|
||||
expect(fs.existsSync('./test/fail.txt')).to.equal(false);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('STOR tést.txt', done => {
|
||||
const buffer = Buffer.from('test text file');
|
||||
client.put(buffer, 'tést.txt', err => {
|
||||
|
||||
Reference in New Issue
Block a user