Compare commits
75 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63777c0d74 | ||
|
|
9be8ffa60d | ||
|
|
b8cd6022e1 | ||
|
|
0618a3c675 | ||
|
|
3c533a5fbc | ||
|
|
3d0a58ca15 | ||
|
|
4b4c809af8 | ||
|
|
a234534de0 | ||
|
|
635fb35341 | ||
|
|
51a6448ac2 | ||
|
|
4d8a69615c | ||
|
|
ab5a2e9641 | ||
|
|
a7f25accd2 | ||
|
|
c49a361c36 | ||
|
|
d3d65aa5cf | ||
|
|
e53848f881 | ||
|
|
b5cf75b09f | ||
|
|
dd0a790519 | ||
|
|
e25ee55865 | ||
|
|
0433bb48cd | ||
|
|
350c2b3e81 | ||
|
|
887bf1fa58 | ||
|
|
70a62f1da1 | ||
|
|
3a1afdb694 | ||
|
|
10127b32e5 | ||
|
|
e1aad3e021 | ||
|
|
a254e6c5f3 | ||
|
|
c46d6086ea | ||
|
|
88f02cd498 | ||
|
|
2cc5d54d7f | ||
|
|
f127d0e7b6 | ||
|
|
13048a96bd | ||
|
|
f6355e66c3 | ||
|
|
6e79e958cc | ||
|
|
db7d88f411 | ||
|
|
323ee62110 | ||
|
|
1e446a7801 | ||
|
|
977fbd4190 | ||
|
|
d5d1b98b04 | ||
|
|
df0a4d640c | ||
|
|
73274191fe | ||
|
|
37c3da3a62 | ||
|
|
9bece5f946 | ||
|
|
83947142df | ||
|
|
c54045e0b9 | ||
|
|
cf71243729 | ||
|
|
7fb43a5790 | ||
|
|
e99059125e | ||
|
|
954e9a1252 | ||
|
|
2b9e163958 | ||
|
|
c6a49d2191 | ||
|
|
14e5f87cc3 | ||
|
|
580b8d6eae | ||
|
|
a75d63df92 | ||
|
|
301ae110e8 | ||
|
|
4d69b48466 | ||
|
|
ec010697bb | ||
|
|
cf3d543f1a | ||
|
|
69bec2b01c | ||
|
|
2eac41d127 | ||
|
|
eb32f93fc6 | ||
|
|
095423606e | ||
|
|
61cf1bda39 | ||
|
|
75f847ed5d | ||
|
|
ad4b32fc13 | ||
|
|
be3c57bed0 | ||
|
|
dc7dd1075c | ||
|
|
543e6cc1cc | ||
|
|
5c1f8f7a65 | ||
|
|
557995a1a9 | ||
|
|
45eca5afe0 | ||
|
|
695e594d97 | ||
|
|
97b55fc92c | ||
|
|
577066850b | ||
|
|
0ec989cf1e |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,5 +2,4 @@ node_modules/
|
||||
|
||||
dist/
|
||||
reports/
|
||||
.env
|
||||
npm-debug.log
|
||||
|
||||
17
.travis.yml
17
.travis.yml
@@ -1,6 +1,11 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "6"
|
||||
- "node"
|
||||
|
||||
env:
|
||||
FTP_URL: ftp://127.0.0.1:8880
|
||||
PASV_RANGE: 8881
|
||||
|
||||
install: npm install
|
||||
|
||||
@@ -8,5 +13,13 @@ script:
|
||||
- npm run verify:js
|
||||
- npm run test:coverage
|
||||
|
||||
after_success:
|
||||
- if [ $TRAVIS_BRANCH = 'master' ]; then npm run semantic-release; fi
|
||||
after_script:
|
||||
- npm run upload-coverage
|
||||
|
||||
deploy:
|
||||
skip_cleanup: true
|
||||
provider: script
|
||||
script: npm run semantic-release
|
||||
on:
|
||||
branch: master
|
||||
node: "6"
|
||||
|
||||
270
README.md
270
README.md
@@ -1,28 +1,33 @@
|
||||
# ftp-srv [](https://badge.fury.io/js/ftp-srv) [](https://travis-ci.org/stewarttylerr/ftp-srv) [](https://github.com/semantic-release/semantic-release) [](http://commitizen.github.io/cz-cli/)
|
||||
[](https://github.com/trs/ftp-srv)
|
||||
|
||||
<!--[RM_DESCRIPTION]-->
|
||||
> Modern, extensible FTP Server
|
||||
|
||||
<!--[]-->
|
||||
|
||||
[](https://badge.fury.io/js/ftp-srv) [](https://travis-ci.org/trs/ftp-srv)
|
||||
[](https://coveralls.io/github/trs/ftp-srv?branch=coveralls) [](https://github.com/semantic-release/semantic-release) [](http://commitizen.github.io/cz-cli/)
|
||||
|
||||
---
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Features](#features)
|
||||
- [Install](#install)
|
||||
- [Usage](#usage)
|
||||
- [API](#api)
|
||||
- [Events](#events)
|
||||
- [File System](#file-system)
|
||||
- [API](#api)
|
||||
- [Events](#events)
|
||||
- [Supported Commands](#supported-commands)
|
||||
- [File System](#file-system)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
|
||||
## Overview
|
||||
> `ftp-srv` is designed to be easy, exensible, and modern.
|
||||
> Configuration is very minimal for a basic FTP server,
|
||||
but can easily grow to fit a larger scale project.
|
||||
`ftp-srv` is a modern and extensible FTP server designed to be simple yet configurable.
|
||||
|
||||
## Features
|
||||
- Supports passive and active connections
|
||||
- Allows extensible [file systems](#file-system) on a per connection basis
|
||||
- Extensible [file systems](#file-system) per connection
|
||||
- Passive and active transfers
|
||||
- [Explicit](https://en.wikipedia.org/wiki/FTPS#Explicit) & [Implicit](https://en.wikipedia.org/wiki/FTPS#Implicit) TLS connections
|
||||
|
||||
## Install
|
||||
`npm install ftp-srv --save`
|
||||
@@ -33,9 +38,10 @@ but can easily grow to fit a larger scale project.
|
||||
// Quick start
|
||||
|
||||
const FtpSvr = require('ftp-srv');
|
||||
const ftpServer = new FtpSvr(url, [{ options ... }]);
|
||||
const ftpServer = new FtpSvr('ftp://0.0.0.0:9876', { options ... });
|
||||
|
||||
ftpServer.on('...', (data, resolve, reject) => { ... })
|
||||
ftpServer.on('login', (data, resolve, reject) => { ... });
|
||||
...
|
||||
|
||||
ftpServer.listen()
|
||||
.then(() => { ... });
|
||||
@@ -43,127 +49,173 @@ ftpServer.listen()
|
||||
|
||||
## API
|
||||
|
||||
#### new FtpSrv(url, [options])
|
||||
### `new FtpSrv(url, [{options}])`
|
||||
#### 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.
|
||||
Supported protocols:
|
||||
- `ftp` Plain FTP
|
||||
- `ftps` Implicit FTP over TLS
|
||||
|
||||
- __url__ :: `ftp://127.0.0.1:21`
|
||||
- A full href url, indicating the protocol, and external IP with port to listen for connections.
|
||||
- Supported protocols:
|
||||
- `ftp`
|
||||
- To accept external connections, the hostname must be the box's external IP address. This can be fetched automatically by setting the hostname to `0.0.0.0`.
|
||||
- __options__ :: `{}`
|
||||
- __pasv_range__ :: `22`
|
||||
- Starting port or min - max range to accept passive connections
|
||||
- Ports will be queried for an unused port in the range to use for the connection.
|
||||
- If none are found, the connection cannot be established
|
||||
- If an integer is supplied: will indicate the minimum allowable port
|
||||
- If a range is defined (`3000-3100`): only ports within that range will be used
|
||||
- __anonymous__ :: `false`
|
||||
- If true, will authenticate connections after passing the `USER` command. Passwords will not be required.
|
||||
- __blacklist__ :: `[]`
|
||||
- Array of commands to be blacklisted globally
|
||||
- `['RMD', 'RNFR', 'RNTO']`
|
||||
- A connection sending one of these commands will be replied with code `502`
|
||||
- __whitelist__ :: `[]`
|
||||
- If set, only commands within this array are allowed
|
||||
- A connection sending any other command will be replied to with code `502`
|
||||
- __file_format__ :: `ls`
|
||||
- Set the format to use for file stat queries, such as `LIST`
|
||||
- Possible values include:
|
||||
- `ls` : [bin/ls format](https://cr.yp.to/ftp/list/binls.html)
|
||||
- `ep` : [Easily Parsed LIST format](https://cr.yp.to/ftp/list/eplf.html)
|
||||
- Function : pass in a function as the parameter to use your own
|
||||
- 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__ :: `bunyan.createLogger()`
|
||||
- A [bunyan logger](https://github.com/trentm/node-bunyan) instance
|
||||
- By default, one is created, but a custom instance can be passed in as well
|
||||
_Note:_ The hostname must be the external IP address to accept external connections. Setting the hostname to `0.0.0.0` will automatically set the external IP.
|
||||
__Default:__ `"ftp://127.0.0.1:21"`
|
||||
|
||||
#### options
|
||||
|
||||
##### `pasv_range`
|
||||
A starting port (eg `8000`) or a range (eg `"8000-9000"`) to accept passive connections.
|
||||
This range is then queried for an available port to use when required.
|
||||
__Default:__ `22`
|
||||
|
||||
##### `greeting`
|
||||
A human readable array of lines or string to send when a client connects.
|
||||
__Default:__ `null`
|
||||
|
||||
##### `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.
|
||||
__Default:__ `false`
|
||||
|
||||
##### `anonymous`
|
||||
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.
|
||||
The `login` event is then sent with the provided username and `@anonymous` as the password.
|
||||
__Default:__ `false`
|
||||
|
||||
##### `blacklist`
|
||||
Array of commands that are not allowed.
|
||||
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.
|
||||
__Default:__ `[]`
|
||||
|
||||
##### `whitelist`
|
||||
Array of commands that are only allowed.
|
||||
Response code `502` is sent to clients sending any other command.
|
||||
__Default:__ `[]`
|
||||
|
||||
##### `file_format`
|
||||
Sets the format to use for file stat queries such as `LIST`.
|
||||
__Default:__ `"ls"`
|
||||
__Allowable values:__
|
||||
- `ls` [bin/ls format](https://cr.yp.to/ftp/list/binls.html)
|
||||
- `ep` [Easily Parsed LIST format](https://cr.yp.to/ftp/list/eplf.html)
|
||||
- `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
|
||||
|
||||
##### `log`
|
||||
A [bunyan logger](https://github.com/trentm/node-bunyan) instance. Created by default.
|
||||
|
||||
## Events
|
||||
|
||||
#### "login" ({connection, username, password}, resolve, reject)
|
||||
> Occurs after `PASS` command is set, or after `USER` if `anonymous` is `true`
|
||||
The `FtpSvr` class extends the [node net.Server](https://nodejs.org/api/net.html#net_class_net_server). Some custom events can be resolved or rejected, such as `login`.
|
||||
|
||||
- __connection__
|
||||
- Instance of the FTP client
|
||||
- __username__
|
||||
- Username provided in the `USER` command
|
||||
- __password__
|
||||
- Password provided in the `PASS` command
|
||||
- Only provided if `anonymous` is set to `false`
|
||||
- __resolve ({fs, cwd, blacklist, whitelist})__
|
||||
- __fs__ _[optional]_
|
||||
- Optional file system class for connection to use
|
||||
- See [File System](#file-system) for implementation details
|
||||
- __cwd__ _[optional]_
|
||||
- If `fs` not provided, will set the starting directory for the connection
|
||||
- __blacklist__ _[optional]_
|
||||
- Commands that are forbidden for this connection only
|
||||
- __whitelist__ _[optional]_
|
||||
- If set, this connection will only be able to use the provided commands
|
||||
- __reject (error)__
|
||||
- __error__
|
||||
- Error object
|
||||
### `login`
|
||||
```js
|
||||
on('login', {connection, username, password}, resolve, reject) => { ... }
|
||||
```
|
||||
|
||||
Occurs when a client is attempting to login. Here you can resolve the login request by username and password.
|
||||
|
||||
`connection` [client class object](src/connection.js)
|
||||
`username` string of username from `USER` command
|
||||
`password` string of password from `PASS` command
|
||||
`resolve` takes an object of arguments:
|
||||
- `fs`
|
||||
- Set a custom file system class for this connection to use.
|
||||
- See [File System](#file-system) for implementation details.
|
||||
- `root`
|
||||
- If `fs` is not provided, this will set the root directory for the connection.
|
||||
- The user cannot traverse lower than this directory.
|
||||
- `cwd`
|
||||
- If `fs` is not provided, will set the starting directory for the connection
|
||||
- This is relative to the `root` directory.
|
||||
- `blacklist`
|
||||
- Commands that are forbidden for only this connection
|
||||
- `whitelist`
|
||||
- If set, this connection will only be able to use the provided commands
|
||||
|
||||
`reject` takes an error object
|
||||
|
||||
### `client-error`
|
||||
```js
|
||||
on('client-error', {connection, context, error}) => { ... }
|
||||
```
|
||||
|
||||
Occurs when an error arises in the client connection.
|
||||
|
||||
`connection` [client class object](src/connection.js)
|
||||
`context` string of where the error occured
|
||||
`error` error object
|
||||
|
||||
## Supported Commands
|
||||
|
||||
See the [command registry](src/commands/registration) for a list of all implemented FTP commands.
|
||||
|
||||
## File System
|
||||
> The default file system can be overriden to use your own implementation. This can allow for virtual file systems and more.
|
||||
> Each connection can be given it's own file system depending on the user.
|
||||
The default [file system](src/fs.js) can be overwritten to use your own implementation.
|
||||
This can allow for virtual file systems, and more.
|
||||
Each connection can set it's own file system based on the user.
|
||||
|
||||
#### Functions
|
||||
`currentDirectory()`
|
||||
Returns a string of the current working directory
|
||||
The default file system is exported and can be extended as needed:
|
||||
```js
|
||||
const {FtpSrv, FileSystem} = require('ftp-srv');
|
||||
|
||||
> Used in: `PWD`
|
||||
class MyFileSystem extends FileSystem {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
}
|
||||
|
||||
`get(fileName)`
|
||||
Returns a file stat object of file or directory
|
||||
get(fileName) {
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Used in: `STAT`, `SIZE`, `RNFR`, `MDTM`
|
||||
Custom file systems can implement the following variables depending on the developers needs:
|
||||
|
||||
`list(path)`
|
||||
Returns array of file and directory stat objects
|
||||
### Methods
|
||||
#### [`currentDirectory()`](src/fs.js#L29)
|
||||
Returns a string of the current working directory
|
||||
__Used in:__ `PWD`
|
||||
|
||||
> Used in `LIST`, `STAT`
|
||||
#### [`get(fileName)`](src/fs.js#L33)
|
||||
Returns a file stat object of file or directory
|
||||
__Used in:__ `LIST`, `NLST`, `STAT`, `SIZE`, `RNFR`, `MDTM`
|
||||
|
||||
`chdir(path)`
|
||||
Returns new directory relative to cwd
|
||||
#### [`list(path)`](src/fs.js#L39)
|
||||
Returns array of file and directory stat objects
|
||||
__Used in:__ `LIST`, `NLST`, `STAT`
|
||||
|
||||
> Used in `CWD`, `CDUP`
|
||||
#### [`chdir(path)`](src/fs.js#L56)
|
||||
Returns new directory relative to current directory
|
||||
__Used in:__ `CWD`, `CDUP`
|
||||
|
||||
`mkdir(path)`
|
||||
Return a path to a newly created directory
|
||||
#### [`mkdir(path)`](src/fs.js#L96)
|
||||
Returns a path to a newly created directory
|
||||
__Used in:__ `MKD`
|
||||
|
||||
> Used in `MKD`
|
||||
|
||||
`write(fileName, options)`
|
||||
#### [`write(fileName, {append = false})`](src/fs.js#L68)
|
||||
Returns a writable stream
|
||||
Options:
|
||||
`append` if true, append to existing file
|
||||
Options: `append` if true, append to existing file
|
||||
__Used in:__ `STOR`, `APPE`
|
||||
|
||||
> Used in `STOR`, `APPE`
|
||||
#### [`read(fileName)`](src/fs.js#L75)
|
||||
Returns a readable stream
|
||||
__Used in:__ `RETR`
|
||||
|
||||
`read(fileName)`
|
||||
Returns a readable stream
|
||||
#### [`delete(path)`](src/fs.js#L87)
|
||||
Delete a file or directory
|
||||
__Used in:__ `DELE`
|
||||
|
||||
> Used in `RETR`
|
||||
#### [`rename(from, to)`](src/fs.js#L102)
|
||||
Rename a file or directory
|
||||
__Used in:__ `RNFR`, `RNTO`
|
||||
|
||||
`delete(path)`
|
||||
Delete a file or directory
|
||||
#### [`chmod(path)`](src/fs.js#L108)
|
||||
Modify a file or directory's permissions
|
||||
__Used in:__ `SITE CHMOD`
|
||||
|
||||
> Used in `DELE`
|
||||
|
||||
`rename(from, to)`
|
||||
Rename a file or directory
|
||||
|
||||
> Used in `RNFR`, `RNTO`
|
||||
|
||||
`chmod(path)`
|
||||
Modify a file or directory's permissions
|
||||
|
||||
> Used in `SITE CHMOD`
|
||||
|
||||
`getUniqueName()`
|
||||
Return a unique file name to write to
|
||||
|
||||
> Used in `STOU`
|
||||
#### [`getUniqueName()`](src/fs.js#L113)
|
||||
Returns a unique file name to write to
|
||||
__Used in:__ `STOU`
|
||||
|
||||
<!--[RM_CONTRIBUTING]-->
|
||||
## Contributing
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Use JS to support loading of threshold data from external file
|
||||
var coverageConfig = {
|
||||
instrumentation: {
|
||||
root: 'src/'
|
||||
root: 'src/',
|
||||
excludes: ['errors.js']
|
||||
},
|
||||
check: require('./thresholds.json'),
|
||||
reporting: {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
test/**/*.spec.js
|
||||
--reporter list
|
||||
--no-timeouts
|
||||
--reporter mocha-pretty-bunyan-nyan
|
||||
--ui bdd
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"global": {
|
||||
"statements": 70,
|
||||
"branches": 60,
|
||||
"functions": 80,
|
||||
"lines": 80
|
||||
"statements": 90,
|
||||
"branches": 80,
|
||||
"functions": 90,
|
||||
"lines": 90
|
||||
},
|
||||
"each": {
|
||||
"statements": 0,
|
||||
"branches": 0,
|
||||
"functions": 0,
|
||||
"lines": 0
|
||||
"statements": 70,
|
||||
"branches": 40,
|
||||
"functions": 60,
|
||||
"lines": 70
|
||||
}
|
||||
}
|
||||
|
||||
6
ftp-srv.js
Normal file
6
ftp-srv.js
Normal file
@@ -0,0 +1,6 @@
|
||||
const FtpSrv = require('./src');
|
||||
const FileSystem = require('./src/fs');
|
||||
|
||||
module.exports = FtpSrv;
|
||||
module.exports.FtpSrv = FtpSrv;
|
||||
module.exports.FileSystem = FileSystem;
|
||||
17
logo/generate.js
Normal file
17
logo/generate.js
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
Send Button by Bruno Bosse from the Noun Project
|
||||
https://thenounproject.com/brunobosse/collection/basics/?i=1054386
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const htmlConvert = require('html-convert');
|
||||
|
||||
const convert = htmlConvert();
|
||||
let ws = fs.createWriteStream('logo.png');
|
||||
let rs = convert('logo/logo.html', {
|
||||
width: 350,
|
||||
height: 76
|
||||
});
|
||||
|
||||
rs.pipe(ws);
|
||||
ws.on('finish', () => process.exit());
|
||||
81
logo/icon.svg
Normal file
81
logo/icon.svg
Normal file
@@ -0,0 +1,81 @@
|
||||
<svg width="566" height="580" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<g>
|
||||
<title>Layer 1</title>
|
||||
<g display="none" id="svg_1">
|
||||
<g display="inline" id="svg_2">
|
||||
<circle cx="337.851" cy="245.093" r="68.103" id="svg_3"/>
|
||||
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm135.007,301.787c-35.898,35.898 -89.692,42.92 -132.482,20.934l-76.709,76.709c-13.651,13.651 -35.783,13.651 -49.434,0c-13.651,-13.651 -13.651,-35.784 0,-49.435l76.777,-76.777c-21.923,-42.853 -15.06,-96.699 20.761,-132.52c44.483,-44.483 116.448,-44.64 160.931,-0.157c44.482,44.485 44.639,116.763 0.156,161.246z" id="svg_4"/>
|
||||
</g>
|
||||
</g>
|
||||
<g display="none" id="svg_5">
|
||||
<g display="inline" id="svg_6">
|
||||
<path d="m282,211.676c-7.801,0 -13,6.324 -13,14.125l0,66.199l-40.682,0c-7.975,0 -14.318,5.978 -14.318,13.953l0,0.597c0,7.975 6.343,14.449 14.318,14.449l54.641,0c0.053,0 -0.495,-0.012 -0.442,-0.013c0.053,0.001 -0.495,0.004 -0.442,0.004c7.801,0 12.925,-6.324 12.925,-14.125l0,-0.315l0,-0.597l0,-80.152c0,-7.801 -5.199,-14.125 -13,-14.125z" id="svg_7"/>
|
||||
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm-139.125,208.04c-0.89,-1.277 -1.569,-2.707 -2.003,-4.244c-3.279,-7.649 -5.111,-16.066 -5.111,-24.915c0,-34.997 28.371,-63.368 63.368,-63.368c14.59,0 28.023,4.937 38.735,13.224c1.019,0.639 1.951,1.4 2.775,2.268c2.431,2.56 3.932,6.012 3.932,9.82c0,5.696 -3.344,10.6 -8.169,12.891c-0.831,0.395 -1.704,0.709 -2.615,0.938c-27.13,10.06 -50.303,28.252 -66.567,51.62c-0.558,1.116 -1.257,2.145 -2.077,3.069c-2.601,2.933 -6.387,4.792 -10.616,4.792c-4.828,0.001 -9.086,-2.414 -11.652,-6.095zm257.1,194.907c-13.91,13.91 -36.463,13.91 -50.373,0l-5.58,-5.58c-18.184,10.084 -39.105,15.833 -61.371,15.833c-22.105,0 -42.885,-5.664 -60.977,-15.613l-5.36,5.36c-13.91,13.91 -36.463,13.91 -50.373,0c-13.91,-13.91 -13.91,-36.464 0,-50.374l5.424,-5.424c-9.849,-18.023 -15.449,-38.698 -15.449,-60.683c0,-69.995 56.741,-126.735 126.735,-126.735s126.735,56.741 126.735,126.735c0,21.823 -5.518,42.358 -15.232,60.286l5.821,5.821c13.91,13.91 13.91,36.464 0,50.374zm24.752,-197.077c-0.402,1.103 -0.924,2.149 -1.573,3.104c-2.536,3.727 -6.81,6.175 -11.658,6.175c-4.483,0 -8.469,-2.099 -11.05,-5.363c-0.508,-0.642 -0.968,-1.323 -1.36,-2.049c-16.153,-23.843 -39.432,-42.457 -66.784,-52.788c-1.048,-0.252 -2.048,-0.618 -2.991,-1.088c-4.671,-2.325 -7.888,-7.133 -7.888,-12.705c0,-3.488 1.263,-6.677 3.349,-9.148c1.025,-1.215 2.254,-2.245 3.628,-3.061c10.753,-8.412 24.288,-13.434 39,-13.434c34.997,0 63.368,28.371 63.368,63.368c-0.001,9.657 -2.176,18.8 -6.041,26.989z" id="svg_8"/>
|
||||
</g>
|
||||
</g>
|
||||
<g display="none" id="svg_9">
|
||||
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm139.455,375.302c0,7.493 -6.315,13.691 -13.808,13.691l-91.192,0l0,-35.34l0,-20.446l0,-2.871c0.038,-7.37 -6.111,-12.343 -15,-12.343s-39,0 -39,0c-3,-0.005 -2.241,0.161 -2.373,0.161c-7.435,0 -14.625,7.165 -14.625,14.6c0,0.127 -0.002,1.239 -0.002,1.239l0,19.66l0,35.34l-89.837,0l-0.494,0c-7.381,0 -13.669,-6.272 -13.669,-13.685l0,-121.53c0,-2.827 1.062,-5.443 2.568,-7.596c0.888,-1.27 2.164,-2.375 3.431,-3.265l124.012,-124.268l2.154,-2.157c2.255,-1.791 5.106,-2.867 8.209,-2.867c3.004,0 5.772,1.014 7.991,2.702l2.557,2.548l124.987,125.215c-0.054,0.045 -0.047,0.091 -0.101,0.136c2.505,2.415 4.191,5.799 4.191,9.554l0,121.522l0.001,0z" id="svg_10"/>
|
||||
</g>
|
||||
<g display="none" id="svg_11">
|
||||
<g display="inline" id="svg_12">
|
||||
<path d="m345.15,271.851c1.183,-1.297 2.547,-2.706 3.842,-4.247c1.421,-1.692 2.659,-3.545 3.417,-5.595c0.433,-1.119 0.713,-2.249 0.713,-3.433c0,-0.07 -0.121,-14.058 -0.121,-14.058l0,-40.854c0,-18.056 -14.646,-31.979 -31.875,-31.979c-2.232,0 -4.372,0.235 -6.66,0.724c-0.07,0.013 -0.098,0.032 -0.168,0.045c-9.206,1.965 -18.404,3.611 -27.845,3.643c-10.388,0.038 -20.532,-1.519 -30.667,-3.688c-2.282,-0.489 -4.222,-0.724 -6.453,-0.724c-17.229,0 -31.331,13.923 -31.331,31.979l0,4.447l0,29.426l0,8.558c0,3.935 -0.94,7.942 -0.673,11.865c0.097,1.434 -0.228,2.931 0.286,4.05c0.791,2.056 2.002,3.895 3.397,5.595c2.021,2.463 4.429,4.628 6.529,6.701c3.923,3.878 8.138,7.475 12.48,10.87c8.742,6.828 18.004,12.937 26.886,19.575c4.857,3.63 11.295,5.67 18.034,5.828c6.739,-0.159 13.176,-2.203 18.033,-5.833c9.734,-7.273 20.006,-13.637 29.409,-21.113c4.536,-3.607 8.857,-7.498 12.767,-11.782zm-85.15,-37.557c0,7.909 -7.091,14.317 -15,14.317c-7.909,0 -15,-6.408 -15,-14.317l0,-14.782c0,-7.909 7.091,-14.317 15,-14.317c7.909,0 15,6.408 15,14.317l0,14.782zm59.5,14.318c-7.909,0 -14.5,-6.408 -14.5,-14.317l0,-14.782c0,-7.909 6.591,-14.317 14.5,-14.317c7.909,0 14.5,6.408 14.5,14.317l0,14.782c0,7.908 -6.591,14.317 -14.5,14.317z" id="svg_13"/>
|
||||
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm108.455,408.03c0,5.283 -4.98,7.963 -10.263,7.963l-103.555,0l-8.55,0l-82.179,0c-5.283,0 -10.453,-2.68 -10.453,-7.963l0,-37.968c0,-11.276 4.59,-23.322 12.77,-35.808c6.836,-10.433 16.918,-21.024 28.32,-30.633c7.486,-6.309 15.463,-11.977 23.502,-16.746c-0.944,-0.673 -1.889,-1.334 -2.831,-2.01c0.055,-0.032 0.106,-0.043 0.16,-0.074c-11.73,-8.489 -19.987,-15.692 -26.141,-21.834c-3.346,-3.388 -5.872,-6.221 -7.879,-9.237c-0.481,-0.51 -0.971,-1.029 -1.433,-1.559c-0.279,-0.321 -0.594,-0.643 -0.863,-0.971c-1.794,-2.187 -3.431,-4.554 -4.449,-7.199c-0.661,-1.441 -1.199,-3.366 -1.325,-5.212c-0.124,-1.817 -0.442,-3.648 -0.429,-5.483c0.024,-3.264 -0.402,-6.543 -0.402,-9.785l0,-18.16l0.549,0.01c0.01,-0.561 0.539,-1.144 0.457,-1.737c-0.82,-5.964 -5.862,-13.494 -7.177,-21.512c-0.104,-0.638 -0.188,-1.279 -0.242,-1.923l0.026,-0.01c-0.401,-4.131 -0.385,-8.308 0.145,-12.457c6.765,-52.927 46.886,-86.808 95.363,-86.808c47.53,0 87.052,32.316 94.942,83.737c0.787,5.139 0.773,10.342 0.025,15.428l0.347,0.109c-0.122,1.478 -0.237,2.94 -0.589,4.384c-1.724,7.058 -5.343,13.637 -6.311,18.973c-0.061,0.336 0.465,0.671 0.465,0.998l0,16.943c0,0 -0.605,6.586 -0.593,11.809c0.008,3.41 -0.606,6.241 -0.606,6.277c0,1.524 -0.42,2.977 -0.977,4.418c-0.975,2.637 -2.594,5.022 -4.423,7.199c-0.278,0.331 -0.559,0.65 -0.841,0.971c-0.657,0.749 -1.313,1.476 -1.963,2.176c-0.905,1.274 -1.887,2.489 -2.972,3.831c-5.301,6.006 -12.847,13.149 -24.136,21.791c-3.328,2.397 -6.672,4.742 -10.056,7.082c8.343,4.984 16.347,10.724 23.081,16.449c0.799,0.68 1.687,1.383 2.49,2.097c19.13,16.999 38.996,43.368 38.996,68.923l0,33.521z" id="svg_14"/>
|
||||
</g>
|
||||
</g>
|
||||
<g display="none" id="svg_15">
|
||||
<g display="inline" id="svg_16">
|
||||
<path d="m312.499,160.826c-9.98,2.131 -19.499,5.134 -28.499,8.871l0,0.309c0,-0.051 -0.324,-0.099 -0.449,-0.15c-13.49,5.7 -25.679,13.103 -35.916,21.847c6.85,0.617 12.365,6.309 12.365,13.318l0,13.871c0,7.422 -6.578,13.438 -14,13.438s-14,-6.017 -14,-13.438l0,-12.16c-5,7.508 -10,15.638 -14,24.243l0,10.724c0,11.767 40.019,38.51 46.415,42.736c1.735,0.958 3.005,1.504 3.005,1.504c4.505,2.922 10.252,4.531 16.122,4.582c0.101,-0.005 0.459,-0.014 0.459,-0.014l0,0.023c6,-0.006 13.014,-1.989 17.751,-5.526c0,0 47.249,-30.509 47.249,-43.305l0,-51.547c-0.001,-19.131 -17.791,-33.327 -36.502,-29.326zm15.501,58.072c0,7.422 -6.078,13.438 -13.5,13.438c-7.422,0 -13.5,-6.017 -13.5,-13.438l0,-13.871c0,-7.422 6.078,-13.438 13.5,-13.438c7.416,0 13.5,6.017 13.5,13.438l0,13.871z" id="svg_17"/>
|
||||
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm105.455,311.424c-9,-3.568 -18.511,-5.577 -28.63,-5.577c-3.612,0 -7.204,0.26 -10.68,0.748c0.319,0.28 0.666,0.55 0.985,0.834c18.856,16.755 38.325,42.746 38.325,67.934l0,33.04c0,5.207 -4.685,8.59 -9.892,8.59l-102.07,0l-8.427,0l-81.001,0c-5.207,0 -9.609,-3.382 -9.609,-8.59l0,-37.423c0,-11.115 4.2,-22.991 12.263,-35.297c6.493,-9.909 15.656,-19.963 26.374,-29.156c-3.329,-0.447 -6.682,-0.68 -10.135,-0.68c-10.119,0 -19.502,2.01 -28.502,5.577l0,-124.877c0,-58.13 47.049,-105.257 105.179,-105.257c0.091,0 0.147,0.001 0.266,0.003c0.074,0 0.175,-0.003 0.249,-0.003c58.13,0 105.306,47.127 105.306,105.257l0,124.877l-0.001,0z" id="svg_18"/>
|
||||
</g>
|
||||
</g>
|
||||
<g display="none" id="svg_19">
|
||||
<g display="inline" id="svg_20">
|
||||
<path d="m349.599,245.278l-79.122,40.948c-0.082,0.043 -0.156,0.096 -0.237,0.141c-5.525,2.007 -10.592,5.215 -14.873,9.495c-15.891,15.892 -15.891,41.749 0,57.642c7.698,7.698 17.933,11.938 28.82,11.938s21.122,-4.24 28.82,-11.938c4.63,-4.63 7.901,-10.109 9.832,-15.938l40.798,-78.203c2.104,-4.033 1.351,-8.964 -1.86,-12.185c-3.209,-3.22 -8.138,-3.988 -12.178,-1.9z" id="svg_21"/>
|
||||
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm157.069,312.993l15.415,0c-1.535,20 -6.792,40.567 -15.092,58.692c-0.006,0.018 -0.01,-0.01 -0.017,0.008c-0.088,0.193 -0.829,1.228 -0.961,1.506c-2.081,3.524 -5.906,4.794 -10.295,4.794l-24.507,0l-267.066,0c-4.166,0 -7.495,-1.264 -9.675,-5.366c-0.216,-0.455 -0.974,-1.57 -0.981,-1.59c-8.851,-19.329 -14.24,-40.044 -15.346,-64.044l15.672,0c5.76,0 10.428,-3.741 10.428,-9.5c0,-5.759 -4.669,-9.5 -10.428,-9.5l-15.463,0c2.802,-38 19.118,-75.51 44.351,-103.289l11.527,11.723c2.036,2.036 4.741,3.054 7.41,3.054c2.669,0 5.41,-1.018 7.446,-3.054c4.073,-4.073 4.217,-10.676 0.144,-14.749l-11.042,-11.331c28.262,-24.456 64.866,-39.891 104.866,-41.796l0,15.959c0,5.759 3.74,10.428 9.5,10.428s9.5,-4.669 9.5,-10.428l0,-15.707c39,2.862 76.041,19.222 103.786,44.5l-10.537,10.895c-4.073,4.073 -4.073,10.688 0,14.761c2.036,2.036 4.705,3.079 7.374,3.079c2.669,0 5.338,-0.969 7.374,-3.004l10.688,-10.588c24.41,28.294 39.792,62.547 41.637,104.547l-15.708,0c-5.76,0 -10.428,4.241 -10.428,10c0,5.759 4.668,10 10.428,10z" id="svg_22"/>
|
||||
</g>
|
||||
</g>
|
||||
<g display="none" id="svg_23">
|
||||
<g display="inline" id="svg_24">
|
||||
<path d="m343.258,200.171c-9.089,0.001 -18.289,2.508 -26.521,7.757c-23.007,14.673 -29.763,45.218 -15.09,68.225c9.423,14.775 25.389,22.847 41.704,22.847c9.089,0 18.29,-2.508 26.521,-7.757c23.007,-14.673 29.762,-45.218 15.09,-68.224c-9.423,-14.777 -25.389,-22.849 -41.704,-22.848zm34.669,57.087c-2.05,9.266 -7.655,17.181 -15.657,22.284c-5.724,3.651 -12.455,5.58 -19.199,5.58c-5.983,0 -11.071,-1.48 -17.071,-4.187l0,-40.485c0,-6.296 5.664,-12.45 11.959,-12.45l34.328,0c0.332,2 0.629,1.465 0.94,1.954c5.103,8.001 6.75,18.037 4.7,27.304z" id="svg_25"/>
|
||||
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm-78.545,383.993l-82,0l0,-77.35c0,-6.296 5.466,-10.65 11.762,-10.65l59.472,0c6.295,0 10.766,4.355 10.766,10.65l0,77.35zm104,0l-84,0l0,-228.013c0,-6.296 4.569,-10.987 10.865,-10.987l54.982,0c-21.431,13 -35.968,35.007 -41.526,59.974c-5.613,25.215 -0.753,50.864 13.108,72.597c11.19,17.547 25.571,30.66 46.571,38.006l0,68.423zm17,0l0,-63.149c6,0.938 11.241,1.433 16.863,1.433c4.727,0 9.725,-0.35 14.392,-1.044l37.24,58.161c1.098,1.722 2.327,3.599 3.647,4.599l-72.142,0zm115.213,-4.086c-4.226,2.695 -8.946,3.982 -13.613,3.982c-8.373,0 -16.569,-4.144 -21.405,-11.727l-42.358,-66.416c-6.831,1.782 -13.783,2.653 -20.694,2.653c-27.275,0 -53.884,-13.574 -69.652,-38.297c-24.541,-38.48 -13.102,-89.658 25.378,-114.199c13.747,-8.767 29.081,-12.948 44.238,-12.948c27.271,0 53.959,13.542 69.733,38.275c19.805,31.054 16.076,70.435 -6.202,97.302l42.32,66.357c7.531,11.809 4.063,27.488 -7.745,35.018z" id="svg_26"/>
|
||||
</g>
|
||||
</g>
|
||||
<g display="none" id="svg_27">
|
||||
<g display="inline" id="svg_28">
|
||||
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm179.255,300.845c-1.873,8.407 -10.159,13.275 -18.415,10.817l-22.428,-6.677c-2.855,9.611 -6.676,18.805 -11.367,27.462l20.632,11.166c7.575,4.1 9.987,13.398 5.36,20.662c0,0 -10.312,16.191 -23.125,29.004c-12.869,12.869 -29.193,23.244 -29.193,23.244c-7.269,4.621 -16.571,2.203 -20.67,-5.372l-11.112,-20.532c-8.656,4.702 -17.851,8.533 -27.463,11.398l6.674,22.419c2.458,8.255 -2.411,16.535 -10.82,18.401c0,0 -18.741,4.157 -36.861,4.157c-18.199,0 -37.079,-4.206 -37.079,-4.206c-8.407,-1.873 -13.275,-10.16 -10.817,-18.415l6.637,-22.292c-9.623,-2.852 -18.828,-6.674 -27.495,-11.366l-11.091,20.495c-4.1,7.575 -13.398,9.987 -20.662,5.36c0,0 -16.191,-10.312 -29.004,-23.126c-12.869,-12.869 -23.245,-29.193 -23.245,-29.193c-4.62,-7.269 -2.203,-16.57 5.372,-20.67l20.393,-11.036c-4.704,-8.666 -8.536,-17.873 -11.4,-27.496l-22.278,6.632c-8.255,2.458 -16.535,-2.412 -18.4,-10.82c0,0 -4.157,-18.74 -4.157,-36.861c0,-18.199 4.206,-37.079 4.206,-37.079c1.873,-8.407 10.159,-13.275 18.415,-10.817l22.212,6.613c2.863,-9.624 6.695,-18.831 11.399,-27.497l-20.448,-11.066c-7.575,-4.1 -9.987,-13.398 -5.36,-20.662c0,0 10.312,-16.191 23.125,-29.004c12.869,-12.869 29.193,-23.245 29.193,-23.245c7.269,-4.62 16.571,-2.203 20.67,5.372l11.057,20.431c8.667,-4.693 17.874,-8.515 27.497,-11.367l-6.653,-22.348c-2.458,-8.255 2.412,-16.535 10.82,-18.401c0,0 18.741,-4.157 36.861,-4.157c18.199,0 37.079,4.206 37.079,4.206c8.407,1.873 13.275,10.16 10.817,18.415l-6.653,22.348c9.612,2.866 18.808,6.697 27.464,11.4l11.14,-20.585c4.1,-7.575 13.398,-9.987 20.662,-5.36c0,0 16.191,10.312 29.004,23.126c12.869,12.869 23.244,29.193 23.244,29.193c4.62,7.269 2.203,16.57 -5.372,20.67l-20.571,11.133c4.69,8.657 8.512,17.852 11.366,27.463l22.49,-6.695c8.255,-2.458 16.535,2.412 18.4,10.82c0,0 4.157,18.74 4.157,36.861c-0.001,18.197 -4.207,37.077 -4.207,37.077z" id="svg_29"/>
|
||||
<circle cx="283.779" cy="287.886" r="37.594" id="svg_30"/>
|
||||
</g>
|
||||
</g>
|
||||
<g display="none" id="svg_31">
|
||||
<g display="inline" id="svg_32">
|
||||
<rect x="326.113" y="201.398" transform="matrix(-0.7071,-0.7071,0.7071,-0.7071,472.8639,611.4978) " width="73.929" height="12.835" id="svg_33"/>
|
||||
<polygon points="170.708,383.917 187.339,400.547 225.134,390.423 180.832,346.122 " id="svg_34"/>
|
||||
<path d="m413.02,192.538c-0.016,-0.268 -0.051,-0.534 -0.072,-0.801c-0.049,-0.602 -0.095,-1.205 -0.173,-1.804c-0.037,-0.284 -0.094,-0.565 -0.138,-0.848c-0.089,-0.58 -0.174,-1.161 -0.291,-1.737c-0.062,-0.305 -0.146,-0.606 -0.215,-0.909c-0.126,-0.55 -0.246,-1.102 -0.397,-1.647c-0.09,-0.325 -0.205,-0.645 -0.304,-0.968c-0.159,-0.518 -0.31,-1.037 -0.492,-1.548c-0.123,-0.346 -0.272,-0.685 -0.406,-1.028c-0.188,-0.48 -0.365,-0.964 -0.574,-1.438c-0.162,-0.367 -0.35,-0.724 -0.525,-1.087c-0.211,-0.44 -0.41,-0.885 -0.64,-1.318c-0.206,-0.388 -0.441,-0.764 -0.661,-1.146c-0.229,-0.396 -0.444,-0.798 -0.688,-1.187c-0.259,-0.413 -0.549,-0.81 -0.826,-1.214c-0.237,-0.345 -0.458,-0.698 -0.708,-1.037c-0.344,-0.466 -0.721,-0.915 -1.089,-1.368c-0.213,-0.262 -0.409,-0.532 -0.63,-0.789c-0.604,-0.703 -1.239,-1.388 -1.905,-2.054l-0.004,0.004c-0.028,-0.03 -0.053,-0.062 -0.083,-0.091c-1.556,-1.556 -4.079,-1.556 -5.634,0c-1.556,1.556 -1.556,4.078 0,5.634c0.042,0.042 0.086,0.078 0.129,0.117c0.172,0.173 0.327,0.356 0.494,0.532c7.992,8.41 9.548,19.621 6.063,30.056c-0.915,1.556 -0.709,3.588 0.626,4.924c1.585,1.585 4.154,1.585 5.739,0c0.537,-0.537 0.888,-1.188 1.06,-1.874c0.08,-0.212 0.141,-0.429 0.218,-0.642c0.211,-0.588 0.421,-1.175 0.601,-1.771c0.072,-0.238 0.128,-0.48 0.195,-0.719c0.17,-0.607 0.339,-1.215 0.478,-1.829c0.054,-0.239 0.091,-0.48 0.141,-0.719c0.127,-0.619 0.253,-1.238 0.348,-1.862c0.036,-0.239 0.056,-0.479 0.088,-0.719c0.083,-0.627 0.166,-1.254 0.217,-1.884c0.02,-0.243 0.022,-0.487 0.037,-0.731c0.039,-0.627 0.077,-1.254 0.084,-1.882c0.003,-0.255 -0.012,-0.509 -0.015,-0.764c-0.007,-0.618 -0.012,-1.236 -0.048,-1.852z" id="svg_35"/>
|
||||
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm129.63,207.768l-3.086,3.086l-19.966,19.966l-143.453,143.453c-1.009,1.736 -2.674,3.036 -4.666,3.569l-54.971,14.725c-0.002,0 -0.003,0.001 -0.004,0.001l-31.304,8.386c-2.657,0.712 -5.493,-0.048 -7.438,-1.993c-1.945,-1.945 -2.705,-4.781 -1.993,-7.438l8.386,-31.304l14.292,-53.352c0.046,-1.907 0.791,-3.801 2.247,-5.257l144.847,-144.847l19.966,-19.966l3.086,-3.086c20.418,-20.418 53.64,-20.418 74.057,0c20.417,20.418 20.417,53.639 0,74.057z" id="svg_36"/>
|
||||
</g>
|
||||
</g>
|
||||
<g display="none" id="svg_37">
|
||||
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm-62.545,379.174c0,11.498 -9.321,20.819 -20.819,20.819l-29.363,0c-11.497,0 -20.818,-9.321 -20.818,-20.819l0,-29.363c0,-11.498 9.321,-20.819 20.819,-20.819l29.363,0c11.498,0 20.819,9.321 20.819,20.819l0,29.363l-0.001,0zm0,-100c0,11.498 -9.321,20.819 -20.819,20.819l-29.363,0c-11.497,0 -20.818,-9.321 -20.818,-20.819l0,-29.363c0,-11.498 9.321,-20.819 20.819,-20.819l29.363,0c11.498,0 20.819,9.321 20.819,20.819l0,29.363l-0.001,0zm0,-102c0,11.498 -9.321,20.819 -20.819,20.819l-29.363,0c-11.497,0 -20.818,-9.321 -20.818,-20.819l0,-29.363c0,-11.498 9.321,-20.819 20.819,-20.819l29.363,0c11.498,0 20.819,9.321 20.819,20.819l0,29.363l-0.001,0zm212,186.032c0,11.48 -9.307,20.787 -20.787,20.787l-138.426,0c-11.48,0 -20.787,-9.307 -20.787,-20.787l0,-0.425c0,-11.481 9.307,-20.787 20.787,-20.787l138.425,0c11.481,0 20.787,9.307 20.787,20.787l0,0.425l0.001,0zm0,-98c0,11.48 -9.307,20.787 -20.787,20.787l-138.426,0c-11.48,0 -20.787,-9.307 -20.787,-20.787l0,-0.425c0,-11.481 9.307,-20.787 20.787,-20.787l138.425,0c11.481,0 20.787,9.307 20.787,20.787l0,0.425l0.001,0zm0,-101c0,11.48 -9.307,20.787 -20.787,20.787l-138.426,0c-11.48,0 -20.787,-9.307 -20.787,-20.787l0,-0.425c0,-11.481 9.307,-20.787 20.787,-20.787l138.425,0c11.481,0 20.787,9.307 20.787,20.787l0,0.425l0.001,0z" id="svg_38"/>
|
||||
</g>
|
||||
<g display="none" id="svg_39">
|
||||
<g display="inline" id="svg_40">
|
||||
<rect x="187" y="256" width="192" height="125" id="svg_41"/>
|
||||
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm39.455,133.723l0,-19.607c0,-11.106 9.394,-20.109 20.5,-20.109s20.5,9.003 20.5,20.109l0,19.607l0,20.612c0,11.106 -9.394,20.109 -20.5,20.109s-20.5,-9.003 -20.5,-20.109l0,-20.612zm-119,0l0,-19.607c0,-11.106 8.394,-20.109 19.5,-20.109s19.5,9.003 19.5,20.109l0,19.607l0,20.612c0,11.106 -8.394,20.109 -19.5,20.109s-19.5,-9.003 -19.5,-20.109l0,-20.612zm217,242.478c0,11.019 -9.318,19.792 -20.337,19.792l-233.587,0c-11.018,0 -21.076,-8.773 -21.076,-19.792l0,-222.527c0,-11.019 10.058,-18.681 21.076,-18.681l19.924,0l0,19.342c0,19.959 17.041,36.197 37,36.197s37,-16.238 37,-36.197l0,-19.342l47,0l0,19.342c0,19.959 15.541,36.197 35.5,36.197s35.5,-16.238 35.5,-36.197l0,-19.342l21.663,0c11.019,0 20.337,7.662 20.337,18.681l0,222.527z" id="svg_42"/>
|
||||
</g>
|
||||
</g>
|
||||
<g display="none" id="svg_43">
|
||||
<g display="inline" id="svg_44">
|
||||
<path d="m348,411.767c7.039,0 12,-5.706 12,-12.745l0,-175.1c0,-7.039 -4.961,-12.745 -12,-12.745s-12,5.706 -12,12.745l0,175.1c0,7.038 4.961,12.745 12,12.745z" id="svg_45"/>
|
||||
<path d="m222,411.767c7.039,0 14,-5.706 14,-12.745l0,-175.1c0,-7.039 -6.961,-12.745 -14,-12.745s-14,5.706 -14,12.745l0,175.1c0,7.038 6.961,12.745 14,12.745z" id="svg_46"/>
|
||||
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm109.455,185.128l0,212.78c0,12.095 -10.073,23.085 -22.167,23.085l-173.233,0c-12.094,0 -21.6,-10.99 -21.6,-23.085l0,-212.78l0,-32.135l217,0l0,32.135zm0,-47.135l-217,0l0,-10.841c0,-12.095 9.505,-21.159 21.6,-21.159l55.634,0c0.497,-12 10.163,-21 21.942,-21l18.081,0c11.78,0 21.445,9 21.942,21l55.634,0c12.094,0 22.167,9.064 22.167,21.159l0,10.841z" id="svg_47"/>
|
||||
<path d="m284,411.767c7.039,0 13,-5.706 13,-12.745l0,-175.1c0,-7.039 -5.961,-12.745 -13,-12.745s-13,5.706 -13,12.745l0,175.1c0,7.038 5.961,12.745 13,12.745z" id="svg_48"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="svg_49">
|
||||
<g id="svg_50">
|
||||
<polygon points="196.661,391.813 223.84,340.524 381.46,229.666 196.625,326.192 " id="svg_51" fill="#03a9f4"/>
|
||||
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm160.376,182.583l-117.664,174.425c-3.558,5.273 -10.504,7.04 -16.15,4.11l-47.144,-24.478l-64.897,56.276c-2.322,2.013 -5.247,3.062 -8.206,3.062c-1.752,0 -3.516,-0.368 -5.173,-1.119c-4.456,-2.023 -7.325,-6.456 -7.345,-11.349l-0.386,-91.883l-70.236,-36.656c-4.681,-2.443 -7.327,-7.562 -6.612,-12.794c0.715,-5.233 4.635,-9.454 9.801,-10.553l321.028,-68.291c4.992,-1.061 10.13,1.012 12.985,5.245c2.854,4.232 2.854,9.773 -0.001,14.005z" id="svg_52" fill="#03a9f4"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 21 KiB |
47
logo/logo.html
Normal file
47
logo/logo.html
Normal file
@@ -0,0 +1,47 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
h1 {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
margin-left: -4px;
|
||||
padding: 0px;
|
||||
font-size: 68px;
|
||||
font-family: 'Source Code Pro', monospace;
|
||||
font-weight: bold;
|
||||
line-height: 0.8em;
|
||||
letter-spacing: -3px;
|
||||
color: #333;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-shadow:
|
||||
1px 0px 0px #ccc, 0px 1px 0px #eee,
|
||||
2px 1px 0px #ccc, 1px 2px 0px #eee,
|
||||
3px 2px 0px #ccc, 2px 3px 0px #eee,
|
||||
4px 3px 0px #ccc;
|
||||
}
|
||||
h1 > span {
|
||||
display: block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
padding-top: 6px;
|
||||
}
|
||||
img {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>
|
||||
<span>ftp-srv</span>
|
||||
</h1>
|
||||
<img src="icon.svg" width="76px" height="76px" />
|
||||
</body>
|
||||
</html>
|
||||
4330
package-lock.json
generated
Normal file
4330
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
43
package.json
43
package.json
@@ -8,13 +8,17 @@
|
||||
"ftp-srv",
|
||||
"ftp-svr",
|
||||
"ftpd",
|
||||
"server"
|
||||
"server",
|
||||
"ftpserver"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "src/index.js",
|
||||
"main": "ftp-srv.js",
|
||||
"files": [
|
||||
"src"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/stewarttylerr/ftp-srv"
|
||||
"url": "https://github.com/trs/ftp-srv"
|
||||
},
|
||||
"scripts": {
|
||||
"pre-release": "npm-run-all verify test:coverage build ",
|
||||
@@ -46,32 +50,33 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"bunyan": "^1.8.9",
|
||||
"date-fns": "^1.28.2",
|
||||
"bunyan": "^1.8.10",
|
||||
"lodash": "^4.17.4",
|
||||
"minimist-string": "^1.0.2",
|
||||
"moment": "^2.18.1",
|
||||
"uuid": "^3.0.1",
|
||||
"when": "^3.7.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^3.5.0",
|
||||
"chai": "^4.0.2",
|
||||
"chokidar-cli": "1.2.0",
|
||||
"coveralls": "2.11.15",
|
||||
"cross-env": "3.1.4",
|
||||
"cz-customizable": "4.0.0",
|
||||
"coveralls": "2.13.1",
|
||||
"cross-env": "5.0.1",
|
||||
"cz-customizable": "5.0.0",
|
||||
"cz-customizable-ghooks": "1.5.0",
|
||||
"dotenv": "^4.0.0",
|
||||
"eslint": "3.14.1",
|
||||
"eslint-config-google": "0.7.1",
|
||||
"eslint-plugin-node": "3.0.5",
|
||||
"eslint": "3.19.0",
|
||||
"eslint-config-google": "0.8.0",
|
||||
"eslint-plugin-node": "5.0.0",
|
||||
"ftp": "^0.3.10",
|
||||
"husky": "0.13.1",
|
||||
"html-convert": "^2.1.7",
|
||||
"husky": "0.13.4",
|
||||
"istanbul": "0.4.5",
|
||||
"mocha": "3.2.0",
|
||||
"npm-run-all": "4.0.1",
|
||||
"rimraf": "2.5.4",
|
||||
"semantic-release": "^6.3.2",
|
||||
"sinon": "^2.1.0"
|
||||
"mocha": "3.4.2",
|
||||
"mocha-pretty-bunyan-nyan": "^1.0.4",
|
||||
"npm-run-all": "4.0.2",
|
||||
"rimraf": "2.6.1",
|
||||
"semantic-release": "^6.3.6",
|
||||
"sinon": "^2.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.x",
|
||||
|
||||
@@ -11,9 +11,25 @@ class FtpCommands {
|
||||
this.whitelist = _.get(this.connection, 'server.options.whitelist', []).map(cmd => _.upperCase(cmd));
|
||||
}
|
||||
|
||||
parse(message) {
|
||||
const [directive, ...args] = message.replace(/"/g, '').split(' ');
|
||||
const command = {
|
||||
directive: _.chain(directive).trim().toUpper().value(),
|
||||
arg: _.compact(args).join(' ') || null,
|
||||
raw: message
|
||||
};
|
||||
return command;
|
||||
}
|
||||
|
||||
handle(command) {
|
||||
const log = this.connection.log.child({command});
|
||||
log.trace('Handle command');
|
||||
if (typeof command === 'string') command = this.parse(command);
|
||||
|
||||
// Obfuscate password from logs
|
||||
const logCommand = _.clone(command);
|
||||
if (logCommand.directive === 'PASS') logCommand.arg = '********';
|
||||
|
||||
const log = this.connection.log.child({directive: command.directive});
|
||||
log.trace({command: logCommand}, 'Handle command');
|
||||
|
||||
if (!REGISTRY.hasOwnProperty(command.directive)) {
|
||||
return this.connection.reply(402, 'Command not allowed');
|
||||
|
||||
@@ -5,6 +5,6 @@ module.exports = {
|
||||
handler: function (args) {
|
||||
return stor.call(this, args);
|
||||
},
|
||||
syntax: '{{cmd}} [path]',
|
||||
syntax: '{{cmd}} <path>',
|
||||
description: 'Append to a file'
|
||||
};
|
||||
|
||||
@@ -1,27 +1,41 @@
|
||||
const _ = require('lodash');
|
||||
const tls = require('tls');
|
||||
|
||||
module.exports = {
|
||||
directive: 'AUTH',
|
||||
handler: function ({command} = {}) {
|
||||
const method = _.upperCase(command._[1]);
|
||||
const method = _.upperCase(command.arg);
|
||||
|
||||
switch (method) {
|
||||
case 'TLS': return handleTLS.call(this);
|
||||
case 'SSL': return handleSSL.call(this);
|
||||
default: return this.reply(504);
|
||||
}
|
||||
},
|
||||
syntax: '{{cmd}} [type]',
|
||||
syntax: '{{cmd}} <type>',
|
||||
description: 'Set authentication mechanism',
|
||||
flags: {
|
||||
no_auth: true
|
||||
no_auth: true,
|
||||
feat: 'AUTH TLS'
|
||||
}
|
||||
};
|
||||
|
||||
function handleTLS() {
|
||||
return this.reply(504);
|
||||
}
|
||||
if (!this.server._tls) return this.reply(504);
|
||||
|
||||
function handleSSL() {
|
||||
return this.reply(504);
|
||||
return this.reply(234)
|
||||
.then(() => {
|
||||
const secureContext = tls.createSecureContext(this.server._tls);
|
||||
const secureSocket = new tls.TLSSocket(this.commandSocket, {
|
||||
isServer: true,
|
||||
secureContext
|
||||
});
|
||||
['data', 'timeout', 'end', 'close', 'drain', 'error'].forEach(event => {
|
||||
function forwardEvent() {
|
||||
this.emit.apply(this, arguments);
|
||||
}
|
||||
secureSocket.on(event, forwardEvent.bind(this.commandSocket, event));
|
||||
});
|
||||
this.commandSocket = secureSocket;
|
||||
this.secure = true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ const cwd = require('./cwd').handler;
|
||||
module.exports = {
|
||||
directive: ['CDUP', 'XCUP'],
|
||||
handler: function (args) {
|
||||
args.command._ = [args.command._[0], '..'];
|
||||
args.command.arg = '..';
|
||||
return cwd.call(this, args);
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
|
||||
@@ -7,7 +7,7 @@ module.exports = {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.chdir) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return when.try(this.fs.chdir.bind(this.fs), command._[1])
|
||||
return when.try(this.fs.chdir.bind(this.fs), command.arg)
|
||||
.then(cwd => {
|
||||
const path = cwd ? `"${escapePath(cwd)}"` : undefined;
|
||||
return this.reply(250, path);
|
||||
@@ -17,6 +17,6 @@ module.exports = {
|
||||
return this.reply(550, err.message);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}}[path]',
|
||||
syntax: '{{cmd}} <path>',
|
||||
description: 'Change working directory'
|
||||
};
|
||||
|
||||
@@ -6,15 +6,15 @@ module.exports = {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.delete) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return when.try(this.fs.delete.bind(this.fs), command._[1])
|
||||
return when.try(this.fs.delete.bind(this.fs), command.arg)
|
||||
.then(() => {
|
||||
return this.reply(250);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550);
|
||||
return this.reply(550, err.message);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} [path]',
|
||||
syntax: '{{cmd}} <path>',
|
||||
description: 'Delete file'
|
||||
};
|
||||
|
||||
22
src/commands/registration/eprt.js
Normal file
22
src/commands/registration/eprt.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const _ = require('lodash');
|
||||
const ActiveConnector = require('../../connector/active');
|
||||
|
||||
const FAMILY = {
|
||||
1: 4,
|
||||
2: 6
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
directive: 'EPRT',
|
||||
handler: function ({command} = {}) {
|
||||
this.connector = new ActiveConnector(this);
|
||||
const [protocol, ip, port] = _.compact(command.arg.split('|'));
|
||||
const family = FAMILY[protocol];
|
||||
if (!family) return this.reply(502, 'Unknown network protocol');
|
||||
|
||||
return this.connector.setupConnection(ip, port, family)
|
||||
.then(() => this.reply(200));
|
||||
},
|
||||
syntax: '{{cmd}} |<protocol>|<address>|<port>|',
|
||||
description: 'Specifies an address and port to which the server should connect'
|
||||
};
|
||||
16
src/commands/registration/epsv.js
Normal file
16
src/commands/registration/epsv.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const PassiveConnector = require('../../connector/passive');
|
||||
|
||||
module.exports = {
|
||||
directive: 'EPSV',
|
||||
handler: function () {
|
||||
this.connector = new PassiveConnector(this);
|
||||
return this.connector.setupServer()
|
||||
.then(server => {
|
||||
const {port} = server.address();
|
||||
|
||||
return this.reply(229, `EPSV OK (|||${port}|)`);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} [<protocol>]',
|
||||
description: 'Initiate passive mode'
|
||||
};
|
||||
@@ -11,7 +11,9 @@ module.exports = {
|
||||
return feats;
|
||||
}, [])
|
||||
.map(feat => ` ${feat}`);
|
||||
return this.reply(211, 'Extensions supported', ...features, 'END');
|
||||
return features.length
|
||||
? this.reply(211, 'Extensions supported', ...features, 'End')
|
||||
: this.reply(211, 'No features');
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'Get the feature list implemented by the server',
|
||||
|
||||
@@ -4,7 +4,7 @@ module.exports = {
|
||||
directive: 'HELP',
|
||||
handler: function ({command} = {}) {
|
||||
const registry = require('../registry');
|
||||
const directive = _.upperCase(command._[1]);
|
||||
const directive = _.upperCase(command.arg);
|
||||
if (directive) {
|
||||
if (!registry.hasOwnProperty(directive)) return this.reply(502, `Unknown command ${directive}.`);
|
||||
|
||||
@@ -16,7 +16,7 @@ module.exports = {
|
||||
return this.reply(211, 'Supported commands:', ...supportedCommands, 'Use "HELP [command]" for syntax help.');
|
||||
}
|
||||
},
|
||||
syntax: '{{cmd}} [command(optional)]',
|
||||
syntax: '{{cmd}} [<command>]',
|
||||
description: 'Returns usage documentation on a command if specified, else a general help document is returned',
|
||||
flags: {
|
||||
no_auth: true
|
||||
|
||||
@@ -8,18 +8,20 @@ module.exports = {
|
||||
directive: 'LIST',
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
||||
if (!this.fs.list) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
const simple = command.directive === 'NLST';
|
||||
|
||||
let dataSocket;
|
||||
const directory = command._[1] || '.';
|
||||
const path = command.arg || '.';
|
||||
return this.connector.waitForConnection()
|
||||
.then(socket => {
|
||||
this.commandSocket.pause();
|
||||
dataSocket = socket;
|
||||
})
|
||||
.then(() => when.try(this.fs.list.bind(this.fs), directory))
|
||||
.then(() => when.try(this.fs.get.bind(this.fs), path))
|
||||
.then(stat => stat.isDirectory() ? when.try(this.fs.list.bind(this.fs), path) : [stat])
|
||||
.then(files => {
|
||||
const getFileMessage = file => {
|
||||
if (simple) return file.name;
|
||||
@@ -48,13 +50,13 @@ module.exports = {
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(err.code || 451, err.message || 'No directory');
|
||||
return this.reply(451, err.message || 'No directory');
|
||||
})
|
||||
.finally(() => {
|
||||
this.connector.end();
|
||||
this.commandSocket.resume();
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} [path(optional)]',
|
||||
syntax: '{{cmd}} [<path>]',
|
||||
description: 'Returns information of a file or directory if specified, else information of the current working directory is returned'
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const when = require('when');
|
||||
const format = require('date-fns/format');
|
||||
const moment = require('moment');
|
||||
|
||||
module.exports = {
|
||||
directive: 'MDTM',
|
||||
@@ -7,17 +7,17 @@ module.exports = {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return when.try(this.fs.get.bind(this.fs), command._[1])
|
||||
return when.try(this.fs.get.bind(this.fs), command.arg)
|
||||
.then(fileStat => {
|
||||
const modificationTime = format(fileStat.mtime, 'YYYYMMDDHHmmss.SSS');
|
||||
const modificationTime = moment.utc(fileStat.mtime).format('YYYYMMDDHHmmss.SSS');
|
||||
return this.reply(213, modificationTime);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550);
|
||||
return this.reply(550, err.message);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} [path]',
|
||||
syntax: '{{cmd}} <path>',
|
||||
description: 'Return the last-modified time of a specified file',
|
||||
flags: {
|
||||
feat: 'MDTM'
|
||||
|
||||
@@ -7,16 +7,16 @@ module.exports = {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.mkdir) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return when.try(this.fs.mkdir.bind(this.fs), command._[1])
|
||||
return when.try(this.fs.mkdir.bind(this.fs), command.arg)
|
||||
.then(dir => {
|
||||
const path = dir ? `"${escapePath(dir)}"` : undefined;
|
||||
return this.reply(257, path);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550);
|
||||
return this.reply(550, err.message);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}}[path]',
|
||||
syntax: '{{cmd}} <path>',
|
||||
description: 'Make directory'
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
module.exports = {
|
||||
directive: 'MODE',
|
||||
handler: function ({command} = {}) {
|
||||
return this.reply(/^S$/i.test(command._[1]) ? 200 : 504);
|
||||
return this.reply(/^S$/i.test(command.arg) ? 200 : 504);
|
||||
},
|
||||
syntax: '{{cmd}} [mode]',
|
||||
syntax: '{{cmd}} <mode>',
|
||||
description: 'Sets the transfer mode (Stream, Block, or Compressed)',
|
||||
flags: {
|
||||
obsolete: true
|
||||
|
||||
@@ -5,6 +5,6 @@ module.exports = {
|
||||
handler: function (args) {
|
||||
return list.call(this, args);
|
||||
},
|
||||
syntax: '{{cmd}} [path(optional)]',
|
||||
syntax: '{{cmd}} [<path>]',
|
||||
description: 'Returns a list of file names in a specified directory'
|
||||
};
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
directive: 'PASS',
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.username) return this.reply(503);
|
||||
if (this.username && this.authenticated &&
|
||||
_.get(this, 'server.options.anonymous') === true) return this.reply(230);
|
||||
if (this.authenticated) return this.reply(202);
|
||||
|
||||
// 332 : require account name (ACCT)
|
||||
|
||||
const password = command._[1];
|
||||
const password = command.arg;
|
||||
if (!password) return this.reply(501, 'Must provide password');
|
||||
return this.login(this.username, password)
|
||||
.then(() => {
|
||||
return this.reply(230);
|
||||
@@ -19,7 +17,7 @@ module.exports = {
|
||||
return this.reply(530, err.message || 'Authentication failed');
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} [password]',
|
||||
syntax: '{{cmd}} <password>',
|
||||
description: 'Authentication password',
|
||||
flags: {
|
||||
no_auth: true
|
||||
|
||||
13
src/commands/registration/pbsz.js
Normal file
13
src/commands/registration/pbsz.js
Normal file
@@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
directive: 'PBSZ',
|
||||
handler: function ({command} = {}) {
|
||||
if (!this.secure) return this.reply(202, 'Not suppored');
|
||||
this.bufferSize = parseInt(command.arg, 10);
|
||||
return this.reply(200, this.bufferSize === 0 ? 'OK' : 'Buffer too large: PBSZ=0');
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'Protection Buffer Size',
|
||||
flags: {
|
||||
no_auth: true
|
||||
}
|
||||
};
|
||||
@@ -5,7 +5,7 @@ module.exports = {
|
||||
directive: 'PORT',
|
||||
handler: function ({command} = {}) {
|
||||
this.connector = new ActiveConnector(this);
|
||||
const rawConnection = _.get(command, '_[1]', '').split(',');
|
||||
const rawConnection = _.get(command, 'arg', '').split(',');
|
||||
if (rawConnection.length !== 6) return this.reply(425);
|
||||
|
||||
const ip = rawConnection.slice(0, 4).join('.');
|
||||
@@ -15,6 +15,6 @@ module.exports = {
|
||||
return this.connector.setupConnection(ip, port)
|
||||
.then(() => this.reply(200));
|
||||
},
|
||||
syntax: '{{cmd}} x,x,x,x,y,y',
|
||||
syntax: '{{cmd}} <x>,<x>,<x>,<x>,<y>,<y>',
|
||||
description: 'Specifies an address and port to which the server should connect'
|
||||
};
|
||||
|
||||
22
src/commands/registration/prot.js
Normal file
22
src/commands/registration/prot.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
directive: 'PROT',
|
||||
handler: function ({command} = {}) {
|
||||
if (!this.secure) return this.reply(202, 'Not suppored');
|
||||
if (!this.bufferSize && typeof this.bufferSize !== 'number') return this.reply(503);
|
||||
|
||||
switch (_.toUpper(command.arg)) {
|
||||
case 'P': return this.reply(200, 'OK');
|
||||
case 'C':
|
||||
case 'S':
|
||||
case 'E': return this.reply(536, 'Not supported');
|
||||
default: return this.reply(504);
|
||||
}
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'Data Channel Protection Level',
|
||||
flags: {
|
||||
no_auth: true
|
||||
}
|
||||
};
|
||||
@@ -12,7 +12,7 @@ module.exports = {
|
||||
this.commandSocket.pause();
|
||||
dataSocket = socket;
|
||||
})
|
||||
.then(() => when.try(this.fs.read.bind(this.fs), command._[1]))
|
||||
.then(() => when.try(this.fs.read.bind(this.fs), command.arg))
|
||||
.then(stream => {
|
||||
return when.promise((resolve, reject) => {
|
||||
dataSocket.on('error', err => stream.emit('error', err));
|
||||
@@ -29,13 +29,13 @@ module.exports = {
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(551);
|
||||
return this.reply(551, err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
this.connector.end();
|
||||
this.commandSocket.resume();
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} [path]',
|
||||
syntax: '{{cmd}} <path>',
|
||||
description: 'Retrieve a copy of the file'
|
||||
};
|
||||
|
||||
@@ -5,6 +5,6 @@ module.exports = {
|
||||
handler: function (args) {
|
||||
return dele.call(this, args);
|
||||
},
|
||||
syntax: '{{cmd}} [path]',
|
||||
syntax: '{{cmd}} <path>',
|
||||
description: 'Remove a directory'
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ module.exports = {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
const fileName = command._[1];
|
||||
const fileName = command.arg;
|
||||
return when.try(this.fs.get.bind(this.fs), fileName)
|
||||
.then(() => {
|
||||
this.renameFrom = fileName;
|
||||
@@ -14,9 +14,9 @@ module.exports = {
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550);
|
||||
return this.reply(550, err.message);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} [name]',
|
||||
syntax: '{{cmd}} <name>',
|
||||
description: 'Rename from'
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ module.exports = {
|
||||
if (!this.fs.rename) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
const from = this.renameFrom;
|
||||
const to = command._[1];
|
||||
const to = command.arg;
|
||||
|
||||
return when.try(this.fs.rename.bind(this.fs), from, to)
|
||||
.then(() => {
|
||||
@@ -17,12 +17,12 @@ module.exports = {
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550);
|
||||
return this.reply(550, err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
delete this.renameFrom;
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} [name]',
|
||||
syntax: '{{cmd}} <name>',
|
||||
description: 'Rename to'
|
||||
};
|
||||
|
||||
@@ -4,7 +4,8 @@ module.exports = function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.chmod) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
const [, mode, fileName] = command._;
|
||||
const [mode, ...fileNameParts] = command.arg.split(' ');
|
||||
const fileName = fileNameParts.join(' ');
|
||||
return when.try(this.fs.chmod.bind(this.fs), fileName, parseInt(mode, 8))
|
||||
.then(() => {
|
||||
return this.reply(200);
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
const _ = require('lodash');
|
||||
const when = require('when');
|
||||
|
||||
module.exports = {
|
||||
directive: 'SITE',
|
||||
handler: function ({log, command} = {}) {
|
||||
const registry = require('./registry');
|
||||
let [, subverb, ...subparameters] = command._;
|
||||
subverb = _.upperCase(subverb);
|
||||
const subLog = log.child({subverb});
|
||||
const subCommand = this.commands.parse(command.arg);
|
||||
const subLog = log.child({subverb: subCommand.directive});
|
||||
|
||||
if (!registry.hasOwnProperty(subverb)) return this.reply(502);
|
||||
if (!registry.hasOwnProperty(subCommand.directive)) return this.reply(502);
|
||||
|
||||
const subCommand = {
|
||||
_: [subverb, ...subparameters],
|
||||
directive: subverb
|
||||
};
|
||||
const handler = registry[subverb].handler.bind(this);
|
||||
const handler = registry[subCommand.directive].handler.bind(this);
|
||||
return when.try(handler, { log: subLog, command: subCommand });
|
||||
},
|
||||
syntax: '{{cmd}} [subVerb] [subParams]',
|
||||
syntax: '{{cmd}} <subVerb> [...<subParams>]',
|
||||
description: 'Sends site specific commands to remote server'
|
||||
};
|
||||
|
||||
@@ -6,16 +6,16 @@ module.exports = {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return when.try(this.fs.get.bind(this.fs), command._[1])
|
||||
return when.try(this.fs.get.bind(this.fs), command.arg)
|
||||
.then(fileStat => {
|
||||
return this.reply(213, {message: fileStat.size});
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(550);
|
||||
return this.reply(550, err.message);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} [path]',
|
||||
syntax: '{{cmd}} <path>',
|
||||
description: 'Return the size of a file',
|
||||
flags: {
|
||||
feat: 'SIZE'
|
||||
|
||||
@@ -6,7 +6,7 @@ module.exports = {
|
||||
directive: 'STAT',
|
||||
handler: function (args = {}) {
|
||||
const {log, command} = args;
|
||||
const path = _.get(command, '_[1]');
|
||||
const path = _.get(command, 'arg');
|
||||
if (path) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
||||
@@ -33,12 +33,12 @@ module.exports = {
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(450);
|
||||
return this.reply(450, err.message);
|
||||
});
|
||||
} else {
|
||||
return this.reply(211, 'Status OK');
|
||||
}
|
||||
},
|
||||
syntax: '{{cmd}} [path(optional)]',
|
||||
syntax: '{{cmd}} [<path>]',
|
||||
description: 'Returns the current status'
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ module.exports = {
|
||||
if (!this.fs.write) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
const append = command.directive === 'APPE';
|
||||
const fileName = command._[1];
|
||||
const fileName = command.arg;
|
||||
|
||||
let dataSocket;
|
||||
return this.connector.waitForConnection()
|
||||
@@ -18,13 +18,18 @@ module.exports = {
|
||||
.then(() => when.try(this.fs.write.bind(this.fs), fileName, {append}))
|
||||
.then(stream => {
|
||||
return when.promise((resolve, reject) => {
|
||||
stream.on('error', err => dataSocket.emit('error', err));
|
||||
stream.once('error', err => dataSocket.emit('error', err));
|
||||
stream.once('finish', () => resolve(this.reply(226, fileName)));
|
||||
|
||||
dataSocket.on('end', () => stream.end(() => resolve(this.reply(226, fileName))));
|
||||
dataSocket.on('error', err => reject(err));
|
||||
// Emit `close` if stream has a close listener, otherwise emit `finish` with the end() method
|
||||
// It is assumed that the `close` handler will call the end() method
|
||||
dataSocket.once('end', () => stream.listenerCount('close') ? stream.emit('close') : stream.end());
|
||||
dataSocket.once('error', err => reject(err));
|
||||
dataSocket.on('data', data => stream.write(data, this.encoding));
|
||||
|
||||
this.reply(150).then(() => dataSocket.resume());
|
||||
});
|
||||
})
|
||||
.finally(() => when.try(stream.destroy.bind(stream)));
|
||||
})
|
||||
.catch(when.TimeoutError, err => {
|
||||
log.error(err);
|
||||
@@ -32,13 +37,13 @@ module.exports = {
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(553);
|
||||
return this.reply(550, err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
this.connector.end();
|
||||
this.commandSocket.resume();
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} [path]',
|
||||
syntax: '{{cmd}} <path>',
|
||||
description: 'Store data as a file at the server site'
|
||||
};
|
||||
|
||||
@@ -8,14 +8,14 @@ module.exports = {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.get || !this.fs.getUniqueName) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
const fileName = args.command._[1];
|
||||
const fileName = args.command.arg;
|
||||
return when.try(() => {
|
||||
return when.try(this.fs.get.bind(this.fs), fileName)
|
||||
.then(() => when.try(this.fs.getUniqueName.bind(this.fs)))
|
||||
.catch(() => when.resolve(fileName));
|
||||
})
|
||||
.then(name => {
|
||||
args.command._[1] = name;
|
||||
args.command.arg = name;
|
||||
return stor.call(this, args);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
module.exports = {
|
||||
directive: 'STRU',
|
||||
handler: function ({command} = {}) {
|
||||
return this.reply(/^F$/i.test(command._[1]) ? 200 : 504);
|
||||
return this.reply(/^F$/i.test(command.arg) ? 200 : 504);
|
||||
},
|
||||
syntax: '{{cmd}} [structure]',
|
||||
syntax: '{{cmd}} <structure>',
|
||||
description: 'Set file transfer structure',
|
||||
flags: {
|
||||
obsolete: true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const _ = require('lodash');
|
||||
|
||||
const ENCODING_TYPES = {
|
||||
A: 'utf-8',
|
||||
A: 'utf8',
|
||||
I: 'binary',
|
||||
L: 'binary'
|
||||
};
|
||||
@@ -9,12 +9,12 @@ const ENCODING_TYPES = {
|
||||
module.exports = {
|
||||
directive: 'TYPE',
|
||||
handler: function ({command} = {}) {
|
||||
const encoding = _.upperCase(command._[1]);
|
||||
const encoding = _.upperCase(command.arg);
|
||||
if (!ENCODING_TYPES.hasOwnProperty(encoding)) return this.reply(501);
|
||||
|
||||
this.encoding = ENCODING_TYPES[encoding];
|
||||
return this.reply(200);
|
||||
},
|
||||
syntax: '{{cmd}} [mode]',
|
||||
description: 'Set the transfer mode, binary (I) or utf-8 (A)'
|
||||
syntax: '{{cmd}} <mode>',
|
||||
description: 'Set the transfer mode, binary (I) or utf8 (A)'
|
||||
};
|
||||
|
||||
@@ -2,23 +2,25 @@ module.exports = {
|
||||
directive: 'USER',
|
||||
handler: function ({log, command} = {}) {
|
||||
if (this.username) return this.reply(530, 'Username already set');
|
||||
if (this.authenticated) return this.reply(230);
|
||||
|
||||
this.username = command._[1];
|
||||
if (!this.username) return this.reply(501, 'Must send username requirement');
|
||||
this.username = command.arg;
|
||||
if (!this.username) return this.reply(501, 'Must provide username');
|
||||
|
||||
if (this.server.options.anonymous === true) {
|
||||
if (this.server.options.anonymous === true && this.username === 'anonymous' ||
|
||||
this.username === this.server.options.anonymous) {
|
||||
return this.login(this.username, '@anonymous')
|
||||
.then(() => {
|
||||
return this.reply(230);
|
||||
})
|
||||
.catch(err => {
|
||||
log.error(err);
|
||||
return this.reply(530, err || 'Authentication failed');
|
||||
return this.reply(530, err.message || 'Authentication failed');
|
||||
});
|
||||
}
|
||||
return this.reply(331);
|
||||
},
|
||||
syntax: '{{cmd}} [username]',
|
||||
syntax: '{{cmd}} <username>',
|
||||
description: 'Authentication username',
|
||||
flags: {
|
||||
no_auth: true
|
||||
|
||||
@@ -20,6 +20,7 @@ const commands = [
|
||||
require('./registration/pasv'),
|
||||
require('./registration/port'),
|
||||
require('./registration/pwd'),
|
||||
require('./registration/quit'),
|
||||
require('./registration/retr'),
|
||||
require('./registration/rmd'),
|
||||
require('./registration/rnfr'),
|
||||
@@ -32,7 +33,9 @@ const commands = [
|
||||
require('./registration/stru'),
|
||||
require('./registration/syst'),
|
||||
require('./registration/type'),
|
||||
require('./registration/user')
|
||||
require('./registration/user'),
|
||||
require('./registration/pbsz'),
|
||||
require('./registration/prot')
|
||||
];
|
||||
|
||||
const registry = commands.reduce((result, cmd) => {
|
||||
|
||||
@@ -2,7 +2,6 @@ const _ = require('lodash');
|
||||
const uuid = require('uuid');
|
||||
const when = require('when');
|
||||
const sequence = require('when/sequence');
|
||||
const parseCommandString = require('minimist-string');
|
||||
|
||||
const BaseConnector = require('./connector/base');
|
||||
const FileSystem = require('./fs');
|
||||
@@ -13,27 +12,20 @@ const DEFAULT_MESSAGE = require('./messages');
|
||||
class FtpConnection {
|
||||
constructor(server, options) {
|
||||
this.server = server;
|
||||
this.commandSocket = options.socket;
|
||||
this.id = uuid.v4();
|
||||
this.log = options.log.child({id: this.id});
|
||||
this.log = options.log.child({id: this.id, ip: this.ip});
|
||||
this.commands = new Commands(this);
|
||||
this.encoding = 'utf-8';
|
||||
this.encoding = 'utf8';
|
||||
this.bufferSize = false;
|
||||
|
||||
this.connector = new BaseConnector(this);
|
||||
|
||||
this.commandSocket = options.socket;
|
||||
this.commandSocket.on('error', err => {
|
||||
this.server.server.emit('error', {connection: this, error: err});
|
||||
});
|
||||
this.commandSocket.on('data', data => {
|
||||
const messages = _.compact(data.toString('utf-8').split('\r\n'));
|
||||
const handleMessage = message => {
|
||||
const command = parseCommandString(message);
|
||||
command.directive = _.upperCase(command._[0]);
|
||||
return this.commands.handle(command);
|
||||
};
|
||||
|
||||
return sequence(messages.map(message => handleMessage.bind(this, message)));
|
||||
this.log.error(err, 'Client error');
|
||||
this.server.emit('client-error', {connection: this, context: 'commandSocket', error: err});
|
||||
});
|
||||
this.commandSocket.on('data', this._handleData.bind(this));
|
||||
this.commandSocket.on('timeout', () => {});
|
||||
this.commandSocket.on('close', () => {
|
||||
if (this.connector) this.connector.end();
|
||||
@@ -41,13 +33,25 @@ class FtpConnection {
|
||||
});
|
||||
}
|
||||
|
||||
_handleData(data) {
|
||||
const messages = _.compact(data.toString('utf8').split('\r\n'));
|
||||
this.log.trace(messages);
|
||||
return sequence(messages.map(message => this.commands.handle.bind(this.commands, message)));
|
||||
}
|
||||
|
||||
get ip() {
|
||||
try {
|
||||
return this.dataSocket ? this.dataSocket.remoteAddress : this.commandSocket.remoteAddress;
|
||||
} catch (ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
close(code = 421, message = 'Closing connection') {
|
||||
return when(() => {
|
||||
if (code) return this.reply(code, message);
|
||||
})
|
||||
.then(() => {
|
||||
if (this.commandSocket) this.commandSocket.end();
|
||||
});
|
||||
return when
|
||||
.resolve(code)
|
||||
.then(_code => _code && this.reply(_code, message))
|
||||
.then(() => this.commandSocket && this.commandSocket.end());
|
||||
}
|
||||
|
||||
login(username, password) {
|
||||
@@ -56,10 +60,10 @@ class FtpConnection {
|
||||
if (!loginListeners || !loginListeners.length) {
|
||||
if (!this.server.options.anoymous) throw new errors.GeneralError('No "login" listener setup', 500);
|
||||
} else {
|
||||
return this.server.emit('login', {connection: this, username, password});
|
||||
return this.server.emitPromise('login', {connection: this, username, password});
|
||||
}
|
||||
})
|
||||
.then(({root = '/', cwd = '/', fs, blacklist = [], whitelist = []} = {}) => {
|
||||
.then(({root, cwd, fs, blacklist = [], whitelist = []} = {}) => {
|
||||
this.authenticated = true;
|
||||
this.commands.blacklist = _.concat(this.commands.blacklist, blacklist);
|
||||
this.commands.whitelist = _.concat(this.commands.whitelist, whitelist);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const {Socket} = require('net');
|
||||
const tls = require('tls');
|
||||
const when = require('when');
|
||||
const Connector = require('./base');
|
||||
|
||||
@@ -17,7 +18,7 @@ class Active extends Connector {
|
||||
.then(() => this.dataSocket);
|
||||
}
|
||||
|
||||
setupConnection(host, port) {
|
||||
setupConnection(host, port, family = 4) {
|
||||
const closeExistingServer = () => this.dataSocket ?
|
||||
when(this.dataSocket.destroy()) :
|
||||
when.resolve();
|
||||
@@ -26,8 +27,18 @@ class Active extends Connector {
|
||||
.then(() => {
|
||||
this.dataSocket = new Socket();
|
||||
this.dataSocket.setEncoding(this.encoding);
|
||||
this.dataSocket.connect({ host, port }, () => {
|
||||
this.dataSocket.on('error', err => this.server.emit('client-error', {connection: this.connection, context: 'dataSocket', error: err}));
|
||||
this.dataSocket.connect({ host, port, family }, () => {
|
||||
this.dataSocket.pause();
|
||||
|
||||
if (this.connection.secure) {
|
||||
const secureContext = tls.createSecureContext(this.server._tls);
|
||||
const secureSocket = new tls.TLSSocket(this.dataSocket, {
|
||||
isServer: true,
|
||||
secureContext
|
||||
});
|
||||
this.dataSocket = secureSocket;
|
||||
}
|
||||
this.dataSocket.connected = true;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,14 +4,20 @@ const errors = require('../errors');
|
||||
class Connector {
|
||||
constructor(connection) {
|
||||
this.connection = connection;
|
||||
this.server = connection.server;
|
||||
this.log = connection.log;
|
||||
|
||||
this.dataSocket = null;
|
||||
this.dataServer = null;
|
||||
this.type = false;
|
||||
}
|
||||
|
||||
get log() {
|
||||
return this.connection.log;
|
||||
}
|
||||
|
||||
get server() {
|
||||
return this.connection.server;
|
||||
}
|
||||
|
||||
waitForConnection() {
|
||||
return when.reject(new errors.ConnectorError('No connector setup, send PASV or PORT'));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const net = require('net');
|
||||
const tls = require('tls');
|
||||
const when = require('when');
|
||||
|
||||
const Connector = require('./base');
|
||||
const findPort = require('../helpers/find-port');
|
||||
const errors = require('../errors');
|
||||
@@ -11,9 +13,7 @@ class Passive extends Connector {
|
||||
}
|
||||
|
||||
waitForConnection({timeout = 5000, delay = 250} = {}) {
|
||||
if (!this.dataServer) {
|
||||
return when.reject(new errors.ConnectorError('Passive server not setup'));
|
||||
}
|
||||
if (!this.dataServer) return when.reject(new errors.ConnectorError('Passive server not setup'));
|
||||
return when.iterate(
|
||||
() => {},
|
||||
() => this.dataServer && this.dataServer.listening && this.dataSocket && this.dataSocket.connected,
|
||||
@@ -30,10 +30,7 @@ class Passive extends Connector {
|
||||
return closeExistingServer()
|
||||
.then(() => this.getPort())
|
||||
.then(port => {
|
||||
this.dataSocket = null;
|
||||
this.dataServer = net.createServer({pauseOnConnect: true});
|
||||
this.dataServer.maxConnections = 1;
|
||||
this.dataServer.on('connection', socket => {
|
||||
const connectionHandler = socket => {
|
||||
if (this.connection.commandSocket.remoteAddress !== socket.remoteAddress) {
|
||||
this.log.error({
|
||||
pasv_connection: socket.remoteAddress,
|
||||
@@ -46,14 +43,29 @@ class Passive extends Connector {
|
||||
}
|
||||
this.log.debug({port}, 'Passive connection fulfilled.');
|
||||
|
||||
this.dataSocket = socket;
|
||||
if (this.connection.secure) {
|
||||
const secureContext = tls.createSecureContext(this.server._tls);
|
||||
const secureSocket = new tls.TLSSocket(socket, {
|
||||
isServer: true,
|
||||
secureContext
|
||||
});
|
||||
this.dataSocket = secureSocket;
|
||||
} else {
|
||||
this.dataSocket = socket;
|
||||
}
|
||||
this.dataSocket.connected = true;
|
||||
this.dataSocket.setEncoding(this.connection.encoding);
|
||||
this.dataSocket.on('error', err => this.server.emit('client-error', {connection: this.connection, context: 'dataSocket', error: err}));
|
||||
this.dataSocket.on('close', () => {
|
||||
this.log.debug('Passive connection closed');
|
||||
this.end();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.dataSocket = null;
|
||||
this.dataServer = net.createServer({ pauseOnConnect: true }, connectionHandler);
|
||||
this.dataServer.maxConnections = 1;
|
||||
this.dataServer.on('error', err => this.server.emit('client-error', {connection: this.connection, context: 'dataServer', error: err}));
|
||||
this.dataServer.on('close', () => {
|
||||
this.log.debug('Passive server closed');
|
||||
this.dataServer = null;
|
||||
@@ -77,7 +89,8 @@ class Passive extends Connector {
|
||||
this.server.options.pasv_range.split('-').map(v => v ? parseInt(v) : v) :
|
||||
[this.server.options.pasv_range];
|
||||
return findPort(min, max);
|
||||
} else return undefined;
|
||||
}
|
||||
throw new errors.ConnectorError('Invalid pasv_range');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
75
src/fs.js
75
src/fs.js
@@ -8,22 +8,22 @@ const fs = whenNode.liftAll(syncFs);
|
||||
const errors = require('./errors');
|
||||
|
||||
class FileSystem {
|
||||
constructor(connection, {
|
||||
root = '/',
|
||||
cwd = '/'
|
||||
} = {}) {
|
||||
constructor(connection, { root, cwd } = {}) {
|
||||
this.connection = connection;
|
||||
this.cwd = cwd;
|
||||
this.root = root;
|
||||
this.cwd = cwd || nodePath.sep;
|
||||
this.root = root || process.cwd();
|
||||
}
|
||||
|
||||
_resolvePath(path) {
|
||||
const pathParts = {
|
||||
root: this.root,
|
||||
base: nodePath.resolve(this.cwd, path)
|
||||
_resolvePath(path = '') {
|
||||
const isFromRoot = _.startsWith(path, '/') || _.startsWith(path, nodePath.sep);
|
||||
const cwd = isFromRoot ? nodePath.sep : this.cwd || nodePath.sep;
|
||||
const serverPath = nodePath.join(nodePath.sep, cwd, path);
|
||||
const fsPath = nodePath.join(this.root, serverPath);
|
||||
|
||||
return {
|
||||
serverPath,
|
||||
fsPath
|
||||
};
|
||||
path = nodePath.format(pathParts);
|
||||
return path;
|
||||
}
|
||||
|
||||
currentDirectory() {
|
||||
@@ -31,17 +31,17 @@ class FileSystem {
|
||||
}
|
||||
|
||||
get(fileName) {
|
||||
const path = this._resolvePath(fileName);
|
||||
return fs.stat(path)
|
||||
const {fsPath} = this._resolvePath(fileName);
|
||||
return fs.stat(fsPath)
|
||||
.then(stat => _.set(stat, 'name', fileName));
|
||||
}
|
||||
|
||||
list(path = '.') {
|
||||
path = this._resolvePath(path);
|
||||
return fs.readdir(path)
|
||||
const {fsPath} = this._resolvePath(path);
|
||||
return fs.readdir(fsPath)
|
||||
.then(fileNames => {
|
||||
return when.map(fileNames, fileName => {
|
||||
const filePath = nodePath.join(path, fileName);
|
||||
const filePath = nodePath.join(fsPath, fileName);
|
||||
return fs.access(filePath, syncFs.constants.F_OK)
|
||||
.then(() => {
|
||||
return fs.stat(filePath)
|
||||
@@ -54,60 +54,61 @@ class FileSystem {
|
||||
}
|
||||
|
||||
chdir(path = '.') {
|
||||
path = this._resolvePath(path);
|
||||
return fs.stat(path)
|
||||
const {fsPath, serverPath} = this._resolvePath(path);
|
||||
return fs.stat(fsPath)
|
||||
.tap(stat => {
|
||||
if (!stat.isDirectory()) throw new errors.FileSystemError('Not a valid directory');
|
||||
})
|
||||
.then(() => {
|
||||
this.cwd = path.replace(new RegExp(`^${this.root}`), '') || '/';
|
||||
this.cwd = serverPath;
|
||||
return this.currentDirectory();
|
||||
});
|
||||
}
|
||||
|
||||
write(fileName, {append = false} = {}) {
|
||||
const path = this._resolvePath(fileName);
|
||||
const stream = syncFs.createWriteStream(path, {flags: !append ? 'w+' : 'a+'});
|
||||
stream.on('error', () => fs.unlink(path));
|
||||
const {fsPath} = this._resolvePath(fileName);
|
||||
const stream = syncFs.createWriteStream(fsPath, {flags: !append ? 'w+' : 'a+'});
|
||||
stream.once('error', () => fs.unlink(fsPath));
|
||||
stream.once('close', () => stream.end());
|
||||
return stream;
|
||||
}
|
||||
|
||||
read(fileName) {
|
||||
const path = this._resolvePath(fileName);
|
||||
return fs.stat(path)
|
||||
const {fsPath} = this._resolvePath(fileName);
|
||||
return fs.stat(fsPath)
|
||||
.tap(stat => {
|
||||
if (stat.isDirectory()) throw new errors.FileSystemError('Cannot read a directory');
|
||||
})
|
||||
.then(() => {
|
||||
const stream = syncFs.createReadStream(path, {flags: 'r'});
|
||||
const stream = syncFs.createReadStream(fsPath, {flags: 'r'});
|
||||
return stream;
|
||||
});
|
||||
}
|
||||
|
||||
delete(path) {
|
||||
path = this._resolvePath(path);
|
||||
return fs.stat(path)
|
||||
const {fsPath} = this._resolvePath(path);
|
||||
return fs.stat(fsPath)
|
||||
.then(stat => {
|
||||
if (stat.isDirectory()) return fs.rmdir(path);
|
||||
else return fs.unlink(path);
|
||||
if (stat.isDirectory()) return fs.rmdir(fsPath);
|
||||
else return fs.unlink(fsPath);
|
||||
});
|
||||
}
|
||||
|
||||
mkdir(path) {
|
||||
path = this._resolvePath(path);
|
||||
return fs.mkdir(path)
|
||||
.then(() => path);
|
||||
const {fsPath} = this._resolvePath(path);
|
||||
return fs.mkdir(fsPath)
|
||||
.then(() => fsPath);
|
||||
}
|
||||
|
||||
rename(from, to) {
|
||||
const fromPath = this._resolvePath(from);
|
||||
const toPath = this._resolvePath(to);
|
||||
const {fsPath: fromPath} = this._resolvePath(from);
|
||||
const {fsPath: toPath} = this._resolvePath(to);
|
||||
return fs.rename(fromPath, toPath);
|
||||
}
|
||||
|
||||
chmod(path, mode) {
|
||||
path = this._resolvePath(path);
|
||||
return fs.chmod(path, mode);
|
||||
const {fsPath} = this._resolvePath(path);
|
||||
return fs.chmod(fsPath, mode);
|
||||
}
|
||||
|
||||
getUniqueName() {
|
||||
|
||||
@@ -1,52 +1,54 @@
|
||||
const _ = require('lodash');
|
||||
const dateFns = require('date-fns');
|
||||
const moment = require('moment');
|
||||
const errors = require('../errors');
|
||||
|
||||
const FORMATS = {
|
||||
ls,
|
||||
ep
|
||||
};
|
||||
|
||||
module.exports = function (fileStat, format = 'ls') {
|
||||
if (typeof format === 'function') return format(fileStat);
|
||||
|
||||
const formats = {
|
||||
ls: ls,
|
||||
ep: ep
|
||||
};
|
||||
if (!formats.hasOwnProperty(format)) {
|
||||
if (!FORMATS.hasOwnProperty(format)) {
|
||||
throw new errors.FileSystemError('Bad file stat formatter');
|
||||
}
|
||||
return formats[format](fileStat);
|
||||
return FORMATS[format](fileStat);
|
||||
};
|
||||
|
||||
function ls(fileStat) {
|
||||
const now = moment.utc();
|
||||
const mtime = moment.utc(new Date(fileStat.mtime));
|
||||
const dateFormat = now.diff(mtime, 'months') < 6 ? 'MMM DD HH:mm' : 'MMM DD YYYY';
|
||||
|
||||
return [
|
||||
fileStat.mode !== null
|
||||
? [
|
||||
fileStat.isDirectory() ? 'd' : '-',
|
||||
400 & fileStat.mode ? 'r' : '-',
|
||||
200 & fileStat.mode ? 'w' : '-',
|
||||
100 & fileStat.mode ? 'x' : '-',
|
||||
40 & fileStat.mode ? 'r' : '-',
|
||||
20 & fileStat.mode ? 'w' : '-',
|
||||
10 & fileStat.mode ? 'x' : '-',
|
||||
4 & fileStat.mode ? 'r' : '-',
|
||||
2 & fileStat.mode ? 'w' : '-',
|
||||
1 & fileStat.mode ? 'x' : '-'
|
||||
].join('')
|
||||
: fileStat.isDirectory() ? 'drwxr-xr-x' : '-rwxr-xr-x',
|
||||
fileStat.mode ? [
|
||||
fileStat.isDirectory() ? 'd' : '-',
|
||||
fileStat.mode & 256 ? 'r' : '-',
|
||||
fileStat.mode & 128 ? 'w' : '-',
|
||||
fileStat.mode & 64 ? 'x' : '-',
|
||||
fileStat.mode & 32 ? 'r' : '-',
|
||||
fileStat.mode & 16 ? 'w' : '-',
|
||||
fileStat.mode & 8 ? 'x' : '-',
|
||||
fileStat.mode & 4 ? 'r' : '-',
|
||||
fileStat.mode & 2 ? 'w' : '-',
|
||||
fileStat.mode & 1 ? 'x' : '-'
|
||||
].join('') : fileStat.isDirectory() ? 'drwxr-xr-x' : '-rwxr-xr-x',
|
||||
'1',
|
||||
fileStat.uid,
|
||||
fileStat.gid,
|
||||
fileStat.uid || 1,
|
||||
fileStat.gid || 1,
|
||||
_.padStart(fileStat.size, 12),
|
||||
_.padStart(dateFns.format(fileStat.mtime, 'MMM DD HH:mm'), 12),
|
||||
_.padStart(mtime.format(dateFormat), 12),
|
||||
fileStat.name
|
||||
].join(' ');
|
||||
}
|
||||
|
||||
function ep(fileStat) {
|
||||
const facts = [
|
||||
const facts = _.compact([
|
||||
fileStat.dev && fileStat.ino ? `i${fileStat.dev.toString(16)}.${fileStat.ino.toString(16)}` : null,
|
||||
fileStat.size ? `s${fileStat.size}` : null,
|
||||
fileStat.mtime ? `m${dateFns.format(dateFns.parse(fileStat.mtime), 'X')}` : null,
|
||||
fileStat.mode ? `up${fileStat.mode.toString(8).substr(fileStat.mode.toString(8).length - 3)}` : null,
|
||||
fileStat.isDirectory() ? 'r' : '/'
|
||||
].join(',');
|
||||
fileStat.mtime ? `m${moment.utc(new Date(fileStat.mtime)).format('X')}` : null,
|
||||
fileStat.mode ? `up${(fileStat.mode & 4095).toString(8)}` : null,
|
||||
fileStat.isDirectory() ? '/' : 'r'
|
||||
]).join(',');
|
||||
return `+${facts}\t${fileStat.name}`;
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@ const net = require('net');
|
||||
const when = require('when');
|
||||
const errors = require('../errors');
|
||||
|
||||
module.exports = function (min = 22, max = undefined) {
|
||||
module.exports = function (min = 1, max = undefined) {
|
||||
return when.promise((resolve, reject) => {
|
||||
let port = min;
|
||||
let checkPort = min;
|
||||
let portCheckServer = net.createServer();
|
||||
portCheckServer.maxConnections = 0;
|
||||
portCheckServer.on('error', () => {
|
||||
if (!max || port < max) {
|
||||
port = port + 1;
|
||||
portCheckServer.listen(port);
|
||||
if (checkPort < 65535 && (!max || checkPort < max)) {
|
||||
checkPort = checkPort + 1;
|
||||
portCheckServer.listen(checkPort);
|
||||
} else {
|
||||
reject(new errors.GeneralError('Unable to find open port', 500));
|
||||
}
|
||||
@@ -22,6 +22,6 @@ module.exports = function (min = 22, max = undefined) {
|
||||
resolve(port);
|
||||
});
|
||||
});
|
||||
portCheckServer.listen(port);
|
||||
portCheckServer.listen(checkPort);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ module.exports = function (hostname) {
|
||||
if (response.statusCode !== 200) {
|
||||
return reject(new errors.GeneralError('Unable to resolve hostname', response.statusCode));
|
||||
}
|
||||
response.setEncoding('utf-8');
|
||||
response.setEncoding('utf8');
|
||||
response.on('data', chunk => {
|
||||
ip += chunk;
|
||||
});
|
||||
|
||||
96
src/index.js
96
src/index.js
@@ -3,6 +3,8 @@ const when = require('when');
|
||||
const nodeUrl = require('url');
|
||||
const buyan = require('bunyan');
|
||||
const net = require('net');
|
||||
const tls = require('tls');
|
||||
const fs = require('fs');
|
||||
|
||||
const Connection = require('./connection');
|
||||
const resolveHost = require('./helpers/resolve-host');
|
||||
@@ -15,28 +17,51 @@ class FtpServer {
|
||||
pasv_range: 22,
|
||||
file_format: 'ls',
|
||||
blacklist: [],
|
||||
whitelist: []
|
||||
whitelist: [],
|
||||
greeting: null,
|
||||
tls: false
|
||||
}, options);
|
||||
this._greeting = this.setupGreeting(this.options.greeting);
|
||||
this._features = this.setupFeaturesMessage();
|
||||
this._tls = this.setupTLS(this.options.tls);
|
||||
|
||||
delete this.options.greeting;
|
||||
delete this.options.tls;
|
||||
|
||||
this.connections = {};
|
||||
this.log = this.options.log;
|
||||
this.url = nodeUrl.parse(url || 'ftp://127.0.0.1:21');
|
||||
this.server = net.createServer({pauseOnConnect: true}, socket => {
|
||||
|
||||
const serverConnectionHandler = socket => {
|
||||
let connection = new Connection(this, {log: this.log, socket});
|
||||
this.connections[connection.id] = connection;
|
||||
|
||||
socket.on('close', () => this.disconnectClient(connection.id));
|
||||
|
||||
const greeting = this.getGreetingMessage();
|
||||
const features = this.getFeaturesMessage();
|
||||
return connection.reply(220, greeting, features)
|
||||
const greeting = this._greeting || [];
|
||||
const features = this._features || 'Ready';
|
||||
return connection.reply(220, ...greeting, features)
|
||||
.finally(() => socket.resume());
|
||||
});
|
||||
this.server.on('error', err => {
|
||||
this.log.error(err);
|
||||
});
|
||||
};
|
||||
const serverOptions = _.assign(this.isTLS ? this._tls : {}, { pauseOnConnect: true });
|
||||
|
||||
this.server = (this.isTLS ? tls : net).createServer(serverOptions, serverConnectionHandler);
|
||||
this.server.on('error', err => this.log.error(err, '[Event] error'));
|
||||
if (this.isTLS) {
|
||||
this.server.on('tlsClientError', err => this.log.error(err, '[Event] tlsClientError'));
|
||||
}
|
||||
this.on = this.server.on.bind(this.server);
|
||||
this.once = this.server.once.bind(this.server);
|
||||
this.listeners = this.server.listeners.bind(this.server);
|
||||
|
||||
process.on('SIGTERM', () => this.close());
|
||||
process.on('SIGINT', () => this.close());
|
||||
process.on('SIGBREAK', () => this.close());
|
||||
process.on('SIGHUP', () => this.close());
|
||||
}
|
||||
|
||||
get isTLS() {
|
||||
return this.url.protocol === 'ftps:' && this._tls;
|
||||
}
|
||||
|
||||
listen() {
|
||||
@@ -46,25 +71,44 @@ class FtpServer {
|
||||
return when.promise((resolve, reject) => {
|
||||
this.server.listen(this.url.port, err => {
|
||||
if (err) return reject(err);
|
||||
this.log.info({port: this.url.port}, 'Listening');
|
||||
this.log.info({
|
||||
protocol: this.url.protocol.replace(/\W/g, ''),
|
||||
ip: this.url.hostname,
|
||||
port: this.url.port
|
||||
}, 'Listening');
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
emit(action, ...data) {
|
||||
emitPromise(action, ...data) {
|
||||
const defer = when.defer();
|
||||
const params = _.concat(data, [defer.resolve, defer.reject]);
|
||||
this.server.emit(action, ...params);
|
||||
return defer.promise;
|
||||
}
|
||||
|
||||
getGreetingMessage() {
|
||||
return null;
|
||||
emit(action, ...data) {
|
||||
this.server.emit(action, ...data);
|
||||
}
|
||||
|
||||
getFeaturesMessage() {
|
||||
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) {
|
||||
if (!greet) return [];
|
||||
const greeting = Array.isArray(greet) ? greet : greet.split('\n');
|
||||
return greeting;
|
||||
}
|
||||
|
||||
setupFeaturesMessage() {
|
||||
let features = [];
|
||||
if (this.options.anonymous) features.push('a');
|
||||
|
||||
@@ -75,32 +119,28 @@ class FtpServer {
|
||||
return features.length ? features.join(' ') : 'Ready';
|
||||
}
|
||||
|
||||
setGreeting(greeting) {
|
||||
if (typeof greeting === 'string') {
|
||||
this.options.greeting = greeting;
|
||||
} else {
|
||||
greeting.then(greet => {
|
||||
this.options.greeting = greet;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
disconnectClient(id) {
|
||||
return when.promise(resolve => {
|
||||
const client = this.connections[id];
|
||||
if (!client) return resolve();
|
||||
delete this.connections[id];
|
||||
client.close(0);
|
||||
resolve();
|
||||
try {
|
||||
client.close(0);
|
||||
} catch (err) {
|
||||
this.log.error(err, 'Error closing connection', {id});
|
||||
} finally {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.log.info('Server closing...');
|
||||
this.server.maxConnections = 0;
|
||||
return when.map(Object.keys(this.connections), id => this.disconnectClient(id))
|
||||
.then(() => when.promise((resolve, reject) => {
|
||||
.then(() => when.promise(resolve => {
|
||||
this.server.close(err => {
|
||||
if (err) return reject(err);
|
||||
if (err) this.log.error(err, 'Error closing server');
|
||||
resolve();
|
||||
});
|
||||
}));
|
||||
|
||||
12
test/cert/server.crt
Normal file
12
test/cert/server.crt
Normal file
@@ -0,0 +1,12 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBtTCCAR4CCQDsFyLCxvy4qzANBgkqhkiG9w0BAQUFADAfMQswCQYDVQQGEwJD
|
||||
QTEQMA4GA1UECBMHQWxiZXJ0YTAeFw0xNzA1MDgyMzQzMjNaFw0xODA1MDgyMzQz
|
||||
MjNaMB8xCzAJBgNVBAYTAkNBMRAwDgYDVQQIEwdBbGJlcnRhMIGfMA0GCSqGSIb3
|
||||
DQEBAQUAA4GNADCBiQKBgQD2uOwjV7Nb7ubBsK/tHZQ5hUpaQ9QD9Jo8qj7DBOia
|
||||
C4xbwpF6w+ZDf5OnkR7Hl3QlcofbkXKkLWmG0Mm5wvFA6kYW6D8vMT5Di7+eksf/
|
||||
agkklBnRtdoBb3lsbMbo3/EXijwHbCKbm+sTTe7dwGK/w6p782K/kgHVWk+L58O7
|
||||
rQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAOkX32keFpo0kKQKpeZgxVYjvn4/Voy6
|
||||
6oLsj7jJYq3oZts1dX6kHVpLEbF9sWKB2iz7nqz7pSN1ATq0IL/5rcxvNwiL4Idv
|
||||
F8CCBvsBui+0gwX755NJK/L57a5i8yQ5HC65NujGAA4I5+2x8HlefMVuBpEYjzQ2
|
||||
6lW2OJJ8xtP/
|
||||
-----END CERTIFICATE-----
|
||||
10
test/cert/server.csr
Normal file
10
test/cert/server.csr
Normal file
@@ -0,0 +1,10 @@
|
||||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIBXjCByAIBADAfMQswCQYDVQQGEwJDQTEQMA4GA1UECBMHQWxiZXJ0YTCBnzAN
|
||||
BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA9rjsI1ezW+7mwbCv7R2UOYVKWkPUA/Sa
|
||||
PKo+wwTomguMW8KResPmQ3+Tp5Eex5d0JXKH25FypC1phtDJucLxQOpGFug/LzE+
|
||||
Q4u/npLH/2oJJJQZ0bXaAW95bGzG6N/xF4o8B2wim5vrE03u3cBiv8Oqe/Niv5IB
|
||||
1VpPi+fDu60CAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4GBACzo+Wecs3CTbItrugdL
|
||||
pP4crsRs+HJljWA0e+WEGKhcd1FjrcLBr4WqzHFQJWHOTz2vM5PiKXPZk9crLxWa
|
||||
Y8kMhU6eQPnCM6+7Gffm32+VS1ipNlhzHyYsjYpgC3ROElqo0J5M5sas4lbaamr+
|
||||
FnlyRjrPSUcFdcbPL6ozND3e
|
||||
-----END CERTIFICATE REQUEST-----
|
||||
15
test/cert/server.key
Normal file
15
test/cert/server.key
Normal file
@@ -0,0 +1,15 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICXQIBAAKBgQD2uOwjV7Nb7ubBsK/tHZQ5hUpaQ9QD9Jo8qj7DBOiaC4xbwpF6
|
||||
w+ZDf5OnkR7Hl3QlcofbkXKkLWmG0Mm5wvFA6kYW6D8vMT5Di7+eksf/agkklBnR
|
||||
tdoBb3lsbMbo3/EXijwHbCKbm+sTTe7dwGK/w6p782K/kgHVWk+L58O7rQIDAQAB
|
||||
AoGANRPVYUkVwfpkVFkBj/5kC/fb5g1fiDZQFCr/846Tx8giOv9hssqAOBczGcKD
|
||||
n6a6iu/XwGnLAvzuDd3O+BKzObrKV36u9HfvCxohKaKvhPg3lBlJ5fFq/UNBoLv8
|
||||
eHz0GUGGoCxwJBAV43ojV1GdyRZ7vdmYw2hzltsHIp7UDqECQQD7yltCCJm3+gcw
|
||||
p+Sde0+M9CubkTETPwpd3XPu6Bs5f2nxNVj6RAInPEBr5//5UY7Q9BBEHrDPsq0j
|
||||
/+gsSlWZAkEA+tjf9qRXk9JHoN3PD0xLNEgUZAsQwDic29jxb3xrGkuUjCebKRuC
|
||||
FU2sAfNgDp+MyG1iyAoZcySzH2Dp3+v7NQJAaBwBo8oelT2in3GsS5ljCSskpMxh
|
||||
+E1Gog0hFJWQPDP8wCmIwuI/6a02Def9pT8dyDRCTYhLH3YHtSzo+Pc7cQJBAJ4G
|
||||
XiD8qwc+o00eLsEOaRoIhn/30JenknmVE5QOJ1KrZmtc0Ax3fd15zvBzp4HO1Vu2
|
||||
PVKTujClYApWfT9JZDkCQQC3Ne79bb5WSsGNbg4eT+FWde4hkdpheBsWraEDN1Pp
|
||||
NanupXMPNP0EduAQ1O+oPRiZ5pG38MQYcPZHTtlULoiO
|
||||
-----END RSA PRIVATE KEY-----
|
||||
18
test/cert/server.key.org
Normal file
18
test/cert/server.key.org
Normal file
@@ -0,0 +1,18 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: DES-EDE3-CBC,A253DC8068106194
|
||||
|
||||
jrYyhujaDsASrOfb2kn5Tvb0mRyIRsz7gVdjwlUdF2+lPdA/w6Os/NQBo1BIUAJp
|
||||
lfS5KDTwiE3QPgrBXUNgpy71Yr+MSzmsYWdonGlXGtchohQKXpxtL3qOpczX3ERR
|
||||
0AZninOKOYLw9+pe/tLNZI78DHxN1X0qTXS56RFydlW3XZbnl2Ux9CGuaCVq6vwh
|
||||
yzr9H+XTqeh95wTfdXkRdFRSTyUuJ72cvMsBFRDhz60epDmUDo1XDf844BpXXfcU
|
||||
kQoXHEtNkWZqzsc4ClOopp3Bgtd7eYoOLQluyovHgXzjtsur0xeMkHn9uTfkJ+IM
|
||||
cYMS71ZKbMePS7XBt3YPLBvVXNcyYhWUP7VdGYxXTPqd1AVWciDB9S5EvfMxnnZz
|
||||
O4M4ejxV8S7fF/cGju+sRzXx9oHPfo091Q3XKV1hrsUcF+ULrA8A8rHr64bDJ3wp
|
||||
luhekzwb+5yFfZDj9XUuGMD6pSWYoWB8Jmk8cxVsdZPtGXbTQHFL9/+UZ65wSGpj
|
||||
CjTLuFyVhY8pliynZH80vsNeRycdfmx93XoLqfS4xwEmI5v/MGUF24eTpF1/VIa5
|
||||
oKDrVuERdXAn4JBKeaMratrl6p1BhkPe7VNnMUFw3U+C4x+QHISxbboUJiTcCe1C
|
||||
pT6+YYkxQJ88rKunSEXkQYt6LeYSDg8Dw5y5Oq68DmW2Rp1m4ptTbqk3+uh83vzV
|
||||
Ff0JnfNfT80GHD3T5hMgizZal2vV8DeH1WAwPzpNaeV6wTy6MSgRLgQ89cCQ0TXV
|
||||
0GYnCZCaoXA2ldvbB3vW3fweOTr8Mp7aSl4s8K0R8sT3eief9/SyWA==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -1,48 +0,0 @@
|
||||
const when = require('when');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'AUTH';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
const mockClient = {
|
||||
reply: () => when.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
sandbox.spy(mockClient, 'reply');
|
||||
});
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('TLS // not supported', done => {
|
||||
cmdFn({command: {_: [CMD, 'TLS'], directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(504);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('SSL // not supported', done => {
|
||||
cmdFn({command: {_: [CMD, 'SSL'], directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(504);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('bad // bad', done => {
|
||||
cmdFn({command: {_: [CMD, 'bad'], directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(504);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
95
test/commands/index.spec.js
Normal file
95
test/commands/index.spec.js
Normal file
@@ -0,0 +1,95 @@
|
||||
const {expect} = require('chai');
|
||||
const when = require('when');
|
||||
const bunyan = require('bunyan');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const FtpCommands = require('../../src/commands');
|
||||
|
||||
describe('FtpCommands', function () {
|
||||
let sandbox;
|
||||
let commands;
|
||||
let mockConnection = {
|
||||
authenticated: false,
|
||||
log: bunyan.createLogger({name: 'FtpCommands'}),
|
||||
reply: () => when.resolve({}),
|
||||
server: {
|
||||
options: {
|
||||
blacklist: ['allo']
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
commands = new FtpCommands(mockConnection);
|
||||
|
||||
sandbox.spy(mockConnection, 'reply');
|
||||
sandbox.spy(commands, 'handle');
|
||||
sandbox.spy(commands, 'parse');
|
||||
});
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('parse', function () {
|
||||
it('no args: test', () => {
|
||||
const cmd = commands.parse('test');
|
||||
expect(cmd.directive).to.equal('TEST');
|
||||
expect(cmd.arg).to.equal(null);
|
||||
expect(cmd.raw).to.equal('test');
|
||||
});
|
||||
|
||||
it('one arg: test arg', () => {
|
||||
const cmd = commands.parse('test arg');
|
||||
expect(cmd.directive).to.equal('TEST');
|
||||
expect(cmd.arg).to.equal('arg');
|
||||
expect(cmd.raw).to.equal('test arg');
|
||||
});
|
||||
|
||||
it('two args: test arg1 arg2', () => {
|
||||
const cmd = commands.parse('test arg1 arg2');
|
||||
expect(cmd.directive).to.equal('TEST');
|
||||
expect(cmd.arg).to.equal('arg1 arg2');
|
||||
expect(cmd.raw).to.equal('test arg1 arg2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('handle', function () {
|
||||
it('fails with unsupported command', () => {
|
||||
return commands.handle('bad')
|
||||
.then(() => {
|
||||
expect(mockConnection.reply.callCount).to.equal(1);
|
||||
expect(mockConnection.reply.args[0][0]).to.equal(402);
|
||||
});
|
||||
});
|
||||
|
||||
it('fails with blacklisted command', () => {
|
||||
return commands.handle('allo')
|
||||
.then(() => {
|
||||
expect(mockConnection.reply.callCount).to.equal(1);
|
||||
expect(mockConnection.reply.args[0][0]).to.equal(502);
|
||||
expect(mockConnection.reply.args[0][1]).to.match(/blacklisted/);
|
||||
});
|
||||
});
|
||||
|
||||
it('fails with non whitelisted command', () => {
|
||||
commands.whitelist.push('USER');
|
||||
return commands.handle('auth')
|
||||
.then(() => {
|
||||
expect(mockConnection.reply.callCount).to.equal(1);
|
||||
expect(mockConnection.reply.args[0][0]).to.equal(502);
|
||||
expect(mockConnection.reply.args[0][1]).to.match(/whitelisted/);
|
||||
});
|
||||
});
|
||||
|
||||
it('fails due to being unauthenticated', () => {
|
||||
return commands.handle('stor')
|
||||
.then(() => {
|
||||
expect(mockConnection.reply.callCount).to.equal(1);
|
||||
expect(mockConnection.reply.args[0][0]).to.equal(530);
|
||||
expect(mockConnection.reply.args[0][1]).to.match(/authentication/);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,112 +0,0 @@
|
||||
const when = require('when');
|
||||
const bunyan = require('bunyan');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'LIST';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
let log = bunyan.createLogger({name: CMD});
|
||||
const mockClient = {
|
||||
reply: () => {},
|
||||
fs: { list: () => {} },
|
||||
connector: {
|
||||
waitForConnection: () => when({}),
|
||||
end: () => {}
|
||||
},
|
||||
commandSocket: {
|
||||
resume: () => {},
|
||||
pause: () => {}
|
||||
}
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
sandbox.stub(mockClient, 'reply').resolves();
|
||||
sandbox.stub(mockClient.fs, 'list').resolves([{
|
||||
name: 'test1',
|
||||
dev: 2114,
|
||||
ino: 48064969,
|
||||
mode: 33188,
|
||||
nlink: 1,
|
||||
uid: 85,
|
||||
gid: 100,
|
||||
rdev: 0,
|
||||
size: 527,
|
||||
blksize: 4096,
|
||||
blocks: 8,
|
||||
atime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
mtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
ctime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
birthtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
isDirectory: () => false
|
||||
}]);
|
||||
});
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('// check', function () {
|
||||
it('fails on no fs', done => {
|
||||
const badMockClient = { reply: () => {} };
|
||||
const badCmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(550);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('fails on no fs list command', done => {
|
||||
const badMockClient = { reply: () => {}, fs: {} };
|
||||
const badCmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(402);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('. // successful', done => {
|
||||
cmdFn({log, command: {_: [CMD], directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(150);
|
||||
expect(mockClient.reply.args[1][1]).to.have.property('raw');
|
||||
expect(mockClient.reply.args[1][1]).to.have.property('message');
|
||||
expect(mockClient.reply.args[1][1]).to.have.property('socket');
|
||||
expect(mockClient.reply.args[2][0]).to.equal(226);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('. // unsuccessful', done => {
|
||||
mockClient.fs.list.restore();
|
||||
sandbox.stub(mockClient.fs, 'list').rejects(new Error());
|
||||
|
||||
cmdFn({log, command: {_: [CMD], directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(451);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('. // unsuccessful (timeout)', done => {
|
||||
sandbox.stub(mockClient.connector, 'waitForConnection').returns(when.reject(new when.TimeoutError()));
|
||||
|
||||
cmdFn({log, command: {_: [CMD], directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(425);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
@@ -1,63 +0,0 @@
|
||||
const when = require('when');
|
||||
const bunyan = require('bunyan');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'NLST';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
let log = bunyan.createLogger({name: CMD});
|
||||
const mockClient = {
|
||||
reply: () => {},
|
||||
fs: { list: () => {} },
|
||||
connector: {
|
||||
waitForConnection: () => when({}),
|
||||
end: () => {}
|
||||
},
|
||||
commandSocket: {
|
||||
resume: () => {},
|
||||
pause: () => {}
|
||||
}
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
sandbox.stub(mockClient, 'reply').resolves();
|
||||
sandbox.stub(mockClient.fs, 'list').resolves([{
|
||||
name: 'test1',
|
||||
dev: 2114,
|
||||
ino: 48064969,
|
||||
mode: 33188,
|
||||
nlink: 1,
|
||||
uid: 85,
|
||||
gid: 100,
|
||||
rdev: 0,
|
||||
size: 527,
|
||||
blksize: 4096,
|
||||
blocks: 8,
|
||||
atime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
mtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
ctime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
birthtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
isDirectory: () => false
|
||||
}]);
|
||||
});
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('. // successful', done => {
|
||||
cmdFn({log, command: {_: [CMD], directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(150);
|
||||
expect(mockClient.reply.args[1][1]).to.have.property('raw');
|
||||
expect(mockClient.reply.args[1][1]).to.have.property('message');
|
||||
expect(mockClient.reply.args[1][1]).to.have.property('socket');
|
||||
expect(mockClient.reply.args[2][0]).to.equal(226);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
@@ -12,7 +12,7 @@ describe(CMD, function () {
|
||||
end: () => when.resolve()
|
||||
}
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -25,29 +25,25 @@ describe(CMD, function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('// successful | no active connection', done => {
|
||||
it('// successful | no active connection', () => {
|
||||
mockClient.connector.waitForConnection.restore();
|
||||
sandbox.stub(mockClient.connector, 'waitForConnection').rejects();
|
||||
|
||||
cmdFn()
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.connector.waitForConnection.callCount).to.equal(1);
|
||||
expect(mockClient.connector.end.callCount).to.equal(0);
|
||||
expect(mockClient.reply.args[0][0]).to.equal(226);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// successful | active connection', done => {
|
||||
cmdFn()
|
||||
it('// successful | active connection', () => {
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.connector.waitForConnection.callCount).to.equal(1);
|
||||
expect(mockClient.connector.end.callCount).to.equal(1);
|
||||
expect(mockClient.reply.args[0][0]).to.equal(426);
|
||||
expect(mockClient.reply.args[1][0]).to.equal(226);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => when.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -19,12 +19,10 @@ describe(CMD, function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('// successful', done => {
|
||||
cmdFn()
|
||||
it('// successful', () => {
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(202);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
46
test/commands/registration/auth.spec.js
Normal file
46
test/commands/registration/auth.spec.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const when = require('when');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'AUTH';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
const mockClient = {
|
||||
reply: () => when.resolve(),
|
||||
server: {
|
||||
_tls: {}
|
||||
}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
sandbox.spy(mockClient, 'reply');
|
||||
});
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('TLS // supported', () => {
|
||||
return cmdFn({command: { arg: 'TLS', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(234);
|
||||
expect(mockClient.secure).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('SSL // not supported', () => {
|
||||
return cmdFn({command: { arg: 'SSL', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(504);
|
||||
});
|
||||
});
|
||||
|
||||
it('bad // bad', () => {
|
||||
return cmdFn({command: { arg: 'bad', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(504);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -13,7 +13,7 @@ describe(CMD, function () {
|
||||
chdir: () => when.resolve()
|
||||
}
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -25,13 +25,11 @@ describe(CMD, function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('.. // successful', done => {
|
||||
cmdFn({log, command: {_: [CMD], directive: CMD}})
|
||||
it('.. // successful', () => {
|
||||
return cmdFn({log, command: {directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(250);
|
||||
expect(mockClient.fs.chdir.args[0][0]).to.equal('..');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -10,7 +10,7 @@ describe(CMD, function () {
|
||||
reply: () => {},
|
||||
fs: { chdir: () => {} }
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -23,63 +23,56 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
describe('// check', function () {
|
||||
it('fails on no fs', done => {
|
||||
it('fails on no fs', () => {
|
||||
const badMockClient = { reply: () => {} };
|
||||
const badCmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
badCmdFn()
|
||||
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(550);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on no fs chdir command', done => {
|
||||
it('fails on no fs chdir command', () => {
|
||||
const badMockClient = { reply: () => {}, fs: {} };
|
||||
const badCmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
badCmdFn()
|
||||
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(402);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('test // successful', done => {
|
||||
cmdFn({log, command: {_: [CMD, 'test'], directive: CMD}})
|
||||
it('test // successful', () => {
|
||||
return cmdFn({log, command: { arg: 'test', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(250);
|
||||
expect(mockClient.fs.chdir.args[0][0]).to.equal('test');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('test // successful', done => {
|
||||
it('test // successful', () => {
|
||||
mockClient.fs.chdir.restore();
|
||||
sandbox.stub(mockClient.fs, 'chdir').resolves('/test');
|
||||
cmdFn({log, command: {_: [CMD, 'test'], directive: CMD}})
|
||||
|
||||
return cmdFn({log, command: { arg: 'test', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(250);
|
||||
expect(mockClient.fs.chdir.args[0][0]).to.equal('test');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('bad // unsuccessful', done => {
|
||||
it('bad // unsuccessful', () => {
|
||||
mockClient.fs.chdir.restore();
|
||||
sandbox.stub(mockClient.fs, 'chdir').rejects(new Error('Bad'));
|
||||
|
||||
cmdFn({log, command: {_: [CMD, 'bad'], directive: CMD}})
|
||||
return cmdFn({log, command: { arg: 'bad', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
expect(mockClient.fs.chdir.args[0][0]).to.equal('bad');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -10,7 +10,7 @@ describe(CMD, function () {
|
||||
reply: () => {},
|
||||
fs: { delete: () => {} }
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -23,51 +23,45 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
describe('// check', function () {
|
||||
it('fails on no fs', done => {
|
||||
it('fails on no fs', () => {
|
||||
const badMockClient = { reply: () => {} };
|
||||
const badCmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
badCmdFn()
|
||||
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(550);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on no fs delete command', done => {
|
||||
it('fails on no fs delete command', () => {
|
||||
const badMockClient = { reply: () => {}, fs: {} };
|
||||
const badCmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
badCmdFn()
|
||||
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(402);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('test // successful', done => {
|
||||
cmdFn({log, command: {_: [CMD, 'test'], directive: CMD}})
|
||||
it('test // successful', () => {
|
||||
return cmdFn({log, command: { arg: 'test', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(250);
|
||||
expect(mockClient.fs.delete.args[0][0]).to.equal('test');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('bad // unsuccessful', done => {
|
||||
it('bad // unsuccessful', () => {
|
||||
mockClient.fs.delete.restore();
|
||||
sandbox.stub(mockClient.fs, 'delete').rejects(new Error('Bad'));
|
||||
|
||||
cmdFn({log, command: {_: [CMD, 'bad'], directive: CMD}})
|
||||
return cmdFn({log, command: { arg: 'bad', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
expect(mockClient.fs.delete.args[0][0]).to.equal('bad');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => when.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -19,39 +19,31 @@ describe(CMD, function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('// successful', done => {
|
||||
cmdFn({command: {_: [CMD], directive: CMD}})
|
||||
it('// successful', () => {
|
||||
return cmdFn({command: { directive: CMD }})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(211);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('help // successful', done => {
|
||||
cmdFn({command: {_: [CMD, 'help'], directive: CMD}})
|
||||
it('help // successful', () => {
|
||||
return cmdFn({command: { arg: 'help', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(214);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('help // successful', done => {
|
||||
cmdFn({command: {_: [CMD, 'allo'], directive: CMD}})
|
||||
it('allo // successful', () => {
|
||||
return cmdFn({command: { arg: 'allo', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(214);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('bad // unsuccessful', done => {
|
||||
cmdFn({command: {_: [CMD, 'bad'], directive: CMD}})
|
||||
it('bad // unsuccessful', () => {
|
||||
return cmdFn({command: { arg: 'bad', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(502);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
175
test/commands/registration/list.spec.js
Normal file
175
test/commands/registration/list.spec.js
Normal file
@@ -0,0 +1,175 @@
|
||||
const when = require('when');
|
||||
const bunyan = require('bunyan');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'LIST';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
let log = bunyan.createLogger({name: CMD});
|
||||
const mockClient = {
|
||||
reply: () => {},
|
||||
fs: {
|
||||
list: () => {},
|
||||
get: () => {}
|
||||
},
|
||||
connector: {
|
||||
waitForConnection: () => when({}),
|
||||
end: () => {}
|
||||
},
|
||||
commandSocket: {
|
||||
resume: () => {},
|
||||
pause: () => {}
|
||||
}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
sandbox.stub(mockClient, 'reply').resolves();
|
||||
sandbox.stub(mockClient.fs, 'get').resolves({
|
||||
name: 'testdir',
|
||||
dev: 2114,
|
||||
ino: 48064969,
|
||||
mode: 33188,
|
||||
nlink: 1,
|
||||
uid: 85,
|
||||
gid: 100,
|
||||
rdev: 0,
|
||||
size: 527,
|
||||
blksize: 4096,
|
||||
blocks: 8,
|
||||
atime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
mtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
ctime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
birthtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
isDirectory: () => true
|
||||
});
|
||||
sandbox.stub(mockClient.fs, 'list').resolves([{
|
||||
name: 'test1',
|
||||
dev: 2114,
|
||||
ino: 48064969,
|
||||
mode: 33188,
|
||||
nlink: 1,
|
||||
uid: 85,
|
||||
gid: 100,
|
||||
rdev: 0,
|
||||
size: 527,
|
||||
blksize: 4096,
|
||||
blocks: 8,
|
||||
atime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
mtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
ctime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
birthtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
isDirectory: () => false
|
||||
}, {
|
||||
name: 'test2',
|
||||
dev: 2114,
|
||||
ino: 48064969,
|
||||
mode: 33188,
|
||||
nlink: 1,
|
||||
uid: 85,
|
||||
gid: 100,
|
||||
rdev: 0,
|
||||
size: 527,
|
||||
blksize: 4096,
|
||||
blocks: 8,
|
||||
atime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
mtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
ctime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
birthtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
isDirectory: () => true
|
||||
}]);
|
||||
});
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('// check', function () {
|
||||
it('fails on no fs', () => {
|
||||
const badMockClient = { reply: () => {} };
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on no fs list command', () => {
|
||||
const badMockClient = { reply: () => {}, fs: {} };
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(402);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('. // successful', () => {
|
||||
return cmdFn({log, command: {directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(150);
|
||||
expect(mockClient.reply.args[1].length).to.equal(3);
|
||||
expect(mockClient.reply.args[1][1]).to.have.property('raw');
|
||||
expect(mockClient.reply.args[1][1]).to.have.property('message');
|
||||
expect(mockClient.reply.args[1][1]).to.have.property('socket');
|
||||
expect(mockClient.reply.args[2][0]).to.equal(226);
|
||||
});
|
||||
});
|
||||
|
||||
it('testfile.txt // successful', () => {
|
||||
mockClient.fs.get.restore();
|
||||
sandbox.stub(mockClient.fs, 'get').resolves({
|
||||
name: 'testfile.txt',
|
||||
dev: 2114,
|
||||
ino: 48064969,
|
||||
mode: 33188,
|
||||
nlink: 1,
|
||||
uid: 85,
|
||||
gid: 100,
|
||||
rdev: 0,
|
||||
size: 527,
|
||||
blksize: 4096,
|
||||
blocks: 8,
|
||||
atime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
mtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
ctime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
birthtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
isDirectory: () => false
|
||||
});
|
||||
|
||||
return cmdFn({log, command: {directive: CMD, arg: 'testfile.txt'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(150);
|
||||
expect(mockClient.reply.args[1].length).to.equal(2);
|
||||
expect(mockClient.reply.args[1][1]).to.have.property('raw');
|
||||
expect(mockClient.reply.args[1][1]).to.have.property('message');
|
||||
expect(mockClient.reply.args[1][1]).to.have.property('socket');
|
||||
expect(mockClient.reply.args[2][0]).to.equal(226);
|
||||
});
|
||||
});
|
||||
|
||||
it('. // unsuccessful', () => {
|
||||
mockClient.fs.list.restore();
|
||||
sandbox.stub(mockClient.fs, 'list').rejects(new Error());
|
||||
|
||||
return cmdFn({log, command: {directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(451);
|
||||
});
|
||||
});
|
||||
|
||||
it('. // unsuccessful (timeout)', () => {
|
||||
sandbox.stub(mockClient.connector, 'waitForConnection').returns(when.reject(new when.TimeoutError()));
|
||||
|
||||
return cmdFn({log, command: {directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(425);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -10,7 +10,7 @@ describe(CMD, function () {
|
||||
reply: () => {},
|
||||
fs: { get: () => {} }
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -23,50 +23,44 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
describe('// check', function () {
|
||||
it('fails on no fs', done => {
|
||||
it('fails on no fs', () => {
|
||||
const badMockClient = { reply: () => {} };
|
||||
const badCmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
badCmdFn()
|
||||
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(550);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on no fs get command', done => {
|
||||
it('fails on no fs get command', () => {
|
||||
const badMockClient = { reply: () => {}, fs: {} };
|
||||
const badCmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
badCmdFn()
|
||||
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(402);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('. // successful', done => {
|
||||
cmdFn({log, command: {_: [CMD], directive: CMD}})
|
||||
it('. // successful', () => {
|
||||
return cmdFn({log, command: {directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(213);
|
||||
//expect(mockClient.reply.args[0][1]).to.equal('20111010172411.000');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('. // unsuccessful', done => {
|
||||
it('. // unsuccessful', () => {
|
||||
mockClient.fs.get.restore();
|
||||
sandbox.stub(mockClient.fs, 'get').rejects(new Error());
|
||||
|
||||
cmdFn({log, command: {_: [CMD], directive: CMD}})
|
||||
return cmdFn({log, command: {directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -10,7 +10,7 @@ describe(CMD, function () {
|
||||
reply: () => {},
|
||||
fs: { mkdir: () => {} }
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -23,63 +23,56 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
describe('// check', function () {
|
||||
it('fails on no fs', done => {
|
||||
it('fails on no fs', () => {
|
||||
const badMockClient = { reply: () => {} };
|
||||
const badCmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
badCmdFn()
|
||||
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(550);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on no fs mkdir command', done => {
|
||||
it('fails on no fs mkdir command', () => {
|
||||
const badMockClient = { reply: () => {}, fs: {} };
|
||||
const badCmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
badCmdFn()
|
||||
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(402);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('test // successful', done => {
|
||||
cmdFn({log, command: {_: [CMD, 'test'], directive: CMD}})
|
||||
it('test // successful', () => {
|
||||
return cmdFn({log, command: {arg: 'test', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(257);
|
||||
expect(mockClient.fs.mkdir.args[0][0]).to.equal('test');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('test // successful', done => {
|
||||
it('test // successful', () => {
|
||||
mockClient.fs.mkdir.restore();
|
||||
sandbox.stub(mockClient.fs, 'mkdir').resolves('test');
|
||||
cmdFn({log, command: {_: [CMD, 'test'], directive: CMD}})
|
||||
|
||||
return cmdFn({log, command: {arg: 'test', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(257);
|
||||
expect(mockClient.fs.mkdir.args[0][0]).to.equal('test');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('bad // unsuccessful', done => {
|
||||
it('bad // unsuccessful', () => {
|
||||
mockClient.fs.mkdir.restore();
|
||||
sandbox.stub(mockClient.fs, 'mkdir').rejects(new Error('Bad'));
|
||||
|
||||
cmdFn({log, command: {_: [CMD, 'bad'], directive: CMD}})
|
||||
return cmdFn({log, command: {arg: 'bad', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
expect(mockClient.fs.mkdir.args[0][0]).to.equal('bad');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => when.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -19,21 +19,17 @@ describe(CMD, function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('S // successful', done => {
|
||||
cmdFn({command: {_: [CMD, 'S']}})
|
||||
it('S // successful', () => {
|
||||
return cmdFn({command: {arg: 'S'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Q // unsuccessful', done => {
|
||||
cmdFn({command: {_: [CMD, 'Q']}})
|
||||
it('Q // unsuccessful', () => {
|
||||
return cmdFn({command: {arg: 'Q'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(504);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
132
test/commands/registration/nlst.spec.js
Normal file
132
test/commands/registration/nlst.spec.js
Normal file
@@ -0,0 +1,132 @@
|
||||
const when = require('when');
|
||||
const bunyan = require('bunyan');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'NLST';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
let log = bunyan.createLogger({name: CMD});
|
||||
const mockClient = {
|
||||
reply: () => {},
|
||||
fs: {
|
||||
get: () => {},
|
||||
list: () => {}
|
||||
},
|
||||
connector: {
|
||||
waitForConnection: () => when({}),
|
||||
end: () => {}
|
||||
},
|
||||
commandSocket: {
|
||||
resume: () => {},
|
||||
pause: () => {}
|
||||
}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
sandbox.stub(mockClient, 'reply').resolves();
|
||||
sandbox.stub(mockClient.fs, 'get').resolves({
|
||||
name: 'testdir',
|
||||
dev: 2114,
|
||||
ino: 48064969,
|
||||
mode: 33188,
|
||||
nlink: 1,
|
||||
uid: 85,
|
||||
gid: 100,
|
||||
rdev: 0,
|
||||
size: 527,
|
||||
blksize: 4096,
|
||||
blocks: 8,
|
||||
atime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
mtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
ctime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
birthtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
isDirectory: () => true
|
||||
});
|
||||
sandbox.stub(mockClient.fs, 'list').resolves([{
|
||||
name: 'test1',
|
||||
dev: 2114,
|
||||
ino: 48064969,
|
||||
mode: 33188,
|
||||
nlink: 1,
|
||||
uid: 85,
|
||||
gid: 100,
|
||||
rdev: 0,
|
||||
size: 527,
|
||||
blksize: 4096,
|
||||
blocks: 8,
|
||||
atime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
mtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
ctime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
birthtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
isDirectory: () => false
|
||||
}, {
|
||||
name: 'test2',
|
||||
dev: 2114,
|
||||
ino: 48064969,
|
||||
mode: 33188,
|
||||
nlink: 1,
|
||||
uid: 85,
|
||||
gid: 100,
|
||||
rdev: 0,
|
||||
size: 527,
|
||||
blksize: 4096,
|
||||
blocks: 8,
|
||||
atime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
mtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
ctime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
birthtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
isDirectory: () => true
|
||||
}]);
|
||||
});
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('. // successful', () => {
|
||||
return cmdFn({log, command: {directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(150);
|
||||
expect(mockClient.reply.args[1].length).to.equal(3);
|
||||
expect(mockClient.reply.args[1][1]).to.have.property('raw');
|
||||
expect(mockClient.reply.args[1][1]).to.have.property('message');
|
||||
expect(mockClient.reply.args[1][1]).to.have.property('socket');
|
||||
expect(mockClient.reply.args[2][0]).to.equal(226);
|
||||
});
|
||||
});
|
||||
|
||||
it('testfile.txt // successful', () => {
|
||||
mockClient.fs.get.restore();
|
||||
sandbox.stub(mockClient.fs, 'get').resolves({
|
||||
name: 'testfile.txt',
|
||||
dev: 2114,
|
||||
ino: 48064969,
|
||||
mode: 33188,
|
||||
nlink: 1,
|
||||
uid: 85,
|
||||
gid: 100,
|
||||
rdev: 0,
|
||||
size: 527,
|
||||
blksize: 4096,
|
||||
blocks: 8,
|
||||
atime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
mtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
ctime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
birthtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
isDirectory: () => false
|
||||
});
|
||||
|
||||
return cmdFn({log, command: {directive: CMD, arg: 'testfile.txt'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(150);
|
||||
expect(mockClient.reply.args[1].length).to.equal(2);
|
||||
expect(mockClient.reply.args[1][1]).to.have.property('raw');
|
||||
expect(mockClient.reply.args[1][1]).to.have.property('message');
|
||||
expect(mockClient.reply.args[1][1]).to.have.property('socket');
|
||||
expect(mockClient.reply.args[2][0]).to.equal(226);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => when.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -19,12 +19,10 @@ describe(CMD, function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('// successful', done => {
|
||||
cmdFn()
|
||||
it('// successful', () => {
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => when.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -19,12 +19,10 @@ describe(CMD, function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('// successful', done => {
|
||||
cmdFn()
|
||||
it('// successful', () => {
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(501);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -10,9 +10,9 @@ describe(CMD, function () {
|
||||
reply: () => {},
|
||||
login: () => {},
|
||||
server: { options: { anonymous: false } },
|
||||
username: 'user'
|
||||
username: 'anonymous'
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -24,61 +24,51 @@ describe(CMD, function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('pass // successful', done => {
|
||||
cmdFn({log, command: {_: [CMD, 'pass'], directive: CMD}})
|
||||
it('pass // successful', () => {
|
||||
return cmdFn({log, command: {arg: 'pass', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(230);
|
||||
expect(mockClient.login.args[0]).to.eql(['user', 'pass']);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
expect(mockClient.login.args[0]).to.eql(['anonymous', 'pass']);
|
||||
});
|
||||
});
|
||||
|
||||
it('// successful (anonymous)', done => {
|
||||
it('// successful (already authenticated)', () => {
|
||||
mockClient.server.options.anonymous = true;
|
||||
mockClient.authenticated = true;
|
||||
cmdFn({log, command: {_: [CMD], directive: CMD}})
|
||||
return cmdFn({log, command: {directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(230);
|
||||
expect(mockClient.reply.args[0][0]).to.equal(202);
|
||||
expect(mockClient.login.callCount).to.equal(0);
|
||||
mockClient.server.options.anonymous = false;
|
||||
mockClient.authenticated = false;
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('bad // unsuccessful', done => {
|
||||
it('bad // unsuccessful', () => {
|
||||
mockClient.login.restore();
|
||||
sandbox.stub(mockClient, 'login').rejects('bad');
|
||||
|
||||
cmdFn({log, command: {_: [CMD, 'bad'], directive: CMD}})
|
||||
return cmdFn({log, command: {arg: 'bad', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(530);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('bad // unsuccessful', done => {
|
||||
it('bad // unsuccessful', () => {
|
||||
mockClient.login.restore();
|
||||
sandbox.stub(mockClient, 'login').rejects({});
|
||||
|
||||
cmdFn({log, command: {_: [CMD, 'bad'], directive: CMD}})
|
||||
return cmdFn({log, command: {arg: 'bad', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(530);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('bad // unsuccessful', done => {
|
||||
it('bad // unsuccessful', () => {
|
||||
delete mockClient.username;
|
||||
cmdFn({log, command: {_: [CMD, 'bad'], directive: CMD}})
|
||||
return cmdFn({log, command: {arg: 'bad', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(503);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
51
test/commands/registration/pbsz.spec.js
Normal file
51
test/commands/registration/pbsz.spec.js
Normal file
@@ -0,0 +1,51 @@
|
||||
const when = require('when');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'PBSZ';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
const mockClient = {
|
||||
reply: () => when.resolve(),
|
||||
server: {}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
sandbox.spy(mockClient, 'reply');
|
||||
});
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('// unsuccessful', () => {
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(202);
|
||||
});
|
||||
});
|
||||
|
||||
it('// successful', () => {
|
||||
mockClient.secure = true;
|
||||
mockClient.server._tls = {};
|
||||
|
||||
return cmdFn({command: {arg: '0'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
expect(mockClient.bufferSize).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('// successful', () => {
|
||||
mockClient.secure = true;
|
||||
mockClient.server._tls = {};
|
||||
|
||||
return cmdFn({command: {arg: '10'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
expect(mockClient.bufferSize).to.equal(10);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,7 @@ const when = require('when');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const ActiveConnector = require('../../src/connector/active');
|
||||
const ActiveConnector = require('../../../src/connector/active');
|
||||
|
||||
const CMD = 'PORT';
|
||||
describe(CMD, function () {
|
||||
@@ -10,7 +10,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => when.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -22,33 +22,27 @@ describe(CMD, function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('// unsuccessful | no argument', done => {
|
||||
cmdFn()
|
||||
it('// unsuccessful | no argument', () => {
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(425);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | invalid argument', done => {
|
||||
cmdFn({ command: { _: [CMD, '1,2,3,4,5'] } })
|
||||
it('// unsuccessful | invalid argument', () => {
|
||||
return cmdFn({ command: { arg: '1,2,3,4,5' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(425);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// successful', done => {
|
||||
cmdFn({ command: { _: [CMD, '192,168,0,100,137,214'] } })
|
||||
it('// successful', () => {
|
||||
return cmdFn({ command: { arg: '192,168,0,100,137,214' } })
|
||||
.then(() => {
|
||||
const [ip, port] = ActiveConnector.prototype.setupConnection.args[0];
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
expect(ip).to.equal('192.168.0.100');
|
||||
expect(port).to.equal(35286);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
65
test/commands/registration/prot.spec.js
Normal file
65
test/commands/registration/prot.spec.js
Normal file
@@ -0,0 +1,65 @@
|
||||
const when = require('when');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'PROT';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
const mockClient = {
|
||||
reply: () => when.resolve(),
|
||||
server: {}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
sandbox.spy(mockClient, 'reply');
|
||||
});
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('// unsuccessful', () => {
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(202);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful - no bufferSize', () => {
|
||||
mockClient.server._tls = {};
|
||||
mockClient.secure = true;
|
||||
|
||||
return cmdFn({command: {arg: 'P'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(503);
|
||||
});
|
||||
});
|
||||
|
||||
it('// successful', () => {
|
||||
mockClient.bufferSize = 0;
|
||||
mockClient.secure = true;
|
||||
|
||||
return cmdFn({command: {arg: 'p'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful - unsupported', () => {
|
||||
mockClient.secure = true;
|
||||
return cmdFn({command: {arg: 'C'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(536);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful - unknown', () => {
|
||||
mockClient.secure = true;
|
||||
return cmdFn({command: {arg: 'QQ'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(504);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -10,7 +10,7 @@ describe(CMD, function () {
|
||||
reply: () => {},
|
||||
fs: { currentDirectory: () => {} }
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -23,61 +23,53 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
describe('// check', function () {
|
||||
it('fails on no fs', done => {
|
||||
it('fails on no fs', () => {
|
||||
const badMockClient = { reply: () => {} };
|
||||
const badCmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
badCmdFn()
|
||||
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(550);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on no fs currentDirectory command', done => {
|
||||
it('fails on no fs currentDirectory command', () => {
|
||||
const badMockClient = { reply: () => {}, fs: {} };
|
||||
const badCmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
badCmdFn()
|
||||
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(402);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('// successful', done => {
|
||||
cmdFn({log, command: {_: [CMD, 'test'], directive: CMD}})
|
||||
it('// successful', () => {
|
||||
return cmdFn({log, command: { arg: 'test', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(257);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// successful', done => {
|
||||
it('// successful', () => {
|
||||
mockClient.fs.currentDirectory.restore();
|
||||
sandbox.stub(mockClient.fs, 'currentDirectory').resolves('/test');
|
||||
|
||||
cmdFn({log, command: {_: [CMD, 'test'], directive: CMD}})
|
||||
return cmdFn({log, command: {arg: 'test', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(257);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful', done => {
|
||||
it('// unsuccessful', () => {
|
||||
mockClient.fs.currentDirectory.restore();
|
||||
sandbox.stub(mockClient.fs, 'currentDirectory').rejects(new Error('Bad'));
|
||||
|
||||
cmdFn({log, command: {_: [CMD, 'bad'], directive: CMD}})
|
||||
return cmdFn({log, command: {arg: 'bad', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
close: () => {}
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -18,12 +18,10 @@ describe(CMD, function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('// successful', done => {
|
||||
cmdFn()
|
||||
it('// successful', () => {
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.close.callCount).to.equal(1);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
75
test/commands/registration/retr.spec.js
Normal file
75
test/commands/registration/retr.spec.js
Normal file
@@ -0,0 +1,75 @@
|
||||
const when = require('when');
|
||||
const bunyan = require('bunyan');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'RETR';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
let log = bunyan.createLogger({name: CMD});
|
||||
const mockClient = {
|
||||
commandSocket: {
|
||||
pause: () => {},
|
||||
resume: () => {}
|
||||
},
|
||||
reply: () => when.resolve(),
|
||||
connector: {
|
||||
waitForConnection: () => when.resolve({
|
||||
resume: () => {}
|
||||
}),
|
||||
end: () => {}
|
||||
}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
mockClient.fs = {
|
||||
read: () => {}
|
||||
};
|
||||
|
||||
sandbox.spy(mockClient, 'reply');
|
||||
});
|
||||
afterEach(() => sandbox.restore());
|
||||
|
||||
it('// unsuccessful | no file system', () => {
|
||||
delete mockClient.fs;
|
||||
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | file system does not have functions', () => {
|
||||
mockClient.fs = {};
|
||||
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(402);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | connector times out', () => {
|
||||
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
|
||||
return when.reject(new when.TimeoutError());
|
||||
});
|
||||
|
||||
return cmdFn({log, command: {arg: 'test.txt'} })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(425);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | connector errors out', () => {
|
||||
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
|
||||
return when.reject(new Error('test'));
|
||||
});
|
||||
|
||||
return cmdFn({log, command: {arg: 'test.txt'} })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(551);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@ describe(CMD, function () {
|
||||
let sandbox;
|
||||
const mockLog = { error: () => {} };
|
||||
const mockClient = { reply: () => when.resolve() };
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -24,47 +24,39 @@ describe(CMD, function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('// unsuccessful | no file system', done => {
|
||||
it('// unsuccessful | no file system', () => {
|
||||
delete mockClient.fs;
|
||||
|
||||
cmdFn()
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | file system does not have functions', done => {
|
||||
it('// unsuccessful | file system does not have functions', () => {
|
||||
mockClient.fs = {};
|
||||
|
||||
cmdFn()
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(402);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('test // unsuccessful | file get fails', done => {
|
||||
it('test // unsuccessful | file get fails', () => {
|
||||
mockClient.fs.get.restore();
|
||||
sandbox.stub(mockClient.fs, 'get').rejects(new Error('test'));
|
||||
|
||||
cmdFn({ log: mockLog, command: { _: [CMD, 'test'] } })
|
||||
return cmdFn({ log: mockLog, command: { arg: 'test' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('test // successful', done => {
|
||||
cmdFn({ log: mockLog, command: { _: [CMD, 'test'] } })
|
||||
it('test // successful', () => {
|
||||
return cmdFn({ log: mockLog, command: { arg: 'test' } })
|
||||
.then(() => {
|
||||
expect(mockClient.fs.get.args[0][0]).to.equal('test');
|
||||
expect(mockClient.reply.args[0][0]).to.equal(350);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@ describe(CMD, function () {
|
||||
let sandbox;
|
||||
const mockLog = { error: () => {} };
|
||||
const mockClient = { reply: () => when.resolve() };
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -25,58 +25,48 @@ describe(CMD, function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('// unsuccessful | no renameFrom set', done => {
|
||||
it('// unsuccessful | no renameFrom set', () => {
|
||||
delete mockClient.renameFrom;
|
||||
|
||||
cmdFn()
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(503);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | no file system', done => {
|
||||
it('// unsuccessful | no file system', () => {
|
||||
delete mockClient.fs;
|
||||
|
||||
cmdFn()
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | file system does not have functions', done => {
|
||||
it('// unsuccessful | file system does not have functions', () => {
|
||||
mockClient.fs = {};
|
||||
|
||||
cmdFn()
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(402);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('new // unsuccessful | rename fails', done => {
|
||||
it('new // unsuccessful | rename fails', () => {
|
||||
mockClient.fs.rename.restore();
|
||||
sandbox.stub(mockClient.fs, 'rename').rejects(new Error('test'));
|
||||
|
||||
cmdFn({ log: mockLog, command: { _: [CMD, 'new'] } })
|
||||
return cmdFn({ log: mockLog, command: { arg: 'new' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('new // successful', done => {
|
||||
cmdFn({ command: { _: [CMD, 'new'] } })
|
||||
it('new // successful', () => {
|
||||
return cmdFn({ command: { arg: 'new' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(250);
|
||||
expect(mockClient.fs.rename.args[0]).to.eql(['test', 'new']);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@ describe(CMD, function () {
|
||||
let sandbox;
|
||||
const mockLog = { error: () => {} };
|
||||
const mockClient = { reply: () => when.resolve() };
|
||||
const cmdFn = require(`../../../src/commands/registration/site/${CMD.toLowerCase()}`).bind(mockClient);
|
||||
const cmdFn = require(`../../../../src/commands/registration/site/${CMD.toLowerCase()}`).bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -49,7 +49,7 @@ describe(CMD, function () {
|
||||
mockClient.fs.chmod.restore();
|
||||
sandbox.stub(mockClient.fs, 'chmod').rejects(new Error('test'));
|
||||
|
||||
cmdFn({ log: mockLog, command: { _: [CMD, '777', 'test'] } })
|
||||
cmdFn({ log: mockLog, command: { arg: '777 test' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(500);
|
||||
done();
|
||||
@@ -58,7 +58,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('777 test // successful', done => {
|
||||
cmdFn({ log: mockLog, command: { _: [CMD, '777', 'test'] } })
|
||||
cmdFn({ log: mockLog, command: { arg: '777 test' } })
|
||||
.then(() => {
|
||||
expect(mockClient.fs.chmod.args[0]).to.eql(['test', 511]);
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
@@ -7,7 +7,7 @@ describe(CMD, function () {
|
||||
let sandbox;
|
||||
const mockLog = { error: () => {} };
|
||||
const mockClient = { reply: () => when.resolve() };
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -22,45 +22,37 @@ describe(CMD, function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('// unsuccessful | no file system', done => {
|
||||
it('// unsuccessful | no file system', () => {
|
||||
delete mockClient.fs;
|
||||
|
||||
cmdFn()
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | file system does not have functions', done => {
|
||||
it('// unsuccessful | file system does not have functions', () => {
|
||||
mockClient.fs = {};
|
||||
|
||||
cmdFn()
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(402);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | file get fails', done => {
|
||||
it('// unsuccessful | file get fails', () => {
|
||||
sandbox.stub(mockClient.fs, 'get').rejects(new Error('test'));
|
||||
|
||||
cmdFn({ log: mockLog, command: { _: [CMD, 'test'] } })
|
||||
return cmdFn({ log: mockLog, command: { arg: 'test' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// successful', done => {
|
||||
cmdFn({ command: { _: [CMD, 'test'] } })
|
||||
it('// successful', () => {
|
||||
return cmdFn({ command: { arg: 'test' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(213);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@ describe(CMD, function () {
|
||||
let sandbox;
|
||||
const mockLog = { error: () => {} };
|
||||
const mockClient = { reply: () => when.resolve() };
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -23,49 +23,41 @@ describe(CMD, function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('// successful', done => {
|
||||
cmdFn()
|
||||
it('// successful', () => {
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(211);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | no file system', done => {
|
||||
it('// unsuccessful | no file system', () => {
|
||||
delete mockClient.fs;
|
||||
|
||||
cmdFn({ command: { _: [CMD, 'test'] } })
|
||||
return cmdFn({ command: { arg: 'test' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | file system does not have functions', done => {
|
||||
it('// unsuccessful | file system does not have functions', () => {
|
||||
mockClient.fs = {};
|
||||
|
||||
cmdFn({ command: { _: [CMD, 'test'] } })
|
||||
return cmdFn({ command: { arg: 'test' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(402);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | file get fails', done => {
|
||||
it('// unsuccessful | file get fails', () => {
|
||||
sandbox.stub(mockClient.fs, 'get').rejects(new Error('test'));
|
||||
|
||||
cmdFn({ log: mockLog, command: { _: [CMD, 'test'] } })
|
||||
return cmdFn({ log: mockLog, command: { arg: 'test' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(450);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// successful | file', done => {
|
||||
it('// successful | file', () => {
|
||||
sandbox.stub(mockClient.fs, 'get').returns({
|
||||
name: 'test_file',
|
||||
dev: 2114,
|
||||
@@ -85,15 +77,13 @@ describe(CMD, function () {
|
||||
isDirectory: () => false
|
||||
});
|
||||
|
||||
cmdFn({ command: { _: [CMD, 'test'] } })
|
||||
return cmdFn({ command: { arg: 'test' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(212);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// successful | directory', done => {
|
||||
it('// successful | directory', () => {
|
||||
sandbox.stub(mockClient.fs, 'list').returns([{
|
||||
name: 'test_file',
|
||||
dev: 2114,
|
||||
@@ -132,11 +122,9 @@ describe(CMD, function () {
|
||||
isDirectory: () => true
|
||||
});
|
||||
|
||||
cmdFn({ command: { _: [CMD, 'test'] } })
|
||||
return cmdFn({ command: { arg: 'test' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(213);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
75
test/commands/registration/stor.spec.js
Normal file
75
test/commands/registration/stor.spec.js
Normal file
@@ -0,0 +1,75 @@
|
||||
const when = require('when');
|
||||
const bunyan = require('bunyan');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'STOR';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
let log = bunyan.createLogger({name: CMD});
|
||||
const mockClient = {
|
||||
commandSocket: {
|
||||
pause: () => {},
|
||||
resume: () => {}
|
||||
},
|
||||
reply: () => when.resolve(),
|
||||
connector: {
|
||||
waitForConnection: () => when.resolve({
|
||||
resume: () => {}
|
||||
}),
|
||||
end: () => {}
|
||||
}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
mockClient.fs = {
|
||||
write: () => {}
|
||||
};
|
||||
|
||||
sandbox.spy(mockClient, 'reply');
|
||||
});
|
||||
afterEach(() => sandbox.restore());
|
||||
|
||||
it('// unsuccessful | no file system', () => {
|
||||
delete mockClient.fs;
|
||||
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | file system does not have functions', () => {
|
||||
mockClient.fs = {};
|
||||
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(402);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | connector times out', () => {
|
||||
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
|
||||
return when.reject(new when.TimeoutError());
|
||||
});
|
||||
|
||||
return cmdFn({log, command: {arg: 'test.txt'} })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(425);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | connector errors out', () => {
|
||||
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
|
||||
return when.reject(new Error('test'));
|
||||
});
|
||||
|
||||
return cmdFn({log, command: {arg: 'test.txt'} })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,7 @@ const when = require('when');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const stor = require('../../src/commands/registration/stor');
|
||||
const stor = require('../../../src/commands/registration/stor');
|
||||
|
||||
const CMD = 'STOU';
|
||||
describe(CMD, function () {
|
||||
@@ -10,7 +10,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => when.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -30,54 +30,46 @@ describe(CMD, function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('// unsuccessful | no file system', done => {
|
||||
it('// unsuccessful | no file system', () => {
|
||||
delete mockClient.fs;
|
||||
|
||||
cmdFn()
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | file system does not have functions', done => {
|
||||
it('// unsuccessful | file system does not have functions', () => {
|
||||
mockClient.fs = {};
|
||||
|
||||
cmdFn()
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(402);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// successful | given name is unique', done => {
|
||||
it('// successful | given name is unique', () => {
|
||||
mockClient.fs.get.restore();
|
||||
sandbox.stub(mockClient.fs, 'get').rejects({});
|
||||
|
||||
cmdFn({ command: { _: [CMD, 'good'] } })
|
||||
return cmdFn({ command: { arg: 'good' } })
|
||||
.then(() => {
|
||||
const call = stor.handler.call.args[0][1];
|
||||
expect(call).to.have.property('command');
|
||||
expect(call.command).to.have.property('_');
|
||||
expect(call.command._).to.eql([CMD, 'good']);
|
||||
expect(call.command).to.have.property('arg');
|
||||
expect(call.command.arg).to.eql('good');
|
||||
expect(mockClient.fs.getUniqueName.callCount).to.equal(0);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// successful | generates unique name', done => {
|
||||
cmdFn({ command: { _: [CMD, 'bad'] } })
|
||||
it('// successful | generates unique name', () => {
|
||||
return cmdFn({ command: { arg: 'bad' } })
|
||||
.then(() => {
|
||||
const call = stor.handler.call.args[0][1];
|
||||
expect(call).to.have.property('command');
|
||||
expect(call.command).to.have.property('_');
|
||||
expect(call.command._).to.eql([CMD, '4']);
|
||||
expect(call.command).to.have.property('arg');
|
||||
expect(call.command.arg).to.eql('4');
|
||||
expect(mockClient.fs.getUniqueName.callCount).to.equal(1);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => when.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -19,21 +19,17 @@ describe(CMD, function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('// successful', done => {
|
||||
cmdFn({command: { _: [CMD, 'F'] } })
|
||||
it('// successful', () => {
|
||||
return cmdFn({command: { arg: 'F' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful', done => {
|
||||
cmdFn({command: { _: [CMD, 'X'] } })
|
||||
it('// unsuccessful', () => {
|
||||
return cmdFn({command: { arg: 'X' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(504);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => when.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -19,12 +19,10 @@ describe(CMD, function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('// successful', done => {
|
||||
cmdFn()
|
||||
it('// successful', () => {
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(215);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,7 +8,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => when.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -20,43 +20,35 @@ describe(CMD, function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('A // successful', done => {
|
||||
cmdFn({ command: { _: [CMD, 'A'] } })
|
||||
it('A // successful', () => {
|
||||
return cmdFn({ command: { arg: 'A' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
expect(mockClient.encoding).to.equal('utf-8');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
expect(mockClient.encoding).to.equal('utf8');
|
||||
});
|
||||
});
|
||||
|
||||
it('I // successful', done => {
|
||||
cmdFn({ command: { _: [CMD, 'I'] } })
|
||||
it('I // successful', () => {
|
||||
return cmdFn({ command: { arg: 'I' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
expect(mockClient.encoding).to.equal('binary');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('L // successful', done => {
|
||||
cmdFn({ command: { _: [CMD, 'L'] } })
|
||||
it('L // successful', () => {
|
||||
return cmdFn({ command: { arg: 'L' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
expect(mockClient.encoding).to.equal('binary');
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('X // successful', done => {
|
||||
cmdFn({ command: { _: [CMD, 'X'] } })
|
||||
it('X // successful', () => {
|
||||
return cmdFn({ command: { arg: 'X' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(501);
|
||||
expect(mockClient.encoding).to.equal(null);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
107
test/commands/registration/user.spec.js
Normal file
107
test/commands/registration/user.spec.js
Normal file
@@ -0,0 +1,107 @@
|
||||
const when = require('when');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'USER';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
const mockLog = {
|
||||
error: () => {}
|
||||
};
|
||||
const mockClient = {
|
||||
reply: () => when.resolve(),
|
||||
server: { options: {} },
|
||||
login: () => when.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
delete mockClient.username;
|
||||
mockClient.server.options = {};
|
||||
|
||||
sandbox.spy(mockClient, 'reply');
|
||||
sandbox.spy(mockClient, 'login');
|
||||
});
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('test // successful | prompt for password', () => {
|
||||
return cmdFn({ command: { arg: 'test' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(331);
|
||||
});
|
||||
});
|
||||
|
||||
it('test // successful | anonymous login', () => {
|
||||
mockClient.server.options = {anonymous: true};
|
||||
|
||||
return cmdFn({ command: { arg: 'anonymous' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(230);
|
||||
expect(mockClient.login.callCount).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('test // unsuccessful | no username provided', () => {
|
||||
return cmdFn({ command: { } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(501);
|
||||
expect(mockClient.login.callCount).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('test // unsuccessful | already set username', () => {
|
||||
mockClient.username = 'test';
|
||||
|
||||
return cmdFn({ command: { arg: 'test' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(530);
|
||||
expect(mockClient.login.callCount).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('test // successful | regular login if anonymous is true', () => {
|
||||
mockClient.server.options = {anonymous: true};
|
||||
|
||||
return cmdFn({ log: mockLog, command: { arg: 'test' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(331);
|
||||
expect(mockClient.login.callCount).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('test // successful | anonymous login with set username', () => {
|
||||
mockClient.server.options = {anonymous: 'sillyrabbit'};
|
||||
|
||||
return cmdFn({ log: mockLog, command: { arg: 'sillyrabbit' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(230);
|
||||
expect(mockClient.login.callCount).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('test // unsuccessful | anonymous login fails', () => {
|
||||
mockClient.server.options = {anonymous: true};
|
||||
mockClient.login.restore();
|
||||
sandbox.stub(mockClient, 'login').rejects(new Error('test'));
|
||||
|
||||
return cmdFn({ log: mockLog, command: { arg: 'anonymous' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(530);
|
||||
expect(mockClient.login.callCount).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('test // successful | does not login if already authenticated', () => {
|
||||
mockClient.authenticated = true;
|
||||
|
||||
return cmdFn({ log: mockLog, command: { arg: 'sillyrabbit' } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(230);
|
||||
expect(mockClient.login.callCount).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,88 +0,0 @@
|
||||
const when = require('when');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'USER';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
const mockLog = {
|
||||
error: () => {}
|
||||
};
|
||||
const mockClient = {
|
||||
reply: () => when.resolve(),
|
||||
server: { options: {} },
|
||||
login: () => when.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
delete mockClient.username;
|
||||
mockClient.server.options = {};
|
||||
|
||||
sandbox.spy(mockClient, 'reply');
|
||||
sandbox.spy(mockClient, 'login');
|
||||
});
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('test // successful | prompt for password', done => {
|
||||
cmdFn({ command: { _: [CMD, 'test'] } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(331);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('test // successful | anonymous login', done => {
|
||||
mockClient.server.options = {anonymous: true};
|
||||
|
||||
cmdFn({ command: { _: [CMD, 'test'] } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(230);
|
||||
expect(mockClient.login.callCount).to.equal(1);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('test // unsuccessful | no username provided', done => {
|
||||
cmdFn({ command: { _: [CMD] } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(501);
|
||||
expect(mockClient.login.callCount).to.equal(0);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('test // unsuccessful | already set username', done => {
|
||||
mockClient.username = 'test';
|
||||
|
||||
cmdFn({ command: { _: [CMD, 'test'] } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(530);
|
||||
expect(mockClient.login.callCount).to.equal(0);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('test // unsuccessful | login function rejects', done => {
|
||||
mockClient.server.options = {anonymous: true};
|
||||
|
||||
mockClient.login.restore();
|
||||
sandbox.stub(mockClient, 'login').rejects(new Error('test'));
|
||||
|
||||
cmdFn({ log: mockLog, command: { _: [CMD, 'test'] } })
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(530);
|
||||
expect(mockClient.login.callCount).to.equal(1);
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,7 @@ const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const net = require('net');
|
||||
const tls = require('tls');
|
||||
|
||||
const ActiveConnector = require('../../src/connector/active');
|
||||
const findPort = require('../../src/helpers/find-port');
|
||||
@@ -33,34 +34,49 @@ describe('Connector - Active //', function () {
|
||||
server.close(done);
|
||||
});
|
||||
|
||||
it('sets up a connection', function (done) {
|
||||
active.setupConnection('127.0.0.1', PORT)
|
||||
it('sets up a connection', function () {
|
||||
return active.setupConnection('127.0.0.1', PORT)
|
||||
.then(() => {
|
||||
expect(active.dataSocket).to.exist;
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('destroys existing connection, then sets up a connection', function (done) {
|
||||
it('destroys existing connection, then sets up a connection', function () {
|
||||
const destroyFnSpy = sandbox.spy(active.dataSocket, 'destroy');
|
||||
|
||||
active.setupConnection('127.0.0.1', PORT)
|
||||
return active.setupConnection('127.0.0.1', PORT)
|
||||
.then(() => {
|
||||
expect(destroyFnSpy.callCount).to.equal(1);
|
||||
expect(active.dataSocket).to.exist;
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('waits for connection', function (done) {
|
||||
active.setupConnection('127.0.0.1', PORT)
|
||||
it('waits for connection', function () {
|
||||
return active.setupConnection('127.0.0.1', PORT)
|
||||
.then(() => {
|
||||
expect(active.dataSocket).to.exist;
|
||||
return active.waitForConnection();
|
||||
})
|
||||
.then(() => done())
|
||||
.catch(done);
|
||||
.then(dataSocket => {
|
||||
expect(dataSocket.connected).to.equal(true);
|
||||
expect(dataSocket instanceof net.Socket).to.equal(true);
|
||||
expect(dataSocket instanceof tls.TLSSocket).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('upgrades to a secure connection', function () {
|
||||
mockConnection.secure = true;
|
||||
mockConnection.server = { _tls: {} };
|
||||
|
||||
return active.setupConnection('127.0.0.1', PORT)
|
||||
.then(() => {
|
||||
expect(active.dataSocket).to.exist;
|
||||
return active.waitForConnection();
|
||||
})
|
||||
.then(dataSocket => {
|
||||
expect(dataSocket.connected).to.equal(true);
|
||||
expect(dataSocket instanceof net.Socket).to.equal(true);
|
||||
expect(dataSocket instanceof tls.TLSSocket).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,10 @@ describe('Connector - Passive //', function () {
|
||||
};
|
||||
let sandbox;
|
||||
|
||||
function shouldNotResolve() {
|
||||
throw new Error('Should not resolve');
|
||||
}
|
||||
|
||||
before(() => {
|
||||
passive = new PassiveConnector(mockConnection);
|
||||
});
|
||||
@@ -27,6 +31,7 @@ describe('Connector - Passive //', function () {
|
||||
sandbox = sinon.sandbox.create();
|
||||
|
||||
sandbox.spy(mockConnection, 'reply');
|
||||
sandbox.spy(mockConnection, 'close');
|
||||
|
||||
mockConnection.commandSocket.remoteAddress = '::ffff:127.0.0.1';
|
||||
mockConnection.server.options.pasv_range = '8000';
|
||||
@@ -35,45 +40,49 @@ describe('Connector - Passive //', function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('cannot wait for connection with no server', function (done) {
|
||||
passive.waitForConnection()
|
||||
.then(() => done('should not happen'))
|
||||
it('cannot wait for connection with no server', function () {
|
||||
return passive.waitForConnection()
|
||||
.then(shouldNotResolve)
|
||||
.catch(err => {
|
||||
expect(err.name).to.equal('ConnectorError');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('has invalid pasv range', function (done) {
|
||||
it('no pasv range provided', function () {
|
||||
delete mockConnection.server.options.pasv_range;
|
||||
|
||||
passive.setupServer()
|
||||
.then(() => done('should not happen'))
|
||||
return passive.setupServer()
|
||||
.then(shouldNotResolve)
|
||||
.catch(err => {
|
||||
expect(err.name).to.equal('RangeError');
|
||||
done();
|
||||
expect(err.name).to.equal('ConnectorError');
|
||||
});
|
||||
});
|
||||
|
||||
it('sets up a server', function (done) {
|
||||
passive.setupServer()
|
||||
.then(() => {
|
||||
expect(passive.dataServer).to.exist;
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
it('has invalid pasv range', function () {
|
||||
mockConnection.server.options.pasv_range = -1;
|
||||
|
||||
return passive.setupServer()
|
||||
.then(shouldNotResolve)
|
||||
.catch(err => {
|
||||
expect(err.name).to.equal('RangeError');
|
||||
});
|
||||
});
|
||||
|
||||
it('destroys existing server, then sets up a server', function (done) {
|
||||
it('sets up a server', function () {
|
||||
return passive.setupServer()
|
||||
.then(() => {
|
||||
expect(passive.dataServer).to.exist;
|
||||
});
|
||||
});
|
||||
|
||||
it('destroys existing server, then sets up a server', function () {
|
||||
const closeFnSpy = sandbox.spy(passive.dataServer, 'close');
|
||||
|
||||
passive.setupServer()
|
||||
return passive.setupServer()
|
||||
.then(() => {
|
||||
expect(closeFnSpy.callCount).to.equal(1);
|
||||
expect(passive.dataServer).to.exist;
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('refuses connection with different remote address', function (done) {
|
||||
@@ -84,17 +93,20 @@ describe('Connector - Passive //', function () {
|
||||
expect(passive.dataServer).to.exist;
|
||||
|
||||
const {port} = passive.dataServer.address();
|
||||
net.createConnection(port, () => {
|
||||
expect(mockConnection.reply.callCount).to.equal(1);
|
||||
expect(mockConnection.reply.args[0][0]).to.equal(550);
|
||||
done();
|
||||
net.createConnection(port);
|
||||
passive.dataServer.once('connection', () => {
|
||||
setTimeout(() => {
|
||||
expect(passive.connection.reply.callCount).to.equal(1);
|
||||
expect(passive.connection.reply.args[0][0]).to.equal(550);
|
||||
done();
|
||||
}, 100);
|
||||
});
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('accepts connection', function (done) {
|
||||
passive.setupServer()
|
||||
it('accepts connection', function () {
|
||||
return passive.setupServer()
|
||||
.then(() => {
|
||||
expect(passive.dataServer).to.exist;
|
||||
|
||||
@@ -105,8 +117,6 @@ describe('Connector - Passive //', function () {
|
||||
.then(() => {
|
||||
expect(passive.dataSocket).to.exist;
|
||||
passive.end();
|
||||
done();
|
||||
})
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,10 +2,9 @@ const {expect} = require('chai');
|
||||
const escapePath = require('../../src/helpers/escape-path');
|
||||
|
||||
describe('helpers // escape-path', function () {
|
||||
it('escapes quotes', done => {
|
||||
it('escapes quotes', () => {
|
||||
const string = '"test"';
|
||||
const escapedString = escapePath(string);
|
||||
expect(escapedString).to.equal('""test""');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user