Compare commits

...

64 Commits

Author SHA1 Message Date
Jorin Vogel
4205caf7ac fix(rest): small typo 2018-03-29 23:27:33 -06:00
Tyler Stewart
a468d4ffd0 chore(readme): add file events to docs 2018-02-09 19:38:04 -07:00
Tyler Stewart
40b08893ac refactor(connection): only return IP of commandSocket
Since the dataSocket and commandSocket must be on the same IP for a data channel to open, it is redundant to check the dataSocket IP.
2018-02-09 19:38:04 -07:00
Tyler Stewart
8a2454ceea fix(connection): remove listeners on close 2018-02-09 19:38:04 -07:00
Tyler Stewart
0c7cc4fe6e test: new events 2018-02-09 19:38:04 -07:00
Tyler Stewart
6ea6baceb0 feat(server): extend event emitter 2018-02-09 19:38:04 -07:00
Tyler Stewart
b07e0189ee feat(connection): extend event emitter
Allow custom events
2018-02-09 19:38:04 -07:00
Tyler Stewart
ec30a5a4f3 feat(stor): emit connection event on upload success/failure 2018-02-09 19:38:04 -07:00
Tyler Stewart
6020409979 feat(retr): emit connection event on success/failure 2018-02-09 19:38:04 -07:00
Tyler Stewart
c60606971a chore(readme): update badges 2018-01-21 14:55:04 -07:00
Tyler Stewart
bd41b31821 chore(readme): update logo placement 2018-01-21 14:31:27 -07:00
Tyler Stewart
ce1c526c41 chore(logo): new logo design 2018-01-21 14:31:27 -07:00
Tyler Stewart
d822101a07 chore(readme): update coveralls badge 2018-01-10 11:13:28 -07:00
Tyler Stewart
47c8eedd3b chore: fix semantic release 2018-01-10 11:01:39 -07:00
Tyler Stewart
6c08cc2aed refactor: assimilate promises using resolve instead of try 2018-01-10 10:49:02 -07:00
Tyler Stewart
e2a5c78b0a fix(abor): send 225 if no file transfer in progress 2018-01-10 10:49:02 -07:00
Tyler Stewart
2cadac3f7e chore(readme): add reference links 2018-01-10 10:49:02 -07:00
Tyler Stewart
2255be9acd feat(connector): return promise on end 2018-01-10 10:49:02 -07:00
Tyler Stewart
d22c911a36 refactor(connection): completely parse message before handling 2018-01-10 10:49:02 -07:00
Tyler Stewart
5dabbc251b refactor(opts): simplify setting 2018-01-10 10:49:02 -07:00
Tyler Stewart
ef89577627 refactor(list): simplify reply 2018-01-10 10:49:02 -07:00
Tyler Stewart
8fbe750086 refactor(stat): simplify replys 2018-01-10 10:49:02 -07:00
Tyler Stewart
3b33508f44 feat: migrate to bluebird
Replace `when` with `bluebird` promise library
2018-01-10 10:49:02 -07:00
Tyler Stewart
23368b04b9 test: update stor fail test timing 2018-01-10 10:24:41 -07:00
Tyler Stewart
876a061e92 refactor: ensure reject is called on destroyConnection 2018-01-10 10:24:41 -07:00
Tyler Stewart
65b1fd27a0 fix(stor): pause connection to avoid memory build up 2018-01-10 10:24:41 -07:00
James Suárez
286c1063fa fix(retr): pause read stream to avoid memory build up 2018-01-10 10:24:41 -07:00
Tyler Stewart
e87c36d7ff chore: fix semantic releasing 2017-12-08 17:50:16 -07:00
Tyler Stewart
de0aafad2f fix(stat): fix stat on file
Ensure message sent raw
2017-12-08 17:50:16 -07:00
Tyler Stewart
4f80e11745 fix(stat): fix file response
Ensures correct response when `stat`-ing a file

```
212-Status begin
...
212 Status end
```
2017-12-08 17:40:10 -07:00
Tyler Stewart
6bbd905379 test: update integration tests 2017-11-12 20:50:11 -07:00
Tyler Stewart
de50f55457 test(circleci): fix workflow 2017-11-12 18:01:26 -07:00
Ozair Patel
32cdedd163 chore: add public properties to typings 2017-11-12 17:50:51 -07:00
Tyler Stewart
6c2c1a87dc test: use mock fs 2017-11-12 17:50:51 -07:00
Tyler Stewart
9e83143690 chore: improve tests 2017-11-12 17:50:51 -07:00
Tyler Stewart
0238529edf chore: change dev dependencies 2017-11-12 17:50:51 -07:00
Tyler Stewart
d0c204eb81 chore: migrate to circle ci 2.0 2017-11-12 17:50:51 -07:00
Tyler Stewart
cdebe9a464 chore: remove .env 2017-11-12 17:50:51 -07:00
Tyler Stewart
eeb8f9ab4d chore(mocha): use pretty bunyan nyan reporter 2017-11-12 17:50:51 -07:00
Tyler Stewart
60d06c21c8 test: update test flow
Wrap each difference in a describe block
2017-11-12 17:50:51 -07:00
Tyler Stewart
8609b1d02e chore(package): update dependenices
Adds back package-lock
2017-11-12 17:50:51 -07:00
Tyler Stewart
80b05215ff chore(config): add timeouts to tests 2017-11-12 17:50:51 -07:00
Peter Keuter
37f0a15549 fix: updated typings 2017-11-06 10:21:38 -07:00
Tyler Stewart
1ba67034b1 chore(README): fix class name references 2017-10-31 16:18:36 -06:00
Tyler Stewart
0a331c5998 fix(package): correct main file
This was changed with the confit update
2017-10-31 16:18:36 -06:00
Tyler Stewart
a7103ded7e fix: add values to resolved promises 2017-10-30 18:48:42 -06:00
Tyler Stewart
d787d4cab6 test(site): test site registry call 2017-10-30 17:52:05 -06:00
Tyler Stewart
154cd5a5d7 chore: eslint --fix 2017-10-30 17:52:05 -06:00
Tyler Stewart
5fc59b50b1 chore: update package versions 2017-10-30 17:52:05 -06:00
Tyler Stewart
043c97c80f chore: update build with confit 2017-10-30 17:52:05 -06:00
Tyler Stewart
772fe5ca06 feat: add eprt and epsv for IPv6 support 2017-10-30 17:52:05 -06:00
Ozair Patel
e272802525 feat(typings): swapped declare class for export interface 2017-10-25 21:46:24 -06:00
Ozair Patel
7589322abc feat(typings): removed Server extension for FtpServer 2017-10-25 21:46:24 -06:00
Ozair Patel
fae5564041 feat(typings): removed typed dependency and fixed import error 2017-10-25 21:46:24 -06:00
Ozair Patel
e9b4a6385d feat(typings): ypdated typescript typings 2017-10-25 21:46:24 -06:00
Tyler Stewart
71621aae4f Merge pull request #44 from trs/include-types-file-on-install
fix(package): include types file
2017-10-25 12:22:57 -06:00
Tyler Stewart
0eaa0f8743 chore(travis): only test v6
Node 8 fails for some reason, will look into in the future
2017-10-25 12:18:14 -06:00
Tyler Stewart
8828a4ea09 fix(package): include types file
Should ensure types are inluded on an install
2017-10-25 12:00:20 -06:00
Tyler Stewart
b33659320f Merge pull request #40 from trs/fix-socket-ref
fix(retr): check for connector socket
2017-09-30 11:14:31 -06:00
Tyler Stewart
6a6b949d3b fix(retr): check for connector socket
Ensures socket still exists and client hasn't disconnected
2017-09-30 11:09:30 -06:00
Tyler Stewart
283be85db3 Merge pull request #38 from trs/improve-connector-stream-handling
Improve connector stream handling
2017-08-18 12:45:32 -06:00
Tyler Stewart
e555ce9230 test(stor): add failure test 2017-08-18 12:06:58 -06:00
Tyler Stewart
e6575808f1 fix(stor): improve event and promise handling 2017-08-18 12:06:42 -06:00
Tyler Stewart
a5e58a106e fix(retr): improve event and promise handling 2017-08-18 12:06:42 -06:00
94 changed files with 5795 additions and 1964 deletions

140
.circleci/config.yml Normal file
View File

@@ -0,0 +1,140 @@
version: 2
jobs:
build_node_8:
docker:
- image: circleci/node:8
steps:
- checkout
- restore_cache:
key: npm-install-node-8-{{ checksum "package.json" }}
- run:
name: Install
command: npm install
- save_cache:
key: npm-install-node-8-{{ checksum "package.json" }}
paths:
- node_modules
build_node_6:
docker:
- image: circleci/node:6
steps:
- checkout
- restore_cache:
key: npm-install-node-6-{{ checksum "package.json" }}
- run:
name: Install
command: npm install
- save_cache:
key: npm-install-node-6-{{ checksum "package.json" }}
paths:
- node_modules
lint:
docker:
- image: circleci/node:8
steps:
- checkout
- restore_cache:
key: npm-install-node-8-{{ checksum "package.json" }}
- run:
name: Lint
command: npm run verify:js
test_node_8:
docker:
- image: circleci/node:8
steps:
- checkout
- restore_cache:
key: npm-install-node-8-{{ checksum "package.json" }}
- run:
name: Test Node 8
command: npm run test:coverage
when: always
- store_test_results:
path: reports
- store_artifacts:
path: reports/coverage
prefix: coverage
test_node_6:
docker:
- image: circleci/node:6
steps:
- checkout
- restore_cache:
key: npm-install-node-6-{{ checksum "package.json" }}
- run:
name: Test Node 6
command: npm run test:coverage
when: always
- store_test_results:
path: reports
- store_artifacts:
path: reports/coverage
prefix: coverage
release:
docker:
- image: circleci/node:8
steps:
- checkout
- restore_cache:
key: npm-install-node-6-{{ checksum "package.json" }}
- run:
name: Update NPM
command: |
npm install npm@5
npm install semantic-release@11
- deploy:
name: Semantic Release
command: |
npm run semantic-release || true
workflows:
version: 2
test_and_tag:
jobs:
- build_node_8:
filters:
branches:
only: master
- build_node_6:
filters:
branches:
only: master
- lint:
requires:
- build_node_8
- test_node_6:
requires:
- build_node_6
- test_node_8:
requires:
- build_node_8
- release:
requires:
- lint
- test_node_6
- test_node_8
build_and_test:
jobs:
- build_node_8:
filters:
branches:
ignore: master
- build_node_6:
filters:
branches:
ignore: master
- lint:
requires:
- build_node_8
- test_node_6:
requires:
- build_node_6
- test_node_8:
requires:
- build_node_8

2
.env
View File

@@ -1,2 +0,0 @@
FTP_URL=ftp://127.0.0.1:8880
PASV_RANGE=8881

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
package-lock.json binary

2
.gitignore vendored
View File

@@ -3,3 +3,5 @@ node_modules/
dist/
reports/
npm-debug.log
.nyc_output/
test_tmp/

24
.nycrc Normal file
View File

@@ -0,0 +1,24 @@
{
"check-coverage": true,
"per-file": false,
"lines": 90,
"statements": 90,
"functions": 85,
"branches": 75,
"include": [
"src/**/*.js"
],
"exclude": [
"test/**/*.spec.js"
],
"reporter": [
"lcovonly",
"html",
"text",
"cobertura",
"json"
],
"cache": true,
"all": true,
"report-dir": "./reports/coverage/"
}

View File

@@ -1,25 +0,0 @@
language: node_js
node_js:
- "6"
- "node"
env:
FTP_URL: ftp://127.0.0.1:8880
PASV_RANGE: 8881
install: npm install
script:
- npm run verify:js
- npm run test:coverage
after_script:
- npm run upload-coverage
deploy:
skip_cleanup: true
provider: script
script: npm run semantic-release
on:
branch: master
node: "6"

View File

@@ -136,6 +136,9 @@ Command | Description
Command | Description
:------ | :----------
<pre>npm run verify</pre> | Verify code style and syntax<ul><li>Verifies source *and test code* aginst customisable rules (unlike Webpack loaders)</li></ul>
<pre>npm run verify:js</pre> | Verify Javascript code style and syntax
<pre>npm run verify:js:fix</pre> | Verify Javascript code style and syntax and fix any errors that can be fixed automatically
<pre>npm run verify:js:watch</pre> | Verify Javascript code style and syntax and watch files for changes
<pre>npm run verify:watch</pre> | Runs verify task whenever JS or CSS code is changed

View File

@@ -1,12 +1,27 @@
[![ftp-srv](logo.png)](https://github.com/trs/ftp-srv)
<p align="center">
<a href="https://github.com/trs/ftp-srv">
<img alt="ftp-srv" src="logo.png" width="600px" />
</a>
</p>
<!--[RM_DESCRIPTION]-->
> Modern, extensible FTP Server
<!--[]-->
<p align="center">
Modern, extensible FTP Server
</p>
[![npm version](https://badge.fury.io/js/ftp-srv.svg)](https://badge.fury.io/js/ftp-srv) [![Build Status](https://travis-ci.org/trs/ftp-srv.svg?branch=master)](https://travis-ci.org/trs/ftp-srv)
[![Coverage Status](https://coveralls.io/repos/github/trs/ftp-srv/badge.svg?branch=coveralls)](https://coveralls.io/github/trs/ftp-srv?branch=coveralls) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
<p align="center">
<a href="https://www.npmjs.com/package/ftp-srv">
<img alt="npm" src="https://img.shields.io/npm/dm/ftp-srv.svg?style=for-the-badge" />
</a>
<a href="https://circleci.com/gh/trs/ftp-srv">
<img alt="npm" src="https://img.shields.io/circleci/project/github/trs/ftp-srv.svg?style=for-the-badge" />
</a>
<a href="https://coveralls.io/github/trs/ftp-srv?branch=master">
<img alt="npm" src="https://img.shields.io/coveralls/github/trs/ftp-srv.svg?style=for-the-badge" />
</a>
</p>
---
@@ -28,6 +43,7 @@
- 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
- Promise based API
## Install
`npm install ftp-srv --save`
@@ -37,8 +53,8 @@
```js
// Quick start
const FtpSvr = require('ftp-srv');
const ftpServer = new FtpSvr('ftp://0.0.0.0:9876', { options ... });
const FtpSrv = require('ftp-srv');
const ftpServer = new FtpSrv('ftp://0.0.0.0:9876', { options ... });
ftpServer.on('login', (data, resolve, reject) => { ... });
...
@@ -105,11 +121,11 @@ A [bunyan logger](https://github.com/trentm/node-bunyan) instance. Created by de
## Events
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`.
The `FtpSrv` 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`.
### `login`
```js
on('login', {connection, username, password}, resolve, reject) => { ... }
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.
@@ -136,7 +152,7 @@ Occurs when a client is attempting to login. Here you can resolve the login requ
### `client-error`
```js
on('client-error', {connection, context, error}) => { ... }
on('client-error', ({connection, context, error}) => { ... });
```
Occurs when an error arises in the client connection.
@@ -145,6 +161,26 @@ Occurs when an error arises in the client connection.
`context` string of where the error occured
`error` error object
### `RETR`
```js
on('RETR', (error, filePath) => { ... });
```
Occurs when a file is downloaded.
`error` if successful, will be `null`
`filePath` location to which file was downloaded
### `STOR`
```js
on('STOR', (error, fileName) => { ... });
```
Occurs when a file is uploaded.
`error` if successful, will be `null`
`fileName` name of the file that was downloaded
## Supported Commands
See the [command registry](src/commands/registration) for a list of all implemented FTP commands.
@@ -235,3 +271,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
This software is licensed under the MIT Licence. See [LICENSE](LICENSE).
<!--[]-->
## References
- [https://cr.yp.to/ftp.html](https://cr.yp.to/ftp.html)

View File

@@ -1,35 +0,0 @@
// Use JS to support loading of threshold data from external file
var coverageConfig = {
instrumentation: {
root: 'src/',
excludes: ['errors.js']
},
check: require('./thresholds.json'),
reporting: {
print: 'both',
dir: 'reports/coverage/',
reports: [
'cobertura',
'html',
'lcovonly',
'html',
'json'
],
'report-config': {
cobertura: {
file: 'cobertura/coverage.xml'
},
json: {
file: 'json/coverage.json'
},
lcovonly: {
file: 'lcov/lcov.info'
},
text: {
file: null
}
}
}
};
module.exports = coverageConfig;

View File

@@ -1,3 +1,5 @@
test/**/*.spec.js
--reporter mocha-pretty-bunyan-nyan
--reporter mocha-multi-reporters
--reporter-options configFile=config/testUnit/reporters.json
--ui bdd
--bail

View File

@@ -0,0 +1,6 @@
{
"reporterEnabled": "mocha-pretty-bunyan-nyan",
"mochaJunitReporterReporterOptions": {
"mochaFile": "reports/junit.xml"
}
}

View File

@@ -1,14 +0,0 @@
{
"global": {
"statements": 90,
"branches": 80,
"functions": 90,
"lines": 90
},
"each": {
"statements": 70,
"branches": 40,
"functions": 60,
"lines": 70
}
}

View File

@@ -1,61 +1,162 @@
# START_CONFIT_GENERATED_CONTENT
confit:
extends: &confit-extends
- plugin:node/recommended
plugins: &confit-plugins
- node
env: &confit-env
commonjs: true # For Webpack, CommonJS
node: true
mocha: true
es6: true
globals: &confit-globals {}
parser: &confit-parser espree
parserOptions: &confit-parserOptions
ecmaVersion: 6
sourceType: module
ecmaFeatures:
globalReturn: false
impliedStrict: true
jsx: false
# END_CONFIT_GENERATED_CONTENT
# Customise this section to meet your needs...
extends: *confit-extends
# Uncomment this next line if you need to add more items to the array, and remove the "*confit-extends" from the line above
# <<: *confit-extends
plugins: *confit-plugins
# Uncomment this next line if you need to add more items to the array, and remove the "*confit-plugins" from the line above
# <<: *confit-extends
env:
<<: *confit-env
globals:
<<: *confit-globals
parser: *confit-parser
parserOptions:
<<: *confit-parserOptions
rules:
no-process-exit: 0
max-len:
- warn
- 200 # Line Length
node/no-unpublished-require:
- 2
- allowModules:
- chai
- dotenv
- ftp
- sinon
- sinon-as-promised
{
"extends": "eslint:recommended",
"env": {
"node": true,
"mocha": true,
"es6": true
},
"plugins": [
"mocha",
"node"
],
"rules": {
"mocha/no-exclusive-tests": 2,
"no-warning-comments": [
1,
{
"terms": ["todo", "fixme", "xxx"],
"location": "start"
},
],
"object-curly-spacing": [
2,
"never"
],
"array-bracket-spacing": [
2,
"never"
],
"brace-style": [
2,
"1tbs"
],
"consistent-return": 0,
"indent": [
"error",
2,
{
"SwitchCase": 1,
"MemberExpression": "off"
}
],
"no-multiple-empty-lines": [
2,
{
"max": 2
}
],
"no-use-before-define": [
2,
"nofunc"
],
"one-var": [
2,
"never"
],
"quote-props": [
2,
"as-needed"
],
"quotes": [
2,
"single"
],
"keyword-spacing": 2,
"space-before-function-paren": [
2,
{
"anonymous": "always",
"named": "never"
}
],
"space-in-parens": [
2,
"never"
],
"strict": [
2,
"global"
],
"curly": [
2,
"multi-line"
],
"eol-last": 2,
"key-spacing": [
2,
{
"beforeColon": false,
"afterColon": true
}
],
"no-eval": 2,
"no-with": 2,
"space-infix-ops": 2,
"dot-notation": [
2,
{
"allowKeywords": true
}
],
"eqeqeq": 2,
"no-alert": 2,
"no-caller": 2,
"no-extend-native": 2,
"no-extra-bind": 2,
"no-implied-eval": 2,
"no-iterator": 2,
"no-label-var": 2,
"no-labels": 2,
"no-lone-blocks": 2,
"no-loop-func": 2,
"no-multi-spaces": 2,
"no-multi-str": 2,
"no-native-reassign": 2,
"no-new": 2,
"no-new-func": 2,
"no-new-wrappers": 2,
"no-octal-escape": 2,
"no-proto": 2,
"no-return-assign": 2,
"no-script-url": 2,
"no-sequences": 2,
"no-unused-expressions": 2,
"yoda": 2,
"no-shadow": 2,
"no-shadow-restricted-names": 2,
"no-undef-init": 2,
"no-console": 1,
"camelcase": [
0,
{
"properties": "never"
}
],
"comma-spacing": 2,
"comma-dangle": 1,
"new-cap": 2,
"new-parens": 2,
"arrow-parens": [2, "as-needed"],
"no-array-constructor": 2,
"array-callback-return": 1,
"no-extra-parens": 2,
"no-new-object": 2,
"no-spaced-func": 2,
"no-trailing-spaces": 2,
"no-underscore-dangle": 0,
"no-fallthrough": 0,
"semi": 2,
"semi-spacing": [
2,
{
"before": false,
"after": true
}
]
},
"parserOptions": {
"emcaVersion": 6,
"sourceType": "module",
"impliedStrict": true
}
}

View File

@@ -1,6 +1,6 @@
generator-confit:
app:
_version: f02196cc5cb7941ca46ec46d23bd6aef0dfcaca0
_version: 462ecd915fd9db1aef6a37c2b5ce8b58b80c18ba
buildProfile: Latest
copyrightOwner: Tyler Stewart
license: MIT
@@ -8,7 +8,7 @@ generator-confit:
publicRepository: true
repositoryType: GitHub
paths:
_version: 7f33e41600b34cd6867478d8f2b3d6b2bbd42508
_version: 780b129e0c7e5cab7e29c4f185bcf78524593a33
config:
configDir: config/
input:
@@ -18,22 +18,23 @@ generator-confit:
prodDir: dist/
reportDir: reports/
buildJS:
_version: df428a706d926204228c5d9ebdbd7b49908926d9
_version: ead8ce4280b07d696aff499a5fca1a933727582f
framework: []
frameworkScripts: []
outputFormat: ES6
sourceFormat: ES6
entryPoint:
_version: de20402bf85c703080ef6daf21e35325a3b9d604
_version: 39082c3df887fbc08744dfd088c25465e7a2e3a4
entryPoints:
main:
- src/index.js
testUnit:
_version: 4472a6d59b434226f463992d3c1914c77a6a115d
_version: 30eee42a88ee42cce4f1ae48fe0cbe81647d189a
testDependencies: []
testFramework: mocha
verify:
_version: 30ae86c5022840a01fc08833e238a82c683fa1c7
jsCodingStandard: eslint
jsCodingStandard: none
documentation:
_version: b1658da3278b16d1982212f5e8bc05348af20e0b
generateDocs: false

70
ftp-srv.d.ts vendored
View File

@@ -1,5 +1,13 @@
declare class FileSystem {
constructor(connection: any, {root, cwd}?: {
import * as tls from 'tls'
import { Stats } from 'fs'
export class FileSystem {
readonly connection: FtpConnection;
readonly root: string;
readonly cwd: string;
constructor(connection: FtpConnection, {root, cwd}?: {
root: any;
cwd: any;
});
@@ -32,8 +40,36 @@ declare class FileSystem {
getUniqueName(): string;
}
declare class FtpServer {
constructor(url: string, options?: {});
export class FtpConnection {
server: FtpServer;
id: string;
log: any;
transferType: string;
encoding: string;
bufferSize: boolean;
readonly ip: string;
restByteCount: number | undefined;
secure: boolean
close (code: number, message: number): Promise<any>
login (username: string, password: string): Promise<any>
reply (options: number | Object, ...letters: Array<any>): Promise<any>
}
export interface FtpServerOptions {
pasv_range?: number | string,
greeting?: string | string[],
tls?: tls.SecureContext | false,
anonymous?: boolean,
blacklist?: Array<string>,
whitelist?: Array<string>,
file_format?: (stat: Stats) => string | Promise<string> | "ls" | "ep",
log?: any
}
export class FtpServer {
constructor(url: string, options?: FtpServerOptions);
readonly isTLS: boolean;
@@ -56,7 +92,31 @@ declare class FtpServer {
disconnectClient(id: string): Promise<any>;
close(): any;
on(event: "login", listener: (
data: {
connection: FtpConnection,
username: string,
password: string
},
resolve: (config: {
fs?: FileSystem,
root?: string,
cwd?: string,
blacklist?: Array<string>,
whitelist?: Array<string>
}) => void,
reject: (err?: Error) => void
) => void)
on(event: "client-error", listener: (
data: {
connection: FtpConnection,
context: string,
error: Error,
}
) => void)
}
declare const FtpSrv: FtpServer;
export {FtpServer as FtpSrv};
export default FtpServer;

BIN
logo.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -1,17 +1,23 @@
/*
Send Button by Bruno Bosse from the Noun Project
https://thenounproject.com/brunobosse/collection/basics/?i=1054386
*/
const puppeteer = require('puppeteer');
const logoPath = `file://${process.cwd()}/logo/logo.html`;
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
puppeteer.launch()
.then(browser => {
return browser.newPage()
.then(page => {
return page.goto(logoPath)
.then(() => page);
})
.then(page => {
return page.setViewport({
width: 600,
height: 250,
deviceScaleFactor: 2
})
.then(() => page.screenshot({
path: 'logo.png',
omitBackground: true
}));
})
.then(() => browser.close());
});
rs.pipe(ws);
ws.on('finish', () => process.exit());

View File

@@ -1,81 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -1,29 +1,44 @@
<!doctype html>
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Overpass+Mono:700" rel="stylesheet">
<style>
body {
padding: 0;
margin: 0;
display: flex;
height: 100vh;
flex-direction: row;
justify-content: center;
}
div {
display: flex;
flex-direction: column;
justify-content: center;
width: 100vw;
}
h1 {
display: inline-block;
display: flex;
flex-direction: column;
align-self: center;
text-align: center;
margin: 0;
margin-left: -4px;
padding: 0px;
width: 75vw;
font-size: 68px;
font-family: 'Source Code Pro', monospace;
font-family: 'Overpass Mono', monospace;
font-weight: bold;
line-height: 0.8em;
letter-spacing: -3px;
color: #333;
color: #fff;
-webkit-font-smoothing: antialiased;
-webkit-text-stroke: 1px #0063B1;
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;
3px 3px 0 #0063B1,
-1px -1px 0 #0063B1,
1px -1px 0 #0063B1,
-1px 1px 0 #0063B1,
1px 1px 0 #0063B1;
}
h1 > span {
display: block;
@@ -31,17 +46,23 @@
margin: 0;
padding-top: 6px;
}
img {
display: inline-block;
vertical-align: top;
h1 > hr {
padding: 0;
margin: 0;
margin-top: 22px;
border: 1px solid #0063B1;
border-radius: 50%;
}
</style>
</head>
<body>
<h1>
<span>ftp-srv</span>
</h1>
<img src="icon.svg" width="76px" height="76px" />
<div>
<h1>
<span>ftp</span>
<hr />
<span>srv</span>
</h1>
</div>
</body>
</html>

5561
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "ftp-srv",
"version": "0.0.0",
"version": "0.0.0-development",
"description": "Modern, extensible FTP Server",
"keywords": [
"ftp",
@@ -8,14 +8,16 @@
"ftp-srv",
"ftp-svr",
"ftpd",
"server",
"ftpserver"
"ftpserver",
"server"
],
"license": "MIT",
"main": "ftp-srv.js",
"files": [
"src"
"src",
"ftp-srv.d.ts"
],
"main": "ftp-srv.js",
"types": "./ftp-srv.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/trs/ftp-srv"
@@ -27,21 +29,23 @@
"commitmsg": "cz-customizable-ghooks",
"dev": "cross-env NODE_ENV=development npm run verify:watch",
"prepush": "npm-run-all verify test:coverage --silent",
"semantic-release": "semantic-release pre && npm publish && semantic-release post",
"semantic-release": "semantic-release",
"start": "npm run dev",
"test": "npm run test:unit",
"test:check-coverage": "cross-env NODE_ENV=test istanbul check-coverage reports/coverage/coverage.json --config config/testUnit/istanbul.js",
"test:check-coverage": "nyc check-coverage",
"test:coverage": "npm-run-all test:unit:once test:check-coverage --silent",
"test:unit": "chokidar 'src/**/*.js' 'test/**/*.js' -c 'npm run test:unit:once' --initial --silent",
"test:unit:once": "cross-env NODE_ENV=test istanbul cover --config config/testUnit/istanbul.js _mocha -- --opts config/testUnit/mocha.opts",
"upload-coverage": "cat reports/coverage/lcov/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
"test:unit": "cross-env NODE_ENV=test nyc mocha --opts config/testUnit/mocha.opts -w",
"test:unit:once": "cross-env NODE_ENV=test nyc mocha --opts config/testUnit/mocha.opts",
"upload-coverage": "cat reports/coverage/lcov.info | coveralls",
"verify": "npm run verify:js --silent",
"verify:js": "eslint -c config/verify/.eslintrc \"src/**/*.js\" \"test/**/*.js\" \"config/**/*.js\" && echo ✅ verify:js success",
"verify:js:fix": "eslint --fix -c config/verify/.eslintrc \"src/**/*.js\" \"test/**/*.js\" \"config/**/*.js\" && echo ✅ verify:js:fix success",
"verify:js:watch": "chokidar 'src/**/*.js' 'test/**/*.js' 'config/**/*.js' -c 'npm run verify:js:fix' --initial --silent",
"verify:watch": "npm run verify:js:watch --silent"
},
"types": "./ftp-srv.d.ts",
"release": {
"verifyConditions": "condition-circle"
},
"config": {
"commitizen": {
"path": "node_modules/cz-customizable"
@@ -51,32 +55,37 @@
}
},
"dependencies": {
"bunyan": "^1.8.10",
"bluebird": "^3.5.1",
"bunyan": "^1.8.12",
"lodash": "^4.17.4",
"moment": "^2.18.1",
"uuid": "^3.1.0",
"when": "^3.7.8"
"moment": "^2.19.1",
"uuid": "^3.1.0"
},
"devDependencies": {
"@icetee/ftp": "^0.3.15",
"chai": "^4.0.2",
"chokidar-cli": "1.2.0",
"condition-circle": "^1.6.0",
"coveralls": "2.13.1",
"cross-env": "5.0.1",
"cz-customizable": "5.0.0",
"cross-env": "3.1.4",
"cz-customizable": "5.2.0",
"cz-customizable-ghooks": "1.5.0",
"dotenv": "^4.0.0",
"eslint": "3.19.0",
"eslint": "4.5.0",
"eslint-config-google": "0.8.0",
"eslint-plugin-node": "5.0.0",
"ftp": "^0.3.10",
"html-convert": "^2.1.7",
"husky": "0.13.4",
"eslint-friendly-formatter": "3.0.0",
"eslint-plugin-mocha": "^4.11.0",
"eslint-plugin-node": "5.1.1",
"husky": "0.13.3",
"istanbul": "0.4.5",
"mocha": "3.4.2",
"mocha": "3.5.0",
"mocha-junit-reporter": "1.13.0",
"mocha-multi-reporters": "1.1.5",
"mocha-pretty-bunyan-nyan": "^1.0.4",
"npm-run-all": "4.0.2",
"nyc": "11.1.0",
"rimraf": "2.6.1",
"semantic-release": "^6.3.6",
"semantic-release": "^11.0.2",
"sinon": "^2.3.5"
},
"engines": {

View File

@@ -1,5 +1,5 @@
const _ = require('lodash');
const when = require('when');
const Promise = require('bluebird');
const REGISTRY = require('./registry');
@@ -62,7 +62,7 @@ class FtpCommands {
}
const handler = commandRegister.handler.bind(this.connection);
return when.try(handler, { log, command, previous_command: this.previousCommand })
return Promise.resolve(handler({log, command, previous_command: this.previousCommand}))
.finally(() => {
this.previousCommand = _.clone(command);
});

View File

@@ -4,10 +4,10 @@ module.exports = {
return this.connector.waitForConnection()
.then(socket => {
return this.reply(426, {socket})
.then(() => this.connector.end());
.then(() => this.connector.end())
.then(() => this.reply(226));
})
.catch(() => {})
.then(() => this.reply(226));
.catch(() => this.reply(225));
},
syntax: '{{cmd}}',
description: 'Abort an active file transfer'

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const escapePath = require('../../helpers/escape-path');
module.exports = {
@@ -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.arg)
return Promise.resolve(this.fs.chdir(command.arg))
.then(cwd => {
const path = cwd ? `"${escapePath(cwd)}"` : undefined;
return this.reply(250, path);

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
module.exports = {
directive: 'DELE',
@@ -6,7 +6,7 @@ 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.arg)
return Promise.resolve(this.fs.delete(command.arg))
.then(() => {
return this.reply(250);
})

View File

@@ -9,11 +9,11 @@ const FAMILY = {
module.exports = {
directive: 'EPRT',
handler: function ({command} = {}) {
this.connector = new ActiveConnector(this);
const [protocol, ip, port] = _.compact(command.arg.split('|'));
const [, protocol, ip, port] = _.chain(command).get('arg', '').split('|').value();
const family = FAMILY[protocol];
if (!family) return this.reply(502, 'Unknown network protocol');
if (!family) return this.reply(504, 'Unknown network protocol');
this.connector = new ActiveConnector(this);
return this.connector.setupConnection(ip, port, family)
.then(() => this.reply(200));
},

View File

@@ -1,5 +1,5 @@
const _ = require('lodash');
const when = require('when');
const Promise = require('bluebird');
const getFileStat = require('../../helpers/file-stat');
// http://cr.yp.to/ftp/list.html
@@ -16,8 +16,8 @@ module.exports = {
const path = command.arg || '.';
return this.connector.waitForConnection()
.tap(() => this.commandSocket.pause())
.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(() => Promise.resolve(this.fs.get(path)))
.then(stat => stat.isDirectory() ? Promise.resolve(this.fs.list(path)) : [stat])
.then(files => {
const getFileMessage = file => {
if (simple) return file.name;
@@ -37,10 +37,8 @@ module.exports = {
if (fileList.length) return this.reply({}, ...fileList);
});
})
.then(() => {
return this.reply(226, 'Transfer OK');
})
.catch(when.TimeoutError, err => {
.then(() => this.reply(226))
.catch(Promise.TimeoutError, err => {
log.error(err);
return this.reply(425, 'No connection established');
})

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const moment = require('moment');
module.exports = {
@@ -7,7 +7,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');
return when.try(this.fs.get.bind(this.fs), command.arg)
return Promise.resolve(this.fs.get(command.arg))
.then(fileStat => {
const modificationTime = moment.utc(fileStat.mtime).format('YYYYMMDDHHmmss.SSS');
return this.reply(213, modificationTime);

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const escapePath = require('../../helpers/escape-path');
module.exports = {
@@ -7,7 +7,7 @@ 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.arg)
return Promise.resolve(this.fs.mkdir(command.arg))
.then(dir => {
const path = dir ? `"${escapePath(dir)}"` : undefined;
return this.reply(257, path);

View File

@@ -21,14 +21,19 @@ module.exports = {
};
function utf8([setting] = []) {
switch (_.toUpper(setting)) {
case 'ON':
this.encoding = 'utf8';
return this.reply(200, 'UTF8 encoding on');
case 'OFF':
this.encoding = 'ascii';
return this.reply(200, 'UTF8 encoding off');
default:
return this.reply(501, 'Unknown setting for option');
}
const getEncoding = () => {
switch (_.toUpper(setting)) {
case 'ON': return 'utf8';
case 'OFF': return 'ascii';
default: return null;
}
};
const encoding = getEncoding();
if (!encoding) return this.reply(501, 'Unknown setting for option');
this.encoding = encoding;
if (this.transferType !== 'binary') this.transferType = this.encoding;
return this.reply(200, `UTF8 encoding ${_.toLower(setting)}`);
}

View File

@@ -5,6 +5,7 @@ module.exports = {
directive: 'PORT',
handler: function ({command} = {}) {
this.connector = new ActiveConnector(this);
const rawConnection = _.get(command, 'arg', '').split(',');
if (rawConnection.length !== 6) return this.reply(425);

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const escapePath = require('../../helpers/escape-path');
module.exports = {
@@ -7,7 +7,7 @@ module.exports = {
if (!this.fs) return this.reply(550, 'File system not instantiated');
if (!this.fs.currentDirectory) return this.reply(402, 'Not supported by file system');
return when.try(this.fs.currentDirectory.bind(this.fs))
return Promise.resolve(this.fs.currentDirectory())
.then(cwd => {
const path = cwd ? `"${escapePath(cwd)}"` : undefined;
return this.reply(257, path);

View File

@@ -9,7 +9,7 @@ module.exports = {
if (isNaN(byteCount) || byteCount < 0) return this.reply(501, 'Byte count must be 0 or greater');
this.restByteCount = byteCount;
return this.reply(350, `Resarting next transfer at ${byteCount}`);
return this.reply(350, `Restarting next transfer at ${byteCount}`);
},
syntax: '{{cmd}} <byte-count>',
description: 'Restart transfer from the specified point. Resets after any STORE or RETRIEVE'

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
module.exports = {
directive: 'RETR',
@@ -6,26 +6,45 @@ module.exports = {
if (!this.fs) return this.reply(550, 'File system not instantiated');
if (!this.fs.read) return this.reply(402, 'Not supported by file system');
const filePath = command.arg;
return this.connector.waitForConnection()
.tap(() => this.commandSocket.pause())
.then(() => when.try(this.fs.read.bind(this.fs), command.arg, {start: this.restByteCount}))
.then(() => Promise.resolve(this.fs.read(filePath, {start: this.restByteCount})))
.then(stream => {
this.restByteCount = 0;
return when.promise((resolve, reject) => {
this.connector.socket.on('error', err => stream.emit('error', err));
const destroyConnection = (connection, reject) => err => {
if (connection) connection.destroy(err);
reject(err);
};
stream.on('data', data => this.connector.socket.write(data, this.transferType));
stream.on('end', () => resolve(this.reply(226)));
stream.on('error', err => reject(err));
this.reply(150).then(() => this.connector.socket.resume());
const eventsPromise = new Promise((resolve, reject) => {
stream.on('data', data => {
if (stream) stream.pause();
if (this.connector.socket) {
this.connector.socket.write(data, this.transferType, () => stream && stream.resume());
}
});
stream.once('end', () => resolve());
stream.once('error', destroyConnection(this.connector.socket, reject));
this.connector.socket.once('error', destroyConnection(stream, reject));
});
this.restByteCount = 0;
return this.reply(150).then(() => stream.resume() && this.connector.socket.resume())
.then(() => eventsPromise)
.tap(() => this.emit('RETR', null, filePath))
.finally(() => stream.destroy && stream.destroy());
})
.catch(when.TimeoutError, err => {
.then(() => this.reply(226))
.catch(Promise.TimeoutError, err => {
log.error(err);
return this.reply(425, 'No connection established');
})
.catch(err => {
log.error(err);
this.emit('RETR', err);
return this.reply(551, err.message);
})
.finally(() => {

View File

@@ -1,4 +1,4 @@
const dele = require('./dele').handler;
const {handler: dele} = require('./dele');
module.exports = {
directive: ['RMD', 'XRMD'],

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
module.exports = {
directive: 'RNFR',
@@ -7,7 +7,7 @@ module.exports = {
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
const fileName = command.arg;
return when.try(this.fs.get.bind(this.fs), fileName)
return Promise.resolve(this.fs.get(fileName))
.then(() => {
this.renameFrom = fileName;
return this.reply(350);

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
module.exports = {
directive: 'RNTO',
@@ -11,7 +11,7 @@ module.exports = {
const from = this.renameFrom;
const to = command.arg;
return when.try(this.fs.rename.bind(this.fs), from, to)
return Promise.resolve(this.fs.rename(from, to))
.then(() => {
return this.reply(250);
})

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
module.exports = function ({log, command} = {}) {
if (!this.fs) return this.reply(550, 'File system not instantiated');
@@ -6,7 +6,7 @@ module.exports = function ({log, command} = {}) {
const [mode, ...fileNameParts] = command.arg.split(' ');
const fileName = fileNameParts.join(' ');
return when.try(this.fs.chmod.bind(this.fs), fileName, parseInt(mode, 8))
return Promise.resolve(this.fs.chmod(fileName, parseInt(mode, 8)))
.then(() => {
return this.reply(200);
})

View File

@@ -1,16 +1,19 @@
const when = require('when');
const Promise = require('bluebird');
const _ = require('lodash');
const registry = require('./registry');
module.exports = {
directive: 'SITE',
handler: function ({log, command} = {}) {
const registry = require('./registry');
const subCommand = this.commands.parse(command.arg);
const rawSubCommand = _.get(command, 'arg', '');
const subCommand = this.commands.parse(rawSubCommand);
const subLog = log.child({subverb: subCommand.directive});
if (!registry.hasOwnProperty(subCommand.directive)) return this.reply(502);
const handler = registry[subCommand.directive].handler.bind(this);
return when.try(handler, { log: subLog, command: subCommand });
return Promise.resolve(handler({log: subLog, command: subCommand}));
},
syntax: '{{cmd}} <subVerb> [...<subParams>]',
description: 'Sends site specific commands to remote server'

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
module.exports = {
directive: 'SIZE',
@@ -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');
return when.try(this.fs.get.bind(this.fs), command.arg)
return Promise.resolve(this.fs.get(command.arg))
.then(fileStat => {
return this.reply(213, {message: fileStat.size});
})

View File

@@ -1,5 +1,5 @@
const _ = require('lodash');
const when = require('when');
const Promise = require('bluebird');
const getFileStat = require('../../helpers/file-stat');
module.exports = {
@@ -11,26 +11,27 @@ 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), path)
return Promise.resolve(this.fs.get(path))
.then(stat => {
if (stat.isDirectory()) {
if (!this.fs.list) return this.reply(402, 'Not supported by file system');
return when.try(this.fs.list.bind(this.fs), path)
.then(files => {
const fileList = files.map(file => {
const message = getFileStat(file, _.get(this, 'server.options.file_format', 'ls'));
return {
raw: true,
message
};
});
return this.reply(213, 'Status begin', ...fileList, 'Status end');
});
} else {
return this.reply(212, getFileStat(stat, _.get(this, 'server.options.file_format', 'ls')));
return Promise.resolve(this.fs.list(path))
.then(stats => [213, stats]);
}
return [212, [stat]];
})
.then(([code, fileStats]) => {
return Promise.map(fileStats, file => {
const message = getFileStat(file, _.get(this, 'server.options.file_format', 'ls'));
return {
raw: true,
message
};
})
.then(messages => [code, messages]);
})
.then(([code, messages]) => this.reply(code, 'Status begin', ...messages, 'Status end'))
.catch(err => {
log.error(err);
return this.reply(450, err.message);

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
module.exports = {
directive: 'STOR',
@@ -11,29 +11,48 @@ module.exports = {
return this.connector.waitForConnection()
.tap(() => this.commandSocket.pause())
.then(() => when.try(this.fs.write.bind(this.fs), fileName, {append, start: this.restByteCount}))
.then(() => Promise.resolve(this.fs.write(fileName, {append, start: this.restByteCount})))
.then(stream => {
const destroyConnection = (connection, reject) => err => {
if (connection) connection.destroy(err);
reject(err);
};
const streamPromise = new Promise((resolve, reject) => {
stream.once('error', destroyConnection(this.connector.socket, reject));
stream.once('finish', () => resolve());
});
const socketPromise = new Promise((resolve, reject) => {
this.connector.socket.on('data', data => {
if (this.connector.socket) this.connector.socket.pause();
if (stream) {
stream.write(data, this.transferType, () => this.connector.socket && this.connector.socket.resume());
}
});
this.connector.socket.once('end', () => {
if (stream.listenerCount('close')) stream.emit('close');
else stream.end();
resolve();
});
this.connector.socket.once('error', destroyConnection(stream, reject));
});
this.restByteCount = 0;
return when.promise((resolve, reject) => {
stream.once('error', err => this.connector.socket.emit('error', err));
stream.once('finish', () => resolve(this.reply(226, fileName)));
// Emit `close` if stream has a close listener, otherwise emit `finish` with the end() method
// It is assumed that the `close` handler will call the end() method
this.connector.socket.once('end', () => stream.listenerCount('close') ? stream.emit('close') : stream.end());
this.connector.socket.once('error', err => reject(err));
this.connector.socket.on('data', data => stream.write(data, this.transferType));
this.reply(150).then(() => this.connector.socket.resume());
})
.finally(() => stream.destroy ? when.try(stream.destroy.bind(stream)) : null);
return this.reply(150).then(() => this.connector.socket.resume())
.then(() => Promise.join(streamPromise, socketPromise))
.tap(() => this.emit('STOR', null, fileName))
.finally(() => stream.destroy && stream.destroy());
})
.catch(when.TimeoutError, err => {
.then(() => this.reply(226, fileName))
.catch(Promise.TimeoutError, err => {
log.error(err);
return this.reply(425, 'No connection established');
})
.catch(err => {
log.error(err);
this.emit('STOR', err);
return this.reply(550, err.message);
})
.finally(() => {

View File

@@ -1,6 +1,5 @@
const when = require('when');
const stor = require('./stor').handler;
const Promise = require('bluebird');
const {handler: stor} = require('./stor');
module.exports = {
directive: 'STOU',
@@ -9,10 +8,10 @@ module.exports = {
if (!this.fs.get || !this.fs.getUniqueName) return this.reply(402, 'Not supported by file system');
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));
return Promise.try(() => {
return Promise.resolve(this.fs.get(fileName))
.then(() => Promise.resolve(this.fs.getUniqueName()))
.catch(() => Promise.resolve(fileName));
})
.then(name => {
args.command.arg = name;

View File

@@ -1,4 +1,3 @@
module.exports = {
directive: 'TYPE',
handler: function ({command} = {}) {

View File

@@ -36,7 +36,9 @@ const commands = [
require('./registration/type'),
require('./registration/user'),
require('./registration/pbsz'),
require('./registration/prot')
require('./registration/prot'),
require('./registration/eprt'),
require('./registration/epsv')
];
const registry = commands.reduce((result, cmd) => {

View File

@@ -1,7 +1,7 @@
const _ = require('lodash');
const uuid = require('uuid');
const when = require('when');
const sequence = require('when/sequence');
const Promise = require('bluebird');
const EventEmitter = require('events');
const BaseConnector = require('./connector/base');
const FileSystem = require('./fs');
@@ -9,8 +9,9 @@ const Commands = require('./commands');
const errors = require('./errors');
const DEFAULT_MESSAGE = require('./messages');
class FtpConnection {
class FtpConnection extends EventEmitter {
constructor(server, options) {
super();
this.server = server;
this.id = uuid.v4();
this.log = options.log.child({id: this.id, ip: this.ip});
@@ -33,18 +34,19 @@ class FtpConnection {
this.commandSocket.on('close', () => {
if (this.connector) this.connector.end();
if (this.commandSocket && !this.commandSocket.destroyed) this.commandSocket.destroy();
this.removeAllListeners();
});
}
_handleData(data) {
const messages = _.compact(data.toString(this.encoding).split('\r\n'));
this.log.trace(messages);
return sequence(messages.map(message => this.commands.handle.bind(this.commands, message)));
return Promise.mapSeries(messages, message => this.commands.handle(message));
}
get ip() {
try {
return this.dataSocket ? this.dataSocket.remoteAddress : this.commandSocket.remoteAddress;
return this.commandSocket ? this.commandSocket.remoteAddress : undefined;
} catch (ex) {
return null;
}
@@ -65,14 +67,13 @@ class FtpConnection {
}
close(code = 421, message = 'Closing connection') {
return when
.resolve(code)
return Promise.resolve(code)
.then(_code => _code && this.reply(_code, message))
.then(() => this.commandSocket && this.commandSocket.end());
}
login(username, password) {
return when.try(() => {
return Promise.try(() => {
const loginListeners = this.server.listeners('login');
if (!loginListeners || !loginListeners.length) {
if (!this.server.options.anonymous) throw new errors.GeneralError('No "login" listener setup', 500);
@@ -93,8 +94,8 @@ class FtpConnection {
if (typeof options === 'number') options = {code: options}; // allow passing in code as first param
if (!Array.isArray(letters)) letters = [letters];
if (!letters.length) letters = [{}];
return when.map(letters, promise => {
return when(promise)
return Promise.map(letters, (promise, index) => {
return Promise.resolve(promise)
.then(letter => {
if (!letter) letter = {};
else if (typeof letter === 'string') letter = {message: letter}; // allow passing in message as first param
@@ -102,8 +103,12 @@ class FtpConnection {
if (!letter.socket) letter.socket = options.socket ? options.socket : this.commandSocket;
if (!letter.message) letter.message = DEFAULT_MESSAGE[options.code] || 'No information';
if (!letter.encoding) letter.encoding = this.encoding;
return when(letter.message) // allow passing in a promise as a message
return Promise.resolve(letter.message) // allow passing in a promise as a message
.then(message => {
const seperator = !options.hasOwnProperty('eol') ?
letters.length - 1 === index ? ' ' : '-' :
options.eol ? ' ' : '-';
message = !letter.raw ? _.compact([letter.code || options.code, message]).join(seperator) : message;
letter.message = message;
return letter;
});
@@ -111,16 +116,11 @@ class FtpConnection {
});
};
const processLetter = (letter, index) => {
return when.promise((resolve, reject) => {
const seperator = !options.hasOwnProperty('eol') ?
letters.length - 1 === index ? ' ' : '-' :
options.eol ? ' ' : '-';
const packet = !letter.raw ? _.compact([letter.code || options.code, letter.message]).join(seperator) : letter.message;
const processLetter = letter => {
return new Promise((resolve, reject) => {
if (letter.socket && letter.socket.writable) {
this.log.trace({port: letter.socket.address().port, encoding: letter.encoding, packet}, 'Reply');
letter.socket.write(packet + '\r\n', letter.encoding, err => {
this.log.trace({port: letter.socket.address().port, encoding: letter.encoding, message: letter.message}, 'Reply');
letter.socket.write(letter.message + '\r\n', letter.encoding, err => {
if (err) {
this.log.error(err);
return reject(err);
@@ -132,7 +132,9 @@ class FtpConnection {
};
return satisfyParameters()
.then(satisfiedLetters => sequence(satisfiedLetters.map((letter, index) => processLetter.bind(this, letter, index))))
.then(satisfiedLetters => Promise.mapSeries(satisfiedLetters, (letter, index) => {
return processLetter(letter, index);
}))
.catch(err => {
this.log.error(err);
});

View File

@@ -1,6 +1,6 @@
const {Socket} = require('net');
const tls = require('tls');
const when = require('when');
const Promise = require('bluebird');
const Connector = require('./base');
class Active extends Connector {
@@ -10,25 +10,27 @@ class Active extends Connector {
}
waitForConnection({timeout = 5000, delay = 250} = {}) {
return when.iterate(
() => {},
() => this.dataSocket && this.dataSocket.connected,
() => when().delay(delay)
).timeout(timeout)
.then(() => this.dataSocket);
const checkSocket = () => {
if (this.dataSocket && this.dataSocket.connected) {
return Promise.resolve(this.dataSocket);
}
return Promise.resolve().delay(delay)
.then(() => checkSocket());
};
return checkSocket().timeout(timeout);
}
setupConnection(host, port, family = 4) {
const closeExistingServer = () => this.dataSocket ?
when(this.dataSocket.destroy()) :
when.resolve();
const closeExistingServer = () => Promise.resolve(
this.dataSocket ? this.dataSocket.destroy() : undefined);
return closeExistingServer()
.then(() => {
this.dataSocket = new Socket();
this.dataSocket.setEncoding(this.connection.transferType);
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.on('error', err => this.server && this.server.emit('client-error', {connection: this.connection, context: 'dataSocket', error: err}));
this.dataSocket.connect({host, port, family}, () => {
this.dataSocket.pause();
if (this.connection.secure) {

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const errors = require('../errors');
class Connector {
@@ -23,15 +23,25 @@ class Connector {
}
waitForConnection() {
return when.reject(new errors.ConnectorError('No connector setup, send PASV or PORT'));
return Promise.reject(new errors.ConnectorError('No connector setup, send PASV or PORT'));
}
end() {
if (this.dataSocket) this.dataSocket.end();
if (this.dataServer) this.dataServer.close();
this.dataSocket = null;
this.dataServer = null;
this.type = false;
const closeDataSocket = new Promise(resolve => {
if (this.dataSocket) this.dataSocket.end();
else resolve();
});
const closeDataServer = new Promise(resolve => {
if (this.dataServer) this.dataServer.close(() => resolve());
else resolve();
});
return Promise.all([closeDataSocket, closeDataServer])
.then(() => {
this.dataSocket = null;
this.dataServer = null;
this.type = false;
});
}
}
module.exports = Connector;

View File

@@ -1,6 +1,6 @@
const net = require('net');
const tls = require('tls');
const when = require('when');
const Promise = require('bluebird');
const Connector = require('./base');
const findPort = require('../helpers/find-port');
@@ -13,19 +13,23 @@ class Passive extends Connector {
}
waitForConnection({timeout = 5000, delay = 250} = {}) {
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,
() => when().delay(delay)
).timeout(timeout)
.then(() => this.dataSocket);
if (!this.dataServer) return Promise.reject(new errors.ConnectorError('Passive server not setup'));
const checkSocket = () => {
if (this.dataServer && this.dataServer.listening && this.dataSocket && this.dataSocket.connected) {
return Promise.resolve(this.dataSocket);
}
return Promise.resolve().delay(delay)
.then(() => checkSocket());
};
return checkSocket().timeout(timeout);
}
setupServer() {
const closeExistingServer = () => this.dataServer ?
when.promise(resolve => this.dataServer.close(() => resolve())) :
when.resolve();
new Promise(resolve => this.dataServer.close(() => resolve())) :
Promise.resolve();
return closeExistingServer()
.then(() => this.getPort())
@@ -55,7 +59,7 @@ class Passive extends Connector {
}
this.dataSocket.connected = true;
this.dataSocket.setEncoding(this.connection.transferType);
this.dataSocket.on('error', err => this.server.emit('client-error', {connection: this.connection, context: 'dataSocket', error: err}));
this.dataSocket.on('error', err => this.server && this.server.emit('client-error', {connection: this.connection, context: 'dataSocket', error: err}));
this.dataSocket.on('close', () => {
this.log.trace('Passive connection closed');
this.end();
@@ -63,15 +67,15 @@ class Passive extends Connector {
};
this.dataSocket = null;
this.dataServer = net.createServer({ pauseOnConnect: true }, connectionHandler);
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('error', err => this.server && this.server.emit('client-error', {connection: this.connection, context: 'dataServer', error: err}));
this.dataServer.on('close', () => {
this.log.trace('Passive server closed');
this.dataServer = null;
});
return when.promise((resolve, reject) => {
return new Promise((resolve, reject) => {
this.dataServer.listen(port, err => {
if (err) reject(err);
else {

View File

@@ -1,14 +1,12 @@
const _ = require('lodash');
const nodePath = require('path');
const uuid = require('uuid');
const when = require('when');
const whenNode = require('when/node');
const syncFs = require('fs');
const fs = whenNode.liftAll(syncFs);
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
const errors = require('./errors');
class FileSystem {
constructor(connection, { root, cwd } = {}) {
constructor(connection, {root, cwd} = {}) {
this.connection = connection;
this.cwd = cwd || nodePath.sep;
this.root = root || process.cwd();
@@ -32,19 +30,19 @@ class FileSystem {
get(fileName) {
const {fsPath} = this._resolvePath(fileName);
return fs.stat(fsPath)
return fs.statAsync(fsPath)
.then(stat => _.set(stat, 'name', fileName));
}
list(path = '.') {
const {fsPath} = this._resolvePath(path);
return fs.readdir(fsPath)
return fs.readdirAsync(fsPath)
.then(fileNames => {
return when.map(fileNames, fileName => {
return Promise.map(fileNames, fileName => {
const filePath = nodePath.join(fsPath, fileName);
return fs.access(filePath, syncFs.constants.F_OK)
return fs.accessAsync(filePath, fs.constants.F_OK)
.then(() => {
return fs.stat(filePath)
return fs.statAsync(filePath)
.then(stat => _.set(stat, 'name', fileName));
})
.catch(() => null);
@@ -55,7 +53,7 @@ class FileSystem {
chdir(path = '.') {
const {fsPath, serverPath} = this._resolvePath(path);
return fs.stat(fsPath)
return fs.statAsync(fsPath)
.tap(stat => {
if (!stat.isDirectory()) throw new errors.FileSystemError('Not a valid directory');
})
@@ -67,48 +65,48 @@ class FileSystem {
write(fileName, {append = false, start = undefined} = {}) {
const {fsPath} = this._resolvePath(fileName);
const stream = syncFs.createWriteStream(fsPath, {flags: !append ? 'w+' : 'a+', start});
stream.once('error', () => fs.unlink(fsPath));
const stream = fs.createWriteStream(fsPath, {flags: !append ? 'w+' : 'a+', start});
stream.once('error', () => fs.unlinkAsync(fsPath));
stream.once('close', () => stream.end());
return stream;
}
read(fileName, {start = undefined} = {}) {
const {fsPath} = this._resolvePath(fileName);
return fs.stat(fsPath)
return fs.statAsync(fsPath)
.tap(stat => {
if (stat.isDirectory()) throw new errors.FileSystemError('Cannot read a directory');
})
.then(() => {
const stream = syncFs.createReadStream(fsPath, {flags: 'r', start});
const stream = fs.createReadStream(fsPath, {flags: 'r', start});
return stream;
});
}
delete(path) {
const {fsPath} = this._resolvePath(path);
return fs.stat(fsPath)
return fs.statAsync(fsPath)
.then(stat => {
if (stat.isDirectory()) return fs.rmdir(fsPath);
else return fs.unlink(fsPath);
if (stat.isDirectory()) return fs.rmdirAsync(fsPath);
else return fs.unlinkAsync(fsPath);
});
}
mkdir(path) {
const {fsPath} = this._resolvePath(path);
return fs.mkdir(fsPath)
return fs.mkdirAsync(fsPath)
.then(() => fsPath);
}
rename(from, to) {
const {fsPath: fromPath} = this._resolvePath(from);
const {fsPath: toPath} = this._resolvePath(to);
return fs.rename(fromPath, toPath);
return fs.renameAsync(fromPath, toPath);
}
chmod(path, mode) {
const {fsPath} = this._resolvePath(path);
return fs.chmod(fsPath, mode);
return fs.chmodAsync(fsPath, mode);
}
getUniqueName() {

View File

@@ -1,9 +1,9 @@
const net = require('net');
const when = require('when');
const Promise = require('bluebird');
const errors = require('../errors');
module.exports = function (min = 1, max = undefined) {
return when.promise((resolve, reject) => {
return new Promise((resolve, reject) => {
let checkPort = min;
let portCheckServer = net.createServer();
portCheckServer.maxConnections = 0;

View File

@@ -1,11 +1,11 @@
const http = require('http');
const when = require('when');
const Promise = require('bluebird');
const errors = require('../errors');
const IP_WEBSITE = 'http://api.ipify.org/';
module.exports = function (hostname) {
return when.promise((resolve, reject) => {
return new Promise((resolve, reject) => {
if (!hostname || hostname === '0.0.0.0') {
let ip = '';
http.get(IP_WEBSITE, response => {

View File

@@ -1,16 +1,18 @@
const _ = require('lodash');
const when = require('when');
const Promise = require('bluebird');
const nodeUrl = require('url');
const buyan = require('bunyan');
const net = require('net');
const tls = require('tls');
const fs = require('fs');
const EventEmitter = require('events');
const Connection = require('./connection');
const resolveHost = require('./helpers/resolve-host');
class FtpServer {
class FtpServer extends EventEmitter {
constructor(url, options = {}) {
super();
this.options = _.merge({
log: buyan.createLogger({name: 'ftp-srv'}),
anonymous: false,
@@ -43,13 +45,10 @@ class FtpServer {
return connection.reply(220, ...greeting, features)
.finally(() => socket.resume());
};
const serverOptions = _.assign(this.isTLS ? this._tls : {}, { pauseOnConnect: true });
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'));
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.quit());
process.on('SIGINT', () => this.quit());
@@ -64,7 +63,7 @@ class FtpServer {
return resolveHost(this.url.hostname)
.then(hostname => {
this.url.hostname = hostname;
return when.promise((resolve, reject) => {
return new Promise((resolve, reject) => {
this.server.listen(this.url.port, err => {
if (err) return reject(err);
this.log.info({
@@ -72,21 +71,17 @@ class FtpServer {
ip: this.url.hostname,
port: this.url.port
}, 'Listening');
resolve();
resolve('Listening');
});
});
});
}
emitPromise(action, ...data) {
const defer = when.defer();
const params = _.concat(data, [defer.resolve, defer.reject]);
this.server.emit(action, ...params);
return defer.promise;
}
emit(action, ...data) {
this.server.emit(action, ...data);
return new Promise((resolve, reject) => {
const params = _.concat(data, [resolve, reject]);
this.emit.call(this, action, ...params);
});
}
setupTLS(_tls) {
@@ -116,7 +111,7 @@ class FtpServer {
}
disconnectClient(id) {
return when.promise(resolve => {
return new Promise(resolve => {
const client = this.connections[id];
if (!client) return resolve();
delete this.connections[id];
@@ -125,7 +120,7 @@ class FtpServer {
} catch (err) {
this.log.error(err, 'Error closing connection', {id});
} finally {
resolve();
resolve('Disconnected');
}
});
}
@@ -138,13 +133,14 @@ class FtpServer {
close() {
this.log.info('Server closing...');
this.server.maxConnections = 0;
return when.map(Object.keys(this.connections), id => when.try(this.disconnectClient.bind(this), id))
.then(() => when.promise(resolve => {
return Promise.map(Object.keys(this.connections), id => Promise.try(this.disconnectClient.bind(this, id)))
.then(() => new Promise(resolve => {
this.server.close(err => {
if (err) this.log.error(err, 'Error closing server');
resolve();
resolve('Closed');
});
}));
}))
.then(() => this.removeAllListeners());
}
}

View File

@@ -1,5 +1,5 @@
const {expect} = require('chai');
const when = require('when');
const Promise = require('bluebird');
const bunyan = require('bunyan');
const sinon = require('sinon');
@@ -11,7 +11,7 @@ describe('FtpCommands', function () {
let mockConnection = {
authenticated: false,
log: bunyan.createLogger({name: 'FtpCommands'}),
reply: () => when.resolve({}),
reply: () => Promise.resolve({}),
server: {
options: {
blacklist: ['allo']

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -6,10 +6,10 @@ const CMD = 'ABOR';
describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => when.resolve(),
reply: () => Promise.resolve(),
connector: {
waitForConnection: () => when.resolve(),
end: () => when.resolve()
waitForConnection: () => Promise.resolve(),
end: () => Promise.resolve()
}
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
@@ -33,7 +33,7 @@ describe(CMD, function () {
.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);
expect(mockClient.reply.args[0][0]).to.equal(225);
});
});

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -6,7 +6,7 @@ const CMD = 'ALLO';
describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => when.resolve()
reply: () => Promise.resolve()
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -6,7 +6,7 @@ const CMD = 'AUTH';
describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => when.resolve(),
reply: () => Promise.resolve(),
server: {
_tls: {}
}
@@ -23,7 +23,7 @@ describe(CMD, function () {
});
it('TLS // supported', () => {
return cmdFn({command: { arg: 'TLS', directive: CMD}})
return cmdFn({command: {arg: 'TLS', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(234);
expect(mockClient.secure).to.equal(true);
@@ -31,14 +31,14 @@ describe(CMD, function () {
});
it('SSL // not supported', () => {
return cmdFn({command: { arg: 'SSL', directive: CMD}})
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}})
return cmdFn({command: {arg: 'bad', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(504);
});

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const bunyan = require('bunyan');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -8,9 +8,9 @@ describe(CMD, function () {
let sandbox;
let log = bunyan.createLogger({name: CMD});
const mockClient = {
reply: () => when.resolve(),
reply: () => Promise.resolve(),
fs: {
chdir: () => when.resolve()
chdir: () => Promise.resolve()
}
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);

View File

@@ -8,7 +8,7 @@ describe(CMD, function () {
let log = bunyan.createLogger({name: CMD});
const mockClient = {
reply: () => {},
fs: { chdir: () => {} }
fs: {chdir: () => {}}
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
@@ -24,7 +24,7 @@ describe(CMD, function () {
describe('// check', function () {
it('fails on no fs', () => {
const badMockClient = { reply: () => {} };
const badMockClient = {reply: () => {}};
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
@@ -35,7 +35,7 @@ describe(CMD, function () {
});
it('fails on no fs chdir command', () => {
const badMockClient = { reply: () => {}, fs: {} };
const badMockClient = {reply: () => {}, fs: {}};
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
@@ -47,7 +47,7 @@ describe(CMD, function () {
});
it('test // successful', () => {
return cmdFn({log, command: { arg: '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');
@@ -58,7 +58,7 @@ describe(CMD, function () {
mockClient.fs.chdir.restore();
sandbox.stub(mockClient.fs, 'chdir').resolves('/test');
return cmdFn({log, command: { arg: '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');
@@ -69,7 +69,7 @@ describe(CMD, function () {
mockClient.fs.chdir.restore();
sandbox.stub(mockClient.fs, 'chdir').rejects(new Error('Bad'));
return cmdFn({log, command: { arg: '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');

View File

@@ -8,7 +8,7 @@ describe(CMD, function () {
let log = bunyan.createLogger({name: CMD});
const mockClient = {
reply: () => {},
fs: { delete: () => {} }
fs: {delete: () => {}}
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
@@ -24,7 +24,7 @@ describe(CMD, function () {
describe('// check', function () {
it('fails on no fs', () => {
const badMockClient = { reply: () => {} };
const badMockClient = {reply: () => {}};
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
@@ -35,7 +35,7 @@ describe(CMD, function () {
});
it('fails on no fs delete command', () => {
const badMockClient = { reply: () => {}, fs: {} };
const badMockClient = {reply: () => {}, fs: {}};
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
@@ -47,7 +47,7 @@ describe(CMD, function () {
});
it('test // successful', () => {
return cmdFn({log, command: { arg: 'test', directive: CMD}})
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');
@@ -58,7 +58,7 @@ describe(CMD, function () {
mockClient.fs.delete.restore();
sandbox.stub(mockClient.fs, 'delete').rejects(new Error('Bad'));
return cmdFn({log, command: { arg: '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');

View File

@@ -0,0 +1,60 @@
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
const ActiveConnector = require('../../../src/connector/active');
const CMD = 'EPRT';
describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => Promise.resolve()
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
beforeEach(() => {
sandbox = sinon.sandbox.create();
sandbox.spy(mockClient, 'reply');
sandbox.stub(ActiveConnector.prototype, 'setupConnection').resolves();
});
afterEach(() => {
sandbox.restore();
});
it('// unsuccessful | no argument', () => {
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(504);
});
});
it('// unsuccessful | invalid argument', () => {
return cmdFn({command: {arg: 'blah'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(504);
});
});
it('// successful IPv4', () => {
return cmdFn({command: {arg: '|1|192.168.0.100|35286|'}})
.then(() => {
const [ip, port, family] = 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');
expect(family).to.equal(4);
});
});
it('// successful IPv6', () => {
return cmdFn({command: {arg: '|2|8536:933f:e7f3:3e91:6dc1:e8c6:8482:7b23|35286|'}})
.then(() => {
const [ip, port, family] = ActiveConnector.prototype.setupConnection.args[0];
expect(mockClient.reply.args[0][0]).to.equal(200);
expect(ip).to.equal('8536:933f:e7f3:3e91:6dc1:e8c6:8482:7b23');
expect(port).to.equal('35286');
expect(family).to.equal(6);
});
});
});

View File

@@ -0,0 +1,35 @@
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
const PassiveConnector = require('../../../src/connector/passive');
const CMD = 'EPSV';
describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => Promise.resolve()
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
beforeEach(() => {
sandbox = sinon.sandbox.create();
sandbox.stub(mockClient, 'reply').resolves();
sandbox.stub(PassiveConnector.prototype, 'setupServer').resolves({
address: () => ({port: 12345})
});
});
afterEach(() => {
sandbox.restore();
});
it('// successful IPv4', () => {
return cmdFn()
.then(() => {
const [code, message] = mockClient.reply.args[0];
expect(code).to.equal(229);
expect(message).to.equal('EPSV OK (|||12345|)');
});
});
});

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -6,7 +6,7 @@ const CMD = 'HELP';
describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => when.resolve()
reply: () => Promise.resolve()
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
@@ -20,28 +20,28 @@ describe(CMD, function () {
});
it('// successful', () => {
return cmdFn({command: { directive: CMD }})
return cmdFn({command: {directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(211);
});
});
it('help // successful', () => {
return cmdFn({command: { arg: 'help', directive: CMD}})
return cmdFn({command: {arg: 'help', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(214);
});
});
it('allo // successful', () => {
return cmdFn({command: { arg: 'allo', directive: CMD}})
return cmdFn({command: {arg: 'allo', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(214);
});
});
it('bad // unsuccessful', () => {
return cmdFn({command: { arg: 'bad', directive: CMD}})
return cmdFn({command: {arg: 'bad', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(502);
});

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const bunyan = require('bunyan');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -14,7 +14,7 @@ describe(CMD, function () {
get: () => {}
},
connector: {
waitForConnection: () => when({}),
waitForConnection: () => Promise.resolve({}),
end: () => {}
},
commandSocket: {
@@ -88,7 +88,7 @@ describe(CMD, function () {
describe('// check', function () {
it('fails on no fs', () => {
const badMockClient = { reply: () => {} };
const badMockClient = {reply: () => {}};
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
@@ -99,7 +99,7 @@ describe(CMD, function () {
});
it('fails on no fs list command', () => {
const badMockClient = { reply: () => {}, fs: {} };
const badMockClient = {reply: () => {}, fs: {}};
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
@@ -165,7 +165,7 @@ describe(CMD, function () {
});
it('. // unsuccessful (timeout)', () => {
sandbox.stub(mockClient.connector, 'waitForConnection').returns(when.reject(new when.TimeoutError()));
sandbox.stub(mockClient.connector, 'waitForConnection').returns(Promise.reject(new Promise.TimeoutError()));
return cmdFn({log, command: {directive: CMD}})
.then(() => {

View File

@@ -8,7 +8,7 @@ describe(CMD, function () {
let log = bunyan.createLogger({name: CMD});
const mockClient = {
reply: () => {},
fs: { get: () => {} }
fs: {get: () => {}}
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
@@ -24,7 +24,7 @@ describe(CMD, function () {
describe('// check', function () {
it('fails on no fs', () => {
const badMockClient = { reply: () => {} };
const badMockClient = {reply: () => {}};
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
@@ -35,7 +35,7 @@ describe(CMD, function () {
});
it('fails on no fs get command', () => {
const badMockClient = { reply: () => {}, fs: {} };
const badMockClient = {reply: () => {}, fs: {}};
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();

View File

@@ -8,7 +8,7 @@ describe(CMD, function () {
let log = bunyan.createLogger({name: CMD});
const mockClient = {
reply: () => {},
fs: { mkdir: () => {} }
fs: {mkdir: () => {}}
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
@@ -24,7 +24,7 @@ describe(CMD, function () {
describe('// check', function () {
it('fails on no fs', () => {
const badMockClient = { reply: () => {} };
const badMockClient = {reply: () => {}};
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
@@ -35,7 +35,7 @@ describe(CMD, function () {
});
it('fails on no fs mkdir command', () => {
const badMockClient = { reply: () => {}, fs: {} };
const badMockClient = {reply: () => {}, fs: {}};
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -6,7 +6,7 @@ const CMD = 'MODE';
describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => when.resolve()
reply: () => Promise.resolve()
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const bunyan = require('bunyan');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -14,7 +14,7 @@ describe(CMD, function () {
list: () => {}
},
connector: {
waitForConnection: () => when({}),
waitForConnection: () => Promise.resolve({}),
end: () => {}
},
commandSocket: {

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -6,7 +6,7 @@ const CMD = 'NOOP';
describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => when.resolve()
reply: () => Promise.resolve()
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -6,7 +6,7 @@ const CMD = 'OPTS';
describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => when.resolve()
reply: () => Promise.resolve()
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);

View File

@@ -9,7 +9,7 @@ describe(CMD, function () {
const mockClient = {
reply: () => {},
login: () => {},
server: { options: { anonymous: false } },
server: {options: {anonymous: false}},
username: 'anonymous'
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -6,7 +6,7 @@ const CMD = 'PBSZ';
describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => when.resolve(),
reply: () => Promise.resolve(),
server: {}
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -8,7 +8,7 @@ const CMD = 'PORT';
describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => when.resolve()
reply: () => Promise.resolve()
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
@@ -30,14 +30,14 @@ describe(CMD, function () {
});
it('// unsuccessful | invalid argument', () => {
return cmdFn({ command: { arg: '1,2,3,4,5' } })
return cmdFn({command: {arg: '1,2,3,4,5'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(425);
});
});
it('// successful', () => {
return cmdFn({ command: { arg: '192,168,0,100,137,214' } })
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);

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -6,7 +6,7 @@ const CMD = 'PROT';
describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => when.resolve(),
reply: () => Promise.resolve(),
server: {}
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);

View File

@@ -8,7 +8,7 @@ describe(CMD, function () {
let log = bunyan.createLogger({name: CMD});
const mockClient = {
reply: () => {},
fs: { currentDirectory: () => {} }
fs: {currentDirectory: () => {}}
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
@@ -24,7 +24,7 @@ describe(CMD, function () {
describe('// check', function () {
it('fails on no fs', () => {
const badMockClient = { reply: () => {} };
const badMockClient = {reply: () => {}};
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
@@ -35,7 +35,7 @@ describe(CMD, function () {
});
it('fails on no fs currentDirectory command', () => {
const badMockClient = { reply: () => {}, fs: {} };
const badMockClient = {reply: () => {}, fs: {}};
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
@@ -47,7 +47,7 @@ describe(CMD, function () {
});
it('// successful', () => {
return cmdFn({log, command: { arg: 'test', directive: CMD}})
return cmdFn({log, command: {arg: 'test', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(257);
});

View File

@@ -1,12 +1,12 @@
const {expect} = require('chai');
const sinon = require('sinon');
const when = require('when');
const Promise = require('bluebird');
const CMD = 'REST';
describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => when.resolve()
reply: () => Promise.resolve()
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
@@ -27,21 +27,21 @@ describe(CMD, function () {
});
it('-1 // unsuccessful', () => {
return cmdFn({command: { arg: '-1', directive: CMD } })
return cmdFn({command: {arg: '-1', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(501);
});
});
it('bad // unsuccessful', () => {
return cmdFn({command: { arg: 'bad', directive: CMD } })
return cmdFn({command: {arg: 'bad', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(501);
});
});
it('1 // successful', () => {
return cmdFn({command: { arg: '1', directive: CMD } })
return cmdFn({command: {arg: '1', directive: CMD}})
.then(() => {
expect(mockClient.restByteCount).to.equal(1);
expect(mockClient.reply.args[0][0]).to.equal(350);
@@ -49,7 +49,7 @@ describe(CMD, function () {
});
it('0 // successful', () => {
return cmdFn({command: { arg: '0', directive: CMD } })
return cmdFn({command: {arg: '0', directive: CMD}})
.then(() => {
expect(mockClient.restByteCount).to.equal(0);
expect(mockClient.reply.args[0][0]).to.equal(350);

View File

@@ -1,20 +1,22 @@
const when = require('when');
const Promise = require('bluebird');
const bunyan = require('bunyan');
const {expect} = require('chai');
const sinon = require('sinon');
const EventEmitter = require('events');
const CMD = 'RETR';
describe(CMD, function () {
let sandbox;
let log = bunyan.createLogger({name: CMD});
let emitter;
const mockClient = {
commandSocket: {
pause: () => {},
resume: () => {}
},
reply: () => when.resolve(),
reply: () => Promise.resolve(),
connector: {
waitForConnection: () => when.resolve({
waitForConnection: () => Promise.resolve({
resume: () => {}
}),
end: () => {}
@@ -29,6 +31,11 @@ describe(CMD, function () {
read: () => {}
};
emitter = new EventEmitter();
mockClient.emit = emitter.emit.bind(emitter);
mockClient.on = emitter.on.bind(emitter);
mockClient.once = emitter.once.bind(emitter);
sandbox.spy(mockClient, 'reply');
});
afterEach(() => sandbox.restore());
@@ -53,10 +60,11 @@ describe(CMD, function () {
it('// unsuccessful | connector times out', () => {
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
return when.reject(new when.TimeoutError());
return Promise.reject(new Promise.TimeoutError());
});
return cmdFn({log, command: {arg: 'test.txt'} })
return cmdFn({log, command: {arg: 'test.txt'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(425);
});
@@ -64,12 +72,28 @@ describe(CMD, function () {
it('// unsuccessful | connector errors out', () => {
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
return when.reject(new Error('test'));
return Promise.reject(new Error('test'));
});
return cmdFn({log, command: {arg: 'test.txt'} })
return cmdFn({log, command: {arg: 'test.txt'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(551);
});
});
it('// unsuccessful | emits error event', () => {
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
return Promise.reject(new Error('test'));
});
let errorEmitted = false;
emitter.once('RETR', err => {
errorEmitted = !!err;
});
return cmdFn({log, command: {arg: 'test.txt'}})
.then(() => {
expect(errorEmitted).to.equal(true);
});
});
});

View File

@@ -1,12 +1,12 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
const CMD = 'RNFR';
describe(CMD, function () {
let sandbox;
const mockLog = { error: () => {} };
const mockClient = { reply: () => when.resolve() };
const mockLog = {error: () => {}};
const mockClient = {reply: () => Promise.resolve()};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
beforeEach(() => {
@@ -14,7 +14,7 @@ describe(CMD, function () {
mockClient.renameFrom = 'test';
mockClient.fs = {
get: () => when.resolve()
get: () => Promise.resolve()
};
sandbox.spy(mockClient, 'reply');
@@ -46,14 +46,14 @@ describe(CMD, function () {
mockClient.fs.get.restore();
sandbox.stub(mockClient.fs, 'get').rejects(new Error('test'));
return cmdFn({ log: mockLog, command: { arg: 'test' } })
return cmdFn({log: mockLog, command: {arg: 'test'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
});
});
it('test // successful', () => {
return cmdFn({ log: mockLog, command: { arg: 'test' } })
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);

View File

@@ -1,12 +1,12 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
const CMD = 'RNTO';
describe(CMD, function () {
let sandbox;
const mockLog = { error: () => {} };
const mockClient = { reply: () => when.resolve() };
const mockLog = {error: () => {}};
const mockClient = {reply: () => Promise.resolve()};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
beforeEach(() => {
@@ -14,8 +14,8 @@ describe(CMD, function () {
mockClient.renameFrom = 'test';
mockClient.fs = {
get: () => when.resolve(),
rename: () => when.resolve()
get: () => Promise.resolve(),
rename: () => Promise.resolve()
};
sandbox.spy(mockClient, 'reply');
@@ -56,14 +56,14 @@ describe(CMD, function () {
mockClient.fs.rename.restore();
sandbox.stub(mockClient.fs, 'rename').rejects(new Error('test'));
return cmdFn({ log: mockLog, command: { arg: 'new' } })
return cmdFn({log: mockLog, command: {arg: 'new'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
});
});
it('new // successful', () => {
return cmdFn({ command: { arg: 'new' } })
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']);

View File

@@ -1,19 +1,19 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
const CMD = 'CHMOD';
describe(CMD, function () {
let sandbox;
const mockLog = { error: () => {} };
const mockClient = { reply: () => when.resolve() };
const mockLog = {error: () => {}};
const mockClient = {reply: () => Promise.resolve()};
const cmdFn = require(`../../../../src/commands/registration/site/${CMD.toLowerCase()}`).bind(mockClient);
beforeEach(() => {
sandbox = sinon.sandbox.create();
mockClient.fs = {
chmod: () => when.resolve()
chmod: () => Promise.resolve()
};
sandbox.spy(mockClient, 'reply');
@@ -49,7 +49,7 @@ describe(CMD, function () {
mockClient.fs.chmod.restore();
sandbox.stub(mockClient.fs, 'chmod').rejects(new Error('test'));
cmdFn({ log: mockLog, command: { arg: '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: { arg: '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);

View File

@@ -0,0 +1,52 @@
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
const bunyan = require('bunyan');
const siteRegistry = require('../../../../src/commands/registration/site/registry');
const FtpCommands = require('../../../../src/commands');
const CMD = 'SITE';
describe(CMD, function () {
let sandbox;
const log = bunyan.createLogger({name: 'site-test'});
const mockClient = {
reply: () => Promise.resolve(),
commands: new FtpCommands()
};
const cmdFn = require(`../../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
beforeEach(() => {
sandbox = sinon.sandbox.create();
sandbox.stub(mockClient, 'reply').resolves();
});
afterEach(() => {
sandbox.restore();
});
it('// unsuccessful', () => {
return cmdFn({log})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(502);
});
});
it('// unsuccessful', () => {
return cmdFn({log, command: {arg: 'BAD'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(502);
});
});
it('// successful', () => {
sandbox.stub(siteRegistry.CHMOD, 'handler').resolves();
return cmdFn({log, command: {arg: 'CHMOD test'}})
.then(() => {
const {command} = siteRegistry.CHMOD.handler.args[0][0];
expect(command.directive).to.equal('CHMOD');
expect(command.arg).to.equal('test');
});
});
});

View File

@@ -1,19 +1,19 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
const CMD = 'SIZE';
describe(CMD, function () {
let sandbox;
const mockLog = { error: () => {} };
const mockClient = { reply: () => when.resolve() };
const mockLog = {error: () => {}};
const mockClient = {reply: () => Promise.resolve()};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
beforeEach(() => {
sandbox = sinon.sandbox.create();
mockClient.fs = {
get: () => when.resolve({size: 1})
get: () => Promise.resolve({size: 1})
};
sandbox.spy(mockClient, 'reply');
@@ -43,14 +43,14 @@ describe(CMD, function () {
it('// unsuccessful | file get fails', () => {
sandbox.stub(mockClient.fs, 'get').rejects(new Error('test'));
return cmdFn({ log: mockLog, command: { arg: 'test' } })
return cmdFn({log: mockLog, command: {arg: 'test'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
});
});
it('// successful', () => {
return cmdFn({ command: { arg: 'test' } })
return cmdFn({command: {arg: 'test'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(213);
});

View File

@@ -1,20 +1,20 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
const CMD = 'STAT';
describe(CMD, function () {
let sandbox;
const mockLog = { error: () => {} };
const mockClient = { reply: () => when.resolve() };
const mockLog = {error: () => {}};
const mockClient = {reply: () => Promise.resolve()};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
beforeEach(() => {
sandbox = sinon.sandbox.create();
mockClient.fs = {
get: () => when.resolve({}),
list: () => when.resolve([])
get: () => Promise.resolve({}),
list: () => Promise.resolve([])
};
sandbox.spy(mockClient, 'reply');
@@ -33,7 +33,7 @@ describe(CMD, function () {
it('// unsuccessful | no file system', () => {
delete mockClient.fs;
return cmdFn({ command: { arg: 'test' } })
return cmdFn({command: {arg: 'test'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
});
@@ -42,7 +42,7 @@ describe(CMD, function () {
it('// unsuccessful | file system does not have functions', () => {
mockClient.fs = {};
return cmdFn({ command: { arg: 'test' } })
return cmdFn({command: {arg: 'test'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(402);
});
@@ -51,7 +51,7 @@ describe(CMD, function () {
it('// unsuccessful | file get fails', () => {
sandbox.stub(mockClient.fs, 'get').rejects(new Error('test'));
return cmdFn({ log: mockLog, command: { arg: 'test' } })
return cmdFn({log: mockLog, command: {arg: 'test'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(450);
});
@@ -77,7 +77,7 @@ describe(CMD, function () {
isDirectory: () => false
});
return cmdFn({ command: { arg: 'test' } })
return cmdFn({command: {arg: 'test'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(212);
});
@@ -122,7 +122,7 @@ describe(CMD, function () {
isDirectory: () => true
});
return cmdFn({ command: { arg: 'test' } })
return cmdFn({command: {arg: 'test'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(213);
});

View File

@@ -1,20 +1,22 @@
const when = require('when');
const Promise = require('bluebird');
const bunyan = require('bunyan');
const {expect} = require('chai');
const sinon = require('sinon');
const EventEmitter = require('events');
const CMD = 'STOR';
describe(CMD, function () {
let sandbox;
let log = bunyan.createLogger({name: CMD});
let emitter;
const mockClient = {
commandSocket: {
pause: () => {},
resume: () => {}
},
reply: () => when.resolve(),
reply: () => Promise.resolve(),
connector: {
waitForConnection: () => when.resolve({
waitForConnection: () => Promise.resolve({
resume: () => {}
}),
end: () => {}
@@ -29,6 +31,11 @@ describe(CMD, function () {
write: () => {}
};
emitter = new EventEmitter();
mockClient.emit = emitter.emit.bind(emitter);
mockClient.on = emitter.on.bind(emitter);
mockClient.once = emitter.once.bind(emitter);
sandbox.spy(mockClient, 'reply');
});
afterEach(() => sandbox.restore());
@@ -53,10 +60,10 @@ describe(CMD, function () {
it('// unsuccessful | connector times out', () => {
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
return when.reject(new when.TimeoutError());
return Promise.reject(new Promise.TimeoutError());
});
return cmdFn({log, command: {arg: 'test.txt'} })
return cmdFn({log, command: {arg: 'test.txt'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(425);
});
@@ -64,12 +71,28 @@ describe(CMD, function () {
it('// unsuccessful | connector errors out', () => {
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
return when.reject(new Error('test'));
return Promise.reject(new Error('test'));
});
return cmdFn({log, command: {arg: 'test.txt'} })
return cmdFn({log, command: {arg: 'test.txt'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
});
});
it('// unsuccessful | emits error event', () => {
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
return Promise.reject(new Error('test'));
});
let errorEmitted = false;
emitter.once('STOR', err => {
errorEmitted = !!err;
});
return cmdFn({log, command: {arg: 'test.txt'}})
.then(() => {
expect(errorEmitted).to.equal(true);
});
});
});

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -8,7 +8,7 @@ const CMD = 'STOU';
describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => when.resolve()
reply: () => Promise.resolve()
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
@@ -16,8 +16,8 @@ describe(CMD, function () {
sandbox = sinon.sandbox.create();
mockClient.fs = {
get: () => when.resolve(),
getUniqueName: () => when.resolve('4')
get: () => Promise.resolve(),
getUniqueName: () => Promise.resolve('4')
};
sandbox.spy(mockClient, 'reply');
@@ -52,7 +52,7 @@ describe(CMD, function () {
mockClient.fs.get.restore();
sandbox.stub(mockClient.fs, 'get').rejects({});
return cmdFn({ command: { arg: 'good' } })
return cmdFn({command: {arg: 'good'}})
.then(() => {
const call = stor.handler.call.args[0][1];
expect(call).to.have.property('command');
@@ -63,7 +63,7 @@ describe(CMD, function () {
});
it('// successful | generates unique name', () => {
return cmdFn({ command: { arg: 'bad' } })
return cmdFn({command: {arg: 'bad'}})
.then(() => {
const call = stor.handler.call.args[0][1];
expect(call).to.have.property('command');

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -6,7 +6,7 @@ const CMD = 'STRU';
describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => when.resolve()
reply: () => Promise.resolve()
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
@@ -20,14 +20,14 @@ describe(CMD, function () {
});
it('// successful', () => {
return cmdFn({command: { arg: 'F' } })
return cmdFn({command: {arg: 'F'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
});
});
it('// unsuccessful', () => {
return cmdFn({command: { arg: 'X' } })
return cmdFn({command: {arg: 'X'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(504);
});

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -6,7 +6,7 @@ const CMD = 'SYST';
describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => when.resolve()
reply: () => Promise.resolve()
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -6,7 +6,7 @@ const CMD = 'TYPE';
describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => when.resolve()
reply: () => Promise.resolve()
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
@@ -21,7 +21,7 @@ describe(CMD, function () {
});
it('A // successful', () => {
return cmdFn({ command: { arg: 'A' } })
return cmdFn({command: {arg: 'A'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
expect(mockClient.transferType).to.equal('ascii');
@@ -29,7 +29,7 @@ describe(CMD, function () {
});
it('I // successful', () => {
return cmdFn({ command: { arg: 'I' } })
return cmdFn({command: {arg: 'I'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
expect(mockClient.transferType).to.equal('binary');
@@ -37,7 +37,7 @@ describe(CMD, function () {
});
it('L // successful', () => {
return cmdFn({ command: { arg: 'L' } })
return cmdFn({command: {arg: 'L'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
expect(mockClient.transferType).to.equal('binary');
@@ -45,7 +45,7 @@ describe(CMD, function () {
});
it('X // successful', () => {
return cmdFn({ command: { arg: 'X' } })
return cmdFn({command: {arg: 'X'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(501);
expect(mockClient.transferType).to.equal(null);

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -9,9 +9,9 @@ describe(CMD, function () {
error: () => {}
};
const mockClient = {
reply: () => when.resolve(),
server: { options: {} },
login: () => when.resolve()
reply: () => Promise.resolve(),
server: {options: {}},
login: () => Promise.resolve()
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
@@ -29,7 +29,7 @@ describe(CMD, function () {
});
it('test // successful | prompt for password', () => {
return cmdFn({ command: { arg: 'test' } })
return cmdFn({command: {arg: 'test'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(331);
});
@@ -38,7 +38,7 @@ describe(CMD, function () {
it('test // successful | anonymous login', () => {
mockClient.server.options = {anonymous: true};
return cmdFn({ command: { arg: 'anonymous' } })
return cmdFn({command: {arg: 'anonymous'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(230);
expect(mockClient.login.callCount).to.equal(1);
@@ -46,7 +46,7 @@ describe(CMD, function () {
});
it('test // unsuccessful | no username provided', () => {
return cmdFn({ command: { } })
return cmdFn({command: { }})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(501);
expect(mockClient.login.callCount).to.equal(0);
@@ -56,7 +56,7 @@ describe(CMD, function () {
it('test // unsuccessful | already set username', () => {
mockClient.username = 'test';
return cmdFn({ command: { arg: 'test' } })
return cmdFn({command: {arg: 'test'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(530);
expect(mockClient.login.callCount).to.equal(0);
@@ -66,7 +66,7 @@ describe(CMD, function () {
it('test // successful | regular login if anonymous is true', () => {
mockClient.server.options = {anonymous: true};
return cmdFn({ log: mockLog, command: { arg: 'test' } })
return cmdFn({log: mockLog, command: {arg: 'test'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(331);
expect(mockClient.login.callCount).to.equal(0);
@@ -76,7 +76,7 @@ describe(CMD, function () {
it('test // successful | anonymous login with set username', () => {
mockClient.server.options = {anonymous: 'sillyrabbit'};
return cmdFn({ log: mockLog, command: { arg: '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);
@@ -88,7 +88,7 @@ describe(CMD, function () {
mockClient.login.restore();
sandbox.stub(mockClient, 'login').rejects(new Error('test'));
return cmdFn({ log: mockLog, command: { arg: 'anonymous' } })
return cmdFn({log: mockLog, command: {arg: 'anonymous'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(530);
expect(mockClient.login.callCount).to.equal(1);
@@ -98,7 +98,7 @@ describe(CMD, function () {
it('test // successful | does not login if already authenticated', () => {
mockClient.authenticated = true;
return cmdFn({ log: mockLog, command: { arg: 'sillyrabbit' } })
return cmdFn({log: mockLog, command: {arg: 'sillyrabbit'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(230);
expect(mockClient.login.callCount).to.equal(0);

View File

@@ -66,7 +66,7 @@ describe('Connector - Active //', function () {
it('upgrades to a secure connection', function () {
mockConnection.secure = true;
mockConnection.server = { _tls: {} };
mockConnection.server = {_tls: {}};
return active.setupConnection('127.0.0.1', PORT)
.then(() => {

View File

@@ -2,7 +2,7 @@
const {expect} = require('chai');
const sinon = require('sinon');
const when = require('when');
const Promise = require('bluebird');
const net = require('net');
const bunyan = require('bunyan');
@@ -11,12 +11,12 @@ const PassiveConnector = require('../../src/connector/passive');
describe('Connector - Passive //', function () {
let passive;
let mockConnection = {
reply: () => when.resolve({}),
close: () => when.resolve({}),
reply: () => Promise.resolve({}),
close: () => Promise.resolve({}),
encoding: 'utf8',
log: bunyan.createLogger({name: 'passive-test'}),
commandSocket: {},
server: { options: {} }
server: {options: {}}
};
let sandbox;

View File

@@ -1,84 +1,137 @@
/* eslint no-unused-expressions: 0 */
const {expect} = require('chai');
const sinon = require('sinon');
const bunyan = require('bunyan');
const Promise = require('bluebird');
const _ = require('lodash');
const fs = require('fs');
const nodePath = require('path');
const FtpServer = require('../src');
const FtpClient = require('ftp');
const FtpClient = require('@icetee/ftp');
before(() => require('dotenv').load());
describe('Integration', function () {
this.timeout(4000);
describe('FtpServer', function () {
this.timeout(2000);
let log = bunyan.createLogger({name: 'test'});
let server;
let client;
let sandbox;
let log = bunyan.createLogger({name: 'test-runner'});
let server;
let connection;
const clientDirectory = `${process.cwd()}/test_tmp`;
before(() => {
server = new FtpServer(process.env.FTP_URL, {
return startServer('ftp://127.0.0.1:8880');
});
beforeEach(() => {
sandbox = sinon.sandbox.create();
});
afterEach(() => sandbox.restore());
after(() => server.close());
before(() => {
directoryPurge(clientDirectory);
fs.mkdirSync(clientDirectory);
});
after(() => directoryPurge(clientDirectory));
function startServer(url, options = {}) {
server = new FtpServer(url, _.assign({
log,
pasv_range: process.env.PASV_RANGE,
tls: {
key: `${process.cwd()}/test/cert/server.key`,
cert: `${process.cwd()}/test/cert/server.crt`,
ca: `${process.cwd()}/test/cert/server.csr`
},
greeting: ['hello', 'world']
});
pasv_range: 8881,
greeting: ['hello', 'world'],
anonymous: true
}, options));
server.on('login', (data, resolve) => {
resolve({root: process.cwd()});
connection = data.connection;
resolve({root: clientDirectory});
});
return server.listen();
});
after(() => {
server.close();
});
}
it('accepts client connection', done => {
expect(server).to.exist;
client = new FtpClient();
client.once('ready', () => done());
client.once('error', err => done(err));
client.connect({
host: server.url.hostname,
port: server.url.port,
user: 'test',
password: 'test'
function connectClient(options = {}) {
return new Promise((resolve, reject) => {
client = new FtpClient();
client.once('ready', () => resolve(client));
client.once('error', err => reject(err));
client.connect(_.assign({
host: server.url.hostname,
port: server.url.port,
user: 'test',
password: 'test'
}, options));
})
.then(instance => {
client = instance;
});
});
}
it('STAT', done => {
client.status((err, status) => {
expect(err).to.not.exist;
expect(status).to.be.a('string');
done();
function closeClient() {
return new Promise((resolve, reject) => {
client.once('close', () => resolve());
client.once('error', err => reject(err));
client.logout(err => {
expect(err).to.be.undefined;
});
});
});
}
it('SYST', done => {
client.system((err, os) => {
expect(err).to.not.exist;
expect(os).to.be.a('string');
done();
function directoryPurge(dir) {
const dirExists = fs.existsSync(dir);
if (!dirExists) return;
const list = fs.readdirSync(dir);
list.map(item => nodePath.resolve(dir, item)).forEach(item => {
const itemExists = fs.existsSync(dir);
if (!itemExists) return;
const stat = fs.statSync(item);
if (stat.isDirectory()) directoryPurge(item);
else fs.unlinkSync(item);
});
});
fs.rmdirSync(dir);
}
const runFileSystemTests = () => {
it('CWD ..', done => {
const dir = '..';
client.cwd(`${dir}`, (err, data) => {
function runFileSystemTests(name) {
before(() => {
directoryPurge(`${clientDirectory}/${name}/`);
fs.mkdirSync(`${clientDirectory}/${name}/`);
fs.writeFileSync(`${clientDirectory}/${name}/fake.txt`, 'Fake file');
});
after(() => directoryPurge(`${clientDirectory}/${name}/`));
it('STAT', done => {
client.status((err, status) => {
expect(err).to.not.exist;
expect(data).to.be.a('string');
expect(status).to.equal('Status OK');
done();
});
});
it('CWD test', done => {
const dir = 'test';
client.cwd(`${dir}`, (err, data) => {
it('SYST', done => {
client.system((err, os) => {
expect(err).to.not.exist;
expect(data).to.be.a('string');
expect(os).to.equal('UNIX');
done();
});
});
it('CWD ..', done => {
client.cwd('..', (err, data) => {
expect(err).to.not.exist;
expect(data).to.equal('/');
done();
});
});
it(`CWD ${name}`, done => {
client.cwd(`${name}`, (err, data) => {
expect(err).to.not.exist;
expect(data).to.equal(`/${name}`);
done();
});
});
@@ -86,7 +139,7 @@ describe('FtpServer', function () {
it('PWD', done => {
client.pwd((err, data) => {
expect(err).to.not.exist;
expect(data).to.be.a('string');
expect(data).to.equal(`/${name}`);
done();
});
});
@@ -95,46 +148,86 @@ describe('FtpServer', function () {
client.list('.', (err, data) => {
expect(err).to.not.exist;
expect(data).to.be.an('array');
expect(data.length).to.be.above(1);
expect(data.length).to.equal(1);
expect(data[0].name).to.equal('fake.txt');
done();
});
});
it('LIST index.spec.js', done => {
client.list('index.spec.js', (err, data) => {
it('LIST fake.txt', done => {
client.list('fake.txt', (err, data) => {
expect(err).to.not.exist;
expect(data).to.be.an('array');
expect(data.length).to.be.equal(1);
expect(data.length).to.equal(1);
expect(data[0].name).to.equal('fake.txt');
done();
});
});
it('STOR fail.txt', done => {
const buffer = Buffer.from('test text file');
const fsPath = `${clientDirectory}/${name}/fail.txt`;
sandbox.stub(connection.fs, 'write').callsFake(function () {
const stream = fs.createWriteStream(fsPath, {flags: 'w+'});
stream.on('error', () => fs.existsSync(fsPath) && fs.unlinkSync(fsPath));
stream.on('close', () => stream.end());
setTimeout(() => stream.emit('error', new Error('STOR fail test')));
return stream;
});
client.put(buffer, 'fail.txt', err => {
setTimeout(() => {
const fileExists = fs.existsSync(fsPath);
expect(err).to.exist;
expect(fileExists).to.equal(false);
done();
});
});
});
it('STOR tést.txt', done => {
const buffer = Buffer.from('test text file');
const fsPath = `${clientDirectory}/${name}/tést.txt`;
connection.once('STOR', err => {
expect(err).to.not.exist;
});
client.put(buffer, 'tést.txt', err => {
expect(err).to.not.exist;
expect(fs.existsSync('./test/tést.txt')).to.equal(true);
fs.readFile('./test/tést.txt', (fserr, data) => {
expect(fserr).to.not.exist;
expect(data.toString()).to.equal('test text file');
done();
setTimeout(() => {
expect(fs.existsSync(fsPath)).to.equal(true);
fs.readFile(fsPath, (fserr, data) => {
expect(fserr).to.not.exist;
expect(data.toString()).to.equal('test text file');
done();
});
});
});
});
it('APPE tést.txt', done => {
const buffer = Buffer.from(', awesome!');
const fsPath = `${clientDirectory}/${name}/tést.txt`;
client.append(buffer, 'tést.txt', err => {
expect(err).to.not.exist;
fs.readFile('./test/tést.txt', (fserr, data) => {
expect(fserr).to.not.exist;
expect(data.toString()).to.equal('test text file, awesome!');
done();
setTimeout(() => {
expect(fs.existsSync(fsPath)).to.equal(true);
fs.readFile(fsPath, (fserr, data) => {
expect(fserr).to.not.exist;
expect(data.toString()).to.equal('test text file, awesome!');
done();
});
});
});
});
it('RETR tést.txt', done => {
connection.once('RETR', err => {
expect(err).to.not.exist;
});
client.get('tést.txt', (err, stream) => {
expect(err).to.not.exist;
let text = '';
@@ -145,15 +238,16 @@ describe('FtpServer', function () {
expect(text).to.equal('test text file, awesome!');
done();
});
stream.resume();
});
});
it('RNFR tést.txt, RNTO awesome.txt', done => {
client.rename('tést.txt', 'awesome.txt', err => {
expect(err).to.not.exist;
expect(fs.existsSync('./test/tést.txt')).to.equal(false);
expect(fs.existsSync('./test/awesome.txt')).to.equal(true);
fs.readFile('./test/awesome.txt', (fserr, data) => {
expect(fs.existsSync(`${clientDirectory}/${name}/tést.txt`)).to.equal(false);
expect(fs.existsSync(`${clientDirectory}/${name}/awesome.txt`)).to.equal(true);
fs.readFile(`${clientDirectory}/${name}/awesome.txt`, (fserr, data) => {
expect(fserr).to.not.exist;
expect(data.toString()).to.equal('test text file, awesome!');
done();
@@ -170,8 +264,16 @@ describe('FtpServer', function () {
});
it('MDTM awesome.txt', done => {
client.lastMod('awesome.txt', err => {
client.lastMod('awesome.txt', (err, modTime) => {
expect(err).to.not.exist;
expect(modTime).to.be.instanceOf(Date);
expect(modTime.toISOString()).to.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/);
done();
});
});
it.skip('MLSD .', done => {
client.mlsd('.', () => {
done();
});
});
@@ -179,7 +281,7 @@ describe('FtpServer', function () {
it('SITE CHMOD 700 awesome.txt', done => {
client.site('CHMOD 600 awesome.txt', err => {
expect(err).to.not.exist;
fs.stat('./test/awesome.txt', (fserr, stats) => {
fs.stat(`${clientDirectory}/${name}/awesome.txt`, (fserr, stats) => {
expect(fserr).to.not.exist;
const mode = stats.mode.toString(8);
expect(/600$/.test(mode)).to.equal(true);
@@ -191,18 +293,19 @@ describe('FtpServer', function () {
it('DELE awesome.txt', done => {
client.delete('awesome.txt', err => {
expect(err).to.not.exist;
expect(fs.existsSync('./test/awesome.txt')).to.equal(false);
expect(fs.existsSync(`${clientDirectory}/${name}/awesome.txt`)).to.equal(false);
done();
});
});
it('MKD témp', done => {
if (fs.existsSync('./test/témp')) {
fs.rmdirSync('./test/témp');
const path = `${clientDirectory}/${name}/témp`;
if (fs.existsSync(path)) {
fs.rmdirSync(path);
}
client.mkdir('témp', err => {
expect(err).to.not.exist;
expect(fs.existsSync('./test/témp')).to.equal(true);
expect(fs.existsSync(path)).to.equal(true);
done();
});
});
@@ -210,7 +313,7 @@ describe('FtpServer', function () {
it('CWD témp', done => {
client.cwd('témp', (err, data) => {
expect(err).to.not.exist;
expect(data).to.be.a('string');
expect(data).to.to.be.a('string');
done();
});
});
@@ -225,7 +328,7 @@ describe('FtpServer', function () {
it('RMD témp', done => {
client.rmdir('témp', err => {
expect(err).to.not.exist;
expect(fs.existsSync('./test/témp')).to.equal(false);
expect(fs.existsSync(`${clientDirectory}/${name}/témp`)).to.equal(false);
done();
});
});
@@ -236,49 +339,101 @@ describe('FtpServer', function () {
done();
});
});
};
}
it('TYPE A', done => {
client.ascii(err => {
expect(err).to.not.exist;
done();
});
});
runFileSystemTests();
it('TYPE I', done => {
client.binary(err => {
expect(err).to.not.exist;
done();
});
});
runFileSystemTests();
it('AUTH TLS', done => {
client.end();
client.once('close', () => {
client = new FtpClient();
client.once('ready', () => done());
client.once('error', err => done(err));
client.connect({
describe('#ASCII', function () {
before(() => {
return connectClient({
host: server.url.hostname,
port: server.url.port,
user: 'test',
password: 'test',
secure: true,
secureOptions: {
rejectUnauthorized: false,
checkServerIdentity: () => undefined
}
password: 'test'
});
});
});
runFileSystemTests();
it('QUIT', done => {
client.once('close', done);
client.logout(err => {
expect(err).to.be.undefined;
after(() => closeClient(client));
it('TYPE A', done => {
client.ascii(err => {
expect(err).to.not.exist;
done();
});
});
runFileSystemTests('ascii');
});
describe('#BINARY', function () {
before(() => {
return connectClient({
host: server.url.hostname,
port: server.url.port,
user: 'test',
password: 'test'
});
});
after(() => closeClient(client));
it('TYPE I', done => {
client.binary(err => {
expect(err).to.not.exist;
done();
});
});
runFileSystemTests('binary');
});
describe.skip('#EXPLICIT', function () {
before(() => {
return server.close()
.then(() => startServer('ftp://127.0.0.1:8880', {
tls: {
key: `${process.cwd()}/test/cert/server.key`,
cert: `${process.cwd()}/test/cert/server.crt`,
ca: `${process.cwd()}/test/cert/server.csr`
}
}))
.then(() => {
return connectClient({
secure: true,
secureOptions: {
rejectUnauthorized: false,
checkServerIdentity: () => undefined
}
});
});
});
after(() => closeClient());
runFileSystemTests('explicit');
});
describe.skip('#IMPLICIT', function () {
before(() => {
return server.close()
.then(() => startServer('ftps://127.0.0.1:8880', {
tls: {
key: `${process.cwd()}/test/cert/server.key`,
cert: `${process.cwd()}/test/cert/server.crt`,
ca: `${process.cwd()}/test/cert/server.csr`
}
}))
.then(() => {
return connectClient({
secure: 'implicit',
secureOptions: {
rejectUnauthorized: false,
checkServerIdentity: () => undefined
}
});
});
});
after(() => closeClient());
runFileSystemTests('implicit');
});
});

View File

@@ -1,5 +1,5 @@
{
"mute":false,
"level":"fatal",
"level":"debug",
"reporter":"spec"
}

View File

@@ -19,7 +19,7 @@ const server = new FtpServer('ftp://127.0.0.1:8880', {
});
server.on('login', ({username, password}, resolve, reject) => {
if (username === 'test' && password === 'test' || username === 'anonymous') {
resolve({ root: require('os').homedir() });
resolve({root: require('os').homedir()});
} else reject('Bad username or password');
});
server.listen();