Compare commits

...

63 Commits

Author SHA1 Message Date
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
Tyler Stewart
ed086e576a Merge pull request #36 from trs/fix-process-exit
Fix process exit
2017-08-14 17:07:05 -06:00
Tyler Stewart
31f0f3b0dc fix: ensure process exits 2017-08-14 16:46:11 -06:00
Tyler Stewart
d763820c86 refactor: connector socket getter 2017-08-14 16:45:37 -06:00
Tyler Stewart
f3183314cc Merge pull request #34 from trs/add-typings
feat(typings): add TypeScript .d.ts files
2017-07-24 11:00:13 -06:00
Chris Rabl
dde7b36c46 feat(typings): add TypeScript .d.ts files
* created outline for TypeScript declarations
* added basic object shapes for FtpServer and FileSystem
2017-07-24 10:53:41 -06:00
Tyler Stewart
00af9e7e61 Merge pull request #32 from trs/command-args
Command args
2017-07-10 10:02:55 -06:00
Tyler Stewart
99a885cd44 test(commands): update parser tests 2017-07-10 09:58:24 -06:00
Tyler Stewart
443051d753 fix(commands): get flags from ftp command 2017-07-07 17:28:09 -06:00
Tyler Stewart
27ecc4d835 fix(feat): order features alphabetically 2017-07-06 17:51:06 -06:00
Tyler Stewart
c8526be1f4 Merge pull request #30 from trs/fix-utf8
Fix utf8
2017-06-26 19:54:51 -06:00
Tyler Stewart
e0b11ff480 fix: cleanup server 2017-06-26 16:39:00 -06:00
Tyler Stewart
58b9d8db9d chore: update fs readme 2017-06-26 16:39:00 -06:00
Tyler Stewart
fa121ba0fd test(REST): add tests 2017-06-26 16:39:00 -06:00
Tyler Stewart
2e02dc20ad feat(REST): add support for REST command
Allows the client to resume a transfer at the specified bytes
2017-06-26 16:38:59 -06:00
Tyler Stewart
8aeb6976d2 fix(auth): update checks, ensure secure is set using ftps 2017-06-26 16:38:59 -06:00
Tyler Stewart
84a68ae03c chore: dont append branch name to commit 2017-06-26 12:34:21 -06:00
Tyler Stewart
9dfc80b99d test(OPTS): update tests
master
2017-06-26 11:19:42 -06:00
Tyler Stewart
090e3d8105 chore: update log levels 2017-06-26 11:13:55 -06:00
Tyler Stewart
c3b0dbf5b0 feat(OPTS): add opts command handler for utf8
master
2017-06-26 11:13:49 -06:00
Tyler Stewart
69a5133936 fix(FEAT): correctly display feature list 2017-06-26 11:13:00 -06:00
Tyler Stewart
5394908a6b Merge pull request #29 from trs/fix-tls-check
Fix encoding/transfer type
2017-06-20 17:20:23 -06:00
Tyler Stewart
3e7bd5bcf9 chore: update licence format
Still MIT, just updating to ensure GitHub recognizes it
2017-06-20 17:09:49 -06:00
Tyler Stewart
175b422c5f chore: update dependencies 2017-06-20 17:02:05 -06:00
Tyler Stewart
b2a9851204 fix: ensure utf8 support; allows accent characters 2017-06-20 16:56:26 -06:00
Tyler Stewart
977dd1579a fix(TYPE): correctly set types, only use for data connections 2017-06-20 16:55:37 -06:00
Tyler Stewart
176b2b7ca8 fix: actually check _tls 2017-06-20 14:47:44 -06:00
Tyler Stewart
63777c0d74 Merge pull request #26 from trs/test-updates
Test updates
2017-06-16 15:17:31 -06:00
Tyler Stewart
9be8ffa60d test: add and update tests 2017-06-09 15:00:01 -06:00
Tyler Stewart
b8cd6022e1 fix: assign tls options to empty object 2017-06-09 14:40:19 -06:00
Tyler Stewart
0618a3c675 fix(passive): throw error on invalid port range 2017-06-09 14:39:21 -06:00
Tyler Stewart
3c533a5fbc Merge pull request #25 from trs/export-fs
Export fs
2017-06-09 10:31:42 -06:00
Tyler Stewart
3d0a58ca15 docs(readme): update file system override details
master

export-fs

export-fs
2017-06-09 10:28:03 -06:00
Tyler Stewart
4b4c809af8 feat: export FileSystem and FtpSrv
Non breaking, as it still exports FtpSrv by default
2017-06-09 10:28:03 -06:00
Tyler Stewart
a234534de0 Merge pull request #23 from trs/write-stream-close
feat(fs): close stream before ending
2017-06-08 17:35:58 -06:00
Tyler Stewart
635fb35341 fix(stor): end stream if no close listener present 2017-06-08 17:31:02 -06:00
Tyler Stewart
51a6448ac2 fix(stor): ensure stream is destroyed once used up 2017-06-08 17:23:14 -06:00
Tyler Stewart
4d8a69615c feat(fs): close stream before ending
This allows processing of the received data before a response is sent to the client.

write-stream-close
2017-06-08 17:17:09 -06:00
Tyler Stewart
ab5a2e9641 chore(travis): dont cleanup on deploy 2017-06-08 15:19:35 -06:00
Tyler Stewart
a7f25accd2 Merge pull request #21 from trs/list-fix
List fix
2017-06-08 15:16:09 -06:00
Tyler Stewart
c49a361c36 chore(package): add package-lock 2017-06-08 14:10:56 -06:00
Tyler Stewart
d3d65aa5cf chore(package): update package dependency versions 2017-06-08 14:09:44 -06:00
Tyler Stewart
e53848f881 chore(travis): test multiple node versions, update deployment script
list-fix

HEAD
2017-06-08 14:03:03 -06:00
Tyler Stewart
b5cf75b09f chore(package): specify npm files 2017-06-08 13:54:22 -06:00
Tyler Stewart
dd0a790519 docs(readme): update fs docs to reflect list changes 2017-06-08 13:40:41 -06:00
Tyler Stewart
e25ee55865 test: update tests to reflect list changes 2017-06-08 13:40:27 -06:00
Tyler Stewart
0433bb48cd fix(stat): allow a file as an argument
If a file is passed as an argument, will send only that file's stat.
Otherwise, send the stats for all items in directory (current or provided)
2017-06-08 13:38:00 -06:00
Tyler Stewart
350c2b3e81 chore(readme): add coveralls badge 2017-05-25 23:24:26 -06:00
Tyler Stewart
887bf1fa58 chore: upload test coverage to coveralls 2017-05-25 23:02:37 -06:00
Tyler Stewart
70a62f1da1 Merge pull request #17 from trs/update-logo
fix: update logo
2017-05-20 21:34:19 -06:00
Tyler Stewart
3a1afdb694 fix: update logo
update-logo

update-logo

update-logo

update-logo

update-logo

update-logo
2017-05-20 21:31:22 -06:00
Tyler Stewart
10127b32e5 chore(readme): move badges under description 2017-05-19 13:08:34 -06:00
Tyler Stewart
e1aad3e021 Merge pull request #16 from trs/fix-secure-check
Fix secure check
2017-05-19 13:01:35 -06:00
Tyler Stewart
a254e6c5f3 chore: update logo, generate png
master

master

master
2017-05-19 12:28:29 -06:00
Tyler Stewart
c46d6086ea fix: simplifiy secure check 2017-05-19 12:28:29 -06:00
72 changed files with 5708 additions and 754 deletions

View File

@@ -1,6 +1,7 @@
language: node_js
node_js:
- "6"
# - "node"
env:
FTP_URL: ftp://127.0.0.1:8880
@@ -12,5 +13,13 @@ script:
- npm run verify:js
- npm run test:coverage
after_success:
- if [ $TRAVIS_BRANCH = 'master' ]; then npm run semantic-release; fi
after_script:
- npm run upload-coverage
deploy:
skip_cleanup: true
provider: script
script: npm run semantic-release
on:
branch: master
node: "6"

22
LICENSE
View File

@@ -1,9 +1,21 @@
ftp-srv Copyright (c) 2017 Tyler Stewart
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
Copyright (c) 2017 Tyler Stewart
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,12 +1,13 @@
![ftp-srv](logo.png)
[![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) [![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/)
[![ftp-srv](logo.png)](https://github.com/trs/ftp-srv)
<!--[RM_DESCRIPTION]-->
> Modern, extensible FTP Server
<!--[]-->
[![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/)
---
- [Overview](#overview)
@@ -149,11 +150,26 @@ Occurs when an error arises in the client connection.
See the [command registry](src/commands/registration) for a list of all implemented FTP commands.
## File System
The default file system can be overwritten to use your own implementation.
The default [file system](src/fs.js) can be overwritten to use your own implementation.
This can allow for virtual file systems, and more.
Each connection can set it's own file system based on the user.
Custom file systems can implement the following variables depending on the developers needs.
The default file system is exported and can be extended as needed:
```js
const {FtpSrv, FileSystem} = require('ftp-srv');
class MyFileSystem extends FileSystem {
constructor() {
super(...arguments);
}
get(fileName) {
...
}
}
```
Custom file systems can implement the following variables depending on the developers needs:
### Methods
#### [`currentDirectory()`](src/fs.js#L29)
@@ -162,11 +178,11 @@ __Used in:__ `PWD`
#### [`get(fileName)`](src/fs.js#L33)
Returns a file stat object of file or directory
__Used in:__ `STAT`, `SIZE`, `RNFR`, `MDTM`
__Used in:__ `LIST`, `NLST`, `STAT`, `SIZE`, `RNFR`, `MDTM`
#### [`list(path)`](src/fs.js#L39)
Returns array of file and directory stat objects
__Used in:__ `LIST`, `STAT`
__Used in:__ `LIST`, `NLST`, `STAT`
#### [`chdir(path)`](src/fs.js#L56)
Returns new directory relative to current directory
@@ -176,13 +192,17 @@ __Used in:__ `CWD`, `CDUP`
Returns a path to a newly created directory
__Used in:__ `MKD`
#### [`write(fileName, {append = false})`](src/fs.js#L68)
#### [`write(fileName, {append, start})`](src/fs.js#L68)
Returns a writable stream
Options: `append` if true, append to existing file
Options:
`append` if true, append to existing file
`start` if set, specifies the byte offset to write to
__Used in:__ `STOR`, `APPE`
#### [`read(fileName)`](src/fs.js#L75)
#### [`read(fileName, {start})`](src/fs.js#L75)
Returns a readable stream
Options:
`start` if set, specifies the byte offset to read from
__Used in:__ `RETR`
#### [`delete(path)`](src/fs.js#L87)
@@ -190,11 +210,11 @@ Delete a file or directory
__Used in:__ `DELE`
#### [`rename(from, to)`](src/fs.js#L102)
Rename a file or directory
Renames a file or directory
__Used in:__ `RNFR`, `RNTO`
#### [`chmod(path)`](src/fs.js#L108)
Modify a file or directory's permissions
Modifies a file or directory's permissions
__Used in:__ `SITE CHMOD`
#### [`getUniqueName()`](src/fs.js#L113)

View File

@@ -15,12 +15,7 @@ module.exports = {
{value: 'WIP', name: 'WIP: Work in progress'}
],
scopes: [
{name: 'accounts'},
{name: 'admin'},
{name: 'exampleScope'},
{name: 'changeMe'}
],
scopes: [],
// it needs to match the value for field type. Eg.: 'fix'
/*
@@ -39,5 +34,5 @@ module.exports = {
allowBreakingChanges: ['feat', 'fix'],
// Appends the branch name to the footer of the commit. Useful for tracking commits after branches have been merged
appendBranchNameToCommitMessage: true
appendBranchNameToCommitMessage: false
};

View File

@@ -47,6 +47,7 @@ parserOptions:
<<: *confit-parserOptions
rules:
no-process-exit: 0
max-len:
- warn
- 200 # Line Length

62
ftp-srv.d.ts vendored Normal file
View File

@@ -0,0 +1,62 @@
declare class FileSystem {
constructor(connection: any, {root, cwd}?: {
root: any;
cwd: any;
});
currentDirectory(): string;
get(fileName: string): Promise<any>;
list(path?: string): Promise<any>;
chdir(path?: string): Promise<string>;
write(fileName: string, {append, start}?: {
append?: boolean;
start?: any;
}): any;
read(fileName: string, {start}?: {
start?: any;
}): Promise<any>;
delete(path: string): Promise<any>;
mkdir(path: string): Promise<any>;
rename(from: string, to: string): Promise<any>;
chmod(path: string, mode: string): Promise<any>;
getUniqueName(): string;
}
declare class FtpServer {
constructor(url: string, options?: {});
readonly isTLS: boolean;
listen(): any;
emitPromise(action: any, ...data: any[]): Promise<any>;
emit(action: any, ...data: any[]): void;
setupTLS(_tls: boolean): boolean | {
cert: string;
key: string;
ca: string
};
setupGreeting(greet: string): string[];
setupFeaturesMessage(): string;
disconnectClient(id: string): Promise<any>;
close(): any;
}
declare const FtpSrv: FtpServer;
export default FtpServer;

6
ftp-srv.js Normal file
View File

@@ -0,0 +1,6 @@
const FtpSrv = require('./src');
const FileSystem = require('./src/fs');
module.exports = FtpSrv;
module.exports.FtpSrv = FtpSrv;
module.exports.FileSystem = FileSystem;

View File

@@ -1,43 +0,0 @@
<!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/icon?family=Material+Icons"
rel="stylesheet">
<style>
body {
padding: 0px;
}
h1 {
display: flex;
margin: 0;
padding: 0;
font-size: 73px;
font-family: 'Source Code Pro', monospace;
font-weight: bold;
line-height: 0.9em;
letter-spacing: -3px;
color: #444;
text-shadow:
1px 0px 1px #ccc, 0px 1px 1px #eee,
2px 1px 1px #ccc, 1px 2px 1px #eee,
3px 2px 1px #ccc, 2px 3px 1px #eee,
4px 3px 1px #ccc, 3px 4px 1px #eee,
5px 4px 1px #ccc, 4px 5px 1px #eee,
6px 5px 1px #ccc, 5px 6px 1px #eee,
7px 6px 1px #ccc;
-webkit-font-smoothing: antialiased;
}
h1 > i {
font-size: 42px !important;
color: #03A9F4;
align-self: center;
padding-top: 15px;
}
</style>
</head>
<body>
<h1><i class="material-icons">markunread_mailbox</i>ftp-srv</h1>
</body>
</html>

BIN
logo.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 104 KiB

17
logo/generate.js Normal file
View File

@@ -0,0 +1,17 @@
/*
Send Button by Bruno Bosse from the Noun Project
https://thenounproject.com/brunobosse/collection/basics/?i=1054386
*/
const fs = require('fs');
const htmlConvert = require('html-convert');
const convert = htmlConvert();
let ws = fs.createWriteStream('logo.png');
let rs = convert('logo/logo.html', {
width: 350,
height: 76
});
rs.pipe(ws);
ws.on('finish', () => process.exit());

81
logo/icon.svg Normal file
View File

@@ -0,0 +1,81 @@
<svg width="566" height="580" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<g>
<title>Layer 1</title>
<g display="none" id="svg_1">
<g display="inline" id="svg_2">
<circle cx="337.851" cy="245.093" r="68.103" id="svg_3"/>
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm135.007,301.787c-35.898,35.898 -89.692,42.92 -132.482,20.934l-76.709,76.709c-13.651,13.651 -35.783,13.651 -49.434,0c-13.651,-13.651 -13.651,-35.784 0,-49.435l76.777,-76.777c-21.923,-42.853 -15.06,-96.699 20.761,-132.52c44.483,-44.483 116.448,-44.64 160.931,-0.157c44.482,44.485 44.639,116.763 0.156,161.246z" id="svg_4"/>
</g>
</g>
<g display="none" id="svg_5">
<g display="inline" id="svg_6">
<path d="m282,211.676c-7.801,0 -13,6.324 -13,14.125l0,66.199l-40.682,0c-7.975,0 -14.318,5.978 -14.318,13.953l0,0.597c0,7.975 6.343,14.449 14.318,14.449l54.641,0c0.053,0 -0.495,-0.012 -0.442,-0.013c0.053,0.001 -0.495,0.004 -0.442,0.004c7.801,0 12.925,-6.324 12.925,-14.125l0,-0.315l0,-0.597l0,-80.152c0,-7.801 -5.199,-14.125 -13,-14.125z" id="svg_7"/>
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm-139.125,208.04c-0.89,-1.277 -1.569,-2.707 -2.003,-4.244c-3.279,-7.649 -5.111,-16.066 -5.111,-24.915c0,-34.997 28.371,-63.368 63.368,-63.368c14.59,0 28.023,4.937 38.735,13.224c1.019,0.639 1.951,1.4 2.775,2.268c2.431,2.56 3.932,6.012 3.932,9.82c0,5.696 -3.344,10.6 -8.169,12.891c-0.831,0.395 -1.704,0.709 -2.615,0.938c-27.13,10.06 -50.303,28.252 -66.567,51.62c-0.558,1.116 -1.257,2.145 -2.077,3.069c-2.601,2.933 -6.387,4.792 -10.616,4.792c-4.828,0.001 -9.086,-2.414 -11.652,-6.095zm257.1,194.907c-13.91,13.91 -36.463,13.91 -50.373,0l-5.58,-5.58c-18.184,10.084 -39.105,15.833 -61.371,15.833c-22.105,0 -42.885,-5.664 -60.977,-15.613l-5.36,5.36c-13.91,13.91 -36.463,13.91 -50.373,0c-13.91,-13.91 -13.91,-36.464 0,-50.374l5.424,-5.424c-9.849,-18.023 -15.449,-38.698 -15.449,-60.683c0,-69.995 56.741,-126.735 126.735,-126.735s126.735,56.741 126.735,126.735c0,21.823 -5.518,42.358 -15.232,60.286l5.821,5.821c13.91,13.91 13.91,36.464 0,50.374zm24.752,-197.077c-0.402,1.103 -0.924,2.149 -1.573,3.104c-2.536,3.727 -6.81,6.175 -11.658,6.175c-4.483,0 -8.469,-2.099 -11.05,-5.363c-0.508,-0.642 -0.968,-1.323 -1.36,-2.049c-16.153,-23.843 -39.432,-42.457 -66.784,-52.788c-1.048,-0.252 -2.048,-0.618 -2.991,-1.088c-4.671,-2.325 -7.888,-7.133 -7.888,-12.705c0,-3.488 1.263,-6.677 3.349,-9.148c1.025,-1.215 2.254,-2.245 3.628,-3.061c10.753,-8.412 24.288,-13.434 39,-13.434c34.997,0 63.368,28.371 63.368,63.368c-0.001,9.657 -2.176,18.8 -6.041,26.989z" id="svg_8"/>
</g>
</g>
<g display="none" id="svg_9">
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm139.455,375.302c0,7.493 -6.315,13.691 -13.808,13.691l-91.192,0l0,-35.34l0,-20.446l0,-2.871c0.038,-7.37 -6.111,-12.343 -15,-12.343s-39,0 -39,0c-3,-0.005 -2.241,0.161 -2.373,0.161c-7.435,0 -14.625,7.165 -14.625,14.6c0,0.127 -0.002,1.239 -0.002,1.239l0,19.66l0,35.34l-89.837,0l-0.494,0c-7.381,0 -13.669,-6.272 -13.669,-13.685l0,-121.53c0,-2.827 1.062,-5.443 2.568,-7.596c0.888,-1.27 2.164,-2.375 3.431,-3.265l124.012,-124.268l2.154,-2.157c2.255,-1.791 5.106,-2.867 8.209,-2.867c3.004,0 5.772,1.014 7.991,2.702l2.557,2.548l124.987,125.215c-0.054,0.045 -0.047,0.091 -0.101,0.136c2.505,2.415 4.191,5.799 4.191,9.554l0,121.522l0.001,0z" id="svg_10"/>
</g>
<g display="none" id="svg_11">
<g display="inline" id="svg_12">
<path d="m345.15,271.851c1.183,-1.297 2.547,-2.706 3.842,-4.247c1.421,-1.692 2.659,-3.545 3.417,-5.595c0.433,-1.119 0.713,-2.249 0.713,-3.433c0,-0.07 -0.121,-14.058 -0.121,-14.058l0,-40.854c0,-18.056 -14.646,-31.979 -31.875,-31.979c-2.232,0 -4.372,0.235 -6.66,0.724c-0.07,0.013 -0.098,0.032 -0.168,0.045c-9.206,1.965 -18.404,3.611 -27.845,3.643c-10.388,0.038 -20.532,-1.519 -30.667,-3.688c-2.282,-0.489 -4.222,-0.724 -6.453,-0.724c-17.229,0 -31.331,13.923 -31.331,31.979l0,4.447l0,29.426l0,8.558c0,3.935 -0.94,7.942 -0.673,11.865c0.097,1.434 -0.228,2.931 0.286,4.05c0.791,2.056 2.002,3.895 3.397,5.595c2.021,2.463 4.429,4.628 6.529,6.701c3.923,3.878 8.138,7.475 12.48,10.87c8.742,6.828 18.004,12.937 26.886,19.575c4.857,3.63 11.295,5.67 18.034,5.828c6.739,-0.159 13.176,-2.203 18.033,-5.833c9.734,-7.273 20.006,-13.637 29.409,-21.113c4.536,-3.607 8.857,-7.498 12.767,-11.782zm-85.15,-37.557c0,7.909 -7.091,14.317 -15,14.317c-7.909,0 -15,-6.408 -15,-14.317l0,-14.782c0,-7.909 7.091,-14.317 15,-14.317c7.909,0 15,6.408 15,14.317l0,14.782zm59.5,14.318c-7.909,0 -14.5,-6.408 -14.5,-14.317l0,-14.782c0,-7.909 6.591,-14.317 14.5,-14.317c7.909,0 14.5,6.408 14.5,14.317l0,14.782c0,7.908 -6.591,14.317 -14.5,14.317z" id="svg_13"/>
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm108.455,408.03c0,5.283 -4.98,7.963 -10.263,7.963l-103.555,0l-8.55,0l-82.179,0c-5.283,0 -10.453,-2.68 -10.453,-7.963l0,-37.968c0,-11.276 4.59,-23.322 12.77,-35.808c6.836,-10.433 16.918,-21.024 28.32,-30.633c7.486,-6.309 15.463,-11.977 23.502,-16.746c-0.944,-0.673 -1.889,-1.334 -2.831,-2.01c0.055,-0.032 0.106,-0.043 0.16,-0.074c-11.73,-8.489 -19.987,-15.692 -26.141,-21.834c-3.346,-3.388 -5.872,-6.221 -7.879,-9.237c-0.481,-0.51 -0.971,-1.029 -1.433,-1.559c-0.279,-0.321 -0.594,-0.643 -0.863,-0.971c-1.794,-2.187 -3.431,-4.554 -4.449,-7.199c-0.661,-1.441 -1.199,-3.366 -1.325,-5.212c-0.124,-1.817 -0.442,-3.648 -0.429,-5.483c0.024,-3.264 -0.402,-6.543 -0.402,-9.785l0,-18.16l0.549,0.01c0.01,-0.561 0.539,-1.144 0.457,-1.737c-0.82,-5.964 -5.862,-13.494 -7.177,-21.512c-0.104,-0.638 -0.188,-1.279 -0.242,-1.923l0.026,-0.01c-0.401,-4.131 -0.385,-8.308 0.145,-12.457c6.765,-52.927 46.886,-86.808 95.363,-86.808c47.53,0 87.052,32.316 94.942,83.737c0.787,5.139 0.773,10.342 0.025,15.428l0.347,0.109c-0.122,1.478 -0.237,2.94 -0.589,4.384c-1.724,7.058 -5.343,13.637 -6.311,18.973c-0.061,0.336 0.465,0.671 0.465,0.998l0,16.943c0,0 -0.605,6.586 -0.593,11.809c0.008,3.41 -0.606,6.241 -0.606,6.277c0,1.524 -0.42,2.977 -0.977,4.418c-0.975,2.637 -2.594,5.022 -4.423,7.199c-0.278,0.331 -0.559,0.65 -0.841,0.971c-0.657,0.749 -1.313,1.476 -1.963,2.176c-0.905,1.274 -1.887,2.489 -2.972,3.831c-5.301,6.006 -12.847,13.149 -24.136,21.791c-3.328,2.397 -6.672,4.742 -10.056,7.082c8.343,4.984 16.347,10.724 23.081,16.449c0.799,0.68 1.687,1.383 2.49,2.097c19.13,16.999 38.996,43.368 38.996,68.923l0,33.521z" id="svg_14"/>
</g>
</g>
<g display="none" id="svg_15">
<g display="inline" id="svg_16">
<path d="m312.499,160.826c-9.98,2.131 -19.499,5.134 -28.499,8.871l0,0.309c0,-0.051 -0.324,-0.099 -0.449,-0.15c-13.49,5.7 -25.679,13.103 -35.916,21.847c6.85,0.617 12.365,6.309 12.365,13.318l0,13.871c0,7.422 -6.578,13.438 -14,13.438s-14,-6.017 -14,-13.438l0,-12.16c-5,7.508 -10,15.638 -14,24.243l0,10.724c0,11.767 40.019,38.51 46.415,42.736c1.735,0.958 3.005,1.504 3.005,1.504c4.505,2.922 10.252,4.531 16.122,4.582c0.101,-0.005 0.459,-0.014 0.459,-0.014l0,0.023c6,-0.006 13.014,-1.989 17.751,-5.526c0,0 47.249,-30.509 47.249,-43.305l0,-51.547c-0.001,-19.131 -17.791,-33.327 -36.502,-29.326zm15.501,58.072c0,7.422 -6.078,13.438 -13.5,13.438c-7.422,0 -13.5,-6.017 -13.5,-13.438l0,-13.871c0,-7.422 6.078,-13.438 13.5,-13.438c7.416,0 13.5,6.017 13.5,13.438l0,13.871z" id="svg_17"/>
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm105.455,311.424c-9,-3.568 -18.511,-5.577 -28.63,-5.577c-3.612,0 -7.204,0.26 -10.68,0.748c0.319,0.28 0.666,0.55 0.985,0.834c18.856,16.755 38.325,42.746 38.325,67.934l0,33.04c0,5.207 -4.685,8.59 -9.892,8.59l-102.07,0l-8.427,0l-81.001,0c-5.207,0 -9.609,-3.382 -9.609,-8.59l0,-37.423c0,-11.115 4.2,-22.991 12.263,-35.297c6.493,-9.909 15.656,-19.963 26.374,-29.156c-3.329,-0.447 -6.682,-0.68 -10.135,-0.68c-10.119,0 -19.502,2.01 -28.502,5.577l0,-124.877c0,-58.13 47.049,-105.257 105.179,-105.257c0.091,0 0.147,0.001 0.266,0.003c0.074,0 0.175,-0.003 0.249,-0.003c58.13,0 105.306,47.127 105.306,105.257l0,124.877l-0.001,0z" id="svg_18"/>
</g>
</g>
<g display="none" id="svg_19">
<g display="inline" id="svg_20">
<path d="m349.599,245.278l-79.122,40.948c-0.082,0.043 -0.156,0.096 -0.237,0.141c-5.525,2.007 -10.592,5.215 -14.873,9.495c-15.891,15.892 -15.891,41.749 0,57.642c7.698,7.698 17.933,11.938 28.82,11.938s21.122,-4.24 28.82,-11.938c4.63,-4.63 7.901,-10.109 9.832,-15.938l40.798,-78.203c2.104,-4.033 1.351,-8.964 -1.86,-12.185c-3.209,-3.22 -8.138,-3.988 -12.178,-1.9z" id="svg_21"/>
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm157.069,312.993l15.415,0c-1.535,20 -6.792,40.567 -15.092,58.692c-0.006,0.018 -0.01,-0.01 -0.017,0.008c-0.088,0.193 -0.829,1.228 -0.961,1.506c-2.081,3.524 -5.906,4.794 -10.295,4.794l-24.507,0l-267.066,0c-4.166,0 -7.495,-1.264 -9.675,-5.366c-0.216,-0.455 -0.974,-1.57 -0.981,-1.59c-8.851,-19.329 -14.24,-40.044 -15.346,-64.044l15.672,0c5.76,0 10.428,-3.741 10.428,-9.5c0,-5.759 -4.669,-9.5 -10.428,-9.5l-15.463,0c2.802,-38 19.118,-75.51 44.351,-103.289l11.527,11.723c2.036,2.036 4.741,3.054 7.41,3.054c2.669,0 5.41,-1.018 7.446,-3.054c4.073,-4.073 4.217,-10.676 0.144,-14.749l-11.042,-11.331c28.262,-24.456 64.866,-39.891 104.866,-41.796l0,15.959c0,5.759 3.74,10.428 9.5,10.428s9.5,-4.669 9.5,-10.428l0,-15.707c39,2.862 76.041,19.222 103.786,44.5l-10.537,10.895c-4.073,4.073 -4.073,10.688 0,14.761c2.036,2.036 4.705,3.079 7.374,3.079c2.669,0 5.338,-0.969 7.374,-3.004l10.688,-10.588c24.41,28.294 39.792,62.547 41.637,104.547l-15.708,0c-5.76,0 -10.428,4.241 -10.428,10c0,5.759 4.668,10 10.428,10z" id="svg_22"/>
</g>
</g>
<g display="none" id="svg_23">
<g display="inline" id="svg_24">
<path d="m343.258,200.171c-9.089,0.001 -18.289,2.508 -26.521,7.757c-23.007,14.673 -29.763,45.218 -15.09,68.225c9.423,14.775 25.389,22.847 41.704,22.847c9.089,0 18.29,-2.508 26.521,-7.757c23.007,-14.673 29.762,-45.218 15.09,-68.224c-9.423,-14.777 -25.389,-22.849 -41.704,-22.848zm34.669,57.087c-2.05,9.266 -7.655,17.181 -15.657,22.284c-5.724,3.651 -12.455,5.58 -19.199,5.58c-5.983,0 -11.071,-1.48 -17.071,-4.187l0,-40.485c0,-6.296 5.664,-12.45 11.959,-12.45l34.328,0c0.332,2 0.629,1.465 0.94,1.954c5.103,8.001 6.75,18.037 4.7,27.304z" id="svg_25"/>
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm-78.545,383.993l-82,0l0,-77.35c0,-6.296 5.466,-10.65 11.762,-10.65l59.472,0c6.295,0 10.766,4.355 10.766,10.65l0,77.35zm104,0l-84,0l0,-228.013c0,-6.296 4.569,-10.987 10.865,-10.987l54.982,0c-21.431,13 -35.968,35.007 -41.526,59.974c-5.613,25.215 -0.753,50.864 13.108,72.597c11.19,17.547 25.571,30.66 46.571,38.006l0,68.423zm17,0l0,-63.149c6,0.938 11.241,1.433 16.863,1.433c4.727,0 9.725,-0.35 14.392,-1.044l37.24,58.161c1.098,1.722 2.327,3.599 3.647,4.599l-72.142,0zm115.213,-4.086c-4.226,2.695 -8.946,3.982 -13.613,3.982c-8.373,0 -16.569,-4.144 -21.405,-11.727l-42.358,-66.416c-6.831,1.782 -13.783,2.653 -20.694,2.653c-27.275,0 -53.884,-13.574 -69.652,-38.297c-24.541,-38.48 -13.102,-89.658 25.378,-114.199c13.747,-8.767 29.081,-12.948 44.238,-12.948c27.271,0 53.959,13.542 69.733,38.275c19.805,31.054 16.076,70.435 -6.202,97.302l42.32,66.357c7.531,11.809 4.063,27.488 -7.745,35.018z" id="svg_26"/>
</g>
</g>
<g display="none" id="svg_27">
<g display="inline" id="svg_28">
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm179.255,300.845c-1.873,8.407 -10.159,13.275 -18.415,10.817l-22.428,-6.677c-2.855,9.611 -6.676,18.805 -11.367,27.462l20.632,11.166c7.575,4.1 9.987,13.398 5.36,20.662c0,0 -10.312,16.191 -23.125,29.004c-12.869,12.869 -29.193,23.244 -29.193,23.244c-7.269,4.621 -16.571,2.203 -20.67,-5.372l-11.112,-20.532c-8.656,4.702 -17.851,8.533 -27.463,11.398l6.674,22.419c2.458,8.255 -2.411,16.535 -10.82,18.401c0,0 -18.741,4.157 -36.861,4.157c-18.199,0 -37.079,-4.206 -37.079,-4.206c-8.407,-1.873 -13.275,-10.16 -10.817,-18.415l6.637,-22.292c-9.623,-2.852 -18.828,-6.674 -27.495,-11.366l-11.091,20.495c-4.1,7.575 -13.398,9.987 -20.662,5.36c0,0 -16.191,-10.312 -29.004,-23.126c-12.869,-12.869 -23.245,-29.193 -23.245,-29.193c-4.62,-7.269 -2.203,-16.57 5.372,-20.67l20.393,-11.036c-4.704,-8.666 -8.536,-17.873 -11.4,-27.496l-22.278,6.632c-8.255,2.458 -16.535,-2.412 -18.4,-10.82c0,0 -4.157,-18.74 -4.157,-36.861c0,-18.199 4.206,-37.079 4.206,-37.079c1.873,-8.407 10.159,-13.275 18.415,-10.817l22.212,6.613c2.863,-9.624 6.695,-18.831 11.399,-27.497l-20.448,-11.066c-7.575,-4.1 -9.987,-13.398 -5.36,-20.662c0,0 10.312,-16.191 23.125,-29.004c12.869,-12.869 29.193,-23.245 29.193,-23.245c7.269,-4.62 16.571,-2.203 20.67,5.372l11.057,20.431c8.667,-4.693 17.874,-8.515 27.497,-11.367l-6.653,-22.348c-2.458,-8.255 2.412,-16.535 10.82,-18.401c0,0 18.741,-4.157 36.861,-4.157c18.199,0 37.079,4.206 37.079,4.206c8.407,1.873 13.275,10.16 10.817,18.415l-6.653,22.348c9.612,2.866 18.808,6.697 27.464,11.4l11.14,-20.585c4.1,-7.575 13.398,-9.987 20.662,-5.36c0,0 16.191,10.312 29.004,23.126c12.869,12.869 23.244,29.193 23.244,29.193c4.62,7.269 2.203,16.57 -5.372,20.67l-20.571,11.133c4.69,8.657 8.512,17.852 11.366,27.463l22.49,-6.695c8.255,-2.458 16.535,2.412 18.4,10.82c0,0 4.157,18.74 4.157,36.861c-0.001,18.197 -4.207,37.077 -4.207,37.077z" id="svg_29"/>
<circle cx="283.779" cy="287.886" r="37.594" id="svg_30"/>
</g>
</g>
<g display="none" id="svg_31">
<g display="inline" id="svg_32">
<rect x="326.113" y="201.398" transform="matrix(-0.7071,-0.7071,0.7071,-0.7071,472.8639,611.4978) " width="73.929" height="12.835" id="svg_33"/>
<polygon points="170.708,383.917 187.339,400.547 225.134,390.423 180.832,346.122 " id="svg_34"/>
<path d="m413.02,192.538c-0.016,-0.268 -0.051,-0.534 -0.072,-0.801c-0.049,-0.602 -0.095,-1.205 -0.173,-1.804c-0.037,-0.284 -0.094,-0.565 -0.138,-0.848c-0.089,-0.58 -0.174,-1.161 -0.291,-1.737c-0.062,-0.305 -0.146,-0.606 -0.215,-0.909c-0.126,-0.55 -0.246,-1.102 -0.397,-1.647c-0.09,-0.325 -0.205,-0.645 -0.304,-0.968c-0.159,-0.518 -0.31,-1.037 -0.492,-1.548c-0.123,-0.346 -0.272,-0.685 -0.406,-1.028c-0.188,-0.48 -0.365,-0.964 -0.574,-1.438c-0.162,-0.367 -0.35,-0.724 -0.525,-1.087c-0.211,-0.44 -0.41,-0.885 -0.64,-1.318c-0.206,-0.388 -0.441,-0.764 -0.661,-1.146c-0.229,-0.396 -0.444,-0.798 -0.688,-1.187c-0.259,-0.413 -0.549,-0.81 -0.826,-1.214c-0.237,-0.345 -0.458,-0.698 -0.708,-1.037c-0.344,-0.466 -0.721,-0.915 -1.089,-1.368c-0.213,-0.262 -0.409,-0.532 -0.63,-0.789c-0.604,-0.703 -1.239,-1.388 -1.905,-2.054l-0.004,0.004c-0.028,-0.03 -0.053,-0.062 -0.083,-0.091c-1.556,-1.556 -4.079,-1.556 -5.634,0c-1.556,1.556 -1.556,4.078 0,5.634c0.042,0.042 0.086,0.078 0.129,0.117c0.172,0.173 0.327,0.356 0.494,0.532c7.992,8.41 9.548,19.621 6.063,30.056c-0.915,1.556 -0.709,3.588 0.626,4.924c1.585,1.585 4.154,1.585 5.739,0c0.537,-0.537 0.888,-1.188 1.06,-1.874c0.08,-0.212 0.141,-0.429 0.218,-0.642c0.211,-0.588 0.421,-1.175 0.601,-1.771c0.072,-0.238 0.128,-0.48 0.195,-0.719c0.17,-0.607 0.339,-1.215 0.478,-1.829c0.054,-0.239 0.091,-0.48 0.141,-0.719c0.127,-0.619 0.253,-1.238 0.348,-1.862c0.036,-0.239 0.056,-0.479 0.088,-0.719c0.083,-0.627 0.166,-1.254 0.217,-1.884c0.02,-0.243 0.022,-0.487 0.037,-0.731c0.039,-0.627 0.077,-1.254 0.084,-1.882c0.003,-0.255 -0.012,-0.509 -0.015,-0.764c-0.007,-0.618 -0.012,-1.236 -0.048,-1.852z" id="svg_35"/>
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm129.63,207.768l-3.086,3.086l-19.966,19.966l-143.453,143.453c-1.009,1.736 -2.674,3.036 -4.666,3.569l-54.971,14.725c-0.002,0 -0.003,0.001 -0.004,0.001l-31.304,8.386c-2.657,0.712 -5.493,-0.048 -7.438,-1.993c-1.945,-1.945 -2.705,-4.781 -1.993,-7.438l8.386,-31.304l14.292,-53.352c0.046,-1.907 0.791,-3.801 2.247,-5.257l144.847,-144.847l19.966,-19.966l3.086,-3.086c20.418,-20.418 53.64,-20.418 74.057,0c20.417,20.418 20.417,53.639 0,74.057z" id="svg_36"/>
</g>
</g>
<g display="none" id="svg_37">
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm-62.545,379.174c0,11.498 -9.321,20.819 -20.819,20.819l-29.363,0c-11.497,0 -20.818,-9.321 -20.818,-20.819l0,-29.363c0,-11.498 9.321,-20.819 20.819,-20.819l29.363,0c11.498,0 20.819,9.321 20.819,20.819l0,29.363l-0.001,0zm0,-100c0,11.498 -9.321,20.819 -20.819,20.819l-29.363,0c-11.497,0 -20.818,-9.321 -20.818,-20.819l0,-29.363c0,-11.498 9.321,-20.819 20.819,-20.819l29.363,0c11.498,0 20.819,9.321 20.819,20.819l0,29.363l-0.001,0zm0,-102c0,11.498 -9.321,20.819 -20.819,20.819l-29.363,0c-11.497,0 -20.818,-9.321 -20.818,-20.819l0,-29.363c0,-11.498 9.321,-20.819 20.819,-20.819l29.363,0c11.498,0 20.819,9.321 20.819,20.819l0,29.363l-0.001,0zm212,186.032c0,11.48 -9.307,20.787 -20.787,20.787l-138.426,0c-11.48,0 -20.787,-9.307 -20.787,-20.787l0,-0.425c0,-11.481 9.307,-20.787 20.787,-20.787l138.425,0c11.481,0 20.787,9.307 20.787,20.787l0,0.425l0.001,0zm0,-98c0,11.48 -9.307,20.787 -20.787,20.787l-138.426,0c-11.48,0 -20.787,-9.307 -20.787,-20.787l0,-0.425c0,-11.481 9.307,-20.787 20.787,-20.787l138.425,0c11.481,0 20.787,9.307 20.787,20.787l0,0.425l0.001,0zm0,-101c0,11.48 -9.307,20.787 -20.787,20.787l-138.426,0c-11.48,0 -20.787,-9.307 -20.787,-20.787l0,-0.425c0,-11.481 9.307,-20.787 20.787,-20.787l138.425,0c11.481,0 20.787,9.307 20.787,20.787l0,0.425l0.001,0z" id="svg_38"/>
</g>
<g display="none" id="svg_39">
<g display="inline" id="svg_40">
<rect x="187" y="256" width="192" height="125" id="svg_41"/>
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm39.455,133.723l0,-19.607c0,-11.106 9.394,-20.109 20.5,-20.109s20.5,9.003 20.5,20.109l0,19.607l0,20.612c0,11.106 -9.394,20.109 -20.5,20.109s-20.5,-9.003 -20.5,-20.109l0,-20.612zm-119,0l0,-19.607c0,-11.106 8.394,-20.109 19.5,-20.109s19.5,9.003 19.5,20.109l0,19.607l0,20.612c0,11.106 -8.394,20.109 -19.5,20.109s-19.5,-9.003 -19.5,-20.109l0,-20.612zm217,242.478c0,11.019 -9.318,19.792 -20.337,19.792l-233.587,0c-11.018,0 -21.076,-8.773 -21.076,-19.792l0,-222.527c0,-11.019 10.058,-18.681 21.076,-18.681l19.924,0l0,19.342c0,19.959 17.041,36.197 37,36.197s37,-16.238 37,-36.197l0,-19.342l47,0l0,19.342c0,19.959 15.541,36.197 35.5,36.197s35.5,-16.238 35.5,-36.197l0,-19.342l21.663,0c11.019,0 20.337,7.662 20.337,18.681l0,222.527z" id="svg_42"/>
</g>
</g>
<g display="none" id="svg_43">
<g display="inline" id="svg_44">
<path d="m348,411.767c7.039,0 12,-5.706 12,-12.745l0,-175.1c0,-7.039 -4.961,-12.745 -12,-12.745s-12,5.706 -12,12.745l0,175.1c0,7.038 4.961,12.745 12,12.745z" id="svg_45"/>
<path d="m222,411.767c7.039,0 14,-5.706 14,-12.745l0,-175.1c0,-7.039 -6.961,-12.745 -14,-12.745s-14,5.706 -14,12.745l0,175.1c0,7.038 6.961,12.745 14,12.745z" id="svg_46"/>
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm109.455,185.128l0,212.78c0,12.095 -10.073,23.085 -22.167,23.085l-173.233,0c-12.094,0 -21.6,-10.99 -21.6,-23.085l0,-212.78l0,-32.135l217,0l0,32.135zm0,-47.135l-217,0l0,-10.841c0,-12.095 9.505,-21.159 21.6,-21.159l55.634,0c0.497,-12 10.163,-21 21.942,-21l18.081,0c11.78,0 21.445,9 21.942,21l55.634,0c12.094,0 22.167,9.064 22.167,21.159l0,10.841z" id="svg_47"/>
<path d="m284,411.767c7.039,0 13,-5.706 13,-12.745l0,-175.1c0,-7.039 -5.961,-12.745 -13,-12.745s-13,5.706 -13,12.745l0,175.1c0,7.038 5.961,12.745 13,12.745z" id="svg_48"/>
</g>
</g>
<g id="svg_49">
<g id="svg_50">
<polygon points="196.661,391.813 223.84,340.524 381.46,229.666 196.625,326.192 " id="svg_51" fill="#03a9f4"/>
<path d="m283.545,24.007c-145.682,0 -263.781,118.099 -263.781,263.781s118.099,263.781 263.781,263.781s263.781,-118.099 263.781,-263.781s-118.099,-263.781 -263.781,-263.781zm160.376,182.583l-117.664,174.425c-3.558,5.273 -10.504,7.04 -16.15,4.11l-47.144,-24.478l-64.897,56.276c-2.322,2.013 -5.247,3.062 -8.206,3.062c-1.752,0 -3.516,-0.368 -5.173,-1.119c-4.456,-2.023 -7.325,-6.456 -7.345,-11.349l-0.386,-91.883l-70.236,-36.656c-4.681,-2.443 -7.327,-7.562 -6.612,-12.794c0.715,-5.233 4.635,-9.454 9.801,-10.553l321.028,-68.291c4.992,-1.061 10.13,1.012 12.985,5.245c2.854,4.232 2.854,9.773 -0.001,14.005z" id="svg_52" fill="#03a9f4"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 21 KiB

47
logo/logo.html Normal file
View File

@@ -0,0 +1,47 @@
<!doctype html>
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:400,700" rel="stylesheet">
<style>
body {
padding: 0;
margin: 0;
}
h1 {
display: inline-block;
margin: 0;
margin-left: -4px;
padding: 0px;
font-size: 68px;
font-family: 'Source Code Pro', monospace;
font-weight: bold;
line-height: 0.8em;
letter-spacing: -3px;
color: #333;
-webkit-font-smoothing: antialiased;
text-shadow:
1px 0px 0px #ccc, 0px 1px 0px #eee,
2px 1px 0px #ccc, 1px 2px 0px #eee,
3px 2px 0px #ccc, 2px 3px 0px #eee,
4px 3px 0px #ccc;
}
h1 > span {
display: block;
padding: 0;
margin: 0;
padding-top: 6px;
}
img {
display: inline-block;
vertical-align: top;
}
</style>
</head>
<body>
<h1>
<span>ftp-srv</span>
</h1>
<img src="icon.svg" width="76px" height="76px" />
</body>
</html>

4330
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -8,10 +8,16 @@
"ftp-srv",
"ftp-svr",
"ftpd",
"server"
"server",
"ftpserver"
],
"license": "MIT",
"main": "src/index.js",
"main": "ftp-srv.js",
"files": [
"src",
"ftp-srv.d.ts"
],
"types": "./ftp-srv.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/trs/ftp-srv"
@@ -46,32 +52,33 @@
}
},
"dependencies": {
"bunyan": "^1.8.9",
"bunyan": "^1.8.10",
"lodash": "^4.17.4",
"moment": "^2.18.1",
"uuid": "^3.0.1",
"uuid": "^3.1.0",
"when": "^3.7.8"
},
"devDependencies": {
"chai": "^3.5.0",
"chai": "^4.0.2",
"chokidar-cli": "1.2.0",
"coveralls": "2.11.15",
"cross-env": "3.1.4",
"cz-customizable": "4.0.0",
"coveralls": "2.13.1",
"cross-env": "5.0.1",
"cz-customizable": "5.0.0",
"cz-customizable-ghooks": "1.5.0",
"dotenv": "^4.0.0",
"eslint": "3.14.1",
"eslint-config-google": "0.7.1",
"eslint-plugin-node": "3.0.5",
"eslint": "3.19.0",
"eslint-config-google": "0.8.0",
"eslint-plugin-node": "5.0.0",
"ftp": "^0.3.10",
"husky": "0.13.1",
"html-convert": "^2.1.7",
"husky": "0.13.4",
"istanbul": "0.4.5",
"mocha": "3.2.0",
"mocha": "3.4.2",
"mocha-pretty-bunyan-nyan": "^1.0.4",
"npm-run-all": "4.0.1",
"rimraf": "2.5.4",
"semantic-release": "^6.3.2",
"sinon": "^2.1.0"
"npm-run-all": "4.0.2",
"rimraf": "2.6.1",
"semantic-release": "^6.3.6",
"sinon": "^2.3.5"
},
"engines": {
"node": ">=6.x",

View File

@@ -12,10 +12,18 @@ class FtpCommands {
}
parse(message) {
const [directive, ...args] = message.replace(/"/g, '').split(' ');
const strippedMessage = message.replace(/"/g, '');
const [directive, ...args] = strippedMessage.split(' ');
const params = args.reduce(({arg, flags}, param) => {
if (/^-{1,2}[a-zA-Z0-9_]+/.test(param)) flags.push(param);
else arg.push(param);
return {arg, flags};
}, {arg: [], flags: []});
const command = {
directive: _.chain(directive).trim().toUpper().value(),
arg: _.compact(args).join(' ') || null,
arg: params.arg.length ? params.arg.join(' ') : null,
flags: params.flags,
raw: message
};
return command;

View File

@@ -20,7 +20,8 @@ module.exports = {
};
function handleTLS() {
if (!this.server._tls) return this.reply(504);
if (!this.server._tls) return this.reply(502);
if (this.secure) return this.reply(202);
return this.reply(234)
.then(() => {

View File

@@ -9,8 +9,12 @@ module.exports = {
const feat = _.get(registry[cmd], 'flags.feat', null);
if (feat) return _.concat(feats, feat);
return feats;
}, [])
.map(feat => ` ${feat}`);
}, ['UTF8'])
.sort()
.map(feat => ({
message: ` ${feat}`,
raw: true
}));
return features.length
? this.reply(211, 'Extensions supported', ...features, 'End')
: this.reply(211, 'No features');

View File

@@ -8,18 +8,16 @@ module.exports = {
directive: 'LIST',
handler: function ({log, command} = {}) {
if (!this.fs) return this.reply(550, 'File system not instantiated');
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
if (!this.fs.list) return this.reply(402, 'Not supported by file system');
const simple = command.directive === 'NLST';
let dataSocket;
const directory = command.arg || '.';
const path = command.arg || '.';
return this.connector.waitForConnection()
.then(socket => {
this.commandSocket.pause();
dataSocket = socket;
})
.then(() => when.try(this.fs.list.bind(this.fs), directory))
.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(files => {
const getFileMessage = file => {
if (simple) return file.name;
@@ -31,7 +29,7 @@ module.exports = {
return {
raw: true,
message,
socket: dataSocket
socket: this.connector.socket
};
});
return this.reply(150)

View File

@@ -1,8 +1,34 @@
const _ = require('lodash');
const OPTIONS = {
UTF8: utf8,
'UTF-8': utf8
};
module.exports = {
directive: 'OPTS',
handler: function () {
return this.reply(501);
handler: function ({command} = {}) {
if (!_.has(command, 'arg')) return this.reply(501);
const [_option, ...args] = command.arg.split(' ');
const option = _.toUpper(_option);
if (!OPTIONS.hasOwnProperty(option)) return this.reply(500);
return OPTIONS[option].call(this, args);
},
syntax: '{{cmd}}',
description: 'Select options for a feature'
};
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');
}
}

View File

@@ -1,13 +1,14 @@
module.exports = {
directive: 'PBSZ',
handler: function ({command} = {}) {
if (!this.server._tls || !this.secure) return this.reply(202, 'Not suppored');
if (!this.secure) return this.reply(202, 'Not suppored');
this.bufferSize = parseInt(command.arg, 10);
return this.reply(200, this.bufferSize === 0 ? 'OK' : 'Buffer too large: PBSZ=0');
},
syntax: '{{cmd}}',
description: 'Protection Buffer Size',
flags: {
no_auth: true
no_auth: true,
feat: 'PBSZ'
}
};

View File

@@ -3,7 +3,7 @@ const _ = require('lodash');
module.exports = {
directive: 'PROT',
handler: function ({command} = {}) {
if (!this.server._tls || !this.secure) return this.reply(202, 'Not suppored');
if (!this.secure) return this.reply(202, 'Not suppored');
if (!this.bufferSize && typeof this.bufferSize !== 'number') return this.reply(503);
switch (_.toUpper(command.arg)) {
@@ -17,6 +17,7 @@ module.exports = {
syntax: '{{cmd}}',
description: 'Data Channel Protection Level',
flags: {
no_auth: true
no_auth: true,
feat: 'PROT'
}
};

View File

@@ -1,7 +1,7 @@
module.exports = {
directive: 'QUIT',
handler: function () {
return this.close(221);
return this.close(221, 'Client called QUIT');
},
syntax: '{{cmd}}',
description: 'Disconnect',

View File

@@ -0,0 +1,16 @@
const _ = require('lodash');
module.exports = {
directive: 'REST',
handler: function ({command} = {}) {
const arg = _.get(command, 'arg');
const byteCount = parseInt(arg, 10);
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}`);
},
syntax: '{{cmd}} <byte-count>',
description: 'Restart transfer from the specified point. Resets after any STORE or RETRIEVE'
};

View File

@@ -6,23 +6,26 @@ 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');
let dataSocket;
return this.connector.waitForConnection()
.then(socket => {
this.commandSocket.pause();
dataSocket = socket;
})
.then(() => when.try(this.fs.read.bind(this.fs), command.arg))
.tap(() => this.commandSocket.pause())
.then(() => when.try(this.fs.read.bind(this.fs), command.arg, {start: this.restByteCount}))
.then(stream => {
return when.promise((resolve, reject) => {
dataSocket.on('error', err => stream.emit('error', err));
this.restByteCount = 0;
stream.on('data', data => dataSocket.write(data, this.encoding));
stream.on('end', () => resolve(this.reply(226)));
stream.on('error', err => reject(err));
this.reply(150).then(() => dataSocket.resume());
const eventsPromise = when.promise((resolve, reject) => {
this.connector.socket.once('error', err => reject(err));
stream.on('data', data => this.connector.socket
&& this.connector.socket.write(data, this.transferType));
stream.once('error', err => reject(err));
stream.once('end', () => resolve());
});
return this.reply(150).then(() => this.connector.socket.resume())
.then(() => eventsPromise)
.finally(() => stream.destroy ? stream.destroy() : null);
})
.then(() => this.reply(226))
.catch(when.TimeoutError, err => {
log.error(err);
return this.reply(425, 'No connection established');

View File

@@ -9,23 +9,32 @@ module.exports = {
const append = command.directive === 'APPE';
const fileName = command.arg;
let dataSocket;
return this.connector.waitForConnection()
.then(socket => {
this.commandSocket.pause();
dataSocket = socket;
})
.then(() => when.try(this.fs.write.bind(this.fs), fileName, {append}))
.tap(() => this.commandSocket.pause())
.then(() => when.try(this.fs.write.bind(this.fs), fileName, {append, start: this.restByteCount}))
.then(stream => {
return when.promise((resolve, reject) => {
stream.on('error', err => dataSocket.emit('error', err));
this.restByteCount = 0;
dataSocket.on('end', () => stream.end(() => resolve(this.reply(226, fileName))));
dataSocket.on('error', err => reject(err));
dataSocket.on('data', data => stream.write(data, this.encoding));
this.reply(150).then(() => dataSocket.resume());
const streamPromise = when.promise((resolve, reject) => {
stream.once('error', err => reject(err));
stream.once('finish', () => resolve());
});
const socketPromise = when.promise((resolve, reject) => {
this.connector.socket.on('data', data => stream.write(data, this.transferType));
this.connector.socket.once('end', () => {
if (stream.listenerCount('close')) stream.emit('close');
else stream.end();
resolve();
});
this.connector.socket.once('error', err => reject(err));
});
return this.reply(150).then(() => this.connector.socket.resume())
.then(() => when.join(streamPromise, socketPromise))
.finally(() => stream.destroy ? stream.destroy() : null);
})
.then(() => this.reply(226, fileName))
.catch(when.TimeoutError, err => {
log.error(err);
return this.reply(425, 'No connection established');

View File

@@ -1,20 +1,19 @@
const _ = require('lodash');
const ENCODING_TYPES = {
A: 'utf8',
I: 'binary',
L: 'binary'
};
module.exports = {
directive: 'TYPE',
handler: function ({command} = {}) {
const encoding = _.upperCase(command.arg);
if (!ENCODING_TYPES.hasOwnProperty(encoding)) return this.reply(501);
this.encoding = ENCODING_TYPES[encoding];
return this.reply(200);
if (/^A[0-9]?$/i.test(command.arg)) {
this.transferType = 'ascii';
} else if (/^L[0-9]?$/i.test(command.arg) || /^I$/i.test(command.arg)) {
this.transferType = 'binary';
} else {
return this.reply(501);
}
return this.reply(200, `Switch to "${this.transferType}" transfer mode.`);
},
syntax: '{{cmd}} <mode>',
description: 'Set the transfer mode, binary (I) or utf8 (A)'
description: 'Set the transfer mode, binary (I) or ascii (A)',
flags: {
feat: 'TYPE A,I,L'
}
};

View File

@@ -21,6 +21,7 @@ const commands = [
require('./registration/port'),
require('./registration/pwd'),
require('./registration/quit'),
require('./registration/rest'),
require('./registration/retr'),
require('./registration/rmd'),
require('./registration/rnfr'),

View File

@@ -15,8 +15,11 @@ class FtpConnection {
this.id = uuid.v4();
this.log = options.log.child({id: this.id, ip: this.ip});
this.commands = new Commands(this);
this.transferType = 'binary';
this.encoding = 'utf8';
this.bufferSize = false;
this._restByteCount = 0;
this._secure = false;
this.connector = new BaseConnector(this);
@@ -34,7 +37,7 @@ class FtpConnection {
}
_handleData(data) {
const messages = _.compact(data.toString('utf8').split('\r\n'));
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)));
}
@@ -47,6 +50,20 @@ class FtpConnection {
}
}
get restByteCount() {
return this._restByteCount > 0 ? this._restByteCount : undefined;
}
set restByteCount(rbc) {
this._restByteCount = rbc;
}
get secure() {
return this.server.isTLS || this._secure;
}
set secure(sec) {
this._secure = sec;
}
close(code = 421, message = 'Closing connection') {
return when
.resolve(code)
@@ -58,7 +75,7 @@ class FtpConnection {
return when.try(() => {
const loginListeners = this.server.listeners('login');
if (!loginListeners || !loginListeners.length) {
if (!this.server.options.anoymous) throw new errors.GeneralError('No "login" listener setup', 500);
if (!this.server.options.anonymous) throw new errors.GeneralError('No "login" listener setup', 500);
} else {
return this.server.emitPromise('login', {connection: this, username, password});
}
@@ -102,7 +119,7 @@ class FtpConnection {
const packet = !letter.raw ? _.compact([letter.code || options.code, letter.message]).join(seperator) : letter.message;
if (letter.socket && letter.socket.writable) {
this.log.trace({port: letter.socket.address().port, packet}, 'Reply');
this.log.trace({port: letter.socket.address().port, encoding: letter.encoding, packet}, 'Reply');
letter.socket.write(packet + '\r\n', letter.encoding, err => {
if (err) {
this.log.error(err);

View File

@@ -26,7 +26,7 @@ class Active extends Connector {
return closeExistingServer()
.then(() => {
this.dataSocket = new Socket();
this.dataSocket.setEncoding(this.encoding);
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.pause();

View File

@@ -14,6 +14,10 @@ class Connector {
return this.connection.log;
}
get socket() {
return this.dataSocket;
}
get server() {
return this.connection.server;
}

View File

@@ -41,7 +41,7 @@ class Passive extends Connector {
return this.connection.reply(550, 'Remote addresses do not match')
.finally(() => this.connection.close());
}
this.log.debug({port}, 'Passive connection fulfilled.');
this.log.trace({port, remoteAddress: socket.remoteAddress}, 'Passive connection fulfilled.');
if (this.connection.secure) {
const secureContext = tls.createSecureContext(this.server._tls);
@@ -54,10 +54,10 @@ class Passive extends Connector {
this.dataSocket = socket;
}
this.dataSocket.connected = true;
this.dataSocket.setEncoding(this.connection.encoding);
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('close', () => {
this.log.debug('Passive connection closed');
this.log.trace('Passive connection closed');
this.end();
});
};
@@ -67,7 +67,7 @@ class Passive extends Connector {
this.dataServer.maxConnections = 1;
this.dataServer.on('error', err => this.server.emit('client-error', {connection: this.connection, context: 'dataServer', error: err}));
this.dataServer.on('close', () => {
this.log.debug('Passive server closed');
this.log.trace('Passive server closed');
this.dataServer = null;
});
@@ -75,7 +75,7 @@ class Passive extends Connector {
this.dataServer.listen(port, err => {
if (err) reject(err);
else {
this.log.info({port}, 'Passive connection listening');
this.log.debug({port}, 'Passive connection listening');
resolve(this.dataServer);
}
});
@@ -89,7 +89,8 @@ class Passive extends Connector {
this.server.options.pasv_range.split('-').map(v => v ? parseInt(v) : v) :
[this.server.options.pasv_range];
return findPort(min, max);
} else return undefined;
}
throw new errors.ConnectorError('Invalid pasv_range');
}
}

View File

@@ -65,21 +65,22 @@ class FileSystem {
});
}
write(fileName, {append = false} = {}) {
write(fileName, {append = false, start = undefined} = {}) {
const {fsPath} = this._resolvePath(fileName);
const stream = syncFs.createWriteStream(fsPath, {flags: !append ? 'w+' : 'a+'});
stream.on('error', () => fs.unlink(fsPath));
const stream = syncFs.createWriteStream(fsPath, {flags: !append ? 'w+' : 'a+', start});
stream.once('error', () => fs.unlink(fsPath));
stream.once('close', () => stream.end());
return stream;
}
read(fileName) {
read(fileName, {start = undefined} = {}) {
const {fsPath} = this._resolvePath(fileName);
return fs.stat(fsPath)
.tap(stat => {
if (stat.isDirectory()) throw new errors.FileSystemError('Cannot read a directory');
})
.then(() => {
const stream = syncFs.createReadStream(fsPath, {flags: 'r'});
const stream = syncFs.createReadStream(fsPath, {flags: 'r', start});
return stream;
});
}

View File

@@ -47,17 +47,13 @@ class FtpServer {
this.server = (this.isTLS ? tls : net).createServer(serverOptions, serverConnectionHandler);
this.server.on('error', err => this.log.error(err, '[Event] error'));
if (this.isTLS) {
this.server.on('tlsClientError', err => this.log.error(err, '[Event] tlsClientError'));
}
this.on = this.server.on.bind(this.server);
this.once = this.server.once.bind(this.server);
this.listeners = this.server.listeners.bind(this.server);
process.on('SIGTERM', () => this.close());
process.on('SIGINT', () => this.close());
process.on('SIGBREAK', () => this.close());
process.on('SIGHUP', () => this.close());
process.on('SIGTERM', () => this.quit());
process.on('SIGINT', () => this.quit());
process.on('SIGQUIT', () => this.quit());
}
get isTLS() {
@@ -94,8 +90,8 @@ class FtpServer {
}
setupTLS(_tls) {
if (!tls) return false;
return _.assign(_tls, {
if (!_tls) return false;
return _.assign({}, _tls, {
cert: _tls.cert ? fs.readFileSync(_tls.cert) : undefined,
key: _tls.key ? fs.readFileSync(_tls.key) : undefined,
ca: _tls.ca ? Array.isArray(_tls.ca) ? _tls.ca.map(_ca => fs.readFileSync(_ca)) : [fs.readFileSync(_tls.ca)] : undefined
@@ -134,10 +130,15 @@ class FtpServer {
});
}
quit() {
return this.close()
.finally(() => process.exit(0));
}
close() {
this.log.info('Server closing...');
this.server.maxConnections = 0;
return when.map(Object.keys(this.connections), id => this.disconnectClient(id))
return when.map(Object.keys(this.connections), id => when.try(this.disconnectClient.bind(this), id))
.then(() => when.promise(resolve => {
this.server.close(err => {
if (err) this.log.error(err, 'Error closing server');

View File

@@ -53,6 +53,29 @@ describe('FtpCommands', function () {
expect(cmd.arg).to.equal('arg1 arg2');
expect(cmd.raw).to.equal('test arg1 arg2');
});
it('two args with quotes: test "hello world"', () => {
const cmd = commands.parse('test "hello world"');
expect(cmd.directive).to.equal('TEST');
expect(cmd.arg).to.equal('hello world');
expect(cmd.raw).to.equal('test "hello world"');
});
it('two args, with flags: test -l arg1 -A arg2 --zz88A', () => {
const cmd = commands.parse('test -l arg1 -A arg2 --zz88A');
expect(cmd.directive).to.equal('TEST');
expect(cmd.arg).to.equal('arg1 arg2');
expect(cmd.flags).to.deep.equal(['-l', '-A', '--zz88A']);
expect(cmd.raw).to.equal('test -l arg1 -A arg2 --zz88A');
});
it('one arg, with flags: list -l', () => {
const cmd = commands.parse('list -l');
expect(cmd.directive).to.equal('LIST');
expect(cmd.arg).to.equal(null);
expect(cmd.flags).to.deep.equal(['-l']);
expect(cmd.raw).to.equal('list -l');
});
});
describe('handle', function () {

View File

@@ -25,29 +25,25 @@ describe(CMD, function () {
sandbox.restore();
});
it('// successful | no active connection', done => {
it('// successful | no active connection', () => {
mockClient.connector.waitForConnection.restore();
sandbox.stub(mockClient.connector, 'waitForConnection').rejects();
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.connector.waitForConnection.callCount).to.equal(1);
expect(mockClient.connector.end.callCount).to.equal(0);
expect(mockClient.reply.args[0][0]).to.equal(226);
done();
})
.catch(done);
});
});
it('// successful | active connection', done => {
cmdFn()
it('// successful | active connection', () => {
return cmdFn()
.then(() => {
expect(mockClient.connector.waitForConnection.callCount).to.equal(1);
expect(mockClient.connector.end.callCount).to.equal(1);
expect(mockClient.reply.args[0][0]).to.equal(426);
expect(mockClient.reply.args[1][0]).to.equal(226);
done();
})
.catch(done);
});
});
});

View File

@@ -19,12 +19,10 @@ describe(CMD, function () {
sandbox.restore();
});
it('// successful', done => {
cmdFn()
it('// successful', () => {
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(202);
done();
})
.catch(done);
});
});
});

View File

@@ -22,31 +22,25 @@ describe(CMD, function () {
sandbox.restore();
});
it('TLS // supported', done => {
cmdFn({command: { arg: 'TLS', directive: CMD}})
it('TLS // supported', () => {
return cmdFn({command: { arg: 'TLS', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(234);
expect(mockClient.secure).to.equal(true);
done();
})
.catch(done);
});
});
it('SSL // not supported', done => {
cmdFn({command: { arg: 'SSL', directive: CMD}})
it('SSL // not supported', () => {
return cmdFn({command: { arg: 'SSL', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(504);
done();
})
.catch(done);
});
});
it('bad // bad', done => {
cmdFn({command: { arg: 'bad', directive: CMD}})
it('bad // bad', () => {
return cmdFn({command: { arg: 'bad', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(504);
done();
})
.catch(done);
});
});
});

View File

@@ -25,13 +25,11 @@ describe(CMD, function () {
sandbox.restore();
});
it('.. // successful', done => {
cmdFn({log, command: {directive: CMD}})
it('.. // successful', () => {
return cmdFn({log, command: {directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(250);
expect(mockClient.fs.chdir.args[0][0]).to.equal('..');
done();
})
.catch(done);
});
});
});

View File

@@ -23,63 +23,56 @@ describe(CMD, function () {
});
describe('// check', function () {
it('fails on no fs', done => {
it('fails on no fs', () => {
const badMockClient = { reply: () => {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('fails on no fs chdir command', done => {
it('fails on no fs chdir command', () => {
const badMockClient = { reply: () => {}, fs: {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
});
it('test // successful', done => {
cmdFn({log, command: { arg: 'test', directive: CMD}})
it('test // successful', () => {
return cmdFn({log, command: { arg: 'test', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(250);
expect(mockClient.fs.chdir.args[0][0]).to.equal('test');
done();
})
.catch(done);
});
});
it('test // successful', done => {
it('test // successful', () => {
mockClient.fs.chdir.restore();
sandbox.stub(mockClient.fs, 'chdir').resolves('/test');
cmdFn({log, command: { 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');
done();
})
.catch(done);
});
});
it('bad // unsuccessful', done => {
it('bad // unsuccessful', () => {
mockClient.fs.chdir.restore();
sandbox.stub(mockClient.fs, 'chdir').rejects(new Error('Bad'));
cmdFn({log, command: { 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');
done();
})
.catch(done);
});
});
});

View File

@@ -23,51 +23,45 @@ describe(CMD, function () {
});
describe('// check', function () {
it('fails on no fs', done => {
it('fails on no fs', () => {
const badMockClient = { reply: () => {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('fails on no fs delete command', done => {
it('fails on no fs delete command', () => {
const badMockClient = { reply: () => {}, fs: {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
});
it('test // successful', done => {
cmdFn({log, command: { arg: 'test', directive: CMD}})
it('test // successful', () => {
return cmdFn({log, command: { arg: 'test', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(250);
expect(mockClient.fs.delete.args[0][0]).to.equal('test');
done();
})
.catch(done);
});
});
it('bad // unsuccessful', done => {
it('bad // unsuccessful', () => {
mockClient.fs.delete.restore();
sandbox.stub(mockClient.fs, 'delete').rejects(new Error('Bad'));
cmdFn({log, command: { 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');
done();
})
.catch(done);
});
});
});

View File

@@ -19,39 +19,31 @@ describe(CMD, function () {
sandbox.restore();
});
it('// successful', done => {
cmdFn({command: { directive: CMD }})
it('// successful', () => {
return cmdFn({command: { directive: CMD }})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(211);
done();
})
.catch(done);
});
});
it('help // successful', done => {
cmdFn({command: { arg: 'help', directive: CMD}})
it('help // successful', () => {
return cmdFn({command: { arg: 'help', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(214);
done();
})
.catch(done);
});
});
it('help // successful', done => {
cmdFn({command: { arg: 'allo', directive: CMD}})
it('allo // successful', () => {
return cmdFn({command: { arg: 'allo', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(214);
done();
})
.catch(done);
});
});
it('bad // unsuccessful', done => {
cmdFn({command: { arg: 'bad', directive: CMD}})
it('bad // unsuccessful', () => {
return cmdFn({command: { arg: 'bad', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(502);
done();
})
.catch(done);
});
});
});

View File

@@ -9,7 +9,10 @@ describe(CMD, function () {
let log = bunyan.createLogger({name: CMD});
const mockClient = {
reply: () => {},
fs: { list: () => {} },
fs: {
list: () => {},
get: () => {}
},
connector: {
waitForConnection: () => when({}),
end: () => {}
@@ -25,6 +28,24 @@ describe(CMD, function () {
sandbox = sinon.sandbox.create();
sandbox.stub(mockClient, 'reply').resolves();
sandbox.stub(mockClient.fs, 'get').resolves({
name: 'testdir',
dev: 2114,
ino: 48064969,
mode: 33188,
nlink: 1,
uid: 85,
gid: 100,
rdev: 0,
size: 527,
blksize: 4096,
blocks: 8,
atime: 'Mon, 10 Oct 2011 23:24:11 GMT',
mtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
ctime: 'Mon, 10 Oct 2011 23:24:11 GMT',
birthtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
isDirectory: () => true
});
sandbox.stub(mockClient.fs, 'list').resolves([{
name: 'test1',
dev: 2114,
@@ -42,6 +63,23 @@ describe(CMD, function () {
ctime: 'Mon, 10 Oct 2011 23:24:11 GMT',
birthtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
isDirectory: () => false
}, {
name: 'test2',
dev: 2114,
ino: 48064969,
mode: 33188,
nlink: 1,
uid: 85,
gid: 100,
rdev: 0,
size: 527,
blksize: 4096,
blocks: 8,
atime: 'Mon, 10 Oct 2011 23:24:11 GMT',
mtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
ctime: 'Mon, 10 Oct 2011 23:24:11 GMT',
birthtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
isDirectory: () => true
}]);
});
afterEach(() => {
@@ -49,64 +87,89 @@ describe(CMD, function () {
});
describe('// check', function () {
it('fails on no fs', done => {
it('fails on no fs', () => {
const badMockClient = { reply: () => {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('fails on no fs list command', done => {
it('fails on no fs list command', () => {
const badMockClient = { reply: () => {}, fs: {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
});
it('. // successful', done => {
cmdFn({log, command: {directive: CMD}})
it('. // successful', () => {
return cmdFn({log, command: {directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(150);
expect(mockClient.reply.args[1].length).to.equal(3);
expect(mockClient.reply.args[1][1]).to.have.property('raw');
expect(mockClient.reply.args[1][1]).to.have.property('message');
expect(mockClient.reply.args[1][1]).to.have.property('socket');
expect(mockClient.reply.args[2][0]).to.equal(226);
done();
})
.catch(done);
});
});
it('. // unsuccessful', done => {
it('testfile.txt // successful', () => {
mockClient.fs.get.restore();
sandbox.stub(mockClient.fs, 'get').resolves({
name: 'testfile.txt',
dev: 2114,
ino: 48064969,
mode: 33188,
nlink: 1,
uid: 85,
gid: 100,
rdev: 0,
size: 527,
blksize: 4096,
blocks: 8,
atime: 'Mon, 10 Oct 2011 23:24:11 GMT',
mtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
ctime: 'Mon, 10 Oct 2011 23:24:11 GMT',
birthtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
isDirectory: () => false
});
return cmdFn({log, command: {directive: CMD, arg: 'testfile.txt'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(150);
expect(mockClient.reply.args[1].length).to.equal(2);
expect(mockClient.reply.args[1][1]).to.have.property('raw');
expect(mockClient.reply.args[1][1]).to.have.property('message');
expect(mockClient.reply.args[1][1]).to.have.property('socket');
expect(mockClient.reply.args[2][0]).to.equal(226);
});
});
it('. // unsuccessful', () => {
mockClient.fs.list.restore();
sandbox.stub(mockClient.fs, 'list').rejects(new Error());
cmdFn({log, command: {directive: CMD}})
return cmdFn({log, command: {directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(451);
done();
})
.catch(done);
});
});
it('. // unsuccessful (timeout)', done => {
it('. // unsuccessful (timeout)', () => {
sandbox.stub(mockClient.connector, 'waitForConnection').returns(when.reject(new when.TimeoutError()));
cmdFn({log, command: {directive: CMD}})
return cmdFn({log, command: {directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(425);
done();
})
.catch(done);
});
});
});

View File

@@ -23,50 +23,44 @@ describe(CMD, function () {
});
describe('// check', function () {
it('fails on no fs', done => {
it('fails on no fs', () => {
const badMockClient = { reply: () => {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('fails on no fs get command', done => {
it('fails on no fs get command', () => {
const badMockClient = { reply: () => {}, fs: {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
});
it('. // successful', done => {
cmdFn({log, command: {directive: CMD}})
it('. // successful', () => {
return cmdFn({log, command: {directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(213);
//expect(mockClient.reply.args[0][1]).to.equal('20111010172411.000');
done();
})
.catch(done);
});
});
it('. // unsuccessful', done => {
it('. // unsuccessful', () => {
mockClient.fs.get.restore();
sandbox.stub(mockClient.fs, 'get').rejects(new Error());
cmdFn({log, command: {directive: CMD}})
return cmdFn({log, command: {directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
});

View File

@@ -23,63 +23,56 @@ describe(CMD, function () {
});
describe('// check', function () {
it('fails on no fs', done => {
it('fails on no fs', () => {
const badMockClient = { reply: () => {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('fails on no fs mkdir command', done => {
it('fails on no fs mkdir command', () => {
const badMockClient = { reply: () => {}, fs: {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
});
it('test // successful', done => {
cmdFn({log, command: {arg: 'test', directive: CMD}})
it('test // successful', () => {
return cmdFn({log, command: {arg: 'test', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(257);
expect(mockClient.fs.mkdir.args[0][0]).to.equal('test');
done();
})
.catch(done);
});
});
it('test // successful', done => {
it('test // successful', () => {
mockClient.fs.mkdir.restore();
sandbox.stub(mockClient.fs, 'mkdir').resolves('test');
cmdFn({log, command: {arg: 'test', directive: CMD}})
return cmdFn({log, command: {arg: 'test', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(257);
expect(mockClient.fs.mkdir.args[0][0]).to.equal('test');
done();
})
.catch(done);
});
});
it('bad // unsuccessful', done => {
it('bad // unsuccessful', () => {
mockClient.fs.mkdir.restore();
sandbox.stub(mockClient.fs, 'mkdir').rejects(new Error('Bad'));
cmdFn({log, command: {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.mkdir.args[0][0]).to.equal('bad');
done();
})
.catch(done);
});
});
});

View File

@@ -19,21 +19,17 @@ describe(CMD, function () {
sandbox.restore();
});
it('S // successful', done => {
cmdFn({command: {arg: 'S'}})
it('S // successful', () => {
return cmdFn({command: {arg: 'S'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
done();
})
.catch(done);
});
});
it('Q // unsuccessful', done => {
cmdFn({command: {arg: 'Q'}})
it('Q // unsuccessful', () => {
return cmdFn({command: {arg: 'Q'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(504);
done();
})
.catch(done);
});
});
});

View File

@@ -9,7 +9,10 @@ describe(CMD, function () {
let log = bunyan.createLogger({name: CMD});
const mockClient = {
reply: () => {},
fs: { list: () => {} },
fs: {
get: () => {},
list: () => {}
},
connector: {
waitForConnection: () => when({}),
end: () => {}
@@ -25,6 +28,24 @@ describe(CMD, function () {
sandbox = sinon.sandbox.create();
sandbox.stub(mockClient, 'reply').resolves();
sandbox.stub(mockClient.fs, 'get').resolves({
name: 'testdir',
dev: 2114,
ino: 48064969,
mode: 33188,
nlink: 1,
uid: 85,
gid: 100,
rdev: 0,
size: 527,
blksize: 4096,
blocks: 8,
atime: 'Mon, 10 Oct 2011 23:24:11 GMT',
mtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
ctime: 'Mon, 10 Oct 2011 23:24:11 GMT',
birthtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
isDirectory: () => true
});
sandbox.stub(mockClient.fs, 'list').resolves([{
name: 'test1',
dev: 2114,
@@ -42,22 +63,70 @@ describe(CMD, function () {
ctime: 'Mon, 10 Oct 2011 23:24:11 GMT',
birthtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
isDirectory: () => false
}, {
name: 'test2',
dev: 2114,
ino: 48064969,
mode: 33188,
nlink: 1,
uid: 85,
gid: 100,
rdev: 0,
size: 527,
blksize: 4096,
blocks: 8,
atime: 'Mon, 10 Oct 2011 23:24:11 GMT',
mtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
ctime: 'Mon, 10 Oct 2011 23:24:11 GMT',
birthtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
isDirectory: () => true
}]);
});
afterEach(() => {
sandbox.restore();
});
it('. // successful', done => {
cmdFn({log, command: {directive: CMD}})
it('. // successful', () => {
return cmdFn({log, command: {directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(150);
expect(mockClient.reply.args[1].length).to.equal(3);
expect(mockClient.reply.args[1][1]).to.have.property('raw');
expect(mockClient.reply.args[1][1]).to.have.property('message');
expect(mockClient.reply.args[1][1]).to.have.property('socket');
expect(mockClient.reply.args[2][0]).to.equal(226);
done();
})
.catch(done);
});
});
it('testfile.txt // successful', () => {
mockClient.fs.get.restore();
sandbox.stub(mockClient.fs, 'get').resolves({
name: 'testfile.txt',
dev: 2114,
ino: 48064969,
mode: 33188,
nlink: 1,
uid: 85,
gid: 100,
rdev: 0,
size: 527,
blksize: 4096,
blocks: 8,
atime: 'Mon, 10 Oct 2011 23:24:11 GMT',
mtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
ctime: 'Mon, 10 Oct 2011 23:24:11 GMT',
birthtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
isDirectory: () => false
});
return cmdFn({log, command: {directive: CMD, arg: 'testfile.txt'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(150);
expect(mockClient.reply.args[1].length).to.equal(2);
expect(mockClient.reply.args[1][1]).to.have.property('raw');
expect(mockClient.reply.args[1][1]).to.have.property('message');
expect(mockClient.reply.args[1][1]).to.have.property('socket');
expect(mockClient.reply.args[2][0]).to.equal(226);
});
});
});

View File

@@ -19,12 +19,10 @@ describe(CMD, function () {
sandbox.restore();
});
it('// successful', done => {
cmdFn()
it('// successful', () => {
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
done();
})
.catch(done);
});
});
});

View File

@@ -19,12 +19,40 @@ describe(CMD, function () {
sandbox.restore();
});
it('// successful', done => {
cmdFn()
it('// unsuccessful', () => {
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(501);
done();
})
.catch(done);
});
});
it('BAD // unsuccessful', () => {
return cmdFn({command: {arg: 'BAD', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(500);
});
});
it('UTF8 BAD // unsuccessful', () => {
return cmdFn({command: {arg: 'UTF8 BAD', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(501);
});
});
it('UTF8 OFF // successful', () => {
return cmdFn({command: {arg: 'UTF8 OFF', directive: CMD}})
.then(() => {
expect(mockClient.encoding).to.equal('ascii');
expect(mockClient.reply.args[0][0]).to.equal(200);
});
});
it('UTF8 ON // successful', () => {
return cmdFn({command: {arg: 'UTF8 ON', directive: CMD}})
.then(() => {
expect(mockClient.encoding).to.equal('utf8');
expect(mockClient.reply.args[0][0]).to.equal(200);
});
});
});

View File

@@ -24,61 +24,51 @@ describe(CMD, function () {
sandbox.restore();
});
it('pass // successful', done => {
cmdFn({log, command: {arg: 'pass', directive: CMD}})
it('pass // successful', () => {
return cmdFn({log, command: {arg: 'pass', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(230);
expect(mockClient.login.args[0]).to.eql(['anonymous', 'pass']);
done();
})
.catch(done);
});
});
it('// successful (already authenticated)', done => {
it('// successful (already authenticated)', () => {
mockClient.server.options.anonymous = true;
mockClient.authenticated = true;
cmdFn({log, command: {directive: CMD}})
return cmdFn({log, command: {directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(202);
expect(mockClient.login.callCount).to.equal(0);
mockClient.server.options.anonymous = false;
mockClient.authenticated = false;
done();
})
.catch(done);
});
});
it('bad // unsuccessful', done => {
it('bad // unsuccessful', () => {
mockClient.login.restore();
sandbox.stub(mockClient, 'login').rejects('bad');
cmdFn({log, command: {arg: 'bad', directive: CMD}})
return cmdFn({log, command: {arg: 'bad', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(530);
done();
})
.catch(done);
});
});
it('bad // unsuccessful', done => {
it('bad // unsuccessful', () => {
mockClient.login.restore();
sandbox.stub(mockClient, 'login').rejects({});
cmdFn({log, command: {arg: 'bad', directive: CMD}})
return cmdFn({log, command: {arg: 'bad', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(530);
done();
})
.catch(done);
});
});
it('bad // unsuccessful', done => {
it('bad // unsuccessful', () => {
delete mockClient.username;
cmdFn({log, command: {arg: 'bad', directive: CMD}})
return cmdFn({log, command: {arg: 'bad', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(503);
done();
})
.catch(done);
});
});
});

View File

@@ -7,8 +7,7 @@ describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => when.resolve(),
server: {},
secure: true
server: {}
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
@@ -21,36 +20,32 @@ describe(CMD, function () {
sandbox.restore();
});
it('// unsuccessful', done => {
cmdFn()
it('// unsuccessful', () => {
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(202);
done();
})
.catch(done);
});
});
it('// successful', done => {
it('// successful', () => {
mockClient.secure = true;
mockClient.server._tls = {};
cmdFn({command: {arg: '0'}})
return cmdFn({command: {arg: '0'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
expect(mockClient.bufferSize).to.equal(0);
done();
})
.catch(done);
});
});
it('// successful', done => {
it('// successful', () => {
mockClient.secure = true;
mockClient.server._tls = {};
cmdFn({command: {arg: '10'}})
return cmdFn({command: {arg: '10'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
expect(mockClient.bufferSize).to.equal(10);
done();
})
.catch(done);
});
});
});

View File

@@ -22,33 +22,27 @@ describe(CMD, function () {
sandbox.restore();
});
it('// unsuccessful | no argument', done => {
cmdFn()
it('// unsuccessful | no argument', () => {
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(425);
done();
})
.catch(done);
});
});
it('// unsuccessful | invalid argument', done => {
cmdFn({ command: { arg: '1,2,3,4,5' } })
it('// unsuccessful | invalid argument', () => {
return cmdFn({ command: { arg: '1,2,3,4,5' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(425);
done();
})
.catch(done);
});
});
it('// successful', done => {
cmdFn({ command: { arg: '192,168,0,100,137,214' } })
it('// successful', () => {
return cmdFn({ command: { arg: '192,168,0,100,137,214' } })
.then(() => {
const [ip, port] = ActiveConnector.prototype.setupConnection.args[0];
expect(mockClient.reply.args[0][0]).to.equal(200);
expect(ip).to.equal('192.168.0.100');
expect(port).to.equal(35286);
done();
})
.catch(done);
});
});
});

View File

@@ -7,8 +7,7 @@ describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => when.resolve(),
server: {},
secure: true
server: {}
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
@@ -21,52 +20,46 @@ describe(CMD, function () {
sandbox.restore();
});
it('// unsuccessful', done => {
cmdFn()
it('// unsuccessful', () => {
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(202);
done();
})
.catch(done);
});
});
it('// unsuccessful - no bufferSize', done => {
it('// unsuccessful - no bufferSize', () => {
mockClient.server._tls = {};
mockClient.secure = true;
cmdFn({command: {arg: 'P'}})
return cmdFn({command: {arg: 'P'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(503);
done();
})
.catch(done);
});
});
it('// successful', done => {
it('// successful', () => {
mockClient.bufferSize = 0;
mockClient.secure = true;
cmdFn({command: {arg: 'p'}})
return cmdFn({command: {arg: 'p'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
done();
})
.catch(done);
});
});
it('// unsuccessful - unsupported', done => {
cmdFn({command: {arg: 'C'}})
it('// unsuccessful - unsupported', () => {
mockClient.secure = true;
return cmdFn({command: {arg: 'C'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(536);
done();
})
.catch(done);
});
});
it('// unsuccessful - unknown', done => {
cmdFn({command: {arg: 'QQ'}})
it('// unsuccessful - unknown', () => {
mockClient.secure = true;
return cmdFn({command: {arg: 'QQ'}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(504);
done();
})
.catch(done);
});
});
});

View File

@@ -23,61 +23,53 @@ describe(CMD, function () {
});
describe('// check', function () {
it('fails on no fs', done => {
it('fails on no fs', () => {
const badMockClient = { reply: () => {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('fails on no fs currentDirectory command', done => {
it('fails on no fs currentDirectory command', () => {
const badMockClient = { reply: () => {}, fs: {} };
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
sandbox.stub(badMockClient, 'reply').resolves();
badCmdFn()
return badCmdFn()
.then(() => {
expect(badMockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
});
it('// successful', done => {
cmdFn({log, command: { arg: 'test', directive: CMD}})
it('// successful', () => {
return cmdFn({log, command: { arg: 'test', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(257);
done();
})
.catch(done);
});
});
it('// successful', done => {
it('// successful', () => {
mockClient.fs.currentDirectory.restore();
sandbox.stub(mockClient.fs, 'currentDirectory').resolves('/test');
cmdFn({log, command: {arg: 'test', directive: CMD}})
return cmdFn({log, command: {arg: 'test', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(257);
done();
})
.catch(done);
});
});
it('// unsuccessful', done => {
it('// unsuccessful', () => {
mockClient.fs.currentDirectory.restore();
sandbox.stub(mockClient.fs, 'currentDirectory').rejects(new Error('Bad'));
cmdFn({log, command: {arg: 'bad', directive: CMD}})
return cmdFn({log, command: {arg: 'bad', directive: CMD}})
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
});

View File

@@ -18,12 +18,10 @@ describe(CMD, function () {
sandbox.restore();
});
it('// successful', done => {
cmdFn()
it('// successful', () => {
return cmdFn()
.then(() => {
expect(mockClient.close.callCount).to.equal(1);
done();
})
.catch(done);
});
});
});

View File

@@ -0,0 +1,58 @@
const {expect} = require('chai');
const sinon = require('sinon');
const when = require('when');
const CMD = 'REST';
describe(CMD, function () {
let sandbox;
const mockClient = {
reply: () => when.resolve()
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
beforeEach(() => {
sandbox = sinon.sandbox.create();
sandbox.spy(mockClient, 'reply');
});
afterEach(() => {
sandbox.restore();
});
it('// unsuccessful', () => {
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(501);
});
});
it('-1 // unsuccessful', () => {
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 } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(501);
});
});
it('1 // successful', () => {
return cmdFn({command: { arg: '1', directive: CMD } })
.then(() => {
expect(mockClient.restByteCount).to.equal(1);
expect(mockClient.reply.args[0][0]).to.equal(350);
});
});
it('0 // successful', () => {
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

@@ -0,0 +1,75 @@
const when = require('when');
const bunyan = require('bunyan');
const {expect} = require('chai');
const sinon = require('sinon');
const CMD = 'RETR';
describe(CMD, function () {
let sandbox;
let log = bunyan.createLogger({name: CMD});
const mockClient = {
commandSocket: {
pause: () => {},
resume: () => {}
},
reply: () => when.resolve(),
connector: {
waitForConnection: () => when.resolve({
resume: () => {}
}),
end: () => {}
}
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
beforeEach(() => {
sandbox = sinon.sandbox.create();
mockClient.fs = {
read: () => {}
};
sandbox.spy(mockClient, 'reply');
});
afterEach(() => sandbox.restore());
it('// unsuccessful | no file system', () => {
delete mockClient.fs;
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
});
});
it('// unsuccessful | file system does not have functions', () => {
mockClient.fs = {};
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(402);
});
});
it('// unsuccessful | connector times out', () => {
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
return when.reject(new when.TimeoutError());
});
return cmdFn({log, command: {arg: 'test.txt'} })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(425);
});
});
it('// unsuccessful | connector errors out', () => {
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
return when.reject(new Error('test'));
});
return cmdFn({log, command: {arg: 'test.txt'} })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(551);
});
});
});

View File

@@ -24,47 +24,39 @@ describe(CMD, function () {
sandbox.restore();
});
it('// unsuccessful | no file system', done => {
it('// unsuccessful | no file system', () => {
delete mockClient.fs;
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('// unsuccessful | file system does not have functions', done => {
it('// unsuccessful | file system does not have functions', () => {
mockClient.fs = {};
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
it('test // unsuccessful | file get fails', done => {
it('test // unsuccessful | file get fails', () => {
mockClient.fs.get.restore();
sandbox.stub(mockClient.fs, 'get').rejects(new Error('test'));
cmdFn({ log: mockLog, command: { arg: 'test' } })
return cmdFn({ log: mockLog, command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('test // successful', done => {
cmdFn({ log: mockLog, command: { arg: 'test' } })
it('test // successful', () => {
return cmdFn({ log: mockLog, command: { arg: 'test' } })
.then(() => {
expect(mockClient.fs.get.args[0][0]).to.equal('test');
expect(mockClient.reply.args[0][0]).to.equal(350);
done();
})
.catch(done);
});
});
});

View File

@@ -25,58 +25,48 @@ describe(CMD, function () {
sandbox.restore();
});
it('// unsuccessful | no renameFrom set', done => {
it('// unsuccessful | no renameFrom set', () => {
delete mockClient.renameFrom;
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(503);
done();
})
.catch(done);
});
});
it('// unsuccessful | no file system', done => {
it('// unsuccessful | no file system', () => {
delete mockClient.fs;
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('// unsuccessful | file system does not have functions', done => {
it('// unsuccessful | file system does not have functions', () => {
mockClient.fs = {};
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
it('new // unsuccessful | rename fails', done => {
it('new // unsuccessful | rename fails', () => {
mockClient.fs.rename.restore();
sandbox.stub(mockClient.fs, 'rename').rejects(new Error('test'));
cmdFn({ log: mockLog, command: { arg: 'new' } })
return cmdFn({ log: mockLog, command: { arg: 'new' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('new // successful', done => {
cmdFn({ command: { arg: 'new' } })
it('new // successful', () => {
return cmdFn({ command: { arg: 'new' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(250);
expect(mockClient.fs.rename.args[0]).to.eql(['test', 'new']);
done();
})
.catch(done);
});
});
});

View File

@@ -22,45 +22,37 @@ describe(CMD, function () {
sandbox.restore();
});
it('// unsuccessful | no file system', done => {
it('// unsuccessful | no file system', () => {
delete mockClient.fs;
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('// unsuccessful | file system does not have functions', done => {
it('// unsuccessful | file system does not have functions', () => {
mockClient.fs = {};
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
it('// unsuccessful | file get fails', done => {
it('// unsuccessful | file get fails', () => {
sandbox.stub(mockClient.fs, 'get').rejects(new Error('test'));
cmdFn({ log: mockLog, command: { arg: 'test' } })
return cmdFn({ log: mockLog, command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('// successful', done => {
cmdFn({ command: { arg: 'test' } })
it('// successful', () => {
return cmdFn({ command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(213);
done();
})
.catch(done);
});
});
});

View File

@@ -23,49 +23,41 @@ describe(CMD, function () {
sandbox.restore();
});
it('// successful', done => {
cmdFn()
it('// successful', () => {
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(211);
done();
})
.catch(done);
});
});
it('// unsuccessful | no file system', done => {
it('// unsuccessful | no file system', () => {
delete mockClient.fs;
cmdFn({ command: { arg: 'test' } })
return cmdFn({ command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('// unsuccessful | file system does not have functions', done => {
it('// unsuccessful | file system does not have functions', () => {
mockClient.fs = {};
cmdFn({ command: { arg: 'test' } })
return cmdFn({ command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
it('// unsuccessful | file get fails', done => {
it('// unsuccessful | file get fails', () => {
sandbox.stub(mockClient.fs, 'get').rejects(new Error('test'));
cmdFn({ log: mockLog, command: { arg: 'test' } })
return cmdFn({ log: mockLog, command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(450);
done();
})
.catch(done);
});
});
it('// successful | file', done => {
it('// successful | file', () => {
sandbox.stub(mockClient.fs, 'get').returns({
name: 'test_file',
dev: 2114,
@@ -85,15 +77,13 @@ describe(CMD, function () {
isDirectory: () => false
});
cmdFn({ command: { arg: 'test' } })
return cmdFn({ command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(212);
done();
})
.catch(done);
});
});
it('// successful | directory', done => {
it('// successful | directory', () => {
sandbox.stub(mockClient.fs, 'list').returns([{
name: 'test_file',
dev: 2114,
@@ -132,11 +122,9 @@ describe(CMD, function () {
isDirectory: () => true
});
cmdFn({ command: { arg: 'test' } })
return cmdFn({ command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(213);
done();
})
.catch(done);
});
});
});

View File

@@ -0,0 +1,75 @@
const when = require('when');
const bunyan = require('bunyan');
const {expect} = require('chai');
const sinon = require('sinon');
const CMD = 'STOR';
describe(CMD, function () {
let sandbox;
let log = bunyan.createLogger({name: CMD});
const mockClient = {
commandSocket: {
pause: () => {},
resume: () => {}
},
reply: () => when.resolve(),
connector: {
waitForConnection: () => when.resolve({
resume: () => {}
}),
end: () => {}
}
};
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
beforeEach(() => {
sandbox = sinon.sandbox.create();
mockClient.fs = {
write: () => {}
};
sandbox.spy(mockClient, 'reply');
});
afterEach(() => sandbox.restore());
it('// unsuccessful | no file system', () => {
delete mockClient.fs;
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
});
});
it('// unsuccessful | file system does not have functions', () => {
mockClient.fs = {};
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(402);
});
});
it('// unsuccessful | connector times out', () => {
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
return when.reject(new when.TimeoutError());
});
return cmdFn({log, command: {arg: 'test.txt'} })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(425);
});
});
it('// unsuccessful | connector errors out', () => {
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
return when.reject(new Error('test'));
});
return cmdFn({log, command: {arg: 'test.txt'} })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
});
});
});

View File

@@ -30,54 +30,46 @@ describe(CMD, function () {
sandbox.restore();
});
it('// unsuccessful | no file system', done => {
it('// unsuccessful | no file system', () => {
delete mockClient.fs;
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(550);
done();
})
.catch(done);
});
});
it('// unsuccessful | file system does not have functions', done => {
it('// unsuccessful | file system does not have functions', () => {
mockClient.fs = {};
cmdFn()
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(402);
done();
})
.catch(done);
});
});
it('// successful | given name is unique', done => {
it('// successful | given name is unique', () => {
mockClient.fs.get.restore();
sandbox.stub(mockClient.fs, 'get').rejects({});
cmdFn({ command: { arg: 'good' } })
return cmdFn({ command: { arg: 'good' } })
.then(() => {
const call = stor.handler.call.args[0][1];
expect(call).to.have.property('command');
expect(call.command).to.have.property('arg');
expect(call.command.arg).to.eql('good');
expect(mockClient.fs.getUniqueName.callCount).to.equal(0);
done();
})
.catch(done);
});
});
it('// successful | generates unique name', done => {
cmdFn({ command: { arg: 'bad' } })
it('// successful | generates unique name', () => {
return cmdFn({ command: { arg: 'bad' } })
.then(() => {
const call = stor.handler.call.args[0][1];
expect(call).to.have.property('command');
expect(call.command).to.have.property('arg');
expect(call.command.arg).to.eql('4');
expect(mockClient.fs.getUniqueName.callCount).to.equal(1);
done();
})
.catch(done);
});
});
});

View File

@@ -19,21 +19,17 @@ describe(CMD, function () {
sandbox.restore();
});
it('// successful', done => {
cmdFn({command: { arg: 'F' } })
it('// successful', () => {
return cmdFn({command: { arg: 'F' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
done();
})
.catch(done);
});
});
it('// unsuccessful', done => {
cmdFn({command: { arg: 'X' } })
it('// unsuccessful', () => {
return cmdFn({command: { arg: 'X' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(504);
done();
})
.catch(done);
});
});
});

View File

@@ -19,12 +19,10 @@ describe(CMD, function () {
sandbox.restore();
});
it('// successful', done => {
cmdFn()
it('// successful', () => {
return cmdFn()
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(215);
done();
})
.catch(done);
});
});
});

View File

@@ -13,50 +13,42 @@ describe(CMD, function () {
beforeEach(() => {
sandbox = sinon.sandbox.create();
mockClient.encoding = null;
mockClient.transferType = null;
sandbox.spy(mockClient, 'reply');
});
afterEach(() => {
sandbox.restore();
});
it('A // successful', done => {
cmdFn({ command: { arg: 'A' } })
it('A // successful', () => {
return cmdFn({ command: { arg: 'A' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
expect(mockClient.encoding).to.equal('utf8');
done();
})
.catch(done);
expect(mockClient.transferType).to.equal('ascii');
});
});
it('I // successful', done => {
cmdFn({ command: { arg: 'I' } })
it('I // successful', () => {
return cmdFn({ command: { arg: 'I' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
expect(mockClient.encoding).to.equal('binary');
done();
})
.catch(done);
expect(mockClient.transferType).to.equal('binary');
});
});
it('L // successful', done => {
cmdFn({ command: { arg: 'L' } })
it('L // successful', () => {
return cmdFn({ command: { arg: 'L' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(200);
expect(mockClient.encoding).to.equal('binary');
done();
})
.catch(done);
expect(mockClient.transferType).to.equal('binary');
});
});
it('X // successful', done => {
cmdFn({ command: { arg: 'X' } })
it('X // successful', () => {
return cmdFn({ command: { arg: 'X' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(501);
expect(mockClient.encoding).to.equal(null);
done();
})
.catch(done);
expect(mockClient.transferType).to.equal(null);
});
});
});

View File

@@ -28,70 +28,80 @@ describe(CMD, function () {
sandbox.restore();
});
it('test // successful | prompt for password', done => {
cmdFn({ command: { arg: 'test' } })
it('test // successful | prompt for password', () => {
return cmdFn({ command: { arg: 'test' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(331);
done();
})
.catch(done);
});
});
it('test // successful | anonymous login', done => {
it('test // successful | anonymous login', () => {
mockClient.server.options = {anonymous: true};
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);
done();
})
.catch(done);
});
});
it('test // unsuccessful | no username provided', done => {
cmdFn({ command: { } })
it('test // unsuccessful | no username provided', () => {
return cmdFn({ command: { } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(501);
expect(mockClient.login.callCount).to.equal(0);
done();
})
.catch(done);
});
});
it('test // unsuccessful | already set username', done => {
it('test // unsuccessful | already set username', () => {
mockClient.username = 'test';
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);
done();
})
.catch(done);
});
});
it('test // successful | regular login if anonymous is true', done => {
it('test // successful | regular login if anonymous is true', () => {
mockClient.server.options = {anonymous: true};
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);
done();
})
.catch(done);
});
});
it('test // successful | anonymous login with set username', done => {
it('test // successful | anonymous login with set username', () => {
mockClient.server.options = {anonymous: 'sillyrabbit'};
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);
done();
})
.catch(done);
});
});
it('test // unsuccessful | anonymous login fails', () => {
mockClient.server.options = {anonymous: true};
mockClient.login.restore();
sandbox.stub(mockClient, 'login').rejects(new Error('test'));
return cmdFn({ log: mockLog, command: { arg: 'anonymous' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(530);
expect(mockClient.login.callCount).to.equal(1);
});
});
it('test // successful | does not login if already authenticated', () => {
mockClient.authenticated = true;
return cmdFn({ log: mockLog, command: { arg: 'sillyrabbit' } })
.then(() => {
expect(mockClient.reply.args[0][0]).to.equal(230);
expect(mockClient.login.callCount).to.equal(0);
});
});
});

View File

@@ -3,6 +3,7 @@ const {expect} = require('chai');
const sinon = require('sinon');
const net = require('net');
const tls = require('tls');
const ActiveConnector = require('../../src/connector/active');
const findPort = require('../../src/helpers/find-port');
@@ -33,34 +34,49 @@ describe('Connector - Active //', function () {
server.close(done);
});
it('sets up a connection', function (done) {
active.setupConnection('127.0.0.1', PORT)
it('sets up a connection', function () {
return active.setupConnection('127.0.0.1', PORT)
.then(() => {
expect(active.dataSocket).to.exist;
done();
})
.catch(done);
});
});
it('destroys existing connection, then sets up a connection', function (done) {
it('destroys existing connection, then sets up a connection', function () {
const destroyFnSpy = sandbox.spy(active.dataSocket, 'destroy');
active.setupConnection('127.0.0.1', PORT)
return active.setupConnection('127.0.0.1', PORT)
.then(() => {
expect(destroyFnSpy.callCount).to.equal(1);
expect(active.dataSocket).to.exist;
done();
})
.catch(done);
});
});
it('waits for connection', function (done) {
active.setupConnection('127.0.0.1', PORT)
it('waits for connection', function () {
return active.setupConnection('127.0.0.1', PORT)
.then(() => {
expect(active.dataSocket).to.exist;
return active.waitForConnection();
})
.then(() => done())
.catch(done);
.then(dataSocket => {
expect(dataSocket.connected).to.equal(true);
expect(dataSocket instanceof net.Socket).to.equal(true);
expect(dataSocket instanceof tls.TLSSocket).to.equal(false);
});
});
it('upgrades to a secure connection', function () {
mockConnection.secure = true;
mockConnection.server = { _tls: {} };
return active.setupConnection('127.0.0.1', PORT)
.then(() => {
expect(active.dataSocket).to.exist;
return active.waitForConnection();
})
.then(dataSocket => {
expect(dataSocket.connected).to.equal(true);
expect(dataSocket instanceof net.Socket).to.equal(true);
expect(dataSocket instanceof tls.TLSSocket).to.equal(true);
});
});
});

View File

@@ -20,6 +20,10 @@ describe('Connector - Passive //', function () {
};
let sandbox;
function shouldNotResolve() {
throw new Error('Should not resolve');
}
before(() => {
passive = new PassiveConnector(mockConnection);
});
@@ -36,45 +40,49 @@ describe('Connector - Passive //', function () {
sandbox.restore();
});
it('cannot wait for connection with no server', function (done) {
passive.waitForConnection()
.then(() => done('should not happen'))
it('cannot wait for connection with no server', function () {
return passive.waitForConnection()
.then(shouldNotResolve)
.catch(err => {
expect(err.name).to.equal('ConnectorError');
done();
});
});
it('has invalid pasv range', function (done) {
it('no pasv range provided', function () {
delete mockConnection.server.options.pasv_range;
return passive.setupServer()
.then(shouldNotResolve)
.catch(err => {
expect(err.name).to.equal('ConnectorError');
});
});
it('has invalid pasv range', function () {
mockConnection.server.options.pasv_range = -1;
passive.setupServer()
.then(() => done('should not happen'))
return passive.setupServer()
.then(shouldNotResolve)
.catch(err => {
expect(err.name).to.equal('RangeError');
done();
});
});
it('sets up a server', function (done) {
passive.setupServer()
it('sets up a server', function () {
return passive.setupServer()
.then(() => {
expect(passive.dataServer).to.exist;
done();
})
.catch(done);
});
});
it('destroys existing server, then sets up a server', function (done) {
it('destroys existing server, then sets up a server', function () {
const closeFnSpy = sandbox.spy(passive.dataServer, 'close');
passive.setupServer()
return passive.setupServer()
.then(() => {
expect(closeFnSpy.callCount).to.equal(1);
expect(passive.dataServer).to.exist;
done();
})
.catch(done);
});
});
it('refuses connection with different remote address', function (done) {
@@ -97,8 +105,8 @@ describe('Connector - Passive //', function () {
.catch(done);
});
it('accepts connection', function (done) {
passive.setupServer()
it('accepts connection', function () {
return passive.setupServer()
.then(() => {
expect(passive.dataServer).to.exist;
@@ -109,8 +117,6 @@ describe('Connector - Passive //', function () {
.then(() => {
expect(passive.dataSocket).to.exist;
passive.end();
done();
})
.catch(done);
});
});
});

View File

@@ -2,10 +2,9 @@ const {expect} = require('chai');
const escapePath = require('../../src/helpers/escape-path');
describe('helpers // escape-path', function () {
it('escapes quotes', done => {
it('escapes quotes', () => {
const string = '"test"';
const escapedString = escapePath(string);
expect(escapedString).to.equal('""test""');
done();
});
});

View File

@@ -17,22 +17,19 @@ describe('helpers // find-port', function () {
sandbox.restore();
});
it('finds a port', done => {
findPort(1)
it('finds a port', () => {
return findPort(1)
.then(port => {
expect(Server.prototype.listen.callCount).to.be.above(1);
expect(port).to.be.above(1);
done();
})
.catch(done);
});
});
it('does not find a port', done => {
findPort(1, 2)
.then(() => done('no'))
it('does not find a port', () => {
return findPort(1, 2)
.then(() => expect(1).to.equal(2)) // should not happen
.catch(err => {
expect(err).to.exist;
done();
});
});
});

View File

@@ -1,36 +1,51 @@
const {expect} = require('chai');
const sinon = require('sinon');
const resolveHost = require('../../src/helpers/resolve-host');
describe('helpers //resolve-host', function () {
this.timeout(4000);
it('fetches ip address', done => {
let sandbox;
beforeEach(() => {
sandbox = sinon.sandbox.create();
});
afterEach(() => sandbox.restore());
it('fetches ip address', () => {
const hostname = '0.0.0.0';
resolveHost(hostname)
return resolveHost(hostname)
.then(resolvedHostname => {
expect(resolvedHostname).to.match(/^\d+\.\d+\.\d+\.\d+$/);
done();
})
.catch(done);
});
});
it('fetches ip address', done => {
it('fetches ip address', () => {
const hostname = null;
resolveHost(hostname)
return resolveHost(hostname)
.then(resolvedHostname => {
expect(resolvedHostname).to.match(/^\d+\.\d+\.\d+\.\d+$/);
done();
})
.catch(done);
});
});
it('does nothing', done => {
it('does nothing', () => {
const hostname = '127.0.0.1';
resolveHost(hostname)
return resolveHost(hostname)
.then(resolvedHostname => {
expect(resolvedHostname).to.equal(hostname);
done();
})
.catch(done);
});
});
it('fails on getting hostname', () => {
sandbox.stub(require('http'), 'get').callsFake(function (url, cb) {
cb({
statusCode: 420
});
});
return resolveHost(null)
.then(() => expect(1).to.equal(2))
.catch(err => {
expect(err.code).to.equal(420);
});
});
});

View File

@@ -1,5 +1,6 @@
/* eslint no-unused-expressions: 0 */
const {expect} = require('chai');
const sinon = require('sinon');
const bunyan = require('bunyan');
const fs = require('fs');
@@ -10,11 +11,14 @@ before(() => require('dotenv').load());
describe('FtpServer', function () {
this.timeout(2000);
let sandbox;
let log = bunyan.createLogger({name: 'test'});
let server;
let client;
before(done => {
let connection;
before(() => {
server = new FtpServer(process.env.FTP_URL, {
log,
pasv_range: process.env.PASV_RANGE,
@@ -22,14 +26,21 @@ describe('FtpServer', function () {
key: `${process.cwd()}/test/cert/server.key`,
cert: `${process.cwd()}/test/cert/server.crt`,
ca: `${process.cwd()}/test/cert/server.csr`
}
},
greeting: ['hello', 'world']
});
server.on('login', (data, resolve) => {
connection = data.connection;
resolve({root: process.cwd()});
});
server.listen()
.then(() => done());
return server.listen();
});
beforeEach(() => {
sandbox = sinon.sandbox.create();
});
afterEach(() => {
sandbox.restore();
});
after(() => {
server.close();
@@ -95,16 +106,42 @@ 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);
done();
});
});
it('STOR test.txt', done => {
const buffer = Buffer.from('test text file');
client.put(buffer, 'test.txt', err => {
it('LIST index.spec.js', done => {
client.list('index.spec.js', (err, data) => {
expect(err).to.not.exist;
expect(fs.existsSync('./test/test.txt')).to.equal(true);
fs.readFile('./test/test.txt', (fserr, data) => {
expect(data).to.be.an('array');
expect(data.length).to.be.equal(1);
done();
});
});
it('STOR fail.txt', done => {
sandbox.stub(connection.fs, 'write').callsFake(function () {
const fsPath = './test/fail.txt';
const stream = require('fs').createWriteStream(fsPath, {flags: 'w+'});
stream.once('error', () => fs.unlink(fsPath));
setTimeout(() => stream.emit('error', new Error('STOR fail test'), 1));
return stream;
});
const buffer = Buffer.from('test text file');
client.put(buffer, 'fail.txt', err => {
expect(err).to.exist;
expect(fs.existsSync('./test/fail.txt')).to.equal(false);
done();
});
});
it('STOR tést.txt', done => {
const buffer = Buffer.from('test text file');
client.put(buffer, 'tést.txt', err => {
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();
@@ -112,11 +149,11 @@ describe('FtpServer', function () {
});
});
it('APPE test.txt', done => {
it('APPE tést.txt', done => {
const buffer = Buffer.from(', awesome!');
client.append(buffer, 'test.txt', err => {
client.append(buffer, 'tést.txt', err => {
expect(err).to.not.exist;
fs.readFile('./test/test.txt', (fserr, data) => {
fs.readFile('./test/tést.txt', (fserr, data) => {
expect(fserr).to.not.exist;
expect(data.toString()).to.equal('test text file, awesome!');
done();
@@ -124,8 +161,8 @@ describe('FtpServer', function () {
});
});
it('RETR test.txt', done => {
client.get('test.txt', (err, stream) => {
it('RETR tést.txt', done => {
client.get('tést.txt', (err, stream) => {
expect(err).to.not.exist;
let text = '';
stream.on('data', data => {
@@ -138,10 +175,10 @@ describe('FtpServer', function () {
});
});
it('RNFR test.txt, RNTO awesome.txt', done => {
client.rename('test.txt', 'awesome.txt', err => {
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/test.txt')).to.equal(false);
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(fserr).to.not.exist;
@@ -186,19 +223,19 @@ describe('FtpServer', function () {
});
});
it('MKD tmp', done => {
if (fs.existsSync('./test/tmp')) {
fs.rmdirSync('./test/tmp');
it('MKD témp', done => {
if (fs.existsSync('./test/témp')) {
fs.rmdirSync('./test/témp');
}
client.mkdir('tmp', err => {
client.mkdir('témp', err => {
expect(err).to.not.exist;
expect(fs.existsSync('./test/tmp')).to.equal(true);
expect(fs.existsSync('./test/témp')).to.equal(true);
done();
});
});
it('CWD tmp', done => {
client.cwd('tmp', (err, data) => {
it('CWD témp', done => {
client.cwd('témp', (err, data) => {
expect(err).to.not.exist;
expect(data).to.be.a('string');
done();
@@ -212,10 +249,10 @@ describe('FtpServer', function () {
});
});
it('RMD tmp', done => {
client.rmdir('tmp', err => {
it('RMD témp', done => {
client.rmdir('témp', err => {
expect(err).to.not.exist;
expect(fs.existsSync('./test/tmp')).to.equal(false);
expect(fs.existsSync('./test/témp')).to.equal(false);
done();
});
});