Compare commits
1 Commits
update-pac
...
v2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a907294b6 |
@@ -23,10 +23,10 @@ base-build: &base-build
|
|||||||
- node_modules
|
- node_modules
|
||||||
- run:
|
- run:
|
||||||
name: Lint
|
name: Lint
|
||||||
command: npm run verify -- --silent
|
command: npm run verify:js
|
||||||
- run:
|
- run:
|
||||||
name: Test
|
name: Test
|
||||||
command: npm run test:once
|
command: npm run test:unit:once
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test_node_10:
|
test_node_10:
|
||||||
@@ -60,10 +60,15 @@ jobs:
|
|||||||
- <<: *create-cache-file
|
- <<: *create-cache-file
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
<<: *package-json-cache
|
<<: *package-json-cache
|
||||||
|
- run:
|
||||||
|
name: Update NPM
|
||||||
|
command: |
|
||||||
|
npm install npm@5
|
||||||
|
npm install semantic-release@11
|
||||||
- deploy:
|
- deploy:
|
||||||
name: Semantic Release
|
name: Semantic Release
|
||||||
command: |
|
command: |
|
||||||
npm run semantic-release
|
npm run semantic-release || true
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
version: 2
|
version: 2
|
||||||
|
|||||||
64
README.md
64
README.md
@@ -36,8 +36,6 @@
|
|||||||
## Overview
|
## Overview
|
||||||
`ftp-srv` is a modern and extensible FTP server designed to be simple yet configurable.
|
`ftp-srv` is a modern and extensible FTP server designed to be simple yet configurable.
|
||||||
|
|
||||||
You can use `ftp-srv` to traverse the file system on the server, but it's biggest strength comes from it's customizable file system. This allows you to serve a custom, dynamic, or unique file system to users. You can even server a different system depending on the user connecting.
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Extensible [file systems](#file-system) per connection
|
- Extensible [file systems](#file-system) per connection
|
||||||
- Passive and active transfers
|
- Passive and active transfers
|
||||||
@@ -53,7 +51,7 @@ You can use `ftp-srv` to traverse the file system on the server, but it's bigges
|
|||||||
// Quick start
|
// Quick start
|
||||||
|
|
||||||
const FtpSrv = require('ftp-srv');
|
const FtpSrv = require('ftp-srv');
|
||||||
const ftpServer = new FtpSrv({ options ... });
|
const ftpServer = new FtpSrv('ftp://0.0.0.0:9876', { options ... });
|
||||||
|
|
||||||
ftpServer.on('login', (data, resolve, reject) => { ... });
|
ftpServer.on('login', (data, resolve, reject) => { ... });
|
||||||
...
|
...
|
||||||
@@ -64,7 +62,7 @@ ftpServer.listen()
|
|||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
### `new FtpSrv({options})`
|
### `new FtpSrv(url, [{options}])`
|
||||||
#### url
|
#### url
|
||||||
[URL string](https://nodejs.org/api/url.html#url_url_strings_and_url_objects) indicating the protocol, hostname, and port to listen on for connections.
|
[URL string](https://nodejs.org/api/url.html#url_url_strings_and_url_objects) indicating the protocol, hostname, and port to listen on for connections.
|
||||||
Supported protocols:
|
Supported protocols:
|
||||||
@@ -74,47 +72,45 @@ Supported protocols:
|
|||||||
_Note:_ The hostname must be the external IP address to accept external connections. `0.0.0.0` will listen on any available hosts for server and passive connections.
|
_Note:_ The hostname must be the external IP address to accept external connections. `0.0.0.0` will listen on any available hosts for server and passive connections.
|
||||||
__Default:__ `"ftp://127.0.0.1:21"`
|
__Default:__ `"ftp://127.0.0.1:21"`
|
||||||
|
|
||||||
#### `pasv_url`
|
#### options
|
||||||
|
|
||||||
|
##### `pasv_url`
|
||||||
The hostname to provide a client when attempting a passive connection (`PASV`). This defaults to the provided `url` hostname.
|
The hostname to provide a client when attempting a passive connection (`PASV`). This defaults to the provided `url` hostname.
|
||||||
|
|
||||||
_Note:_ If set to `0.0.0.0`, this will automatically resolve to the external IP of the box.
|
_Note:_ If set to `0.0.0.0`, this will automatically resolve to the external IP of the box.
|
||||||
__Default:__ `"127.0.0.1"`
|
__Default:__ `"127.0.0.1"`
|
||||||
|
|
||||||
#### `pasv_min`
|
##### `pasv_range`
|
||||||
Tne starting port to accept passive connections.
|
A starting port (eg `8000`) or a range (eg `"8000-9000"`) to accept passive connections.
|
||||||
__Default:__ `1024`
|
This range is then queried for an available port to use when required.
|
||||||
|
__Default:__ `22`
|
||||||
|
|
||||||
#### `pasv_max`
|
##### `greeting`
|
||||||
The ending port to accept passive connections.
|
|
||||||
The range is then queried for an available port to use when required.
|
|
||||||
__Default:__ `65535`
|
|
||||||
|
|
||||||
#### `greeting`
|
|
||||||
A human readable array of lines or string to send when a client connects.
|
A human readable array of lines or string to send when a client connects.
|
||||||
__Default:__ `null`
|
__Default:__ `null`
|
||||||
|
|
||||||
#### `tls`
|
##### `tls`
|
||||||
Node [TLS secure context object](https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options) used for implicit (`ftps` protocol) or explicit (`AUTH TLS`) connections.
|
Node [TLS secure context object](https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options) used for implicit (`ftps` protocol) or explicit (`AUTH TLS`) connections.
|
||||||
__Default:__ `false`
|
__Default:__ `false`
|
||||||
|
|
||||||
#### `anonymous`
|
##### `anonymous`
|
||||||
If true, will allow clients to authenticate using the username `anonymous`, not requiring a password from the user.
|
If true, will allow clients to authenticate using the username `anonymous`, not requiring a password from the user.
|
||||||
Can also set as a string which allows users to authenticate using the username provided.
|
Can also set as a string which allows users to authenticate using the username provided.
|
||||||
The `login` event is then sent with the provided username and `@anonymous` as the password.
|
The `login` event is then sent with the provided username and `@anonymous` as the password.
|
||||||
__Default:__ `false`
|
__Default:__ `false`
|
||||||
|
|
||||||
#### `blacklist`
|
##### `blacklist`
|
||||||
Array of commands that are not allowed.
|
Array of commands that are not allowed.
|
||||||
Response code `502` is sent to clients sending one of these commands.
|
Response code `502` is sent to clients sending one of these commands.
|
||||||
__Example:__ `['RMD', 'RNFR', 'RNTO']` will not allow users to delete directories or rename any files.
|
__Example:__ `['RMD', 'RNFR', 'RNTO']` will not allow users to delete directories or rename any files.
|
||||||
__Default:__ `[]`
|
__Default:__ `[]`
|
||||||
|
|
||||||
#### `whitelist`
|
##### `whitelist`
|
||||||
Array of commands that are only allowed.
|
Array of commands that are only allowed.
|
||||||
Response code `502` is sent to clients sending any other command.
|
Response code `502` is sent to clients sending any other command.
|
||||||
__Default:__ `[]`
|
__Default:__ `[]`
|
||||||
|
|
||||||
#### `file_format`
|
##### `file_format`
|
||||||
Sets the format to use for file stat queries such as `LIST`.
|
Sets the format to use for file stat queries such as `LIST`.
|
||||||
__Default:__ `"ls"`
|
__Default:__ `"ls"`
|
||||||
__Allowable values:__
|
__Allowable values:__
|
||||||
@@ -123,14 +119,9 @@ __Allowable values:__
|
|||||||
- `function () {}` A custom function returning a format or promise for one.
|
- `function () {}` A custom function returning a format or promise for one.
|
||||||
- Only one argument is passed in: a node [file stat](https://nodejs.org/api/fs.html#fs_class_fs_stats) object with additional file `name` parameter
|
- Only one argument is passed in: a node [file stat](https://nodejs.org/api/fs.html#fs_class_fs_stats) object with additional file `name` parameter
|
||||||
|
|
||||||
#### `log`
|
##### `log`
|
||||||
A [bunyan logger](https://github.com/trentm/node-bunyan) instance. Created by default.
|
A [bunyan logger](https://github.com/trentm/node-bunyan) instance. Created by default.
|
||||||
|
|
||||||
Piping the output into bunyan will format logs nicely, eg:
|
|
||||||
```
|
|
||||||
$ node ./test/start.js | npx bunyan
|
|
||||||
```
|
|
||||||
|
|
||||||
## CLI
|
## CLI
|
||||||
|
|
||||||
`ftp-srv` also comes with a builtin CLI.
|
`ftp-srv` also comes with a builtin CLI.
|
||||||
@@ -269,52 +260,52 @@ class MyFileSystem extends FileSystem {
|
|||||||
Custom file systems can implement the following variables depending on the developers needs:
|
Custom file systems can implement the following variables depending on the developers needs:
|
||||||
|
|
||||||
### Methods
|
### Methods
|
||||||
#### [`currentDirectory()`](src/fs.js#L40)
|
#### [`currentDirectory()`](src/fs.js#L29)
|
||||||
Returns a string of the current working directory
|
Returns a string of the current working directory
|
||||||
__Used in:__ `PWD`
|
__Used in:__ `PWD`
|
||||||
|
|
||||||
#### [`get(fileName)`](src/fs.js#L44)
|
#### [`get(fileName)`](src/fs.js#L33)
|
||||||
Returns a file stat object of file or directory
|
Returns a file stat object of file or directory
|
||||||
__Used in:__ `LIST`, `NLST`, `STAT`, `SIZE`, `RNFR`, `MDTM`
|
__Used in:__ `LIST`, `NLST`, `STAT`, `SIZE`, `RNFR`, `MDTM`
|
||||||
|
|
||||||
#### [`list(path)`](src/fs.js#L50)
|
#### [`list(path)`](src/fs.js#L39)
|
||||||
Returns array of file and directory stat objects
|
Returns array of file and directory stat objects
|
||||||
__Used in:__ `LIST`, `NLST`, `STAT`
|
__Used in:__ `LIST`, `NLST`, `STAT`
|
||||||
|
|
||||||
#### [`chdir(path)`](src/fs.js#L67)
|
#### [`chdir(path)`](src/fs.js#L56)
|
||||||
Returns new directory relative to current directory
|
Returns new directory relative to current directory
|
||||||
__Used in:__ `CWD`, `CDUP`
|
__Used in:__ `CWD`, `CDUP`
|
||||||
|
|
||||||
#### [`mkdir(path)`](src/fs.js#L114)
|
#### [`mkdir(path)`](src/fs.js#L96)
|
||||||
Returns a path to a newly created directory
|
Returns a path to a newly created directory
|
||||||
__Used in:__ `MKD`
|
__Used in:__ `MKD`
|
||||||
|
|
||||||
#### [`write(fileName, {append, start})`](src/fs.js#L79)
|
#### [`write(fileName, {append, start})`](src/fs.js#L68)
|
||||||
Returns a writable stream
|
Returns a writable stream
|
||||||
Options:
|
Options:
|
||||||
`append` if true, append to existing file
|
`append` if true, append to existing file
|
||||||
`start` if set, specifies the byte offset to write to
|
`start` if set, specifies the byte offset to write to
|
||||||
__Used in:__ `STOR`, `APPE`
|
__Used in:__ `STOR`, `APPE`
|
||||||
|
|
||||||
#### [`read(fileName, {start})`](src/fs.js#L90)
|
#### [`read(fileName, {start})`](src/fs.js#L75)
|
||||||
Returns a readable stream
|
Returns a readable stream
|
||||||
Options:
|
Options:
|
||||||
`start` if set, specifies the byte offset to read from
|
`start` if set, specifies the byte offset to read from
|
||||||
__Used in:__ `RETR`
|
__Used in:__ `RETR`
|
||||||
|
|
||||||
#### [`delete(path)`](src/fs.js#L105)
|
#### [`delete(path)`](src/fs.js#L87)
|
||||||
Delete a file or directory
|
Delete a file or directory
|
||||||
__Used in:__ `DELE`
|
__Used in:__ `DELE`
|
||||||
|
|
||||||
#### [`rename(from, to)`](src/fs.js#L120)
|
#### [`rename(from, to)`](src/fs.js#L102)
|
||||||
Renames a file or directory
|
Renames a file or directory
|
||||||
__Used in:__ `RNFR`, `RNTO`
|
__Used in:__ `RNFR`, `RNTO`
|
||||||
|
|
||||||
#### [`chmod(path)`](src/fs.js#L126)
|
#### [`chmod(path)`](src/fs.js#L108)
|
||||||
Modifies a file or directory's permissions
|
Modifies a file or directory's permissions
|
||||||
__Used in:__ `SITE CHMOD`
|
__Used in:__ `SITE CHMOD`
|
||||||
|
|
||||||
#### [`getUniqueName()`](src/fs.js#L131)
|
#### [`getUniqueName()`](src/fs.js#L113)
|
||||||
Returns a unique file name to write to
|
Returns a unique file name to write to
|
||||||
__Used in:__ `STOU`
|
__Used in:__ `STOU`
|
||||||
|
|
||||||
@@ -335,7 +326,6 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|||||||
- [TimLuq](https://github.com/TimLuq)
|
- [TimLuq](https://github.com/TimLuq)
|
||||||
- [edin-mg](https://github.com/edin-m)
|
- [edin-mg](https://github.com/edin-m)
|
||||||
- [DiegoRBaquero](https://github.com/DiegoRBaquero)
|
- [DiegoRBaquero](https://github.com/DiegoRBaquero)
|
||||||
- [Johnnyrook777](https://github.com/Johnnyrook777)
|
|
||||||
|
|
||||||
<!--[RM_LICENSE]-->
|
<!--[RM_LICENSE]-->
|
||||||
## License
|
## License
|
||||||
|
|||||||
30
bin/index.js
30
bin/index.js
@@ -19,8 +19,7 @@ function setupYargs() {
|
|||||||
})
|
})
|
||||||
.option('username', {
|
.option('username', {
|
||||||
describe: 'Blank for anonymous',
|
describe: 'Blank for anonymous',
|
||||||
type: 'string',
|
type: 'string'
|
||||||
default: ''
|
|
||||||
})
|
})
|
||||||
.option('password', {
|
.option('password', {
|
||||||
describe: 'Password for given username',
|
describe: 'Password for given username',
|
||||||
@@ -37,20 +36,6 @@ function setupYargs() {
|
|||||||
boolean: true,
|
boolean: true,
|
||||||
default: false
|
default: false
|
||||||
})
|
})
|
||||||
.option('pasv_url', {
|
|
||||||
describe: 'URL to provide for passive connections',
|
|
||||||
type: 'string'
|
|
||||||
})
|
|
||||||
.option('pasv_min', {
|
|
||||||
describe: 'Starting point to use when creating passive connections',
|
|
||||||
type: 'number',
|
|
||||||
default: 1024
|
|
||||||
})
|
|
||||||
.option('pasv_max', {
|
|
||||||
describe: 'Ending port to use when creating passive connections',
|
|
||||||
type: 'number',
|
|
||||||
default: 65535
|
|
||||||
})
|
|
||||||
.parse();
|
.parse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,18 +46,15 @@ function setupState(_args) {
|
|||||||
if (_args._ && _args._.length > 0) {
|
if (_args._ && _args._.length > 0) {
|
||||||
_state.url = _args._[0];
|
_state.url = _args._[0];
|
||||||
}
|
}
|
||||||
_state.pasv_url = _args.pasv_url;
|
|
||||||
_state.pasv_min = _args.pasv_min;
|
|
||||||
_state.pasv_max = _args.pasv_max;
|
|
||||||
_state.anonymous = _args.username === '';
|
_state.anonymous = _args.username === '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupRoot() {
|
function setupRoot() {
|
||||||
const dirPath = _args.root;
|
const dirPath = _args.root;
|
||||||
if (dirPath) {
|
if (dirPath) {
|
||||||
_state.root = dirPath;
|
|
||||||
} else {
|
|
||||||
_state.root = process.cwd();
|
_state.root = process.cwd();
|
||||||
|
} else {
|
||||||
|
_state.root = dirPath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,11 +105,7 @@ function startFtpServer(_state) {
|
|||||||
return reject(new errors.GeneralError('Invalid username or password', 401));
|
return reject(new errors.GeneralError('Invalid username or password', 401));
|
||||||
}
|
}
|
||||||
|
|
||||||
const ftpServer = new FtpSrv({
|
const ftpServer = new FtpSrv(_state.url, {
|
||||||
url: _state.url,
|
|
||||||
pasv_url: _state.pasv_url,
|
|
||||||
pasv_min: _state.pasv_min,
|
|
||||||
pasv_max: _state.pasv_max,
|
|
||||||
anonymous: _state.anonymous,
|
anonymous: _state.anonymous,
|
||||||
blacklist: _state.blacklist
|
blacklist: _state.blacklist
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
# Migration Guide - v2 to v3
|
|
||||||
|
|
||||||
The `FtpServer` constructor has been changed to only take one object option. Combining the two just made sense.
|
|
||||||
|
|
||||||
### From:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const server = new FtpServer('ftp://0.0.0.0:21');
|
|
||||||
```
|
|
||||||
|
|
||||||
### To:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const server = new FtpServer({
|
|
||||||
url: 'ftp://0.0.0.0:21'
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
The `pasv_range` option has been changed to separate integer variables: `pasv_min`, `pasv_max`.
|
|
||||||
|
|
||||||
### From:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const server = new FtpServer(..., {
|
|
||||||
pasv_range: '1000-2000'
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### To:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const server = new FtpServer({
|
|
||||||
pasv_min: 1000,
|
|
||||||
pasv_max: 2000
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
The default passive port range has been changed to `1024` - `65535`
|
|
||||||
|
|
||||||
----
|
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
"comma-dangle": 1,
|
"comma-dangle": 1,
|
||||||
"new-cap": 2,
|
"new-cap": 2,
|
||||||
"new-parens": 2,
|
"new-parens": 2,
|
||||||
"arrow-parens": [2, "always"],
|
"arrow-parens": [2, "as-needed"],
|
||||||
"no-array-constructor": 2,
|
"no-array-constructor": 2,
|
||||||
"array-callback-return": 1,
|
"array-callback-return": 1,
|
||||||
"no-extra-parens": 2,
|
"no-extra-parens": 2,
|
||||||
|
|||||||
19
ftp-srv.d.ts
vendored
19
ftp-srv.d.ts
vendored
@@ -41,7 +41,7 @@ export class FileSystem {
|
|||||||
getUniqueName(): string;
|
getUniqueName(): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FtpConnection extends EventEmitter {
|
export class FtpConnection {
|
||||||
server: FtpServer;
|
server: FtpServer;
|
||||||
id: string;
|
id: string;
|
||||||
log: any;
|
log: any;
|
||||||
@@ -59,21 +59,18 @@ export class FtpConnection extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface FtpServerOptions {
|
export interface FtpServerOptions {
|
||||||
url?: string,
|
pasv_range?: number | string,
|
||||||
pasv_min?: number,
|
|
||||||
pasv_max?: number,
|
|
||||||
pasv_url?: string,
|
|
||||||
greeting?: string | string[],
|
greeting?: string | string[],
|
||||||
tls?: tls.SecureContext | false,
|
tls?: tls.SecureContext | false,
|
||||||
anonymous?: boolean,
|
anonymous?: boolean,
|
||||||
blacklist?: Array<string>,
|
blacklist?: Array<string>,
|
||||||
whitelist?: Array<string>,
|
whitelist?: Array<string>,
|
||||||
file_format?: (stat: Stats) => string | Promise<string> | "ls" | "ep",
|
file_format?: (stat: Stats) => string | Promise<string> | "ls" | "ep",
|
||||||
log?: any,
|
log?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export class FtpServer extends EventEmitter {
|
export class FtpServer {
|
||||||
constructor(options?: FtpServerOptions);
|
constructor(url: string, options?: FtpServerOptions);
|
||||||
|
|
||||||
readonly isTLS: boolean;
|
readonly isTLS: boolean;
|
||||||
|
|
||||||
@@ -81,7 +78,7 @@ export class FtpServer extends EventEmitter {
|
|||||||
|
|
||||||
emitPromise(action: any, ...data: any[]): Promise<any>;
|
emitPromise(action: any, ...data: any[]): Promise<any>;
|
||||||
|
|
||||||
// emit is exported from super class
|
emit(action: any, ...data: any[]): void;
|
||||||
|
|
||||||
setupTLS(_tls: boolean): boolean | {
|
setupTLS(_tls: boolean): boolean | {
|
||||||
cert: string;
|
cert: string;
|
||||||
@@ -111,7 +108,7 @@ export class FtpServer extends EventEmitter {
|
|||||||
whitelist?: Array<string>
|
whitelist?: Array<string>
|
||||||
}) => void,
|
}) => void,
|
||||||
reject: (err?: Error) => void
|
reject: (err?: Error) => void
|
||||||
) => void): this;
|
) => void): EventEmitter;
|
||||||
|
|
||||||
on(event: "client-error", listener: (
|
on(event: "client-error", listener: (
|
||||||
data: {
|
data: {
|
||||||
@@ -119,7 +116,7 @@ export class FtpServer extends EventEmitter {
|
|||||||
context: string,
|
context: string,
|
||||||
error: Error,
|
error: Error,
|
||||||
}
|
}
|
||||||
) => void): this;
|
) => void): EventEmitter;
|
||||||
}
|
}
|
||||||
|
|
||||||
export {FtpServer as FtpSrv};
|
export {FtpServer as FtpSrv};
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
const FtpSrv = require('./src');
|
const FtpSrv = require('./src');
|
||||||
const FileSystem = require('./src/fs');
|
const FileSystem = require('./src/fs');
|
||||||
const errors = require('./src/errors');
|
|
||||||
|
|
||||||
module.exports = FtpSrv;
|
module.exports = FtpSrv;
|
||||||
module.exports.FtpSrv = FtpSrv;
|
module.exports.FtpSrv = FtpSrv;
|
||||||
module.exports.FileSystem = FileSystem;
|
module.exports.FileSystem = FileSystem;
|
||||||
module.exports.ftpErrors = errors;
|
|
||||||
|
|||||||
11892
package-lock.json
generated
11892
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
54
package.json
54
package.json
@@ -28,12 +28,17 @@
|
|||||||
"pre-release": "npm run verify",
|
"pre-release": "npm run verify",
|
||||||
"commitmsg": "cz-customizable-ghooks",
|
"commitmsg": "cz-customizable-ghooks",
|
||||||
"dev": "cross-env NODE_ENV=development npm run verify:watch",
|
"dev": "cross-env NODE_ENV=development npm run verify:watch",
|
||||||
"prepush": "npm run verify && npm run test:once --silent",
|
"prepush": "npm-run-all verify test:unit:once --silent",
|
||||||
"semantic-release": "semantic-release",
|
"semantic-release": "semantic-release",
|
||||||
"start": "npm run dev",
|
"start": "npm run dev",
|
||||||
"test": "cross-env NODE_ENV=test mocha --opts config/testUnit/mocha.opts -w",
|
"test": "npm run test:unit",
|
||||||
"test:once": "cross-env NODE_ENV=test mocha --opts config/testUnit/mocha.opts",
|
"test:unit": "cross-env NODE_ENV=test mocha --opts config/testUnit/mocha.opts -w",
|
||||||
"verify": "eslint -c config/verify/.eslintrc \"src/**/*.js\" \"test/**/*.js\""
|
"test:unit:once": "cross-env NODE_ENV=test mocha --opts config/testUnit/mocha.opts",
|
||||||
|
"verify": "npm run verify:js --silent",
|
||||||
|
"verify:js": "eslint -c config/verify/.eslintrc \"src/**/*.js\" \"test/**/*.js\" \"config/**/*.js\" && echo ✅ verify:js success",
|
||||||
|
"verify:js:fix": "eslint --fix -c config/verify/.eslintrc \"src/**/*.js\" \"test/**/*.js\" \"config/**/*.js\" && echo ✅ verify:js:fix success",
|
||||||
|
"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"
|
||||||
},
|
},
|
||||||
"release": {
|
"release": {
|
||||||
"verifyConditions": "condition-circle"
|
"verifyConditions": "condition-circle"
|
||||||
@@ -51,30 +56,33 @@
|
|||||||
"bunyan": "^1.8.12",
|
"bunyan": "^1.8.12",
|
||||||
"ip": "^1.1.5",
|
"ip": "^1.1.5",
|
||||||
"lodash": "^4.17.10",
|
"lodash": "^4.17.10",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.1",
|
||||||
"uuid": "^3.3.2",
|
"uuid": "^3.2.1",
|
||||||
"yargs": "^12.0.1"
|
"yargs": "^11.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@icetee/ftp": "^1.0.3",
|
"@icetee/ftp": "^1.0.2",
|
||||||
"chai": "^4.1.2",
|
"chai": "^4.0.2",
|
||||||
"condition-circle": "^2.0.1",
|
"chokidar-cli": "1.2.0",
|
||||||
"cross-env": "5.2.0",
|
"condition-circle": "^1.6.0",
|
||||||
|
"cross-env": "3.1.4",
|
||||||
"cz-customizable": "5.2.0",
|
"cz-customizable": "5.2.0",
|
||||||
"cz-customizable-ghooks": "1.5.0",
|
"cz-customizable-ghooks": "1.5.0",
|
||||||
"eslint": "5.3.0",
|
"dotenv": "^4.0.0",
|
||||||
"eslint-config-google": "0.9.1",
|
"eslint": "4.5.0",
|
||||||
"eslint-friendly-formatter": "4.0.1",
|
"eslint-config-google": "0.8.0",
|
||||||
"eslint-plugin-mocha": "^5.1.0",
|
"eslint-friendly-formatter": "3.0.0",
|
||||||
"eslint-plugin-node": "7.0.1",
|
"eslint-plugin-mocha": "^4.11.0",
|
||||||
"husky": "0.14.3",
|
"eslint-plugin-node": "5.1.1",
|
||||||
|
"husky": "0.13.3",
|
||||||
"istanbul": "0.4.5",
|
"istanbul": "0.4.5",
|
||||||
"mocha": "^5.2.0",
|
"mocha": "3.5.0",
|
||||||
"mocha-junit-reporter": "1.18.0",
|
"mocha-junit-reporter": "1.13.0",
|
||||||
"mocha-multi-reporters": "1.1.7",
|
"mocha-multi-reporters": "1.1.5",
|
||||||
"rimraf": "2.6.2",
|
"npm-run-all": "^4.1.3",
|
||||||
"semantic-release": "^15.9.8",
|
"rimraf": "2.6.1",
|
||||||
"sinon": "^6.1.5"
|
"semantic-release": "^11.0.2",
|
||||||
|
"sinon": "^2.3.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.x",
|
"node": ">=6.x",
|
||||||
|
|||||||
@@ -3,30 +3,25 @@ const Promise = require('bluebird');
|
|||||||
|
|
||||||
const REGISTRY = require('./registry');
|
const REGISTRY = require('./registry');
|
||||||
|
|
||||||
const CMD_FLAG_REGEX = new RegExp(/^-(\w{1})$/);
|
|
||||||
|
|
||||||
class FtpCommands {
|
class FtpCommands {
|
||||||
constructor(connection) {
|
constructor(connection) {
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.previousCommand = {};
|
this.previousCommand = {};
|
||||||
this.blacklist = _.get(this.connection, 'server.options.blacklist', []).map((cmd) => _.upperCase(cmd));
|
this.blacklist = _.get(this.connection, 'server.options.blacklist', []).map(cmd => _.upperCase(cmd));
|
||||||
this.whitelist = _.get(this.connection, 'server.options.whitelist', []).map((cmd) => _.upperCase(cmd));
|
this.whitelist = _.get(this.connection, 'server.options.whitelist', []).map(cmd => _.upperCase(cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
parse(message) {
|
parse(message) {
|
||||||
const strippedMessage = message.replace(/"/g, '');
|
const strippedMessage = message.replace(/"/g, '');
|
||||||
let [directive, ...args] = strippedMessage.split(' ');
|
const [directive, ...args] = strippedMessage.split(' ');
|
||||||
directive = _.chain(directive).trim().toUpper().value();
|
|
||||||
|
|
||||||
const parseCommandFlags = !['RETR', 'SIZE', 'STOR'].includes(directive);
|
|
||||||
const params = args.reduce(({arg, flags}, param) => {
|
const params = args.reduce(({arg, flags}, param) => {
|
||||||
if (parseCommandFlags && CMD_FLAG_REGEX.test(param)) flags.push(param);
|
if (/^-{1,2}[a-zA-Z0-9_]+/.test(param)) flags.push(param);
|
||||||
else arg.push(param);
|
else arg.push(param);
|
||||||
return {arg, flags};
|
return {arg, flags};
|
||||||
}, {arg: [], flags: []});
|
}, {arg: [], flags: []});
|
||||||
|
|
||||||
const command = {
|
const command = {
|
||||||
directive,
|
directive: _.chain(directive).trim().toUpper().value(),
|
||||||
arg: params.arg.length ? params.arg.join(' ') : null,
|
arg: params.arg.length ? params.arg.join(' ') : null,
|
||||||
flags: params.flags,
|
flags: params.flags,
|
||||||
raw: message
|
raw: message
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ module.exports = {
|
|||||||
directive: 'ABOR',
|
directive: 'ABOR',
|
||||||
handler: function () {
|
handler: function () {
|
||||||
return this.connector.waitForConnection()
|
return this.connector.waitForConnection()
|
||||||
.then((socket) => {
|
.then(socket => {
|
||||||
return this.reply(426, {socket})
|
return this.reply(426, {socket})
|
||||||
|
.then(() => this.connector.end())
|
||||||
.then(() => this.reply(226));
|
.then(() => this.reply(226));
|
||||||
})
|
})
|
||||||
.catch(() => this.reply(225))
|
.catch(() => this.reply(225));
|
||||||
.finally(() => this.connector.end());
|
|
||||||
},
|
},
|
||||||
syntax: '{{cmd}}',
|
syntax: '{{cmd}}',
|
||||||
description: 'Abort an active file transfer'
|
description: 'Abort an active file transfer'
|
||||||
|
|||||||
@@ -20,17 +20,17 @@ module.exports = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function handleTLS() {
|
function handleTLS() {
|
||||||
if (!this.server.options.tls) return this.reply(502);
|
if (!this.server._tls) return this.reply(502);
|
||||||
if (this.secure) return this.reply(202);
|
if (this.secure) return this.reply(202);
|
||||||
|
|
||||||
return this.reply(234)
|
return this.reply(234)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const secureContext = tls.createSecureContext(this.server.options.tls);
|
const secureContext = tls.createSecureContext(this.server._tls);
|
||||||
const secureSocket = new tls.TLSSocket(this.commandSocket, {
|
const secureSocket = new tls.TLSSocket(this.commandSocket, {
|
||||||
isServer: true,
|
isServer: true,
|
||||||
secureContext
|
secureContext
|
||||||
});
|
});
|
||||||
['data', 'timeout', 'end', 'close', 'drain', 'error'].forEach((event) => {
|
['data', 'timeout', 'end', 'close', 'drain', 'error'].forEach(event => {
|
||||||
function forwardEvent() {
|
function forwardEvent() {
|
||||||
this.emit.apply(this, arguments);
|
this.emit.apply(this, arguments);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ module.exports = {
|
|||||||
if (!this.fs.chdir) return this.reply(402, 'Not supported by file system');
|
if (!this.fs.chdir) return this.reply(402, 'Not supported by file system');
|
||||||
|
|
||||||
return Promise.try(() => this.fs.chdir(command.arg))
|
return Promise.try(() => this.fs.chdir(command.arg))
|
||||||
.then((cwd) => {
|
.then(cwd => {
|
||||||
const path = cwd ? `"${escapePath(cwd)}"` : undefined;
|
const path = cwd ? `"${escapePath(cwd)}"` : undefined;
|
||||||
return this.reply(250, path);
|
return this.reply(250, path);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
return this.reply(550, err.message);
|
return this.reply(550, err.message);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ module.exports = {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
return this.reply(250);
|
return this.reply(250);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
return this.reply(550, err.message);
|
return this.reply(550, err.message);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module.exports = {
|
|||||||
handler: function () {
|
handler: function () {
|
||||||
this.connector = new PassiveConnector(this);
|
this.connector = new PassiveConnector(this);
|
||||||
return this.connector.setupServer()
|
return this.connector.setupServer()
|
||||||
.then((server) => {
|
.then(server => {
|
||||||
const {port} = server.address();
|
const {port} = server.address();
|
||||||
|
|
||||||
return this.reply(229, `EPSV OK (|||${port}|)`);
|
return this.reply(229, `EPSV OK (|||${port}|)`);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ module.exports = {
|
|||||||
return feats;
|
return feats;
|
||||||
}, ['UTF8'])
|
}, ['UTF8'])
|
||||||
.sort()
|
.sort()
|
||||||
.map((feat) => ({
|
.map(feat => ({
|
||||||
message: ` ${feat}`,
|
message: ` ${feat}`,
|
||||||
raw: true
|
raw: true
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ module.exports = {
|
|||||||
const reply = _.concat([syntax.replace('{{cmd}}', directive), description]);
|
const reply = _.concat([syntax.replace('{{cmd}}', directive), description]);
|
||||||
return this.reply(214, ...reply);
|
return this.reply(214, ...reply);
|
||||||
} else {
|
} else {
|
||||||
const supportedCommands = _.chunk(Object.keys(registry), 5).map((chunk) => chunk.join('\t'));
|
const supportedCommands = _.chunk(Object.keys(registry), 5).map(chunk => chunk.join('\t'));
|
||||||
return this.reply(211, 'Supported commands:', ...supportedCommands, 'Use "HELP [command]" for syntax help.');
|
return this.reply(211, 'Supported commands:', ...supportedCommands, 'Use "HELP [command]" for syntax help.');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,32 +17,32 @@ module.exports = {
|
|||||||
return this.connector.waitForConnection()
|
return this.connector.waitForConnection()
|
||||||
.tap(() => this.commandSocket.pause())
|
.tap(() => this.commandSocket.pause())
|
||||||
.then(() => Promise.try(() => this.fs.get(path)))
|
.then(() => Promise.try(() => this.fs.get(path)))
|
||||||
.then((stat) => stat.isDirectory() ? Promise.try(() => this.fs.list(path)) : [stat])
|
.then(stat => stat.isDirectory() ? Promise.try(() => this.fs.list(path)) : [stat])
|
||||||
.then((files) => {
|
.then(files => {
|
||||||
const getFileMessage = (file) => {
|
const getFileMessage = file => {
|
||||||
if (simple) return file.name;
|
if (simple) return file.name;
|
||||||
return getFileStat(file, _.get(this, 'server.options.file_format', 'ls'));
|
return getFileStat(file, _.get(this, 'server.options.file_format', 'ls'));
|
||||||
};
|
};
|
||||||
|
|
||||||
return Promise.try(() => files.map((file) => {
|
const fileList = files.map(file => {
|
||||||
const message = getFileMessage(file);
|
const message = getFileMessage(file);
|
||||||
return {
|
return {
|
||||||
raw: true,
|
raw: true,
|
||||||
message,
|
message,
|
||||||
socket: this.connector.socket
|
socket: this.connector.socket
|
||||||
};
|
};
|
||||||
}));
|
});
|
||||||
|
return this.reply(150)
|
||||||
|
.then(() => {
|
||||||
|
if (fileList.length) return this.reply({}, ...fileList);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.tap(() => this.reply(150))
|
.then(() => this.reply(226))
|
||||||
.then((fileList) => {
|
.catch(Promise.TimeoutError, err => {
|
||||||
if (fileList.length) return this.reply({}, ...fileList);
|
|
||||||
})
|
|
||||||
.tap(() => this.reply(226))
|
|
||||||
.catch(Promise.TimeoutError, (err) => {
|
|
||||||
log.error(err);
|
log.error(err);
|
||||||
return this.reply(425, 'No connection established');
|
return this.reply(425, 'No connection established');
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
return this.reply(451, err.message || 'No directory');
|
return this.reply(451, err.message || 'No directory');
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ module.exports = {
|
|||||||
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
||||||
|
|
||||||
return Promise.try(() => this.fs.get(command.arg))
|
return Promise.try(() => this.fs.get(command.arg))
|
||||||
.then((fileStat) => {
|
.then(fileStat => {
|
||||||
const modificationTime = moment.utc(fileStat.mtime).format('YYYYMMDDHHmmss.SSS');
|
const modificationTime = moment.utc(fileStat.mtime).format('YYYYMMDDHHmmss.SSS');
|
||||||
return this.reply(213, modificationTime);
|
return this.reply(213, modificationTime);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
return this.reply(550, err.message);
|
return this.reply(550, err.message);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ module.exports = {
|
|||||||
if (!this.fs.mkdir) return this.reply(402, 'Not supported by file system');
|
if (!this.fs.mkdir) return this.reply(402, 'Not supported by file system');
|
||||||
|
|
||||||
return Promise.try(() => this.fs.mkdir(command.arg))
|
return Promise.try(() => this.fs.mkdir(command.arg))
|
||||||
.then((dir) => {
|
.then(dir => {
|
||||||
const path = dir ? `"${escapePath(dir)}"` : undefined;
|
const path = dir ? `"${escapePath(dir)}"` : undefined;
|
||||||
return this.reply(257, path);
|
return this.reply(257, path);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
return this.reply(550, err.message);
|
return this.reply(550, err.message);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ module.exports = {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
return this.reply(230);
|
return this.reply(230);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
return this.reply(530, err.message || 'Authentication failed');
|
return this.reply(530, err.message || 'Authentication failed');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ module.exports = {
|
|||||||
handler: function () {
|
handler: function () {
|
||||||
this.connector = new PassiveConnector(this);
|
this.connector = new PassiveConnector(this);
|
||||||
return this.connector.setupServer()
|
return this.connector.setupServer()
|
||||||
.then((server) => {
|
.then(server => {
|
||||||
const address = this.server.options.pasv_url;
|
const address = this.server.options.pasv_url;
|
||||||
const {port} = server.address();
|
const {port} = server.address();
|
||||||
const host = address.replace(/\./g, ',');
|
const host = address.replace(/\./g, ',');
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ module.exports = {
|
|||||||
if (rawConnection.length !== 6) return this.reply(425);
|
if (rawConnection.length !== 6) return this.reply(425);
|
||||||
|
|
||||||
const ip = rawConnection.slice(0, 4).join('.');
|
const ip = rawConnection.slice(0, 4).join('.');
|
||||||
const portBytes = rawConnection.slice(4).map((p) => parseInt(p));
|
const portBytes = rawConnection.slice(4).map(p => parseInt(p));
|
||||||
const port = portBytes[0] * 256 + portBytes[1];
|
const port = portBytes[0] * 256 + portBytes[1];
|
||||||
|
|
||||||
return this.connector.setupConnection(ip, port)
|
return this.connector.setupConnection(ip, port)
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ module.exports = {
|
|||||||
if (!this.fs.currentDirectory) return this.reply(402, 'Not supported by file system');
|
if (!this.fs.currentDirectory) return this.reply(402, 'Not supported by file system');
|
||||||
|
|
||||||
return Promise.try(() => this.fs.currentDirectory())
|
return Promise.try(() => this.fs.currentDirectory())
|
||||||
.then((cwd) => {
|
.then(cwd => {
|
||||||
const path = cwd ? `"${escapePath(cwd)}"` : undefined;
|
const path = cwd ? `"${escapePath(cwd)}"` : undefined;
|
||||||
return this.reply(257, path);
|
return this.reply(257, path);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
return this.reply(550, err.message);
|
return this.reply(550, err.message);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,21 +11,14 @@ module.exports = {
|
|||||||
return this.connector.waitForConnection()
|
return this.connector.waitForConnection()
|
||||||
.tap(() => this.commandSocket.pause())
|
.tap(() => this.commandSocket.pause())
|
||||||
.then(() => Promise.try(() => this.fs.read(filePath, {start: this.restByteCount})))
|
.then(() => Promise.try(() => this.fs.read(filePath, {start: this.restByteCount})))
|
||||||
.then((fsResponse) => {
|
.then(stream => {
|
||||||
let {stream, clientPath} = fsResponse;
|
const destroyConnection = (connection, reject) => err => {
|
||||||
if (!stream && !clientPath) {
|
|
||||||
stream = fsResponse;
|
|
||||||
clientPath = filePath;
|
|
||||||
}
|
|
||||||
const serverPath = stream.path || filePath;
|
|
||||||
|
|
||||||
const destroyConnection = (connection, reject) => (err) => {
|
|
||||||
if (connection) connection.destroy(err);
|
if (connection) connection.destroy(err);
|
||||||
reject(err);
|
reject(err);
|
||||||
};
|
};
|
||||||
|
|
||||||
const eventsPromise = new Promise((resolve, reject) => {
|
const eventsPromise = new Promise((resolve, reject) => {
|
||||||
stream.on('data', (data) => {
|
stream.on('data', data => {
|
||||||
if (stream) stream.pause();
|
if (stream) stream.pause();
|
||||||
if (this.connector.socket) {
|
if (this.connector.socket) {
|
||||||
this.connector.socket.write(data, this.transferType, () => stream && stream.resume());
|
this.connector.socket.write(data, this.transferType, () => stream && stream.resume());
|
||||||
@@ -41,15 +34,15 @@ module.exports = {
|
|||||||
|
|
||||||
return this.reply(150).then(() => stream.resume() && this.connector.socket.resume())
|
return this.reply(150).then(() => stream.resume() && this.connector.socket.resume())
|
||||||
.then(() => eventsPromise)
|
.then(() => eventsPromise)
|
||||||
.tap(() => this.emit('RETR', null, serverPath))
|
.tap(() => this.emit('RETR', null, filePath))
|
||||||
.then(() => this.reply(226, clientPath))
|
|
||||||
.finally(() => stream.destroy && stream.destroy());
|
.finally(() => stream.destroy && stream.destroy());
|
||||||
})
|
})
|
||||||
.catch(Promise.TimeoutError, (err) => {
|
.then(() => this.reply(226))
|
||||||
|
.catch(Promise.TimeoutError, err => {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
return this.reply(425, 'No connection established');
|
return this.reply(425, 'No connection established');
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
this.emit('RETR', err);
|
this.emit('RETR', err);
|
||||||
return this.reply(551, err.message);
|
return this.reply(551, err.message);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ module.exports = {
|
|||||||
this.renameFrom = fileName;
|
this.renameFrom = fileName;
|
||||||
return this.reply(350);
|
return this.reply(350);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
return this.reply(550, err.message);
|
return this.reply(550, err.message);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ module.exports = {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
return this.reply(250);
|
return this.reply(250);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
return this.reply(550, err.message);
|
return this.reply(550, err.message);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ module.exports = function ({log, command} = {}) {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
return this.reply(200);
|
return this.reply(200);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
return this.reply(500);
|
return this.reply(500);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ module.exports = {
|
|||||||
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
||||||
|
|
||||||
return Promise.try(() => this.fs.get(command.arg))
|
return Promise.try(() => this.fs.get(command.arg))
|
||||||
.then((fileStat) => {
|
.then(fileStat => {
|
||||||
return this.reply(213, {message: fileStat.size});
|
return this.reply(213, {message: fileStat.size});
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
return this.reply(550, err.message);
|
return this.reply(550, err.message);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,27 +12,27 @@ module.exports = {
|
|||||||
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
||||||
|
|
||||||
return Promise.try(() => this.fs.get(path))
|
return Promise.try(() => this.fs.get(path))
|
||||||
.then((stat) => {
|
.then(stat => {
|
||||||
if (stat.isDirectory()) {
|
if (stat.isDirectory()) {
|
||||||
if (!this.fs.list) return this.reply(402, 'Not supported by file system');
|
if (!this.fs.list) return this.reply(402, 'Not supported by file system');
|
||||||
|
|
||||||
return Promise.try(() => this.fs.list(path))
|
return Promise.try(() => this.fs.list(path))
|
||||||
.then((stats) => [213, stats]);
|
.then(stats => [213, stats]);
|
||||||
}
|
}
|
||||||
return [212, [stat]];
|
return [212, [stat]];
|
||||||
})
|
})
|
||||||
.then(([code, fileStats]) => {
|
.then(([code, fileStats]) => {
|
||||||
return Promise.map(fileStats, (file) => {
|
return Promise.map(fileStats, file => {
|
||||||
const message = getFileStat(file, _.get(this, 'server.options.file_format', 'ls'));
|
const message = getFileStat(file, _.get(this, 'server.options.file_format', 'ls'));
|
||||||
return {
|
return {
|
||||||
raw: true,
|
raw: true,
|
||||||
message
|
message
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.then((messages) => [code, messages]);
|
.then(messages => [code, messages]);
|
||||||
})
|
})
|
||||||
.then(([code, messages]) => this.reply(code, 'Status begin', ...messages, 'Status end'))
|
.then(([code, messages]) => this.reply(code, 'Status begin', ...messages, 'Status end'))
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
return this.reply(450, err.message);
|
return this.reply(450, err.message);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,19 +12,9 @@ module.exports = {
|
|||||||
return this.connector.waitForConnection()
|
return this.connector.waitForConnection()
|
||||||
.tap(() => this.commandSocket.pause())
|
.tap(() => this.commandSocket.pause())
|
||||||
.then(() => Promise.try(() => this.fs.write(fileName, {append, start: this.restByteCount})))
|
.then(() => Promise.try(() => this.fs.write(fileName, {append, start: this.restByteCount})))
|
||||||
.then((fsResponse) => {
|
.then(stream => {
|
||||||
let {stream, clientPath} = fsResponse;
|
const destroyConnection = (connection, reject) => err => {
|
||||||
if (!stream && !clientPath) {
|
if (connection) connection.destroy(err);
|
||||||
stream = fsResponse;
|
|
||||||
clientPath = fileName;
|
|
||||||
}
|
|
||||||
const serverPath = stream.path || fileName;
|
|
||||||
|
|
||||||
const destroyConnection = (connection, reject) => (err) => {
|
|
||||||
if (connection) {
|
|
||||||
if (connection.writeable) connection.end();
|
|
||||||
connection.destroy(err);
|
|
||||||
}
|
|
||||||
reject(err);
|
reject(err);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -34,7 +24,7 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const socketPromise = new Promise((resolve, reject) => {
|
const socketPromise = new Promise((resolve, reject) => {
|
||||||
this.connector.socket.on('data', (data) => {
|
this.connector.socket.on('data', data => {
|
||||||
if (this.connector.socket) this.connector.socket.pause();
|
if (this.connector.socket) this.connector.socket.pause();
|
||||||
if (stream) {
|
if (stream) {
|
||||||
stream.write(data, this.transferType, () => this.connector.socket && this.connector.socket.resume());
|
stream.write(data, this.transferType, () => this.connector.socket && this.connector.socket.resume());
|
||||||
@@ -51,16 +41,16 @@ module.exports = {
|
|||||||
this.restByteCount = 0;
|
this.restByteCount = 0;
|
||||||
|
|
||||||
return this.reply(150).then(() => this.connector.socket.resume())
|
return this.reply(150).then(() => this.connector.socket.resume())
|
||||||
.then(() => Promise.all([streamPromise, socketPromise]))
|
.then(() => Promise.join(streamPromise, socketPromise))
|
||||||
.tap(() => this.emit('STOR', null, serverPath))
|
.tap(() => this.emit('STOR', null, fileName))
|
||||||
.then(() => this.reply(226, clientPath))
|
|
||||||
.finally(() => stream.destroy && stream.destroy());
|
.finally(() => stream.destroy && stream.destroy());
|
||||||
})
|
})
|
||||||
.catch(Promise.TimeoutError, (err) => {
|
.then(() => this.reply(226, fileName))
|
||||||
|
.catch(Promise.TimeoutError, err => {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
return this.reply(425, 'No connection established');
|
return this.reply(425, 'No connection established');
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
this.emit('STOR', err);
|
this.emit('STOR', err);
|
||||||
return this.reply(550, err.message);
|
return this.reply(550, err.message);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ module.exports = {
|
|||||||
return Promise.try(() => this.fs.get(fileName))
|
return Promise.try(() => this.fs.get(fileName))
|
||||||
.then(() => Promise.try(() => this.fs.getUniqueName()))
|
.then(() => Promise.try(() => this.fs.getUniqueName()))
|
||||||
.catch(() => fileName)
|
.catch(() => fileName)
|
||||||
.then((name) => {
|
.then(name => {
|
||||||
args.command.arg = name;
|
args.command.arg = name;
|
||||||
return stor.call(this, args);
|
return stor.call(this, args);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ module.exports = {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
return this.reply(230);
|
return this.reply(230);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
return this.reply(530, err.message || 'Authentication failed');
|
return this.reply(530, err.message || 'Authentication failed');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const commands = [
|
|||||||
|
|
||||||
const registry = commands.reduce((result, cmd) => {
|
const registry = commands.reduce((result, cmd) => {
|
||||||
const aliases = Array.isArray(cmd.directive) ? cmd.directive : [cmd.directive];
|
const aliases = Array.isArray(cmd.directive) ? cmd.directive : [cmd.directive];
|
||||||
aliases.forEach((alias) => result[alias] = cmd);
|
aliases.forEach(alias => result[alias] = cmd);
|
||||||
return result;
|
return result;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class FtpConnection extends EventEmitter {
|
|||||||
this.connector = new BaseConnector(this);
|
this.connector = new BaseConnector(this);
|
||||||
|
|
||||||
this.commandSocket = options.socket;
|
this.commandSocket = options.socket;
|
||||||
this.commandSocket.on('error', (err) => {
|
this.commandSocket.on('error', err => {
|
||||||
this.log.error(err, 'Client error');
|
this.log.error(err, 'Client error');
|
||||||
this.server.emit('client-error', {connection: this, context: 'commandSocket', error: err});
|
this.server.emit('client-error', {connection: this, context: 'commandSocket', error: err});
|
||||||
});
|
});
|
||||||
@@ -41,7 +41,7 @@ class FtpConnection extends EventEmitter {
|
|||||||
_handleData(data) {
|
_handleData(data) {
|
||||||
const messages = _.compact(data.toString(this.encoding).split('\r\n'));
|
const messages = _.compact(data.toString(this.encoding).split('\r\n'));
|
||||||
this.log.trace(messages);
|
this.log.trace(messages);
|
||||||
return Promise.mapSeries(messages, (message) => this.commands.handle(message));
|
return Promise.mapSeries(messages, message => this.commands.handle(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
get ip() {
|
get ip() {
|
||||||
@@ -68,7 +68,7 @@ class FtpConnection extends EventEmitter {
|
|||||||
|
|
||||||
close(code = 421, message = 'Closing connection') {
|
close(code = 421, message = 'Closing connection') {
|
||||||
return Promise.resolve(code)
|
return Promise.resolve(code)
|
||||||
.then((_code) => _code && this.reply(_code, message))
|
.then(_code => _code && this.reply(_code, message))
|
||||||
.then(() => this.commandSocket && this.commandSocket.end());
|
.then(() => this.commandSocket && this.commandSocket.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ class FtpConnection extends EventEmitter {
|
|||||||
if (!letters.length) letters = [{}];
|
if (!letters.length) letters = [{}];
|
||||||
return Promise.map(letters, (promise, index) => {
|
return Promise.map(letters, (promise, index) => {
|
||||||
return Promise.resolve(promise)
|
return Promise.resolve(promise)
|
||||||
.then((letter) => {
|
.then(letter => {
|
||||||
if (!letter) letter = {};
|
if (!letter) letter = {};
|
||||||
else if (typeof letter === 'string') letter = {message: letter}; // allow passing in message as first param
|
else if (typeof letter === 'string') letter = {message: letter}; // allow passing in message as first param
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ class FtpConnection extends EventEmitter {
|
|||||||
if (!letter.message) letter.message = DEFAULT_MESSAGE[options.code] || 'No information';
|
if (!letter.message) letter.message = DEFAULT_MESSAGE[options.code] || 'No information';
|
||||||
if (!letter.encoding) letter.encoding = this.encoding;
|
if (!letter.encoding) letter.encoding = this.encoding;
|
||||||
return Promise.resolve(letter.message) // allow passing in a promise as a message
|
return Promise.resolve(letter.message) // allow passing in a promise as a message
|
||||||
.then((message) => {
|
.then(message => {
|
||||||
const seperator = !options.hasOwnProperty('eol') ?
|
const seperator = !options.hasOwnProperty('eol') ?
|
||||||
letters.length - 1 === index ? ' ' : '-' :
|
letters.length - 1 === index ? ' ' : '-' :
|
||||||
options.eol ? ' ' : '-';
|
options.eol ? ' ' : '-';
|
||||||
@@ -116,11 +116,11 @@ class FtpConnection extends EventEmitter {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const processLetter = (letter) => {
|
const processLetter = letter => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (letter.socket && letter.socket.writable) {
|
if (letter.socket && letter.socket.writable) {
|
||||||
this.log.trace({port: letter.socket.address().port, encoding: letter.encoding, message: letter.message}, 'Reply');
|
this.log.trace({port: letter.socket.address().port, encoding: letter.encoding, message: letter.message}, 'Reply');
|
||||||
letter.socket.write(letter.message + '\r\n', letter.encoding, (err) => {
|
letter.socket.write(letter.message + '\r\n', letter.encoding, err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
this.log.error(err);
|
this.log.error(err);
|
||||||
return reject(err);
|
return reject(err);
|
||||||
@@ -132,10 +132,10 @@ class FtpConnection extends EventEmitter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return satisfyParameters()
|
return satisfyParameters()
|
||||||
.then((satisfiedLetters) => Promise.mapSeries(satisfiedLetters, (letter, index) => {
|
.then(satisfiedLetters => Promise.mapSeries(satisfiedLetters, (letter, index) => {
|
||||||
return processLetter(letter, index);
|
return processLetter(letter, index);
|
||||||
}))
|
}))
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
this.log.error(err);
|
this.log.error(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ class Active extends Connector {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
this.dataSocket = new Socket();
|
this.dataSocket = new Socket();
|
||||||
this.dataSocket.setEncoding(this.connection.transferType);
|
this.dataSocket.setEncoding(this.connection.transferType);
|
||||||
this.dataSocket.on('error', (err) => this.server && this.server.emit('client-error', {connection: this.connection, context: 'dataSocket', error: err}));
|
this.dataSocket.on('error', err => this.server && this.server.emit('client-error', {connection: this.connection, context: 'dataSocket', error: err}));
|
||||||
this.dataSocket.connect({host, port, family}, () => {
|
this.dataSocket.connect({host, port, family}, () => {
|
||||||
this.dataSocket.pause();
|
this.dataSocket.pause();
|
||||||
|
|
||||||
if (this.connection.secure) {
|
if (this.connection.secure) {
|
||||||
const secureContext = tls.createSecureContext(this.server.options.tls);
|
const secureContext = tls.createSecureContext(this.server._tls);
|
||||||
const secureSocket = new tls.TLSSocket(this.dataSocket, {
|
const secureSocket = new tls.TLSSocket(this.dataSocket, {
|
||||||
isServer: true,
|
isServer: true,
|
||||||
secureContext
|
secureContext
|
||||||
|
|||||||
@@ -26,28 +26,22 @@ class Connector {
|
|||||||
return Promise.reject(new errors.ConnectorError('No connector setup, send PASV or PORT'));
|
return Promise.reject(new errors.ConnectorError('No connector setup, send PASV or PORT'));
|
||||||
}
|
}
|
||||||
|
|
||||||
closeSocket() {
|
|
||||||
if (this.dataSocket) {
|
|
||||||
const socket = this.dataSocket;
|
|
||||||
this.dataSocket.end(() => socket.destroy());
|
|
||||||
this.dataSocket = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
closeServer() {
|
|
||||||
if (this.dataServer) {
|
|
||||||
this.dataServer.close();
|
|
||||||
this.dataServer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
end() {
|
end() {
|
||||||
this.closeSocket();
|
const closeDataSocket = new Promise(resolve => {
|
||||||
this.closeServer();
|
if (this.dataSocket) this.dataSocket.end();
|
||||||
|
else resolve();
|
||||||
|
});
|
||||||
|
const closeDataServer = new Promise(resolve => {
|
||||||
|
if (this.dataServer) this.dataServer.close(() => resolve());
|
||||||
|
else resolve();
|
||||||
|
});
|
||||||
|
|
||||||
this.type = false;
|
return Promise.all([closeDataSocket, closeDataServer])
|
||||||
this.connection.connector = new Connector(this);
|
.then(() => {
|
||||||
|
this.dataSocket = null;
|
||||||
|
this.dataServer = null;
|
||||||
|
this.type = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module.exports = Connector;
|
module.exports = Connector;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const ip = require('ip');
|
|||||||
const Promise = require('bluebird');
|
const Promise = require('bluebird');
|
||||||
|
|
||||||
const Connector = require('./base');
|
const Connector = require('./base');
|
||||||
|
const findPort = require('../helpers/find-port');
|
||||||
const errors = require('../errors');
|
const errors = require('../errors');
|
||||||
|
|
||||||
class Passive extends Connector {
|
class Passive extends Connector {
|
||||||
@@ -12,7 +13,7 @@ class Passive extends Connector {
|
|||||||
this.type = 'passive';
|
this.type = 'passive';
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForConnection({timeout = 5000, delay = 50} = {}) {
|
waitForConnection({timeout = 5000, delay = 250} = {}) {
|
||||||
if (!this.dataServer) return Promise.reject(new errors.ConnectorError('Passive server not setup'));
|
if (!this.dataServer) return Promise.reject(new errors.ConnectorError('Passive server not setup'));
|
||||||
|
|
||||||
const checkSocket = () => {
|
const checkSocket = () => {
|
||||||
@@ -27,10 +28,14 @@ class Passive extends Connector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupServer() {
|
setupServer() {
|
||||||
this.closeServer();
|
const closeExistingServer = () => this.dataServer ?
|
||||||
return this.server.getNextPasvPort()
|
new Promise(resolve => this.dataServer.close(() => resolve())) :
|
||||||
.then((port) => {
|
Promise.resolve();
|
||||||
const connectionHandler = (socket) => {
|
|
||||||
|
return closeExistingServer()
|
||||||
|
.then(() => this.getPort())
|
||||||
|
.then(port => {
|
||||||
|
const connectionHandler = socket => {
|
||||||
if (!ip.isEqual(this.connection.commandSocket.remoteAddress, socket.remoteAddress)) {
|
if (!ip.isEqual(this.connection.commandSocket.remoteAddress, socket.remoteAddress)) {
|
||||||
this.log.error({
|
this.log.error({
|
||||||
pasv_connection: socket.remoteAddress,
|
pasv_connection: socket.remoteAddress,
|
||||||
@@ -43,35 +48,36 @@ class Passive extends Connector {
|
|||||||
}
|
}
|
||||||
this.log.trace({port, remoteAddress: socket.remoteAddress}, 'Passive connection fulfilled.');
|
this.log.trace({port, remoteAddress: socket.remoteAddress}, 'Passive connection fulfilled.');
|
||||||
|
|
||||||
this.dataSocket = socket;
|
if (this.connection.secure) {
|
||||||
this.dataSocket.setEncoding(this.connection.transferType);
|
const secureContext = tls.createSecureContext(this.server._tls);
|
||||||
this.dataSocket.on('error', (err) => this.server && this.server.emit('client-error', {connection: this.connection, context: 'dataSocket', error: err}));
|
const secureSocket = new tls.TLSSocket(socket, {
|
||||||
|
isServer: true,
|
||||||
if (!this.connection.secure) {
|
secureContext
|
||||||
this.dataSocket.connected = true;
|
});
|
||||||
|
this.dataSocket = secureSocket;
|
||||||
|
} else {
|
||||||
|
this.dataSocket = socket;
|
||||||
}
|
}
|
||||||
|
this.dataSocket.connected = true;
|
||||||
|
this.dataSocket.setEncoding(this.connection.transferType);
|
||||||
|
this.dataSocket.on('error', err => this.server && this.server.emit('client-error', {connection: this.connection, context: 'dataSocket', error: err}));
|
||||||
|
this.dataSocket.on('close', () => {
|
||||||
|
this.log.trace('Passive connection closed');
|
||||||
|
this.end();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.dataSocket = null;
|
this.dataSocket = null;
|
||||||
|
this.dataServer = net.createServer({pauseOnConnect: true}, connectionHandler);
|
||||||
const serverOptions = Object.assign({}, this.connection.secure ? this.server.options.tls : {}, {pauseOnConnect: true});
|
|
||||||
this.dataServer = (this.connection.secure ? tls : net).createServer(serverOptions, connectionHandler);
|
|
||||||
this.dataServer.maxConnections = 1;
|
this.dataServer.maxConnections = 1;
|
||||||
|
this.dataServer.on('error', err => this.server && this.server.emit('client-error', {connection: this.connection, context: 'dataServer', error: err}));
|
||||||
this.dataServer.on('error', (err) => this.server && this.server.emit('client-error', {connection: this.connection, context: 'dataServer', error: err}));
|
this.dataServer.on('close', () => {
|
||||||
this.dataServer.once('close', () => {
|
|
||||||
this.log.trace('Passive server closed');
|
this.log.trace('Passive server closed');
|
||||||
this.end();
|
this.dataServer = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.connection.secure) {
|
|
||||||
this.dataServer.on('secureConnection', (socket) => {
|
|
||||||
socket.connected = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.dataServer.listen(port, this.server.url.hostname, (err) => {
|
this.dataServer.listen(port, this.server.url.hostname, err => {
|
||||||
if (err) reject(err);
|
if (err) reject(err);
|
||||||
else {
|
else {
|
||||||
this.log.debug({port}, 'Passive connection listening');
|
this.log.debug({port}, 'Passive connection listening');
|
||||||
@@ -82,5 +88,15 @@ class Passive extends Connector {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPort() {
|
||||||
|
if (this.server.options.pasv_range) {
|
||||||
|
const [min, max] = typeof this.server.options.pasv_range === 'string' ?
|
||||||
|
this.server.options.pasv_range.split('-').map(v => v ? parseInt(v) : v) :
|
||||||
|
[this.server.options.pasv_range];
|
||||||
|
return findPort(min, max);
|
||||||
|
}
|
||||||
|
throw new errors.ConnectorError('Invalid pasv_range');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
module.exports = Passive;
|
module.exports = Passive;
|
||||||
|
|||||||
38
src/fs.js
38
src/fs.js
@@ -17,7 +17,7 @@ class FileSystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_resolvePath(path = '.') {
|
_resolvePath(path = '.') {
|
||||||
const clientPath = (() => {
|
const serverPath = (() => {
|
||||||
path = nodePath.normalize(path);
|
path = nodePath.normalize(path);
|
||||||
if (nodePath.isAbsolute(path)) {
|
if (nodePath.isAbsolute(path)) {
|
||||||
return nodePath.join(path);
|
return nodePath.join(path);
|
||||||
@@ -27,12 +27,12 @@ class FileSystem {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
const fsPath = (() => {
|
const fsPath = (() => {
|
||||||
const resolvedPath = nodePath.resolve(this.root, `.${nodePath.sep}${clientPath}`);
|
const resolvedPath = nodePath.resolve(this.root, `.${nodePath.sep}${serverPath}`);
|
||||||
return nodePath.join(resolvedPath);
|
return nodePath.join(resolvedPath);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
clientPath,
|
serverPath,
|
||||||
fsPath
|
fsPath
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -44,19 +44,19 @@ class FileSystem {
|
|||||||
get(fileName) {
|
get(fileName) {
|
||||||
const {fsPath} = this._resolvePath(fileName);
|
const {fsPath} = this._resolvePath(fileName);
|
||||||
return fs.statAsync(fsPath)
|
return fs.statAsync(fsPath)
|
||||||
.then((stat) => _.set(stat, 'name', fileName));
|
.then(stat => _.set(stat, 'name', fileName));
|
||||||
}
|
}
|
||||||
|
|
||||||
list(path = '.') {
|
list(path = '.') {
|
||||||
const {fsPath} = this._resolvePath(path);
|
const {fsPath} = this._resolvePath(path);
|
||||||
return fs.readdirAsync(fsPath)
|
return fs.readdirAsync(fsPath)
|
||||||
.then((fileNames) => {
|
.then(fileNames => {
|
||||||
return Promise.map(fileNames, (fileName) => {
|
return Promise.map(fileNames, fileName => {
|
||||||
const filePath = nodePath.join(fsPath, fileName);
|
const filePath = nodePath.join(fsPath, fileName);
|
||||||
return fs.accessAsync(filePath, fs.constants.F_OK)
|
return fs.accessAsync(filePath, fs.constants.F_OK)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return fs.statAsync(filePath)
|
return fs.statAsync(filePath)
|
||||||
.then((stat) => _.set(stat, 'name', fileName));
|
.then(stat => _.set(stat, 'name', fileName));
|
||||||
})
|
})
|
||||||
.catch(() => null);
|
.catch(() => null);
|
||||||
});
|
});
|
||||||
@@ -65,47 +65,41 @@ class FileSystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
chdir(path = '.') {
|
chdir(path = '.') {
|
||||||
const {fsPath, clientPath} = this._resolvePath(path);
|
const {fsPath, serverPath} = this._resolvePath(path);
|
||||||
return fs.statAsync(fsPath)
|
return fs.statAsync(fsPath)
|
||||||
.tap((stat) => {
|
.tap(stat => {
|
||||||
if (!stat.isDirectory()) throw new errors.FileSystemError('Not a valid directory');
|
if (!stat.isDirectory()) throw new errors.FileSystemError('Not a valid directory');
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.cwd = clientPath;
|
this.cwd = serverPath;
|
||||||
return this.currentDirectory();
|
return this.currentDirectory();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
write(fileName, {append = false, start = undefined} = {}) {
|
write(fileName, {append = false, start = undefined} = {}) {
|
||||||
const {fsPath, clientPath} = this._resolvePath(fileName);
|
const {fsPath} = this._resolvePath(fileName);
|
||||||
const stream = fs.createWriteStream(fsPath, {flags: !append ? 'w+' : 'a+', start});
|
const stream = fs.createWriteStream(fsPath, {flags: !append ? 'w+' : 'a+', start});
|
||||||
stream.once('error', () => fs.unlinkAsync(fsPath));
|
stream.once('error', () => fs.unlinkAsync(fsPath));
|
||||||
stream.once('close', () => stream.end());
|
stream.once('close', () => stream.end());
|
||||||
return {
|
return stream;
|
||||||
stream,
|
|
||||||
clientPath
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
read(fileName, {start = undefined} = {}) {
|
read(fileName, {start = undefined} = {}) {
|
||||||
const {fsPath, clientPath} = this._resolvePath(fileName);
|
const {fsPath} = this._resolvePath(fileName);
|
||||||
return fs.statAsync(fsPath)
|
return fs.statAsync(fsPath)
|
||||||
.tap((stat) => {
|
.tap(stat => {
|
||||||
if (stat.isDirectory()) throw new errors.FileSystemError('Cannot read a directory');
|
if (stat.isDirectory()) throw new errors.FileSystemError('Cannot read a directory');
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
const stream = fs.createReadStream(fsPath, {flags: 'r', start});
|
const stream = fs.createReadStream(fsPath, {flags: 'r', start});
|
||||||
return {
|
return stream;
|
||||||
stream,
|
|
||||||
clientPath
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(path) {
|
delete(path) {
|
||||||
const {fsPath} = this._resolvePath(path);
|
const {fsPath} = this._resolvePath(path);
|
||||||
return fs.statAsync(fsPath)
|
return fs.statAsync(fsPath)
|
||||||
.then((stat) => {
|
.then(stat => {
|
||||||
if (stat.isDirectory()) return fs.rmdirAsync(fsPath);
|
if (stat.isDirectory()) return fs.rmdirAsync(fsPath);
|
||||||
else return fs.unlinkAsync(fsPath);
|
else return fs.unlinkAsync(fsPath);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,35 +2,26 @@ const net = require('net');
|
|||||||
const Promise = require('bluebird');
|
const Promise = require('bluebird');
|
||||||
const errors = require('../errors');
|
const errors = require('../errors');
|
||||||
|
|
||||||
function* portNumberGenerator(min, max) {
|
module.exports = function (min = 1, max = undefined) {
|
||||||
let current = min;
|
return new Promise((resolve, reject) => {
|
||||||
while (true) {
|
let checkPort = min;
|
||||||
if (current > 65535 || current > max) {
|
let portCheckServer = net.createServer();
|
||||||
current = min;
|
portCheckServer.maxConnections = 0;
|
||||||
}
|
portCheckServer.on('error', () => {
|
||||||
yield current++;
|
if (checkPort < 65535 && (!max || checkPort < max)) {
|
||||||
}
|
checkPort = checkPort + 1;
|
||||||
}
|
portCheckServer.listen(checkPort);
|
||||||
|
} else {
|
||||||
function getNextPortFactory(min, max = Infinity) {
|
reject(new errors.GeneralError('Unable to find open port', 500));
|
||||||
const nextPortNumber = portNumberGenerator(min, max);
|
}
|
||||||
const portCheckServer = net.createServer();
|
|
||||||
portCheckServer.maxConnections = 0;
|
|
||||||
portCheckServer.on('error', () => {
|
|
||||||
portCheckServer.listen(nextPortNumber.next().value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => new Promise((resolve) => {
|
|
||||||
portCheckServer.once('listening', () => {
|
|
||||||
const {port} = portCheckServer.address();
|
|
||||||
portCheckServer.close(() => resolve(port));
|
|
||||||
});
|
});
|
||||||
portCheckServer.listen(nextPortNumber.next().value);
|
portCheckServer.on('listening', () => {
|
||||||
})
|
const {port} = portCheckServer.address();
|
||||||
.catch(RangeError, (err) => Promise.reject(new errors.ConnectorError(err.message)));
|
portCheckServer.close(() => {
|
||||||
}
|
portCheckServer = null;
|
||||||
|
resolve(port);
|
||||||
module.exports = {
|
});
|
||||||
getNextPortFactory,
|
});
|
||||||
portNumberGenerator
|
portCheckServer.listen(checkPort);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ module.exports = function (hostname) {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (!hostname || hostname === '0.0.0.0') {
|
if (!hostname || hostname === '0.0.0.0') {
|
||||||
let ip = '';
|
let ip = '';
|
||||||
http.get(IP_WEBSITE, (response) => {
|
http.get(IP_WEBSITE, response => {
|
||||||
if (response.statusCode !== 200) {
|
if (response.statusCode !== 200) {
|
||||||
return reject(new errors.GeneralError('Unable to resolve hostname', response.statusCode));
|
return reject(new errors.GeneralError('Unable to resolve hostname', response.statusCode));
|
||||||
}
|
}
|
||||||
response.setEncoding('utf8');
|
response.setEncoding('utf8');
|
||||||
response.on('data', (chunk) => {
|
response.on('data', chunk => {
|
||||||
ip += chunk;
|
ip += chunk;
|
||||||
});
|
});
|
||||||
response.on('end', () => {
|
response.on('end', () => {
|
||||||
|
|||||||
49
src/index.js
49
src/index.js
@@ -4,42 +4,38 @@ const nodeUrl = require('url');
|
|||||||
const buyan = require('bunyan');
|
const buyan = require('bunyan');
|
||||||
const net = require('net');
|
const net = require('net');
|
||||||
const tls = require('tls');
|
const tls = require('tls');
|
||||||
|
const fs = require('fs');
|
||||||
const EventEmitter = require('events');
|
const EventEmitter = require('events');
|
||||||
|
|
||||||
const Connection = require('./connection');
|
const Connection = require('./connection');
|
||||||
const resolveHost = require('./helpers/resolve-host');
|
const resolveHost = require('./helpers/resolve-host');
|
||||||
const {getNextPortFactory} = require('./helpers/find-port');
|
|
||||||
|
|
||||||
class FtpServer extends EventEmitter {
|
class FtpServer extends EventEmitter {
|
||||||
constructor(options = {}) {
|
constructor(url, options = {}) {
|
||||||
super();
|
super();
|
||||||
this.options = Object.assign({
|
this.options = _.merge({
|
||||||
log: buyan.createLogger({name: 'ftp-srv'}),
|
log: buyan.createLogger({name: 'ftp-srv'}),
|
||||||
url: 'ftp://127.0.0.1:21',
|
|
||||||
pasv_min: 1024,
|
|
||||||
pasv_max: 65535,
|
|
||||||
pasv_url: null,
|
|
||||||
anonymous: false,
|
anonymous: false,
|
||||||
|
pasv_range: 22,
|
||||||
|
pasv_url: null,
|
||||||
file_format: 'ls',
|
file_format: 'ls',
|
||||||
blacklist: [],
|
blacklist: [],
|
||||||
whitelist: [],
|
whitelist: [],
|
||||||
greeting: null,
|
greeting: null,
|
||||||
tls: false
|
tls: false
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
this._greeting = this.setupGreeting(this.options.greeting);
|
this._greeting = this.setupGreeting(this.options.greeting);
|
||||||
this._features = this.setupFeaturesMessage();
|
this._features = this.setupFeaturesMessage();
|
||||||
|
this._tls = this.setupTLS(this.options.tls);
|
||||||
|
|
||||||
delete this.options.greeting;
|
delete this.options.greeting;
|
||||||
|
delete this.options.tls;
|
||||||
|
|
||||||
this.connections = {};
|
this.connections = {};
|
||||||
this.log = this.options.log;
|
this.log = this.options.log;
|
||||||
this.url = nodeUrl.parse(this.options.url);
|
this.url = nodeUrl.parse(url || 'ftp://127.0.0.1:21');
|
||||||
this.getNextPasvPort = getNextPortFactory(
|
|
||||||
_.get(this, 'options.pasv_min'),
|
|
||||||
_.get(this, 'options.pasv_max'));
|
|
||||||
|
|
||||||
const serverConnectionHandler = (socket) => {
|
const serverConnectionHandler = socket => {
|
||||||
let connection = new Connection(this, {log: this.log, socket});
|
let connection = new Connection(this, {log: this.log, socket});
|
||||||
this.connections[connection.id] = connection;
|
this.connections[connection.id] = connection;
|
||||||
|
|
||||||
@@ -50,10 +46,10 @@ class FtpServer extends EventEmitter {
|
|||||||
return connection.reply(220, ...greeting, features)
|
return connection.reply(220, ...greeting, features)
|
||||||
.finally(() => socket.resume());
|
.finally(() => socket.resume());
|
||||||
};
|
};
|
||||||
const serverOptions = Object.assign({}, this.isTLS ? this.options.tls : {}, {pauseOnConnect: true});
|
const serverOptions = _.assign(this.isTLS ? this._tls : {}, {pauseOnConnect: true});
|
||||||
|
|
||||||
this.server = (this.isTLS ? tls : net).createServer(serverOptions, serverConnectionHandler);
|
this.server = (this.isTLS ? tls : net).createServer(serverOptions, serverConnectionHandler);
|
||||||
this.server.on('error', (err) => this.log.error(err, '[Event] error'));
|
this.server.on('error', err => this.log.error(err, '[Event] error'));
|
||||||
|
|
||||||
const quit = _.debounce(this.quit.bind(this), 100);
|
const quit = _.debounce(this.quit.bind(this), 100);
|
||||||
|
|
||||||
@@ -63,17 +59,17 @@ class FtpServer extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isTLS() {
|
get isTLS() {
|
||||||
return this.url.protocol === 'ftps:' && this.options.tls;
|
return this.url.protocol === 'ftps:' && this._tls;
|
||||||
}
|
}
|
||||||
|
|
||||||
listen() {
|
listen() {
|
||||||
return resolveHost(this.options.pasv_url || this.url.hostname)
|
return resolveHost(this.options.pasv_url || this.url.hostname)
|
||||||
.then((pasvUrl) => {
|
.then(pasvUrl => {
|
||||||
this.options.pasv_url = pasvUrl;
|
this.options.pasv_url = pasvUrl;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.server.once('error', reject);
|
this.server.once('error', reject);
|
||||||
this.server.listen(this.url.port, this.url.hostname, (err) => {
|
this.server.listen(this.url.port, this.url.hostname, err => {
|
||||||
this.server.removeListener('error', reject);
|
this.server.removeListener('error', reject);
|
||||||
if (err) return reject(err);
|
if (err) return reject(err);
|
||||||
this.log.info({
|
this.log.info({
|
||||||
@@ -94,6 +90,15 @@ class FtpServer extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupTLS(_tls) {
|
||||||
|
if (!_tls) return false;
|
||||||
|
return _.assign({}, _tls, {
|
||||||
|
cert: _tls.cert ? fs.readFileSync(_tls.cert) : undefined,
|
||||||
|
key: _tls.key ? fs.readFileSync(_tls.key) : undefined,
|
||||||
|
ca: _tls.ca ? Array.isArray(_tls.ca) ? _tls.ca.map(_ca => fs.readFileSync(_ca)) : [fs.readFileSync(_tls.ca)] : undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setupGreeting(greet) {
|
setupGreeting(greet) {
|
||||||
if (!greet) return [];
|
if (!greet) return [];
|
||||||
const greeting = Array.isArray(greet) ? greet : greet.split('\n');
|
const greeting = Array.isArray(greet) ? greet : greet.split('\n');
|
||||||
@@ -112,7 +117,7 @@ class FtpServer extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
disconnectClient(id) {
|
disconnectClient(id) {
|
||||||
return new Promise((resolve) => {
|
return new Promise(resolve => {
|
||||||
const client = this.connections[id];
|
const client = this.connections[id];
|
||||||
if (!client) return resolve();
|
if (!client) return resolve();
|
||||||
delete this.connections[id];
|
delete this.connections[id];
|
||||||
@@ -134,9 +139,9 @@ class FtpServer extends EventEmitter {
|
|||||||
close() {
|
close() {
|
||||||
this.log.info('Server closing...');
|
this.log.info('Server closing...');
|
||||||
this.server.maxConnections = 0;
|
this.server.maxConnections = 0;
|
||||||
return Promise.map(Object.keys(this.connections), (id) => Promise.try(this.disconnectClient.bind(this, id)))
|
return Promise.map(Object.keys(this.connections), id => Promise.try(this.disconnectClient.bind(this, id)))
|
||||||
.then(() => new Promise((resolve) => {
|
.then(() => new Promise(resolve => {
|
||||||
this.server.close((err) => {
|
this.server.close(err => {
|
||||||
if (err) this.log.error(err, 'Error closing server');
|
if (err) this.log.error(err, 'Error closing server');
|
||||||
resolve('Closed');
|
resolve('Closed');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ describe('FtpCommands', function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
commands = new FtpCommands(mockConnection);
|
commands = new FtpCommands(mockConnection);
|
||||||
|
|
||||||
@@ -64,8 +64,8 @@ describe('FtpCommands', function () {
|
|||||||
it('two args, with flags: test -l arg1 -A arg2 --zz88A', () => {
|
it('two args, with flags: test -l arg1 -A arg2 --zz88A', () => {
|
||||||
const cmd = commands.parse('test -l arg1 -A arg2 --zz88A');
|
const cmd = commands.parse('test -l arg1 -A arg2 --zz88A');
|
||||||
expect(cmd.directive).to.equal('TEST');
|
expect(cmd.directive).to.equal('TEST');
|
||||||
expect(cmd.arg).to.equal('arg1 arg2 --zz88A');
|
expect(cmd.arg).to.equal('arg1 arg2');
|
||||||
expect(cmd.flags).to.deep.equal(['-l', '-A']);
|
expect(cmd.flags).to.deep.equal(['-l', '-A', '--zz88A']);
|
||||||
expect(cmd.raw).to.equal('test -l arg1 -A arg2 --zz88A');
|
expect(cmd.raw).to.equal('test -l arg1 -A arg2 --zz88A');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -76,13 +76,6 @@ describe('FtpCommands', function () {
|
|||||||
expect(cmd.flags).to.deep.equal(['-l']);
|
expect(cmd.flags).to.deep.equal(['-l']);
|
||||||
expect(cmd.raw).to.equal('list -l');
|
expect(cmd.raw).to.equal('list -l');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not check for option flags', () => {
|
|
||||||
const cmd = commands.parse('retr -test');
|
|
||||||
expect(cmd.directive).to.equal('RETR');
|
|
||||||
expect(cmd.arg).to.equal('-test');
|
|
||||||
expect(cmd.flags).to.deep.equal([]);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('handle', function () {
|
describe('handle', function () {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const {expect} = require('chai');
|
|||||||
const sinon = require('sinon');
|
const sinon = require('sinon');
|
||||||
|
|
||||||
const CMD = 'ABOR';
|
const CMD = 'ABOR';
|
||||||
describe.skip(CMD, function () {
|
describe(CMD, function () {
|
||||||
let sandbox;
|
let sandbox;
|
||||||
const mockClient = {
|
const mockClient = {
|
||||||
reply: () => Promise.resolve(),
|
reply: () => Promise.resolve(),
|
||||||
@@ -15,7 +15,7 @@ describe.skip(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.spy(mockClient, 'reply');
|
sandbox.spy(mockClient, 'reply');
|
||||||
sandbox.spy(mockClient.connector, 'waitForConnection');
|
sandbox.spy(mockClient.connector, 'waitForConnection');
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.spy(mockClient, 'reply');
|
sandbox.spy(mockClient, 'reply');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,15 +8,13 @@ describe(CMD, function () {
|
|||||||
const mockClient = {
|
const mockClient = {
|
||||||
reply: () => Promise.resolve(),
|
reply: () => Promise.resolve(),
|
||||||
server: {
|
server: {
|
||||||
options: {
|
_tls: {}
|
||||||
tls: {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.spy(mockClient, 'reply');
|
sandbox.spy(mockClient, 'reply');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.spy(mockClient, 'reply');
|
sandbox.spy(mockClient, 'reply');
|
||||||
sandbox.spy(mockClient.fs, 'chdir');
|
sandbox.spy(mockClient.fs, 'chdir');
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.stub(mockClient, 'reply').resolves();
|
sandbox.stub(mockClient, 'reply').resolves();
|
||||||
sandbox.stub(mockClient.fs, 'chdir').resolves();
|
sandbox.stub(mockClient.fs, 'chdir').resolves();
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.stub(mockClient, 'reply').resolves();
|
sandbox.stub(mockClient, 'reply').resolves();
|
||||||
sandbox.stub(mockClient.fs, 'delete').resolves();
|
sandbox.stub(mockClient.fs, 'delete').resolves();
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.spy(mockClient, 'reply');
|
sandbox.spy(mockClient, 'reply');
|
||||||
sandbox.stub(ActiveConnector.prototype, 'setupConnection').resolves();
|
sandbox.stub(ActiveConnector.prototype, 'setupConnection').resolves();
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.stub(mockClient, 'reply').resolves();
|
sandbox.stub(mockClient, 'reply').resolves();
|
||||||
sandbox.stub(PassiveConnector.prototype, 'setupServer').resolves({
|
sandbox.stub(PassiveConnector.prototype, 'setupServer').resolves({
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.spy(mockClient, 'reply');
|
sandbox.spy(mockClient, 'reply');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ describe(CMD, function () {
|
|||||||
},
|
},
|
||||||
connector: {
|
connector: {
|
||||||
waitForConnection: () => Promise.resolve({}),
|
waitForConnection: () => Promise.resolve({}),
|
||||||
end: () => Promise.resolve({})
|
end: () => {}
|
||||||
},
|
},
|
||||||
commandSocket: {
|
commandSocket: {
|
||||||
resume: () => {},
|
resume: () => {},
|
||||||
@@ -25,7 +25,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.stub(mockClient, 'reply').resolves();
|
sandbox.stub(mockClient, 'reply').resolves();
|
||||||
sandbox.stub(mockClient.fs, 'get').resolves({
|
sandbox.stub(mockClient.fs, 'get').resolves({
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.stub(mockClient, 'reply').resolves();
|
sandbox.stub(mockClient, 'reply').resolves();
|
||||||
sandbox.stub(mockClient.fs, 'get').resolves({mtime: 'Mon, 10 Oct 2011 23:24:11 GMT'});
|
sandbox.stub(mockClient.fs, 'get').resolves({mtime: 'Mon, 10 Oct 2011 23:24:11 GMT'});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.stub(mockClient, 'reply').resolves();
|
sandbox.stub(mockClient, 'reply').resolves();
|
||||||
sandbox.stub(mockClient.fs, 'mkdir').resolves();
|
sandbox.stub(mockClient.fs, 'mkdir').resolves();
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.spy(mockClient, 'reply');
|
sandbox.spy(mockClient, 'reply');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ describe(CMD, function () {
|
|||||||
},
|
},
|
||||||
connector: {
|
connector: {
|
||||||
waitForConnection: () => Promise.resolve({}),
|
waitForConnection: () => Promise.resolve({}),
|
||||||
end: () => Promise.resolve({})
|
end: () => {}
|
||||||
},
|
},
|
||||||
commandSocket: {
|
commandSocket: {
|
||||||
resume: () => {},
|
resume: () => {},
|
||||||
@@ -25,7 +25,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.stub(mockClient, 'reply').resolves();
|
sandbox.stub(mockClient, 'reply').resolves();
|
||||||
sandbox.stub(mockClient.fs, 'get').resolves({
|
sandbox.stub(mockClient.fs, 'get').resolves({
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.spy(mockClient, 'reply');
|
sandbox.spy(mockClient, 'reply');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.spy(mockClient, 'reply');
|
sandbox.spy(mockClient, 'reply');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.stub(mockClient, 'reply').resolves();
|
sandbox.stub(mockClient, 'reply').resolves();
|
||||||
sandbox.stub(mockClient, 'login').resolves();
|
sandbox.stub(mockClient, 'login').resolves();
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.spy(mockClient, 'reply');
|
sandbox.spy(mockClient, 'reply');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.spy(mockClient, 'reply');
|
sandbox.spy(mockClient, 'reply');
|
||||||
sandbox.stub(ActiveConnector.prototype, 'setupConnection').resolves();
|
sandbox.stub(ActiveConnector.prototype, 'setupConnection').resolves();
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.spy(mockClient, 'reply');
|
sandbox.spy(mockClient, 'reply');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.stub(mockClient, 'reply').resolves();
|
sandbox.stub(mockClient, 'reply').resolves();
|
||||||
sandbox.stub(mockClient.fs, 'currentDirectory').resolves();
|
sandbox.stub(mockClient.fs, 'currentDirectory').resolves();
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.stub(mockClient, 'close').resolves();
|
sandbox.stub(mockClient, 'close').resolves();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.spy(mockClient, 'reply');
|
sandbox.spy(mockClient, 'reply');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ describe(CMD, function () {
|
|||||||
waitForConnection: () => Promise.resolve({
|
waitForConnection: () => Promise.resolve({
|
||||||
resume: () => {}
|
resume: () => {}
|
||||||
}),
|
}),
|
||||||
end: () => Promise.resolve({})
|
end: () => {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
mockClient.fs = {
|
mockClient.fs = {
|
||||||
read: () => {}
|
read: () => {}
|
||||||
@@ -87,7 +87,7 @@ describe(CMD, function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let errorEmitted = false;
|
let errorEmitted = false;
|
||||||
emitter.once('RETR', (err) => {
|
emitter.once('RETR', err => {
|
||||||
errorEmitted = !!err;
|
errorEmitted = !!err;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
mockClient.renameFrom = 'test';
|
mockClient.renameFrom = 'test';
|
||||||
mockClient.fs = {
|
mockClient.fs = {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
mockClient.renameFrom = 'test';
|
mockClient.renameFrom = 'test';
|
||||||
mockClient.fs = {
|
mockClient.fs = {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../../src/commands/registration/site/${CMD.toLowerCase()}`).bind(mockClient);
|
const cmdFn = require(`../../../../src/commands/registration/site/${CMD.toLowerCase()}`).bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
mockClient.fs = {
|
mockClient.fs = {
|
||||||
chmod: () => Promise.resolve()
|
chmod: () => Promise.resolve()
|
||||||
@@ -23,7 +23,7 @@ describe(CMD, function () {
|
|||||||
sandbox.restore();
|
sandbox.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('// unsuccessful | no file system', (done) => {
|
it('// unsuccessful | no file system', done => {
|
||||||
delete mockClient.fs;
|
delete mockClient.fs;
|
||||||
|
|
||||||
cmdFn()
|
cmdFn()
|
||||||
@@ -34,7 +34,7 @@ describe(CMD, function () {
|
|||||||
.catch(done);
|
.catch(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('// unsuccessful | file system does not have functions', (done) => {
|
it('// unsuccessful | file system does not have functions', done => {
|
||||||
mockClient.fs = {};
|
mockClient.fs = {};
|
||||||
|
|
||||||
cmdFn()
|
cmdFn()
|
||||||
@@ -45,7 +45,7 @@ describe(CMD, function () {
|
|||||||
.catch(done);
|
.catch(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('777 test // unsuccessful | file chmod fails', (done) => {
|
it('777 test // unsuccessful | file chmod fails', done => {
|
||||||
mockClient.fs.chmod.restore();
|
mockClient.fs.chmod.restore();
|
||||||
sandbox.stub(mockClient.fs, 'chmod').rejects(new Error('test'));
|
sandbox.stub(mockClient.fs, 'chmod').rejects(new Error('test'));
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ describe(CMD, function () {
|
|||||||
.catch(done);
|
.catch(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('777 test // successful', (done) => {
|
it('777 test // successful', done => {
|
||||||
cmdFn({log: mockLog, command: {arg: '777 test'}})
|
cmdFn({log: mockLog, command: {arg: '777 test'}})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
expect(mockClient.fs.chmod.args[0]).to.eql(['test', 511]);
|
expect(mockClient.fs.chmod.args[0]).to.eql(['test', 511]);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.stub(mockClient, 'reply').resolves();
|
sandbox.stub(mockClient, 'reply').resolves();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
mockClient.fs = {
|
mockClient.fs = {
|
||||||
get: () => Promise.resolve({size: 1})
|
get: () => Promise.resolve({size: 1})
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
mockClient.fs = {
|
mockClient.fs = {
|
||||||
get: () => Promise.resolve({}),
|
get: () => Promise.resolve({}),
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ describe(CMD, function () {
|
|||||||
waitForConnection: () => Promise.resolve({
|
waitForConnection: () => Promise.resolve({
|
||||||
resume: () => {}
|
resume: () => {}
|
||||||
}),
|
}),
|
||||||
end: () => Promise.resolve({})
|
end: () => {}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
mockClient.fs = {
|
mockClient.fs = {
|
||||||
write: () => {}
|
write: () => {}
|
||||||
@@ -86,7 +86,7 @@ describe(CMD, function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let errorEmitted = false;
|
let errorEmitted = false;
|
||||||
emitter.once('STOR', (err) => {
|
emitter.once('STOR', err => {
|
||||||
errorEmitted = !!err;
|
errorEmitted = !!err;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
mockClient.fs = {
|
mockClient.fs = {
|
||||||
get: () => Promise.resolve(),
|
get: () => Promise.resolve(),
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.spy(mockClient, 'reply');
|
sandbox.spy(mockClient, 'reply');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.spy(mockClient, 'reply');
|
sandbox.spy(mockClient, 'reply');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
mockClient.transferType = null;
|
mockClient.transferType = null;
|
||||||
sandbox.spy(mockClient, 'reply');
|
sandbox.spy(mockClient, 'reply');
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ describe(CMD, function () {
|
|||||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
delete mockClient.username;
|
delete mockClient.username;
|
||||||
mockClient.server.options = {};
|
mockClient.server.options = {};
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ const net = require('net');
|
|||||||
const tls = require('tls');
|
const tls = require('tls');
|
||||||
|
|
||||||
const ActiveConnector = require('../../src/connector/active');
|
const ActiveConnector = require('../../src/connector/active');
|
||||||
const {getNextPortFactory} = require('../../src/helpers/find-port');
|
const findPort = require('../../src/helpers/find-port');
|
||||||
|
|
||||||
describe('Connector - Active //', function () {
|
describe('Connector - Active //', function () {
|
||||||
let getNextPort = getNextPortFactory(1024);
|
|
||||||
let PORT;
|
let PORT;
|
||||||
let active;
|
let active;
|
||||||
let mockConnection = {};
|
let mockConnection = {};
|
||||||
@@ -19,18 +18,18 @@ describe('Connector - Active //', function () {
|
|||||||
before(() => {
|
before(() => {
|
||||||
active = new ActiveConnector(mockConnection);
|
active = new ActiveConnector(mockConnection);
|
||||||
});
|
});
|
||||||
beforeEach((done) => {
|
beforeEach(done => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
getNextPort()
|
findPort()
|
||||||
.then((port) => {
|
.then(port => {
|
||||||
PORT = port;
|
PORT = port;
|
||||||
server = net.createServer()
|
server = net.createServer()
|
||||||
.on('connection', (socket) => socket.destroy())
|
.on('connection', socket => socket.destroy())
|
||||||
.listen(PORT, () => done());
|
.listen(PORT, () => done());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
afterEach((done) => {
|
afterEach(done => {
|
||||||
sandbox.restore();
|
sandbox.restore();
|
||||||
server.close(done);
|
server.close(done);
|
||||||
});
|
});
|
||||||
@@ -58,7 +57,7 @@ describe('Connector - Active //', function () {
|
|||||||
expect(active.dataSocket).to.exist;
|
expect(active.dataSocket).to.exist;
|
||||||
return active.waitForConnection();
|
return active.waitForConnection();
|
||||||
})
|
})
|
||||||
.then((dataSocket) => {
|
.then(dataSocket => {
|
||||||
expect(dataSocket.connected).to.equal(true);
|
expect(dataSocket.connected).to.equal(true);
|
||||||
expect(dataSocket instanceof net.Socket).to.equal(true);
|
expect(dataSocket instanceof net.Socket).to.equal(true);
|
||||||
expect(dataSocket instanceof tls.TLSSocket).to.equal(false);
|
expect(dataSocket instanceof tls.TLSSocket).to.equal(false);
|
||||||
@@ -67,18 +66,14 @@ describe('Connector - Active //', function () {
|
|||||||
|
|
||||||
it('upgrades to a secure connection', function () {
|
it('upgrades to a secure connection', function () {
|
||||||
mockConnection.secure = true;
|
mockConnection.secure = true;
|
||||||
mockConnection.server = {
|
mockConnection.server = {_tls: {}};
|
||||||
options: {
|
|
||||||
tls: {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return active.setupConnection('127.0.0.1', PORT)
|
return active.setupConnection('127.0.0.1', PORT)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
expect(active.dataSocket).to.exist;
|
expect(active.dataSocket).to.exist;
|
||||||
return active.waitForConnection();
|
return active.waitForConnection();
|
||||||
})
|
})
|
||||||
.then((dataSocket) => {
|
.then(dataSocket => {
|
||||||
expect(dataSocket.connected).to.equal(true);
|
expect(dataSocket.connected).to.equal(true);
|
||||||
expect(dataSocket instanceof net.Socket).to.equal(true);
|
expect(dataSocket instanceof net.Socket).to.equal(true);
|
||||||
expect(dataSocket instanceof tls.TLSSocket).to.equal(true);
|
expect(dataSocket instanceof tls.TLSSocket).to.equal(true);
|
||||||
|
|||||||
@@ -7,117 +7,87 @@ const net = require('net');
|
|||||||
const bunyan = require('bunyan');
|
const bunyan = require('bunyan');
|
||||||
|
|
||||||
const PassiveConnector = require('../../src/connector/passive');
|
const PassiveConnector = require('../../src/connector/passive');
|
||||||
const {getNextPortFactory} = require('../../src/helpers/find-port');
|
|
||||||
|
|
||||||
describe('Connector - Passive //', function () {
|
describe('Connector - Passive //', function () {
|
||||||
|
let passive;
|
||||||
let mockConnection = {
|
let mockConnection = {
|
||||||
reply: () => Promise.resolve({}),
|
reply: () => Promise.resolve({}),
|
||||||
close: () => Promise.resolve({}),
|
close: () => Promise.resolve({}),
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
log: bunyan.createLogger({name: 'passive-test'}),
|
log: bunyan.createLogger({name: 'passive-test'}),
|
||||||
commandSocket: {
|
commandSocket: {},
|
||||||
remoteAddress: '::ffff:127.0.0.1'
|
server: {options: {}, url: {}}
|
||||||
},
|
|
||||||
server: {
|
|
||||||
url: '',
|
|
||||||
getNextPasvPort: getNextPortFactory(1024)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let sandbox;
|
let sandbox;
|
||||||
|
|
||||||
before(() => {
|
function shouldNotResolve() {
|
||||||
sandbox = sinon.sandbox.create().usingPromise(Promise);
|
throw new Error('Should not resolve');
|
||||||
});
|
}
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
passive = new PassiveConnector(mockConnection);
|
||||||
|
});
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
sandbox.spy(mockConnection, 'reply');
|
sandbox.spy(mockConnection, 'reply');
|
||||||
sandbox.spy(mockConnection, 'close');
|
sandbox.spy(mockConnection, 'close');
|
||||||
|
|
||||||
|
mockConnection.commandSocket.remoteAddress = '::ffff:127.0.0.1';
|
||||||
|
mockConnection.server.options.pasv_range = '8000';
|
||||||
});
|
});
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
sandbox.restore();
|
sandbox.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('cannot wait for connection with no server', function (done) {
|
it('cannot wait for connection with no server', function () {
|
||||||
let passive = new PassiveConnector(mockConnection);
|
return passive.waitForConnection()
|
||||||
passive.waitForConnection()
|
.then(shouldNotResolve)
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
expect(err.name).to.equal('ConnectorError');
|
expect(err.name).to.equal('ConnectorError');
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setup', function () {
|
it('no pasv range provided', function () {
|
||||||
before(function () {
|
delete mockConnection.server.options.pasv_range;
|
||||||
sandbox.stub(mockConnection.server, 'getNextPasvPort').value(getNextPortFactory());
|
|
||||||
});
|
|
||||||
|
|
||||||
it('no pasv range provided', function (done) {
|
return passive.setupServer()
|
||||||
let passive = new PassiveConnector(mockConnection);
|
.then(shouldNotResolve)
|
||||||
passive.setupServer()
|
.catch(err => {
|
||||||
.catch((err) => {
|
expect(err.name).to.equal('ConnectorError');
|
||||||
try {
|
|
||||||
expect(err.name).to.equal('ConnectorError');
|
|
||||||
done();
|
|
||||||
} catch (ex) {
|
|
||||||
done(ex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setup', function () {
|
it('has invalid pasv range', function () {
|
||||||
let connection;
|
mockConnection.server.options.pasv_range = -1;
|
||||||
before(function () {
|
|
||||||
sandbox.stub(mockConnection.server, 'getNextPasvPort').value(getNextPortFactory(-1, -1));
|
|
||||||
|
|
||||||
connection = new PassiveConnector(mockConnection);
|
return passive.setupServer()
|
||||||
});
|
.then(shouldNotResolve)
|
||||||
|
.catch(err => {
|
||||||
it('has invalid pasv range', function (done) {
|
expect(err).to.be.instanceOf(RangeError);
|
||||||
connection.setupServer()
|
|
||||||
.catch((err) => {
|
|
||||||
expect(err.name).to.equal('ConnectorError');
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sets up a server', function () {
|
it('sets up a server', function () {
|
||||||
let passive = new PassiveConnector(mockConnection);
|
|
||||||
return passive.setupServer()
|
return passive.setupServer()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
expect(passive.dataServer).to.exist;
|
expect(passive.dataServer).to.exist;
|
||||||
return passive.end();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('setup', function () {
|
it('destroys existing server, then sets up a server', function () {
|
||||||
let passive;
|
const closeFnSpy = sandbox.spy(passive.dataServer, 'close');
|
||||||
let closeFnSpy;
|
|
||||||
beforeEach(function () {
|
|
||||||
passive = new PassiveConnector(mockConnection);
|
|
||||||
return passive.setupServer()
|
|
||||||
.then(() => {
|
|
||||||
closeFnSpy = sandbox.spy(passive.dataServer, 'close');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
afterEach(function () {
|
|
||||||
return passive.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('destroys existing server, then sets up a server', function () {
|
return passive.setupServer()
|
||||||
return passive.setupServer()
|
.then(() => {
|
||||||
.then(() => {
|
expect(closeFnSpy.callCount).to.equal(1);
|
||||||
expect(closeFnSpy.callCount).to.equal(1);
|
expect(passive.dataServer).to.exist;
|
||||||
expect(passive.dataServer).to.exist;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('refuses connection with different remote address', function (done) {
|
it('refuses connection with different remote address', function (done) {
|
||||||
sandbox.stub(mockConnection.commandSocket, 'remoteAddress').value('bad');
|
mockConnection.commandSocket.remoteAddress = 'bad';
|
||||||
|
|
||||||
let passive = new PassiveConnector(mockConnection);
|
|
||||||
passive.setupServer()
|
passive.setupServer()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
expect(passive.dataServer).to.exist;
|
expect(passive.dataServer).to.exist;
|
||||||
@@ -128,8 +98,6 @@ describe('Connector - Passive //', function () {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
expect(passive.connection.reply.callCount).to.equal(1);
|
expect(passive.connection.reply.callCount).to.equal(1);
|
||||||
expect(passive.connection.reply.args[0][0]).to.equal(550);
|
expect(passive.connection.reply.args[0][0]).to.equal(550);
|
||||||
|
|
||||||
passive.end();
|
|
||||||
done();
|
done();
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
@@ -138,7 +106,6 @@ describe('Connector - Passive //', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('accepts connection', function () {
|
it('accepts connection', function () {
|
||||||
let passive = new PassiveConnector(mockConnection);
|
|
||||||
return passive.setupServer()
|
return passive.setupServer()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
expect(passive.dataServer).to.exist;
|
expect(passive.dataServer).to.exist;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ describe('FileSystem', function () {
|
|||||||
|
|
||||||
it('handles error', function () {
|
it('handles error', function () {
|
||||||
return Promise.try(() => ovFs.chdir())
|
return Promise.try(() => ovFs.chdir())
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
expect(err).to.be.instanceof(errors.FileSystemError);
|
expect(err).to.be.instanceof(errors.FileSystemError);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -38,7 +38,7 @@ describe('FileSystem', function () {
|
|||||||
it('gets correct relative path', function () {
|
it('gets correct relative path', function () {
|
||||||
const result = fs._resolvePath();
|
const result = fs._resolvePath();
|
||||||
expect(result).to.be.an('object');
|
expect(result).to.be.an('object');
|
||||||
expect(result.clientPath).to.equal(
|
expect(result.serverPath).to.equal(
|
||||||
nodePath.normalize('/file/1/2/3'));
|
nodePath.normalize('/file/1/2/3'));
|
||||||
expect(result.fsPath).to.equal(
|
expect(result.fsPath).to.equal(
|
||||||
nodePath.resolve('/tmp/ftp-srv/file/1/2/3'));
|
nodePath.resolve('/tmp/ftp-srv/file/1/2/3'));
|
||||||
@@ -47,7 +47,7 @@ describe('FileSystem', function () {
|
|||||||
it('gets correct relative path', function () {
|
it('gets correct relative path', function () {
|
||||||
const result = fs._resolvePath('..');
|
const result = fs._resolvePath('..');
|
||||||
expect(result).to.be.an('object');
|
expect(result).to.be.an('object');
|
||||||
expect(result.clientPath).to.equal(
|
expect(result.serverPath).to.equal(
|
||||||
nodePath.normalize('/file/1/2'));
|
nodePath.normalize('/file/1/2'));
|
||||||
expect(result.fsPath).to.equal(
|
expect(result.fsPath).to.equal(
|
||||||
nodePath.resolve('/tmp/ftp-srv/file/1/2'));
|
nodePath.resolve('/tmp/ftp-srv/file/1/2'));
|
||||||
@@ -56,7 +56,7 @@ describe('FileSystem', function () {
|
|||||||
it('gets correct absolute path', function () {
|
it('gets correct absolute path', function () {
|
||||||
const result = fs._resolvePath('/other');
|
const result = fs._resolvePath('/other');
|
||||||
expect(result).to.be.an('object');
|
expect(result).to.be.an('object');
|
||||||
expect(result.clientPath).to.equal(
|
expect(result.serverPath).to.equal(
|
||||||
nodePath.normalize('/other'));
|
nodePath.normalize('/other'));
|
||||||
expect(result.fsPath).to.equal(
|
expect(result.fsPath).to.equal(
|
||||||
nodePath.resolve('/tmp/ftp-srv/other'));
|
nodePath.resolve('/tmp/ftp-srv/other'));
|
||||||
@@ -65,7 +65,7 @@ describe('FileSystem', function () {
|
|||||||
it('cannot escape root', function () {
|
it('cannot escape root', function () {
|
||||||
const result = fs._resolvePath('../../../../../../../../../../..');
|
const result = fs._resolvePath('../../../../../../../../../../..');
|
||||||
expect(result).to.be.an('object');
|
expect(result).to.be.an('object');
|
||||||
expect(result.clientPath).to.equal(
|
expect(result.serverPath).to.equal(
|
||||||
nodePath.normalize('/'));
|
nodePath.normalize('/'));
|
||||||
expect(result.fsPath).to.equal(
|
expect(result.fsPath).to.equal(
|
||||||
nodePath.resolve('/tmp/ftp-srv'));
|
nodePath.resolve('/tmp/ftp-srv'));
|
||||||
@@ -74,7 +74,7 @@ describe('FileSystem', function () {
|
|||||||
it('resolves to file', function () {
|
it('resolves to file', function () {
|
||||||
const result = fs._resolvePath('/cool/file.txt');
|
const result = fs._resolvePath('/cool/file.txt');
|
||||||
expect(result).to.be.an('object');
|
expect(result).to.be.an('object');
|
||||||
expect(result.clientPath).to.equal(
|
expect(result.serverPath).to.equal(
|
||||||
nodePath.normalize('/cool/file.txt'));
|
nodePath.normalize('/cool/file.txt'));
|
||||||
expect(result.fsPath).to.equal(
|
expect(result.fsPath).to.equal(
|
||||||
nodePath.resolve('/tmp/ftp-srv/cool/file.txt'));
|
nodePath.resolve('/tmp/ftp-srv/cool/file.txt'));
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ describe('helpers // file-stat', function () {
|
|||||||
let sandbox;
|
let sandbox;
|
||||||
|
|
||||||
before(function () {
|
before(function () {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
});
|
});
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
sandbox.restore();
|
sandbox.restore();
|
||||||
|
|||||||
@@ -1,52 +1,35 @@
|
|||||||
/* eslint no-unused-expressions: 0 */
|
/* eslint no-unused-expressions: 0 */
|
||||||
const {expect} = require('chai');
|
const {expect} = require('chai');
|
||||||
const net = require('net');
|
const {Server} = require('net');
|
||||||
const sinon = require('sinon');
|
const sinon = require('sinon');
|
||||||
|
|
||||||
const {getNextPortFactory} = require('../../src/helpers/find-port');
|
const findPort = require('../../src/helpers/find-port');
|
||||||
|
|
||||||
describe('helpers // find-port', function () {
|
describe('helpers // find-port', function () {
|
||||||
let sandbox;
|
let sandbox;
|
||||||
let getNextPort;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
|
|
||||||
getNextPort = getNextPortFactory(1, 2);
|
sandbox.spy(Server.prototype, 'listen');
|
||||||
});
|
});
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
sandbox.restore();
|
sandbox.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('finds a port', () => {
|
it('finds a port', () => {
|
||||||
sandbox.stub(net.Server.prototype, 'listen').callsFake(function (port) {
|
return findPort(1)
|
||||||
this.address = () => ({port});
|
.then(port => {
|
||||||
setImmediate(() => this.emit('listening'));
|
expect(Server.prototype.listen.callCount).to.be.above(1);
|
||||||
});
|
expect(port).to.be.above(1);
|
||||||
|
|
||||||
return getNextPort()
|
|
||||||
.then((port) => {
|
|
||||||
expect(port).to.equal(1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('restarts count', () => {
|
it('does not find a port', () => {
|
||||||
sandbox.stub(net.Server.prototype, 'listen').callsFake(function (port) {
|
return findPort(1, 2)
|
||||||
this.address = () => ({port});
|
.then(() => expect(1).to.equal(2)) // should not happen
|
||||||
setImmediate(() => this.emit('listening'));
|
.catch(err => {
|
||||||
});
|
expect(err).to.exist;
|
||||||
|
|
||||||
return getNextPort()
|
|
||||||
.then((port) => {
|
|
||||||
expect(port).to.equal(1);
|
|
||||||
})
|
|
||||||
.then(() => getNextPort())
|
|
||||||
.then((port) => {
|
|
||||||
expect(port).to.equal(2);
|
|
||||||
})
|
|
||||||
.then(() => getNextPort())
|
|
||||||
.then((port) => {
|
|
||||||
expect(port).to.equal(1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ describe('helpers //resolve-host', function () {
|
|||||||
|
|
||||||
let sandbox;
|
let sandbox;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
});
|
});
|
||||||
afterEach(() => sandbox.restore());
|
afterEach(() => sandbox.restore());
|
||||||
|
|
||||||
it('fetches ip address', () => {
|
it('fetches ip address', () => {
|
||||||
const hostname = '0.0.0.0';
|
const hostname = '0.0.0.0';
|
||||||
return resolveHost(hostname)
|
return resolveHost(hostname)
|
||||||
.then((resolvedHostname) => {
|
.then(resolvedHostname => {
|
||||||
expect(resolvedHostname).to.match(/^\d+\.\d+\.\d+\.\d+$/);
|
expect(resolvedHostname).to.match(/^\d+\.\d+\.\d+\.\d+$/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -22,7 +22,7 @@ describe('helpers //resolve-host', function () {
|
|||||||
it('fetches ip address', () => {
|
it('fetches ip address', () => {
|
||||||
const hostname = null;
|
const hostname = null;
|
||||||
return resolveHost(hostname)
|
return resolveHost(hostname)
|
||||||
.then((resolvedHostname) => {
|
.then(resolvedHostname => {
|
||||||
expect(resolvedHostname).to.match(/^\d+\.\d+\.\d+\.\d+$/);
|
expect(resolvedHostname).to.match(/^\d+\.\d+\.\d+\.\d+$/);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -30,7 +30,7 @@ describe('helpers //resolve-host', function () {
|
|||||||
it('does nothing', () => {
|
it('does nothing', () => {
|
||||||
const hostname = '127.0.0.1';
|
const hostname = '127.0.0.1';
|
||||||
return resolveHost(hostname)
|
return resolveHost(hostname)
|
||||||
.then((resolvedHostname) => {
|
.then(resolvedHostname => {
|
||||||
expect(resolvedHostname).to.equal(hostname);
|
expect(resolvedHostname).to.equal(hostname);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -44,7 +44,7 @@ describe('helpers //resolve-host', function () {
|
|||||||
|
|
||||||
return resolveHost(null)
|
return resolveHost(null)
|
||||||
.then(() => expect(1).to.equal(2))
|
.then(() => expect(1).to.equal(2))
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
expect(err.code).to.equal(420);
|
expect(err.code).to.equal(420);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ describe('Integration', function () {
|
|||||||
const clientDirectory = `${process.cwd()}/test_tmp`;
|
const clientDirectory = `${process.cwd()}/test_tmp`;
|
||||||
|
|
||||||
before(() => {
|
before(() => {
|
||||||
return startServer({url: 'ftp://127.0.0.1:8880'});
|
return startServer('ftp://127.0.0.1:8880');
|
||||||
});
|
});
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sandbox = sinon.createSandbox().usingPromise(Promise);
|
sandbox = sinon.sandbox.create();
|
||||||
});
|
});
|
||||||
afterEach(() => sandbox.restore());
|
afterEach(() => sandbox.restore());
|
||||||
after(() => server.close());
|
after(() => server.close());
|
||||||
@@ -36,19 +36,10 @@ describe('Integration', function () {
|
|||||||
});
|
});
|
||||||
after(() => directoryPurge(clientDirectory));
|
after(() => directoryPurge(clientDirectory));
|
||||||
|
|
||||||
function readFile(path) {
|
function startServer(url, options = {}) {
|
||||||
return new Promise((resolve, reject) => {
|
server = new FtpServer(url, _.assign({
|
||||||
fs.readFile(path, 'utf8', (err, data) => {
|
|
||||||
if (err) reject(err);
|
|
||||||
else resolve(data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function startServer(options = {}) {
|
|
||||||
server = new FtpServer(_.assign({
|
|
||||||
log,
|
log,
|
||||||
pasv_min: 8881,
|
pasv_range: 8881,
|
||||||
greeting: ['hello', 'world'],
|
greeting: ['hello', 'world'],
|
||||||
anonymous: true
|
anonymous: true
|
||||||
}, options));
|
}, options));
|
||||||
@@ -64,7 +55,7 @@ describe('Integration', function () {
|
|||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
client = new FtpClient();
|
client = new FtpClient();
|
||||||
client.once('ready', () => resolve(client));
|
client.once('ready', () => resolve(client));
|
||||||
client.once('error', (err) => reject(err));
|
client.once('error', err => reject(err));
|
||||||
client.connect(_.assign({
|
client.connect(_.assign({
|
||||||
host: server.url.hostname,
|
host: server.url.hostname,
|
||||||
port: server.url.port,
|
port: server.url.port,
|
||||||
@@ -72,7 +63,7 @@ describe('Integration', function () {
|
|||||||
password: 'test'
|
password: 'test'
|
||||||
}, options));
|
}, options));
|
||||||
})
|
})
|
||||||
.then((instance) => {
|
.then(instance => {
|
||||||
client = instance;
|
client = instance;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -80,8 +71,8 @@ describe('Integration', function () {
|
|||||||
function closeClient() {
|
function closeClient() {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
client.once('close', () => resolve());
|
client.once('close', () => resolve());
|
||||||
client.once('error', (err) => reject(err));
|
client.once('error', err => reject(err));
|
||||||
client.logout((err) => {
|
client.logout(err => {
|
||||||
expect(err).to.be.undefined;
|
expect(err).to.be.undefined;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -92,7 +83,7 @@ describe('Integration', function () {
|
|||||||
if (!dirExists) return;
|
if (!dirExists) return;
|
||||||
|
|
||||||
const list = fs.readdirSync(dir);
|
const list = fs.readdirSync(dir);
|
||||||
list.map((item) => nodePath.resolve(dir, item)).forEach((item) => {
|
list.map(item => nodePath.resolve(dir, item)).forEach(item => {
|
||||||
const itemExists = fs.existsSync(dir);
|
const itemExists = fs.existsSync(dir);
|
||||||
if (!itemExists) return;
|
if (!itemExists) return;
|
||||||
|
|
||||||
@@ -113,7 +104,7 @@ describe('Integration', function () {
|
|||||||
|
|
||||||
after(() => directoryPurge(`${clientDirectory}/${name}/`));
|
after(() => directoryPurge(`${clientDirectory}/${name}/`));
|
||||||
|
|
||||||
it('STAT', (done) => {
|
it('STAT', done => {
|
||||||
client.status((err, status) => {
|
client.status((err, status) => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
expect(status).to.equal('Status OK');
|
expect(status).to.equal('Status OK');
|
||||||
@@ -121,7 +112,7 @@ describe('Integration', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('SYST', (done) => {
|
it('SYST', done => {
|
||||||
client.system((err, os) => {
|
client.system((err, os) => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
expect(os).to.equal('UNIX');
|
expect(os).to.equal('UNIX');
|
||||||
@@ -129,7 +120,7 @@ describe('Integration', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('CWD ..', (done) => {
|
it('CWD ..', done => {
|
||||||
client.cwd('..', (err, data) => {
|
client.cwd('..', (err, data) => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
expect(data).to.equal('/');
|
expect(data).to.equal('/');
|
||||||
@@ -137,7 +128,7 @@ describe('Integration', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`CWD ${name}`, (done) => {
|
it(`CWD ${name}`, done => {
|
||||||
client.cwd(`${name}`, (err, data) => {
|
client.cwd(`${name}`, (err, data) => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
expect(data).to.equal(`/${name}`);
|
expect(data).to.equal(`/${name}`);
|
||||||
@@ -145,7 +136,7 @@ describe('Integration', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('PWD', (done) => {
|
it('PWD', done => {
|
||||||
client.pwd((err, data) => {
|
client.pwd((err, data) => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
expect(data).to.equal(`/${name}`);
|
expect(data).to.equal(`/${name}`);
|
||||||
@@ -153,7 +144,7 @@ describe('Integration', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('LIST .', (done) => {
|
it('LIST .', done => {
|
||||||
client.list('.', (err, data) => {
|
client.list('.', (err, data) => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
expect(data).to.be.an('array');
|
expect(data).to.be.an('array');
|
||||||
@@ -163,7 +154,7 @@ describe('Integration', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('LIST fake.txt', (done) => {
|
it('LIST fake.txt', done => {
|
||||||
client.list('fake.txt', (err, data) => {
|
client.list('fake.txt', (err, data) => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
expect(data).to.be.an('array');
|
expect(data).to.be.an('array');
|
||||||
@@ -173,7 +164,7 @@ describe('Integration', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('STOR fail.txt', (done) => {
|
it('STOR fail.txt', done => {
|
||||||
const buffer = Buffer.from('test text file');
|
const buffer = Buffer.from('test text file');
|
||||||
const fsPath = `${clientDirectory}/${name}/fail.txt`;
|
const fsPath = `${clientDirectory}/${name}/fail.txt`;
|
||||||
|
|
||||||
@@ -185,7 +176,7 @@ describe('Integration', function () {
|
|||||||
return stream;
|
return stream;
|
||||||
});
|
});
|
||||||
|
|
||||||
client.put(buffer, 'fail.txt', (err) => {
|
client.put(buffer, 'fail.txt', err => {
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
const fileExists = fs.existsSync(fsPath);
|
const fileExists = fs.existsSync(fsPath);
|
||||||
expect(err).to.exist;
|
expect(err).to.exist;
|
||||||
@@ -195,15 +186,15 @@ describe('Integration', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('STOR tést.txt', (done) => {
|
it('STOR tést.txt', done => {
|
||||||
const buffer = Buffer.from('test text file');
|
const buffer = Buffer.from('test text file');
|
||||||
const fsPath = `${clientDirectory}/${name}/tést.txt`;
|
const fsPath = `${clientDirectory}/${name}/tést.txt`;
|
||||||
|
|
||||||
connection.once('STOR', (err) => {
|
connection.once('STOR', err => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
client.put(buffer, 'tést.txt', (err) => {
|
client.put(buffer, 'tést.txt', err => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
expect(fs.existsSync(fsPath)).to.equal(true);
|
expect(fs.existsSync(fsPath)).to.equal(true);
|
||||||
@@ -216,10 +207,10 @@ describe('Integration', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('APPE tést.txt', (done) => {
|
it('APPE tést.txt', done => {
|
||||||
const buffer = Buffer.from(', awesome!');
|
const buffer = Buffer.from(', awesome!');
|
||||||
const fsPath = `${clientDirectory}/${name}/tést.txt`;
|
const fsPath = `${clientDirectory}/${name}/tést.txt`;
|
||||||
client.append(buffer, 'tést.txt', (err) => {
|
client.append(buffer, 'tést.txt', err => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
expect(fs.existsSync(fsPath)).to.equal(true);
|
expect(fs.existsSync(fsPath)).to.equal(true);
|
||||||
@@ -232,15 +223,15 @@ describe('Integration', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('RETR tést.txt', (done) => {
|
it('RETR tést.txt', done => {
|
||||||
connection.once('RETR', (err) => {
|
connection.once('RETR', err => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
client.get('tést.txt', (err, stream) => {
|
client.get('tést.txt', (err, stream) => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
let text = '';
|
let text = '';
|
||||||
stream.on('data', (data) => {
|
stream.on('data', data => {
|
||||||
text += data.toString();
|
text += data.toString();
|
||||||
});
|
});
|
||||||
stream.on('end', () => {
|
stream.on('end', () => {
|
||||||
@@ -251,8 +242,8 @@ describe('Integration', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('RNFR tést.txt, RNTO awesome.txt', (done) => {
|
it('RNFR tést.txt, RNTO awesome.txt', done => {
|
||||||
client.rename('tést.txt', 'awesome.txt', (err) => {
|
client.rename('tést.txt', 'awesome.txt', err => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
expect(fs.existsSync(`${clientDirectory}/${name}/tést.txt`)).to.equal(false);
|
expect(fs.existsSync(`${clientDirectory}/${name}/tést.txt`)).to.equal(false);
|
||||||
expect(fs.existsSync(`${clientDirectory}/${name}/awesome.txt`)).to.equal(true);
|
expect(fs.existsSync(`${clientDirectory}/${name}/awesome.txt`)).to.equal(true);
|
||||||
@@ -264,7 +255,7 @@ describe('Integration', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('SIZE awesome.txt', (done) => {
|
it('SIZE awesome.txt', done => {
|
||||||
client.size('awesome.txt', (err, size) => {
|
client.size('awesome.txt', (err, size) => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
expect(size).to.be.a('number');
|
expect(size).to.be.a('number');
|
||||||
@@ -272,7 +263,7 @@ describe('Integration', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('MDTM awesome.txt', (done) => {
|
it('MDTM awesome.txt', done => {
|
||||||
client.lastMod('awesome.txt', (err, modTime) => {
|
client.lastMod('awesome.txt', (err, modTime) => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
expect(modTime).to.be.instanceOf(Date);
|
expect(modTime).to.be.instanceOf(Date);
|
||||||
@@ -281,14 +272,14 @@ describe('Integration', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it.skip('MLSD .', (done) => {
|
it.skip('MLSD .', done => {
|
||||||
client.mlsd('.', () => {
|
client.mlsd('.', () => {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('SITE CHMOD 700 awesome.txt', (done) => {
|
it('SITE CHMOD 700 awesome.txt', done => {
|
||||||
client.site('CHMOD 600 awesome.txt', (err) => {
|
client.site('CHMOD 600 awesome.txt', err => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
fs.stat(`${clientDirectory}/${name}/awesome.txt`, (fserr, stats) => {
|
fs.stat(`${clientDirectory}/${name}/awesome.txt`, (fserr, stats) => {
|
||||||
expect(fserr).to.not.exist;
|
expect(fserr).to.not.exist;
|
||||||
@@ -299,27 +290,27 @@ describe('Integration', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('DELE awesome.txt', (done) => {
|
it('DELE awesome.txt', done => {
|
||||||
client.delete('awesome.txt', (err) => {
|
client.delete('awesome.txt', err => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
expect(fs.existsSync(`${clientDirectory}/${name}/awesome.txt`)).to.equal(false);
|
expect(fs.existsSync(`${clientDirectory}/${name}/awesome.txt`)).to.equal(false);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('MKD témp', (done) => {
|
it('MKD témp', done => {
|
||||||
const path = `${clientDirectory}/${name}/témp`;
|
const path = `${clientDirectory}/${name}/témp`;
|
||||||
if (fs.existsSync(path)) {
|
if (fs.existsSync(path)) {
|
||||||
fs.rmdirSync(path);
|
fs.rmdirSync(path);
|
||||||
}
|
}
|
||||||
client.mkdir('témp', (err) => {
|
client.mkdir('témp', err => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
expect(fs.existsSync(path)).to.equal(true);
|
expect(fs.existsSync(path)).to.equal(true);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('CWD témp', (done) => {
|
it('CWD témp', done => {
|
||||||
client.cwd('témp', (err, data) => {
|
client.cwd('témp', (err, data) => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
expect(data).to.to.be.a('string');
|
expect(data).to.to.be.a('string');
|
||||||
@@ -327,23 +318,23 @@ describe('Integration', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('CDUP', (done) => {
|
it('CDUP', done => {
|
||||||
client.cdup((err) => {
|
client.cdup(err => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('RMD témp', (done) => {
|
it('RMD témp', done => {
|
||||||
client.rmdir('témp', (err) => {
|
client.rmdir('témp', err => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
expect(fs.existsSync(`${clientDirectory}/${name}/témp`)).to.equal(false);
|
expect(fs.existsSync(`${clientDirectory}/${name}/témp`)).to.equal(false);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('CDUP', (done) => {
|
it('CDUP', done => {
|
||||||
client.cdup((err) => {
|
client.cdup(err => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -362,8 +353,8 @@ describe('Integration', function () {
|
|||||||
|
|
||||||
after(() => closeClient(client));
|
after(() => closeClient(client));
|
||||||
|
|
||||||
it('TYPE A', (done) => {
|
it('TYPE A', done => {
|
||||||
client.ascii((err) => {
|
client.ascii(err => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -384,8 +375,8 @@ describe('Integration', function () {
|
|||||||
|
|
||||||
after(() => closeClient(client));
|
after(() => closeClient(client));
|
||||||
|
|
||||||
it('TYPE I', (done) => {
|
it('TYPE I', done => {
|
||||||
client.binary((err) => {
|
client.binary(err => {
|
||||||
expect(err).to.not.exist;
|
expect(err).to.not.exist;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -397,14 +388,12 @@ describe('Integration', function () {
|
|||||||
describe('#EXPLICIT', function () {
|
describe('#EXPLICIT', function () {
|
||||||
before(() => {
|
before(() => {
|
||||||
return server.close()
|
return server.close()
|
||||||
.then(() => Promise.all([
|
.then(() => startServer('ftp://127.0.0.1:8880', {
|
||||||
readFile(`${process.cwd()}/test/cert/server.key`),
|
tls: {
|
||||||
readFile(`${process.cwd()}/test/cert/server.crt`),
|
key: `${process.cwd()}/test/cert/server.key`,
|
||||||
readFile(`${process.cwd()}/test/cert/server.csr`)
|
cert: `${process.cwd()}/test/cert/server.crt`,
|
||||||
]))
|
ca: `${process.cwd()}/test/cert/server.csr`
|
||||||
.then(([key, cert, ca]) => startServer({
|
}
|
||||||
url: 'ftp://127.0.0.1:8880',
|
|
||||||
tls: {key, cert, ca}
|
|
||||||
}))
|
}))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return connectClient({
|
return connectClient({
|
||||||
@@ -425,14 +414,12 @@ describe('Integration', function () {
|
|||||||
describe.skip('#IMPLICIT', function () {
|
describe.skip('#IMPLICIT', function () {
|
||||||
before(() => {
|
before(() => {
|
||||||
return server.close()
|
return server.close()
|
||||||
.then(() => Promise.all([
|
.then(() => startServer('ftps://127.0.0.1:8880', {
|
||||||
readFile(`${process.cwd()}/test/cert/server.key`),
|
tls: {
|
||||||
readFile(`${process.cwd()}/test/cert/server.crt`),
|
key: `${process.cwd()}/test/cert/server.key`,
|
||||||
readFile(`${process.cwd()}/test/cert/server.csr`)
|
cert: `${process.cwd()}/test/cert/server.crt`,
|
||||||
]))
|
ca: `${process.cwd()}/test/cert/server.csr`
|
||||||
.then(([key, cert, ca]) => startServer({
|
}
|
||||||
url: 'ftps://127.0.0.1:8880',
|
|
||||||
tls: {key, cert, ca}
|
|
||||||
}))
|
}))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return connectClient({
|
return connectClient({
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
|
require('dotenv').load();
|
||||||
const bunyan = require('bunyan');
|
const bunyan = require('bunyan');
|
||||||
const fs = require('fs');
|
|
||||||
const FtpServer = require('../src');
|
const FtpServer = require('../src');
|
||||||
|
|
||||||
const server = new FtpServer({
|
const log = bunyan.createLogger({name: 'test'});
|
||||||
log: bunyan.createLogger({name: 'test', level: 'trace'}),
|
log.level('trace');
|
||||||
url: 'ftp://127.0.0.1:8880',
|
const server = new FtpServer('ftp://127.0.0.1:8880', {
|
||||||
pasv_min: 8881,
|
log,
|
||||||
|
pasv_range: 8881,
|
||||||
greeting: ['Welcome', 'to', 'the', 'jungle!'],
|
greeting: ['Welcome', 'to', 'the', 'jungle!'],
|
||||||
tls: {
|
tls: {
|
||||||
key: fs.readFileSync(`${process.cwd()}/test/cert/server.key`),
|
key: `${process.cwd()}/test/cert/server.key`,
|
||||||
cert: fs.readFileSync(`${process.cwd()}/test/cert/server.crt`),
|
cert: `${process.cwd()}/test/cert/server.crt`,
|
||||||
ca: fs.readFileSync(`${process.cwd()}/test/cert/server.csr`)
|
ca: `${process.cwd()}/test/cert/server.csr`
|
||||||
},
|
},
|
||||||
file_format: 'ep',
|
file_format: 'ep',
|
||||||
anonymous: 'sillyrabbit'
|
anonymous: 'sillyrabbit'
|
||||||
|
|||||||
Reference in New Issue
Block a user