Compare commits

...

29 Commits

Author SHA1 Message Date
Tyler Stewart
a794f1e5b3 test: fix tests 2018-05-07 11:08:16 -06:00
Mateusz Kucharczyk
1b5d22a3ca fix(typings): updated typescript typings to allow compiling with noImplicitAny option enabled 2018-05-07 10:07:31 -06:00
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
73 changed files with 711 additions and 1108 deletions

View File

@@ -85,8 +85,8 @@ jobs:
- run:
name: Update NPM
command: |
npm install npm@latest
npm install semantic-release@latest
npm install npm@5
npm install semantic-release@11
- deploy:
name: Semantic Release
command: |

2
.gitignore vendored
View File

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

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`
@@ -109,7 +125,7 @@ The `FtpSrv` class extends the [node net.Server](https://nodejs.org/api/net.html
### `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)

5
ftp-srv.d.ts vendored
View File

@@ -1,5 +1,6 @@
import * as tls from 'tls'
import { Stats } from 'fs'
import { EventEmitter } from 'events';
export class FileSystem {
@@ -107,7 +108,7 @@ export class FtpServer {
whitelist?: Array<string>
}) => void,
reject: (err?: Error) => void
) => void)
) => void): EventEmitter;
on(event: "client-error", listener: (
data: {
@@ -115,7 +116,7 @@ export class FtpServer {
context: string,
error: Error,
}
) => void)
) => void): EventEmitter;
}
export {FtpServer as FtpSrv};

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>

896
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",
@@ -29,7 +29,7 @@
"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": "nyc check-coverage",
@@ -55,11 +55,11 @@
}
},
"dependencies": {
"bluebird": "^3.5.1",
"bunyan": "^1.8.12",
"lodash": "^4.17.4",
"moment": "^2.19.1",
"uuid": "^3.1.0",
"when": "^3.7.8"
"uuid": "^3.1.0"
},
"devDependencies": {
"@icetee/ftp": "^0.3.15",
@@ -85,7 +85,7 @@
"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

@@ -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

@@ -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,32 +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;
const destroyConnection = (connection, reject) => err => {
if (connection) connection.destroy(err);
reject(err);
};
const eventsPromise = when.promise((resolve, reject) => {
this.connector.socket.once('error', err => reject(err));
stream.on('data', data => this.connector.socket
&& this.connector.socket.write(data, this.transferType));
stream.once('error', err => reject(err));
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));
});
return this.reply(150).then(() => this.connector.socket.resume())
this.restByteCount = 0;
return this.reply(150).then(() => stream.resume() && this.connector.socket.resume())
.then(() => eventsPromise)
.finally(() => stream.destroy ? stream.destroy() : null);
.tap(() => this.emit('RETR', null, filePath))
.finally(() => stream.destroy && stream.destroy());
})
.then(() => this.reply(226))
.catch(when.TimeoutError, err => {
.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,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const _ = require('lodash');
const registry = require('./registry');
@@ -13,7 +13,7 @@ module.exports = {
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,27 +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 {
const message = getFileStat(stat, _.get(this, 'server.options.file_format', 'ls'));
return this.reply(212, 'Status begin', {raw: true, message}, 'Status end');
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,36 +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 => {
this.restByteCount = 0;
const destroyConnection = (connection, reject) => err => {
if (connection) connection.destroy(err);
reject(err);
};
const streamPromise = when.promise((resolve, reject) => {
stream.once('error', err => reject(err));
const streamPromise = new Promise((resolve, reject) => {
stream.once('error', destroyConnection(this.connector.socket, reject));
stream.once('finish', () => resolve());
});
const socketPromise = when.promise((resolve, reject) => {
this.connector.socket.on('data', data => stream.write(data, this.transferType));
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', err => reject(err));
this.connector.socket.once('error', destroyConnection(stream, reject));
});
this.restByteCount = 0;
return this.reply(150).then(() => this.connector.socket.resume())
.then(() => when.join(streamPromise, socketPromise))
.finally(() => stream.destroy ? stream.destroy() : null);
.then(() => Promise.join(streamPromise, socketPromise))
.tap(() => this.emit('STOR', null, fileName))
.finally(() => stream.destroy && stream.destroy());
})
.then(() => this.reply(226, fileName))
.catch(when.TimeoutError, err => {
.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

@@ -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,24 +10,26 @@ 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.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();

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();
@@ -65,13 +69,13 @@ class Passive extends Connector {
this.dataSocket = null;
this.dataServer = net.createServer({pauseOnConnect: true}, connectionHandler);
this.dataServer.maxConnections = 1;
this.dataServer.on('error', err => this.server.emit('client-error', {connection: this.connection, context: 'dataServer', error: err}));
this.dataServer.on('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,10 +1,8 @@
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 {
@@ -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

@@ -18,7 +18,8 @@ module.exports = function (fileStat, format = 'ls') {
function ls(fileStat) {
const now = moment.utc();
const mtime = moment.utc(new Date(fileStat.mtime));
const dateFormat = now.diff(mtime, 'months') < 6 ? 'MMM DD HH:mm' : 'MMM DD YYYY';
const timeDiff = now.diff(mtime, 'months');
const dateFormat = timeDiff < 6 ? 'MMM DD HH:mm' : 'MMM DD YYYY';
return [
fileStat.mode ? [

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,
@@ -47,9 +49,6 @@ class FtpServer {
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({
@@ -79,14 +78,10 @@ class FtpServer {
}
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];
@@ -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('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: {}
}

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

@@ -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 = 'EPRT';
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');
@@ -8,7 +8,7 @@ const CMD = 'EPSV';
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 = '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);

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: {
@@ -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

@@ -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

@@ -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);

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

@@ -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);

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,9 +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'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(425);
@@ -64,7 +72,7 @@ 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'}})
@@ -72,4 +80,20 @@ describe(CMD, function () {
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,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 = 'RNFR';
describe(CMD, function () {
let sandbox;
const mockLog = {error: () => {}};
const mockClient = {reply: () => when.resolve()};
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');

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 = 'RNTO';
describe(CMD, function () {
let sandbox;
const mockLog = {error: () => {}};
const mockClient = {reply: () => when.resolve()};
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');

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -6,14 +6,14 @@ const CMD = 'CHMOD';
describe(CMD, function () {
let sandbox;
const mockLog = {error: () => {}};
const mockClient = {reply: () => when.resolve()};
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');

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
const bunyan = require('bunyan');
@@ -11,7 +11,7 @@ describe(CMD, function () {
let sandbox;
const log = bunyan.createLogger({name: 'site-test'});
const mockClient = {
reply: () => when.resolve(),
reply: () => Promise.resolve(),
commands: new FtpCommands()
};
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,14 +6,14 @@ const CMD = 'SIZE';
describe(CMD, function () {
let sandbox;
const mockLog = {error: () => {}};
const mockClient = {reply: () => when.resolve()};
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');

View File

@@ -1,4 +1,4 @@
const when = require('when');
const Promise = require('bluebird');
const {expect} = require('chai');
const sinon = require('sinon');
@@ -6,15 +6,15 @@ const CMD = 'STAT';
describe(CMD, function () {
let sandbox;
const mockLog = {error: () => {}};
const mockClient = {reply: () => when.resolve()};
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');

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,7 +60,7 @@ 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'}})
@@ -64,7 +71,7 @@ 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'}})
@@ -72,4 +79,20 @@ describe(CMD, function () {
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');

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);

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);

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(),
reply: () => Promise.resolve(),
server: {options: {}},
login: () => when.resolve()
login: () => Promise.resolve()
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);

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,8 +11,8 @@ 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: {},
@@ -64,7 +64,7 @@ describe('Connector - Passive //', function () {
return passive.setupServer()
.then(shouldNotResolve)
.catch(err => {
expect(err.name).to.equal('RangeError');
expect(err).to.be.instanceOf(RangeError);
});
});

View File

@@ -1,9 +1,20 @@
const {expect} = require('chai');
const sinon = require('sinon');
const moment = require('moment');
const fileStat = require('../../src/helpers/file-stat');
const errors = require('../../src/errors');
describe('helpers // file-stat', function () {
let sandbox;
before(function () {
sandbox = sinon.sandbox.create();
});
afterEach(function () {
sandbox.restore();
});
const STAT = {
name: 'test1',
dev: 2114,
@@ -44,6 +55,11 @@ describe('helpers // file-stat', function () {
describe('format - ls //', function () {
it('formats correctly', () => {
const momentStub = sandbox.stub(moment, 'utc').callThrough();
momentStub.onFirstCall().callsFake(function () {
return moment.utc(new Date('Sept 10 2016'));
});
const format = fileStat(STAT, 'ls');
expect(format).to.equal('-rwxrwxrwx 1 85 100 527 Oct 10 23:24 test1');
});

View File

@@ -2,7 +2,7 @@
const {expect} = require('chai');
const sinon = require('sinon');
const bunyan = require('bunyan');
const when = require('when');
const Promise = require('bluebird');
const _ = require('lodash');
const fs = require('fs');
const nodePath = require('path');
@@ -52,7 +52,7 @@ describe('Integration', function () {
}
function connectClient(options = {}) {
return when.promise((resolve, reject) => {
return new Promise((resolve, reject) => {
client = new FtpClient();
client.once('ready', () => resolve(client));
client.once('error', err => reject(err));
@@ -69,7 +69,7 @@ describe('Integration', function () {
}
function closeClient() {
return when.promise((resolve, reject) => {
return new Promise((resolve, reject) => {
client.once('close', () => resolve());
client.once('error', err => reject(err));
client.logout(err => {
@@ -154,7 +154,7 @@ describe('Integration', function () {
});
});
it.skip('LIST fake.txt', done => {
it('LIST fake.txt', done => {
client.list('fake.txt', (err, data) => {
expect(err).to.not.exist;
expect(data).to.be.an('array');
@@ -165,25 +165,35 @@ describe('Integration', function () {
});
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', () => setTimeout(() => stream.end()));
stream.on('close', () => stream.end());
setTimeout(() => stream.emit('error', new Error('STOR fail test')));
return stream;
});
const buffer = Buffer.from('test text file');
client.put(buffer, 'fail.txt', err => {
expect(err).to.exist;
expect(fs.existsSync(fsPath)).to.equal(false);
done();
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;
setTimeout(() => {
@@ -214,6 +224,10 @@ describe('Integration', function () {
});
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 = '';