Compare commits
3 Commits
logger
...
update-doc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90b4b78ddc | ||
|
|
5e20556a0e | ||
|
|
74760189ed |
@@ -1,65 +1,87 @@
|
||||
version: 2
|
||||
|
||||
create-cache-file: &create-cache-file
|
||||
run:
|
||||
name: Setup cache
|
||||
command: echo "$NODE_VERSION" > _cache_node_version
|
||||
|
||||
package-json-cache: &package-json-cache
|
||||
key: npm-install-{{ checksum "_cache_node_version" }}-{{ checksum "package-lock.json" }}
|
||||
|
||||
base-build: &base-build
|
||||
steps:
|
||||
- checkout
|
||||
- <<: *create-cache-file
|
||||
- restore_cache:
|
||||
<<: *package-json-cache
|
||||
- run:
|
||||
name: Install
|
||||
command: npm install
|
||||
- save_cache:
|
||||
<<: *package-json-cache
|
||||
paths:
|
||||
- node_modules
|
||||
- run:
|
||||
name: Lint
|
||||
command: npm run verify:js
|
||||
- run:
|
||||
name: Test
|
||||
command: npm run test:unit:once
|
||||
|
||||
jobs:
|
||||
test_node_10:
|
||||
build_node_8:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
environment:
|
||||
- NODE_VERSION: 10
|
||||
<<: *base-build
|
||||
- image: circleci/node:8
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: npm-install-node-8-{{ checksum "package.json" }}
|
||||
- run:
|
||||
name: Install
|
||||
command: npm install
|
||||
- save_cache:
|
||||
key: npm-install-node-8-{{ checksum "package.json" }}
|
||||
paths:
|
||||
- node_modules
|
||||
|
||||
build_node_6:
|
||||
docker:
|
||||
- image: circleci/node:6
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: npm-install-node-6-{{ checksum "package.json" }}
|
||||
- run:
|
||||
name: Install
|
||||
command: npm install
|
||||
- save_cache:
|
||||
key: npm-install-node-6-{{ checksum "package.json" }}
|
||||
paths:
|
||||
- node_modules
|
||||
|
||||
lint:
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: npm-install-node-8-{{ checksum "package.json" }}
|
||||
- run:
|
||||
name: Lint
|
||||
command: npm run verify:js
|
||||
|
||||
test_node_8:
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
environment:
|
||||
- NODE_VERSION: 8
|
||||
<<: *base-build
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: npm-install-node-8-{{ checksum "package.json" }}
|
||||
- run:
|
||||
name: Test Node 8
|
||||
command: npm run test:coverage
|
||||
when: always
|
||||
- store_test_results:
|
||||
path: reports
|
||||
- store_artifacts:
|
||||
path: reports/coverage
|
||||
prefix: coverage
|
||||
|
||||
test_node_6:
|
||||
docker:
|
||||
- image: circleci/node:6
|
||||
environment:
|
||||
- NODE_VERSION: 6
|
||||
<<: *base-build
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: npm-install-node-6-{{ checksum "package.json" }}
|
||||
- run:
|
||||
name: Test Node 6
|
||||
command: npm run test:coverage
|
||||
when: always
|
||||
- store_test_results:
|
||||
path: reports
|
||||
- store_artifacts:
|
||||
path: reports/coverage
|
||||
prefix: coverage
|
||||
|
||||
release:
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
environment:
|
||||
- NODE_VERSION: 8
|
||||
steps:
|
||||
- checkout
|
||||
- <<: *create-cache-file
|
||||
- restore_cache:
|
||||
<<: *package-json-cache
|
||||
key: npm-install-node-6-{{ checksum "package.json" }}
|
||||
- run:
|
||||
name: Update NPM
|
||||
command: |
|
||||
@@ -74,35 +96,45 @@ workflows:
|
||||
version: 2
|
||||
test_and_tag:
|
||||
jobs:
|
||||
- test_node_10:
|
||||
- build_node_8:
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
- test_node_8:
|
||||
- build_node_6:
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
- lint:
|
||||
requires:
|
||||
- build_node_8
|
||||
- test_node_6:
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
requires:
|
||||
- build_node_6
|
||||
- test_node_8:
|
||||
requires:
|
||||
- build_node_8
|
||||
- release:
|
||||
requires:
|
||||
- lint
|
||||
- test_node_6
|
||||
- test_node_8
|
||||
- test_node_10
|
||||
|
||||
build_and_test:
|
||||
jobs:
|
||||
- test_node_10:
|
||||
- build_node_8:
|
||||
filters:
|
||||
branches:
|
||||
ignore: master
|
||||
- test_node_8:
|
||||
- build_node_6:
|
||||
filters:
|
||||
branches:
|
||||
ignore: master
|
||||
- lint:
|
||||
requires:
|
||||
- build_node_8
|
||||
- test_node_6:
|
||||
filters:
|
||||
branches:
|
||||
ignore: master
|
||||
requires:
|
||||
- build_node_6
|
||||
- test_node_8:
|
||||
requires:
|
||||
- build_node_8
|
||||
24
.nycrc
Normal file
24
.nycrc
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"check-coverage": true,
|
||||
"per-file": false,
|
||||
"lines": 90,
|
||||
"statements": 90,
|
||||
"functions": 85,
|
||||
"branches": 75,
|
||||
"include": [
|
||||
"src/**/*.js"
|
||||
],
|
||||
"exclude": [
|
||||
"test/**/*.spec.js"
|
||||
],
|
||||
"reporter": [
|
||||
"lcovonly",
|
||||
"html",
|
||||
"text",
|
||||
"cobertura",
|
||||
"json"
|
||||
],
|
||||
"cache": true,
|
||||
"all": true,
|
||||
"report-dir": "./reports/coverage/"
|
||||
}
|
||||
221
CONTRIBUTING.md
221
CONTRIBUTING.md
@@ -1,20 +1,211 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/trs/ftp-srv">
|
||||
<img alt="ftp-srv" src="logo.png" width="400px" />
|
||||
</a>
|
||||
</p>
|
||||
<!--[CN_HEADING]-->
|
||||
# Contributing
|
||||
|
||||
<h1 align="center">
|
||||
Contributing Guide
|
||||
</h1>
|
||||
Welcome! This document explains how you can contribute to making **ftp-srv** even better.
|
||||
|
||||
## Welcome
|
||||
|
||||
- Thank you for your eagerness to contribute, pull requests are encouraged!
|
||||
<!--[]-->
|
||||
|
||||
<!--[CN_GETTING_STARTED]-->
|
||||
# Getting Started
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
git clone <this repo>
|
||||
npm install -g commitizen
|
||||
npm install -g semantic-release-cli
|
||||
npm install
|
||||
```
|
||||
|
||||
|
||||
<!--[]-->
|
||||
|
||||
<!--[RM_DIR_STRUCTURE]-->
|
||||
## Directory Structure
|
||||
|
||||
Code is organised into modules which contain one-or-more components. This a great way to ensure maintainable code by encapsulation of behavior logic. A component is basically a self contained app usually in a single file or a folder with each concern as a file: style, template, specs, e2e, and component class. Here's how it looks:
|
||||
```
|
||||
ftp-srv/
|
||||
├──config/ * configuration files live here (e.g. eslint, verify, testUnit)
|
||||
│
|
||||
├──src/ * source code files should be here
|
||||
│
|
||||
├──dist/ * production-build code should live here
|
||||
│
|
||||
├──reports/ * test reports appear here
|
||||
│
|
||||
├──test/ * unit test specifications live here
|
||||
│
|
||||
├──confit.yml * the project config file generated by 'yo confit'
|
||||
├──CONTRIBUTING.md * how to contribute to the project
|
||||
├──README.md * this file
|
||||
└──package.json * NPM package description file
|
||||
```
|
||||
|
||||
|
||||
<!--[]-->
|
||||
|
||||
<!--[CN_GITFLOW_PROCESS]-->
|
||||
# GitFlow Development Process
|
||||
|
||||
This project uses the [GitHub Flow](https://guides.github.com/introduction/flow/index.html) workflow.
|
||||
|
||||
## Create a branch
|
||||
When you're working on a project, you're going to have a bunch of different features or ideas in progress at any given time – some of which are ready to go, and others which are not. Branching exists to help you manage this workflow.
|
||||
|
||||
When you create a branch in your project, you're creating an environment where you can try out new ideas. Changes you make on a branch don't affect the `master` branch, so you're free to experiment and commit changes, safe in the knowledge that your branch won't be merged until it's ready to be reviewed by someone you're collaborating with.
|
||||
|
||||
###ProTip
|
||||
|
||||
Branching is a core concept in Git, and the entire GitHub Flow is based upon it. There's only one rule: anything in the `master` branch is always deployable.
|
||||
|
||||
Because of this, it's extremely important that your new branch is created off of `master` when working on a feature or a fix. Your branch name should be descriptive (e.g., `refactor-authentication`, `user-content-cache-key`, `make-retina-avatars`), so that others can see what is being worked on.
|
||||
|
||||
## Add commits
|
||||
Once your branch has been created, it's time to start making changes. Whenever you add, edit, or delete a file, you're making a commit, and adding them to your branch. This process of adding commits keeps track of your progress as you work on a feature branch.
|
||||
|
||||
Commits also create a transparent history of your work that others can follow to understand what you've done and why. Each commit has an associated commit message, which is a description explaining why a particular change was made. Furthermore, each commit is considered a separate unit of change. This lets you roll back changes if a bug is found, or if you decide to head in a different direction.
|
||||
|
||||
###ProTip
|
||||
|
||||
Commit messages are important, especially since Git tracks your changes and then displays them as commits once they're pushed to the server. By writing clear commit messages, you can make it easier for other people to follow along and provide feedback.
|
||||
|
||||
## Open a pull request
|
||||
|
||||
Pull Requests initiate discussion about your commits. Because they're tightly integrated with the underlying Git repository, anyone can see exactly what changes would be merged if they accept your request.
|
||||
|
||||
You can open a Pull Request at any point during the development process: when you have little or no code but want to share some screenshots or general ideas, when you're stuck and need help or advice, or when you're ready for someone to review your work. By using GitHub's @mention system in your Pull Request message, you can ask for feedback from specific people or teams, whether they're down the hall or ten time zones away.
|
||||
|
||||
###ProTip
|
||||
|
||||
Pull Requests are useful for contributing to open source projects and for managing changes to shared repositories. If you're using a Fork & Pull Model, Pull Requests provide a way to notify project maintainers about the changes you'd like them to consider. If you're using a Shared Repository Model, Pull Requests help start code review and conversation about proposed changes before they're merged into the `master` branch.
|
||||
|
||||
## Discuss and review your code
|
||||
Once a Pull Request has been opened, the person or team reviewing your changes may have questions or comments. Perhaps the coding style doesn't match project guidelines, the change is missing unit tests, or maybe everything looks great and props are in order. Pull Requests are designed to encourage and capture this type of conversation.
|
||||
|
||||
You can also continue to push to your branch in light of discussion and feedback about your commits. If someone comments that you forgot to do something or if there is a bug in the code, you can fix it in your branch and push up the change. GitHub will show your new commits and any additional feedback you may receive in the unified Pull Request view.
|
||||
|
||||
###ProTip
|
||||
|
||||
Pull Request comments are written in Markdown, so you can embed images and emoji, use pre-formatted text blocks, and other lightweight formatting.
|
||||
|
||||
## Merge to `master`
|
||||
|
||||
Once your PR has passed any the integration tests and received approval to merge, it is time to merge your code into the `master` branch.
|
||||
|
||||
Once merged, Pull Requests preserve a record of the historical changes to your code. Because they're searchable, they let anyone go back in time to understand why and how a decision was made.
|
||||
|
||||
###ProTip
|
||||
|
||||
By incorporating certain keywords into the text of your Pull Request, you can associate issues with code. When your Pull Request is merged, the related issues are also closed. For example, entering the phrase Closes #32 would close issue number 32 in the repository. For more information, check out our help article.
|
||||
|
||||
|
||||
<!--[]-->
|
||||
|
||||
<!--[CN_BUILD_TASKS]-->
|
||||
## Build Tasks
|
||||
|
||||
Command | Description
|
||||
:------ | :----------
|
||||
<pre>npm run build</pre> | Generate production build into [dist/](dist/) folder
|
||||
<pre>npm run dev</pre> | Run project in development mode (verify code, and re-verify when code is changed)
|
||||
<pre>npm start</pre> | Alias for `npm run dev` task
|
||||
|
||||
|
||||
|
||||
<!--[]-->
|
||||
|
||||
<!--[CN_TEST_TASKS]-->
|
||||
## Test Tasks
|
||||
|
||||
Command | Description
|
||||
:------ | :----------
|
||||
<pre>npm test</pre> | Alias for `npm run test:unit` task
|
||||
<pre>npm run test:coverage</pre> | Run instrumented unit tests then verify coverage meets defined thresholds<ul><li>Returns non-zero exit code when coverage does not meet thresholds (as defined in istanbul.js)</li></ul>
|
||||
<pre>npm run test:unit</pre> | Run unit tests whenever JS source or tests change<ul><li>Uses Mocha</li><li>Code coverage</li><li>Runs continuously (best to run in a separate window)</li></ul>
|
||||
<pre>npm run test:unit:once</pre> | Run unit tests once<ul><li>Uses Mocha</li><li>Code coverage</li></ul>
|
||||
|
||||
|
||||
|
||||
<!--[]-->
|
||||
|
||||
<!--[CN_VERIFY_TASKS]-->
|
||||
## Verification (Linting) Tasks
|
||||
|
||||
Command | Description
|
||||
:------ | :----------
|
||||
<pre>npm run verify</pre> | Verify code style and syntax<ul><li>Verifies source *and test code* aginst customisable rules (unlike Webpack loaders)</li></ul>
|
||||
<pre>npm run verify:js</pre> | Verify Javascript code style and syntax
|
||||
<pre>npm run verify:js:fix</pre> | Verify Javascript code style and syntax and fix any errors that can be fixed automatically
|
||||
<pre>npm run verify:js:watch</pre> | Verify Javascript code style and syntax and watch files for changes
|
||||
<pre>npm run verify:watch</pre> | Runs verify task whenever JS or CSS code is changed
|
||||
|
||||
|
||||
|
||||
<!--[]-->
|
||||
|
||||
<!--[CN_COMMIT_TASKS]-->
|
||||
## Commit Tasks
|
||||
|
||||
Command | Description
|
||||
:------ | :----------
|
||||
<pre>git status</pre> | Lists the current branch and the status of changed files
|
||||
<pre>git log</pre> | Displays the commit log (press Q to quit viewing)
|
||||
<pre>git add .</pre> | Stages all modified & untracked files, ready to be committed
|
||||
<pre>git cz</pre> | Commit changes to local repository using Commitizen.<ul><li>Asks questions about the change to generate a valid conventional commit message</li><li>Can be customised by modifying [config/release/commitMessageConfig.js](config/release/commitMessageConfig.js)</li></ul>
|
||||
<pre>git push</pre> | Push local repository changes to remote repository
|
||||
|
||||
|
||||
<!--[]-->
|
||||
|
||||
<!--[CN_DOCUMENTATION_TASKS]-->
|
||||
|
||||
|
||||
|
||||
<!--[]-->
|
||||
|
||||
<!--[CN_RELEASE_TASKS]-->
|
||||
## Release Tasks
|
||||
|
||||
Command | Description
|
||||
:------ | :----------
|
||||
<pre>npm run commitmsg</pre> | Git commit message hook that validates the commit message conforms to your commit message conventions.
|
||||
<pre>npm run pre-release</pre> | Verify code, run unit tests, check test coverage, build software. This task is designed to be run before
|
||||
the `semantic-release` task.
|
||||
<ul><li>Run `semantic-release-cli setup` once you have a remote repository. See https://github.com/semantic-release/cli for details.</li><li>Semantic-release integrates with Travis CI (or similar tools) to generate release notes
|
||||
for each release (which appears in the "Releases" section in GitHub) and
|
||||
publishes the package to NPM (when all the tests are successful) with a semantic version number.
|
||||
</li></ul>
|
||||
<pre>npm run prepush</pre> | Git pre-push hook that verifies code and checks unit test coverage meet minimum thresholds.
|
||||
<pre>npm run upload-coverage</pre> | Uploads code-coverage metrics to Coveralls.io<ul><li>Setup - https://coveralls.zendesk.com/hc/en-us/articles/201347419-Coveralls-currently-supports</li><li>Define an environment variable called COVERALLS_REPO_TOKEN in your build environment with the repo token from https://coveralls.io/github/<repo-name>/settings</li><li>In your CI configuration (e.g. `travis.yml`), call `npm run upload-coverage` if the build is successful.</li></ul>
|
||||
|
||||
|
||||
|
||||
<!--[]-->
|
||||
|
||||
|
||||
<!--[CN_CHANGING_BUILD_TOOL_CONFIG]-->
|
||||
## Changing build-tool configuration
|
||||
|
||||
There are 3 ways you can change the build-tool configuration for this project:
|
||||
|
||||
1. BEST: Modify the Confit configuration file ([confit.yml](confit.yml)) by hand, then re-run `yo confit` and tell it to use the existing configuration.
|
||||
1. OK: Re-run `yo confit` and provide new answers to the questions. **Confit will attempt to overwrite your existing configuration (it will prompt for confirmation), so make sure you have committed your code to a source control (e.g. git) first**.
|
||||
There are certain configuration settings which can **only** be specified by hand, in which case the first approach is still best.
|
||||
1. RISKY: Modify the generated build-tool config by hand. Be aware that if you re-run `yo confit` it will attempt to overwrite your changes. So commit your changes to source control first.
|
||||
|
||||
Additionally, the **currently-generated** configuration can be extended in the following ways:
|
||||
|
||||
- The task configuration is defined in [package.json](package.json). It is possible to change the task definitions to add your own sub-tasks.
|
||||
You can also use the `pre...` and `post...` script-name prefixes to run commands before (pre) and after (post) the generated commands.
|
||||
|
||||
- The `entryPoint.entryPoints` string in [confit.yml](confit.yml) is designed to be edited manually. It represents the starting-point(s) of the application (like a `main()` function). A NodeJS application has one entry point. E.g. `src/index.js`
|
||||
|
||||
|
||||
|
||||
|
||||
<!--[]-->
|
||||
|
||||
|
||||
## Guidelines
|
||||
|
||||
- Any new fixes are features should include new or updated [tests](/test).
|
||||
- Commits follow the [AngularJS Git Commit Message Conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit), please review and commit accordingly
|
||||
- Submit your pull requests to the `master` branch, these will normally be merged into a seperate branch for any finally changes before being merged into `master`.
|
||||
- Submit any bugs or requests to the issues page in Github.
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Tyler Stewart
|
||||
Copyright (c) 2017 Tyler Stewart
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
313
README.md
313
README.md
@@ -15,318 +15,71 @@
|
||||
</a>
|
||||
|
||||
<a href="https://circleci.com/gh/trs/ftp-srv">
|
||||
<img alt="circleci" src="https://img.shields.io/circleci/project/github/trs/ftp-srv.svg?style=for-the-badge" />
|
||||
<img alt="npm" src="https://img.shields.io/circleci/project/github/trs/ftp-srv.svg?style=for-the-badge" />
|
||||
</a>
|
||||
|
||||
<a href="https://coveralls.io/github/trs/ftp-srv?branch=master">
|
||||
<img alt="npm" src="https://img.shields.io/coveralls/github/trs/ftp-srv.svg?style=for-the-badge" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Features](#features)
|
||||
- [Install](#install)
|
||||
- [Usage](#usage)
|
||||
- [API](#api)
|
||||
- [CLI](#cli)
|
||||
- [Events](#events)
|
||||
- [Supported Commands](#supported-commands)
|
||||
- [File System](#file-system)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
## Synopsis
|
||||
|
||||
## Overview
|
||||
`ftp-srv` is a modern and extensible FTP server designed to be simple yet configurable.
|
||||
`ftp-srv` is an extensible FTP server solution that enables custom file systems per connection allowing the use of virtual file systems. By default, it acts like a regular FTP server. Just include it in your project and start listening.
|
||||
|
||||
## Features
|
||||
- Extensible [file systems](#file-system) per connection
|
||||
- Passive and active transfers
|
||||
|
||||
- Passive and Active transfer support
|
||||
- [Explicit](https://en.wikipedia.org/wiki/FTPS#Explicit) & [Implicit](https://en.wikipedia.org/wiki/FTPS#Implicit) TLS connections
|
||||
- Extensible [file systems](#file-system) per connection
|
||||
- Promise based API
|
||||
|
||||
## Install
|
||||
`npm install ftp-srv --save`
|
||||
|
||||
## Usage
|
||||
```
|
||||
$ npm install ftp-srv
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```js
|
||||
// Quick start
|
||||
|
||||
const FtpSrv = require('ftp-srv');
|
||||
const ftpServer = new FtpSrv('ftp://0.0.0.0:9876', { options ... });
|
||||
|
||||
ftpServer.on('login', (data, resolve, reject) => { ... });
|
||||
...
|
||||
const ftpServer = new FtpSrv('ftp://0.0.0.0:9876');
|
||||
|
||||
ftpServer.on('login', ({connection, username, password}, resolve, reject) => {
|
||||
// fetch credentials from database, file, or hard coded
|
||||
database.users.fetch({username, password})
|
||||
.then(() => {
|
||||
connection.on('STOR', (err, file) => console.log(`Uploaded file: ${file}`));
|
||||
|
||||
resolve({
|
||||
root: '/'
|
||||
});
|
||||
})
|
||||
.catch(() => reject);
|
||||
});
|
||||
|
||||
ftpServer.listen()
|
||||
.then(() => { ... });
|
||||
.then(() => {
|
||||
console.log('Waiting for connections!');
|
||||
});
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### `new FtpSrv(url, [{options}])`
|
||||
#### url
|
||||
[URL string](https://nodejs.org/api/url.html#url_url_strings_and_url_objects) indicating the protocol, hostname, and port to listen on for connections.
|
||||
Supported protocols:
|
||||
- `ftp` Plain FTP
|
||||
- `ftps` Implicit FTP over TLS
|
||||
Checkout the [Documentation](/docs).
|
||||
|
||||
_Note:_ The hostname must be the external IP address to accept external connections. Setting the hostname to `0.0.0.0` will automatically set the external IP.
|
||||
__Default:__ `"ftp://127.0.0.1:21"`
|
||||
|
||||
#### options
|
||||
|
||||
##### `pasv_range`
|
||||
A starting port (eg `8000`) or a range (eg `"8000-9000"`) to accept passive connections.
|
||||
This range is then queried for an available port to use when required.
|
||||
__Default:__ `22`
|
||||
|
||||
##### `greeting`
|
||||
A human readable array of lines or string to send when a client connects.
|
||||
__Default:__ `null`
|
||||
|
||||
##### `tls`
|
||||
Node [TLS secure context object](https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options) used for implicit (`ftps` protocol) or explicit (`AUTH TLS`) connections.
|
||||
__Default:__ `false`
|
||||
|
||||
##### `anonymous`
|
||||
If true, will allow clients to authenticate using the username `anonymous`, not requiring a password from the user.
|
||||
Can also set as a string which allows users to authenticate using the username provided.
|
||||
The `login` event is then sent with the provided username and `@anonymous` as the password.
|
||||
__Default:__ `false`
|
||||
|
||||
##### `blacklist`
|
||||
Array of commands that are not allowed.
|
||||
Response code `502` is sent to clients sending one of these commands.
|
||||
__Example:__ `['RMD', 'RNFR', 'RNTO']` will not allow users to delete directories or rename any files.
|
||||
__Default:__ `[]`
|
||||
|
||||
##### `whitelist`
|
||||
Array of commands that are only allowed.
|
||||
Response code `502` is sent to clients sending any other command.
|
||||
__Default:__ `[]`
|
||||
|
||||
##### `file_format`
|
||||
Sets the format to use for file stat queries such as `LIST`.
|
||||
__Default:__ `"ls"`
|
||||
__Allowable values:__
|
||||
- `ls` [bin/ls format](https://cr.yp.to/ftp/list/binls.html)
|
||||
- `ep` [Easily Parsed LIST format](https://cr.yp.to/ftp/list/eplf.html)
|
||||
- `function () {}` A custom function returning a format or promise for one.
|
||||
- Only one argument is passed in: a node [file stat](https://nodejs.org/api/fs.html#fs_class_fs_stats) object with additional file `name` parameter
|
||||
|
||||
##### `log`
|
||||
A [signale logger](https://github.com/klauscfhq/signale) instance. Created by default.
|
||||
|
||||
## CLI
|
||||
|
||||
`ftp-srv` also comes with a builtin CLI.
|
||||
|
||||
```bash
|
||||
$ ftp-srv [url] [options]
|
||||
```
|
||||
|
||||
```bash
|
||||
$ ftp-srv ftp://0.0.0.0:9876 --root ~/Documents
|
||||
```
|
||||
|
||||
#### `url`
|
||||
|
||||
Set the listening URL.
|
||||
|
||||
Defaults to `ftp://127.0.0.1:21`
|
||||
|
||||
#### `--root` / `-r`
|
||||
|
||||
Set the default root directory for users.
|
||||
|
||||
Defaults to the current directory.
|
||||
|
||||
#### `--credentials` / `-c`
|
||||
|
||||
Set the path to a json credentials file.
|
||||
|
||||
Format:
|
||||
|
||||
```js
|
||||
[
|
||||
{
|
||||
"username": "...",
|
||||
"password": "...",
|
||||
"root": "..." // Root directory
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
#### `--username`
|
||||
|
||||
Set the username for the only user. Do not provide an argument to allow anonymous login.
|
||||
|
||||
#### `--password`
|
||||
|
||||
Set the password for the given `username`.
|
||||
|
||||
## Events
|
||||
|
||||
The `FtpSrv` class extends the [node net.Server](https://nodejs.org/api/net.html#net_class_net_server). Some custom events can be resolved or rejected, such as `login`.
|
||||
|
||||
### `login`
|
||||
```js
|
||||
on('login', ({connection, username, password}, resolve, reject) => { ... });
|
||||
```
|
||||
|
||||
Occurs when a client is attempting to login. Here you can resolve the login request by username and password.
|
||||
|
||||
`connection` [client class object](src/connection.js)
|
||||
`username` string of username from `USER` command
|
||||
`password` string of password from `PASS` command
|
||||
`resolve` takes an object of arguments:
|
||||
- `fs`
|
||||
- Set a custom file system class for this connection to use.
|
||||
- See [File System](#file-system) for implementation details.
|
||||
- `root`
|
||||
- If `fs` is not provided, this will set the root directory for the connection.
|
||||
- The user cannot traverse lower than this directory.
|
||||
- `cwd`
|
||||
- If `fs` is not provided, will set the starting directory for the connection
|
||||
- This is relative to the `root` directory.
|
||||
- `blacklist`
|
||||
- Commands that are forbidden for only this connection
|
||||
- `whitelist`
|
||||
- If set, this connection will only be able to use the provided commands
|
||||
|
||||
`reject` takes an error object
|
||||
|
||||
### `client-error`
|
||||
```js
|
||||
on('client-error', ({connection, context, error}) => { ... });
|
||||
```
|
||||
|
||||
Occurs when an error arises in the client connection.
|
||||
|
||||
`connection` [client class object](src/connection.js)
|
||||
`context` string of where the error occurred
|
||||
`error` error object
|
||||
|
||||
### `RETR`
|
||||
```js
|
||||
on('RETR', (error, filePath) => { ... });
|
||||
```
|
||||
|
||||
Occurs when a file is downloaded.
|
||||
|
||||
`error` if successful, will be `null`
|
||||
`filePath` location to which file was downloaded
|
||||
|
||||
### `STOR`
|
||||
```js
|
||||
on('STOR', (error, fileName) => { ... });
|
||||
```
|
||||
|
||||
Occurs when a file is uploaded.
|
||||
|
||||
`error` if successful, will be `null`
|
||||
`fileName` name of the file that was downloaded
|
||||
|
||||
## Supported Commands
|
||||
|
||||
See the [command registry](src/commands/registration) for a list of all implemented FTP commands.
|
||||
|
||||
## File System
|
||||
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.
|
||||
|
||||
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)
|
||||
Returns a string of the current working directory
|
||||
__Used in:__ `PWD`
|
||||
|
||||
#### [`get(fileName)`](src/fs.js#L33)
|
||||
Returns a file stat object of file or directory
|
||||
__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`, `NLST`, `STAT`
|
||||
|
||||
#### [`chdir(path)`](src/fs.js#L56)
|
||||
Returns new directory relative to current directory
|
||||
__Used in:__ `CWD`, `CDUP`
|
||||
|
||||
#### [`mkdir(path)`](src/fs.js#L96)
|
||||
Returns a path to a newly created directory
|
||||
__Used in:__ `MKD`
|
||||
|
||||
#### [`write(fileName, {append, start})`](src/fs.js#L68)
|
||||
Returns a writable stream
|
||||
Options:
|
||||
`append` if true, append to existing file
|
||||
`start` if set, specifies the byte offset to write to
|
||||
__Used in:__ `STOR`, `APPE`
|
||||
|
||||
#### [`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)
|
||||
Delete a file or directory
|
||||
__Used in:__ `DELE`
|
||||
|
||||
#### [`rename(from, to)`](src/fs.js#L102)
|
||||
Renames a file or directory
|
||||
__Used in:__ `RNFR`, `RNTO`
|
||||
|
||||
#### [`chmod(path)`](src/fs.js#L108)
|
||||
Modifies a file or directory's permissions
|
||||
__Used in:__ `SITE CHMOD`
|
||||
|
||||
#### [`getUniqueName()`](src/fs.js#L113)
|
||||
Returns a unique file name to write to
|
||||
__Used in:__ `STOU`
|
||||
|
||||
<!--[RM_CONTRIBUTING]-->
|
||||
## Contributing
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
<!--[]-->
|
||||
|
||||
## Contributors
|
||||
|
||||
- [OzairP](https://github.com/OzairP)
|
||||
- [qchar](https://github.com/qchar)
|
||||
- [jorinvo](https://github.com/jorinvo)
|
||||
- [voxsoftware](https://github.com/voxsoftware)
|
||||
- [pkeuter](https://github.com/pkeuter)
|
||||
- [TimLuq](https://github.com/TimLuq)
|
||||
- [edin-mg](https://github.com/edin-m)
|
||||
|
||||
<!--[RM_LICENSE]-->
|
||||
## License
|
||||
|
||||
This software is licensed under the MIT Licence. See [LICENSE](LICENSE).
|
||||
|
||||
<!--[]-->
|
||||
|
||||
## References
|
||||
|
||||
- [https://cr.yp.to/ftp.html](https://cr.yp.to/ftp.html)
|
||||
|
||||
115
bin/index.js
115
bin/index.js
@@ -1,115 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const yargs = require('yargs');
|
||||
const path = require('path');
|
||||
|
||||
const FtpSrv = require('../src');
|
||||
const errors = require('../src/errors');
|
||||
|
||||
const args = setupYargs();
|
||||
const state = setupState(args);
|
||||
startFtpServer(state);
|
||||
|
||||
function setupYargs() {
|
||||
return yargs
|
||||
.option('credentials', {
|
||||
alias: 'c',
|
||||
describe: 'Load user & pass from json file',
|
||||
normalize: true
|
||||
})
|
||||
.option('username', {
|
||||
describe: 'Blank for anonymous',
|
||||
type: 'string'
|
||||
})
|
||||
.option('password', {
|
||||
describe: 'Password for given username',
|
||||
type: 'string'
|
||||
})
|
||||
.option('root', {
|
||||
alias: 'r',
|
||||
describe: 'Default root directory for users',
|
||||
type: 'string',
|
||||
normalize: true
|
||||
})
|
||||
.option('read-only', {
|
||||
describe: 'Disable write actions such as upload, delete, etc',
|
||||
boolean: true,
|
||||
default: false
|
||||
})
|
||||
.parse();
|
||||
}
|
||||
|
||||
function setupState(_args) {
|
||||
const _state = {};
|
||||
|
||||
function setupOptions() {
|
||||
if (_args._ && _args._.length > 0) {
|
||||
_state.url = _args._[0];
|
||||
}
|
||||
_state.anonymous = _args.username === '';
|
||||
}
|
||||
|
||||
function setupRoot() {
|
||||
const dirPath = _args.root;
|
||||
if (dirPath) {
|
||||
_state.root = process.cwd();
|
||||
} else {
|
||||
_state.root = dirPath;
|
||||
}
|
||||
}
|
||||
|
||||
function setupCredentials() {
|
||||
_state.credentials = {};
|
||||
|
||||
const setCredentials = (username, password, root = null) => {
|
||||
_state.credentials[_state.credentials] = {
|
||||
password,
|
||||
root
|
||||
};
|
||||
};
|
||||
|
||||
if (_args.credentials) {
|
||||
const credentialsFile = path.resolve(_args.credentials);
|
||||
const credentials = require(credentialsFile);
|
||||
|
||||
for (const cred of Object.entries(credentials)) {
|
||||
setCredentials(cred.username, cred.password, cred.root);
|
||||
}
|
||||
} else if (_args.username) {
|
||||
setCredentials(_args.username, _args.password);
|
||||
}
|
||||
}
|
||||
|
||||
function setupCommandBlacklist() {
|
||||
if (_args.readOnly) {
|
||||
_state.blacklist = ['ALLO', 'APPE', 'DELE', 'MKD', 'RMD', 'RNRF', 'RNTO', 'STOR', 'STRU'];
|
||||
}
|
||||
}
|
||||
|
||||
setupOptions();
|
||||
setupRoot();
|
||||
setupCredentials();
|
||||
setupCommandBlacklist();
|
||||
|
||||
return _state;
|
||||
}
|
||||
|
||||
function startFtpServer(_state) {
|
||||
|
||||
function checkLogin(data, resolve, reject) {
|
||||
const {password, root} = _state.credentials[data.username];
|
||||
if (_state.anonymous || password === data.password) {
|
||||
return resolve({root: root || _state.root});
|
||||
}
|
||||
|
||||
return reject(new errors.GeneralError('Invalid username or password', 401));
|
||||
}
|
||||
|
||||
const ftpServer = new FtpSrv(_state.url, {
|
||||
anonymous: _state.anonymous,
|
||||
blacklist: _state.blacklist
|
||||
});
|
||||
|
||||
ftpServer.on('login', checkLogin);
|
||||
ftpServer.listen();
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"reporterEnabled": "spec",
|
||||
"reporterEnabled": "mocha-pretty-bunyan-nyan",
|
||||
"mochaJunitReporterReporterOptions": {
|
||||
"mochaFile": "reports/junit.xml"
|
||||
}
|
||||
|
||||
49
confit.yml
Normal file
49
confit.yml
Normal file
@@ -0,0 +1,49 @@
|
||||
generator-confit:
|
||||
app:
|
||||
_version: 462ecd915fd9db1aef6a37c2b5ce8b58b80c18ba
|
||||
buildProfile: Latest
|
||||
copyrightOwner: Tyler Stewart
|
||||
license: MIT
|
||||
projectType: node
|
||||
publicRepository: true
|
||||
repositoryType: GitHub
|
||||
paths:
|
||||
_version: 780b129e0c7e5cab7e29c4f185bcf78524593a33
|
||||
config:
|
||||
configDir: config/
|
||||
input:
|
||||
srcDir: src/
|
||||
unitTestDir: test/
|
||||
output:
|
||||
prodDir: dist/
|
||||
reportDir: reports/
|
||||
buildJS:
|
||||
_version: ead8ce4280b07d696aff499a5fca1a933727582f
|
||||
framework: []
|
||||
frameworkScripts: []
|
||||
outputFormat: ES6
|
||||
sourceFormat: ES6
|
||||
entryPoint:
|
||||
_version: 39082c3df887fbc08744dfd088c25465e7a2e3a4
|
||||
entryPoints:
|
||||
main:
|
||||
- ftp-srv.js
|
||||
testUnit:
|
||||
_version: 30eee42a88ee42cce4f1ae48fe0cbe81647d189a
|
||||
testDependencies: []
|
||||
testFramework: mocha
|
||||
verify:
|
||||
_version: 30ae86c5022840a01fc08833e238a82c683fa1c7
|
||||
jsCodingStandard: none
|
||||
documentation:
|
||||
_version: b1658da3278b16d1982212f5e8bc05348af20e0b
|
||||
generateDocs: false
|
||||
release:
|
||||
_version: 47f220593935b502abf17cb34a396f692e453c49
|
||||
checkCodeCoverage: true
|
||||
commitMessageFormat: Conventional
|
||||
useSemantic: true
|
||||
sampleApp:
|
||||
_version: 00c0a2c6fc0ed17fcccce2d548d35896121e58ba
|
||||
createSampleApp: false
|
||||
zzfinish: {}
|
||||
2
docs/README.md
Normal file
2
docs/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
## Documentation
|
||||
|
||||
5
ftp-srv.d.ts
vendored
5
ftp-srv.d.ts
vendored
@@ -1,6 +1,5 @@
|
||||
import * as tls from 'tls'
|
||||
import { Stats } from 'fs'
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
export class FileSystem {
|
||||
|
||||
@@ -108,7 +107,7 @@ export class FtpServer {
|
||||
whitelist?: Array<string>
|
||||
}) => void,
|
||||
reject: (err?: Error) => void
|
||||
) => void): EventEmitter;
|
||||
) => void)
|
||||
|
||||
on(event: "client-error", listener: (
|
||||
data: {
|
||||
@@ -116,7 +115,7 @@ export class FtpServer {
|
||||
context: string,
|
||||
error: Error,
|
||||
}
|
||||
) => void): EventEmitter;
|
||||
) => void)
|
||||
}
|
||||
|
||||
export {FtpServer as FtpSrv};
|
||||
|
||||
7859
package-lock.json
generated
7859
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
40
package.json
40
package.json
@@ -14,35 +14,35 @@
|
||||
"license": "MIT",
|
||||
"files": [
|
||||
"src",
|
||||
"bin",
|
||||
"ftp-srv.d.ts"
|
||||
],
|
||||
"main": "ftp-srv.js",
|
||||
"bin": "./bin/index.js",
|
||||
"types": "./ftp-srv.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/trs/ftp-srv"
|
||||
},
|
||||
"scripts": {
|
||||
"pre-release": "npm run verify",
|
||||
"pre-release": "npm-run-all verify test:coverage build ",
|
||||
"build": "cross-env NODE_ENV=production npm run clean:prod",
|
||||
"clean:prod": "rimraf dist/",
|
||||
"commitmsg": "cz-customizable-ghooks",
|
||||
"dev": "cross-env NODE_ENV=development npm run verify:watch",
|
||||
"prepush": "npm-run-all verify test:unit:once --silent",
|
||||
"prepush": "npm-run-all verify test:coverage --silent",
|
||||
"semantic-release": "semantic-release",
|
||||
"start": "npm run dev",
|
||||
"test": "npm run test:unit",
|
||||
"test:unit": "cross-env NODE_ENV=test mocha --opts config/testUnit/mocha.opts -w",
|
||||
"test:unit:once": "cross-env NODE_ENV=test mocha --opts config/testUnit/mocha.opts",
|
||||
"test:check-coverage": "nyc check-coverage",
|
||||
"test:coverage": "npm-run-all test:unit:once test:check-coverage --silent",
|
||||
"test:unit": "cross-env NODE_ENV=test nyc mocha --opts config/testUnit/mocha.opts -w",
|
||||
"test:unit:once": "cross-env NODE_ENV=test nyc mocha --opts config/testUnit/mocha.opts",
|
||||
"upload-coverage": "cat reports/coverage/lcov.info | coveralls",
|
||||
"verify": "npm run verify:js --silent",
|
||||
"verify:js": "eslint -c config/verify/.eslintrc \"src/**/*.js\" \"test/**/*.js\" \"config/**/*.js\" && echo ✅ verify:js success",
|
||||
"verify:js:fix": "eslint --fix -c config/verify/.eslintrc \"src/**/*.js\" \"test/**/*.js\" \"config/**/*.js\" && echo ✅ verify:js:fix success",
|
||||
"verify:js:watch": "chokidar 'src/**/*.js' 'test/**/*.js' 'config/**/*.js' -c 'npm run verify:js:fix' --initial --silent",
|
||||
"verify:watch": "npm run verify:js:watch --silent"
|
||||
},
|
||||
"release": {
|
||||
"verifyConditions": "condition-circle"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "node_modules/cz-customizable"
|
||||
@@ -53,18 +53,17 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"bluebird": "^3.5.1",
|
||||
"ip": "^1.1.5",
|
||||
"lodash": "^4.17.10",
|
||||
"moment": "^2.22.1",
|
||||
"signale": "^1.0.1",
|
||||
"uuid": "^3.2.1",
|
||||
"yargs": "^11.0.0"
|
||||
"bunyan": "^1.8.12",
|
||||
"lodash": "^4.17.4",
|
||||
"moment": "^2.19.1",
|
||||
"uuid": "^3.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@icetee/ftp": "^1.0.2",
|
||||
"@icetee/ftp": "^0.3.15",
|
||||
"chai": "^4.0.2",
|
||||
"chokidar-cli": "1.2.0",
|
||||
"condition-circle": "^1.6.0",
|
||||
"coveralls": "2.13.1",
|
||||
"cross-env": "3.1.4",
|
||||
"cz-customizable": "5.2.0",
|
||||
"cz-customizable-ghooks": "1.5.0",
|
||||
@@ -79,13 +78,18 @@
|
||||
"mocha": "3.5.0",
|
||||
"mocha-junit-reporter": "1.13.0",
|
||||
"mocha-multi-reporters": "1.1.5",
|
||||
"npm-run-all": "^4.1.3",
|
||||
"mocha-pretty-bunyan-nyan": "^1.0.4",
|
||||
"npm-run-all": "4.0.2",
|
||||
"nyc": "11.1.0",
|
||||
"rimraf": "2.6.1",
|
||||
"semantic-release": "^11.0.2",
|
||||
"sinon": "^2.3.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.x",
|
||||
"npm": ">=5.x"
|
||||
"npm": ">=3.9.5"
|
||||
},
|
||||
"release": {
|
||||
"verifyConditions": "condition-circle"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,16 +36,19 @@ class FtpCommands {
|
||||
const logCommand = _.clone(command);
|
||||
if (logCommand.directive === 'PASS') logCommand.arg = '********';
|
||||
|
||||
const log = this.connection.log.child({directive: command.directive});
|
||||
log.trace({command: logCommand}, 'Handle command');
|
||||
|
||||
if (!REGISTRY.hasOwnProperty(command.directive)) {
|
||||
return this.connection.reply(402, 'Command not allowed');
|
||||
}
|
||||
|
||||
if (_.includes(this.blacklist, command.directive)) {
|
||||
return this.connection.reply(502, 'Command on blacklist');
|
||||
return this.connection.reply(502, 'Command blacklisted');
|
||||
}
|
||||
|
||||
if (this.whitelist.length > 0 && !_.includes(this.whitelist, command.directive)) {
|
||||
return this.connection.reply(502, 'Command not on whitelist');
|
||||
return this.connection.reply(502, 'Command not whitelisted');
|
||||
}
|
||||
|
||||
const commandRegister = REGISTRY[command.directive];
|
||||
@@ -58,7 +61,8 @@ class FtpCommands {
|
||||
return this.connection.reply(502, 'Handler not set on command');
|
||||
}
|
||||
|
||||
return Promise.try(() => commandRegister.handler.call(this, this.connection, command, this.previousCommand))
|
||||
const handler = commandRegister.handler.bind(this.connection);
|
||||
return Promise.resolve(handler({log, command, previous_command: this.previousCommand}))
|
||||
.finally(() => {
|
||||
this.previousCommand = _.clone(command);
|
||||
});
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
module.exports = {
|
||||
directive: 'ABOR',
|
||||
handler: function (connection) {
|
||||
return connection.connector.waitForConnection()
|
||||
handler: function () {
|
||||
return this.connector.waitForConnection()
|
||||
.then(socket => {
|
||||
return connection.reply(426, {socket})
|
||||
.then(() => connection.connector.end())
|
||||
.then(() => connection.reply(226));
|
||||
return this.reply(426, {socket})
|
||||
.then(() => this.connector.end())
|
||||
.then(() => this.reply(226));
|
||||
})
|
||||
.catch(err => {
|
||||
connection.emit('error', err);
|
||||
return connection.reply(225);
|
||||
});
|
||||
.catch(() => this.reply(225));
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'Abort an active file transfer'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
directive: 'ALLO',
|
||||
handler: function (connection) {
|
||||
return connection.reply(202);
|
||||
handler: function () {
|
||||
return this.reply(202);
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'Allocate sufficient disk space to receive a file',
|
||||
|
||||
@@ -2,8 +2,8 @@ const stor = require('./stor').handler;
|
||||
|
||||
module.exports = {
|
||||
directive: 'APPE',
|
||||
handler: function () {
|
||||
return stor.call(this, ...arguments);
|
||||
handler: function (args) {
|
||||
return stor.call(this, args);
|
||||
},
|
||||
syntax: '{{cmd}} <path>',
|
||||
description: 'Append to a file'
|
||||
|
||||
@@ -3,12 +3,12 @@ const tls = require('tls');
|
||||
|
||||
module.exports = {
|
||||
directive: 'AUTH',
|
||||
handler: function (connection, command) {
|
||||
handler: function ({command} = {}) {
|
||||
const method = _.upperCase(command.arg);
|
||||
|
||||
switch (method) {
|
||||
case 'TLS': return handleTLS.call(this, connection);
|
||||
default: return connection.reply(504);
|
||||
case 'TLS': return handleTLS.call(this);
|
||||
default: return this.reply(504);
|
||||
}
|
||||
},
|
||||
syntax: '{{cmd}} <type>',
|
||||
@@ -19,24 +19,24 @@ module.exports = {
|
||||
}
|
||||
};
|
||||
|
||||
function handleTLS(connection) {
|
||||
if (!connection.server._tls) return connection.reply(502);
|
||||
if (connection.secure) return connection.reply(202);
|
||||
function handleTLS() {
|
||||
if (!this.server._tls) return this.reply(502);
|
||||
if (this.secure) return this.reply(202);
|
||||
|
||||
return connection.reply(234)
|
||||
return this.reply(234)
|
||||
.then(() => {
|
||||
const secureContext = tls.createSecureContext(connection.server._tls);
|
||||
const secureSocket = new tls.TLSSocket(connection.commandSocket, {
|
||||
const secureContext = tls.createSecureContext(this.server._tls);
|
||||
const secureSocket = new tls.TLSSocket(this.commandSocket, {
|
||||
isServer: true,
|
||||
secureContext
|
||||
});
|
||||
['data', 'timeout', 'end', 'close', 'drain', 'error'].forEach(event => {
|
||||
function forwardEvent() {
|
||||
connection.emit.apply(this, arguments);
|
||||
this.emit.apply(this, arguments);
|
||||
}
|
||||
secureSocket.on(event, forwardEvent.bind(connection.commandSocket, event));
|
||||
secureSocket.on(event, forwardEvent.bind(this.commandSocket, event));
|
||||
});
|
||||
connection.commandSocket = secureSocket;
|
||||
connection.secure = true;
|
||||
this.commandSocket = secureSocket;
|
||||
this.secure = true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ const cwd = require('./cwd').handler;
|
||||
|
||||
module.exports = {
|
||||
directive: ['CDUP', 'XCUP'],
|
||||
handler: function (connection, command, ...args) {
|
||||
command.arg = '..';
|
||||
return cwd.call(this, connection, command, ...args);
|
||||
handler: function (args) {
|
||||
args.command.arg = '..';
|
||||
return cwd.call(this, args);
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'Change to Parent Directory'
|
||||
|
||||
@@ -3,18 +3,18 @@ const escapePath = require('../../helpers/escape-path');
|
||||
|
||||
module.exports = {
|
||||
directive: ['CWD', 'XCWD'],
|
||||
handler: function (connection, command) {
|
||||
if (!connection.fs) return connection.reply(550, 'File system not instantiated');
|
||||
if (!connection.fs.chdir) return connection.reply(402, 'Not supported by file system');
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.chdir) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return Promise.resolve(connection.fs.chdir(command.arg))
|
||||
return Promise.resolve(this.fs.chdir(command.arg))
|
||||
.then(cwd => {
|
||||
const path = cwd ? `"${escapePath(cwd)}"` : undefined;
|
||||
return connection.reply(250, path);
|
||||
return this.reply(250, path);
|
||||
})
|
||||
.catch(err => {
|
||||
connection.emit('error', err);
|
||||
return connection.reply(550, err.message);
|
||||
log.error(err);
|
||||
return this.reply(550, err.message);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} <path>',
|
||||
|
||||
@@ -2,17 +2,17 @@ const Promise = require('bluebird');
|
||||
|
||||
module.exports = {
|
||||
directive: 'DELE',
|
||||
handler: function (connection, command) {
|
||||
if (!connection.fs) return connection.reply(550, 'File system not instantiated');
|
||||
if (!connection.fs.delete) return connection.reply(402, 'Not supported by file system');
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.delete) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return Promise.resolve(connection.fs.delete(command.arg))
|
||||
return Promise.resolve(this.fs.delete(command.arg))
|
||||
.then(() => {
|
||||
return connection.reply(250);
|
||||
return this.reply(250);
|
||||
})
|
||||
.catch(err => {
|
||||
connection.emit('error', err);
|
||||
return connection.reply(550, err.message);
|
||||
log.error(err);
|
||||
return this.reply(550, err.message);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} <path>',
|
||||
|
||||
@@ -8,14 +8,14 @@ const FAMILY = {
|
||||
|
||||
module.exports = {
|
||||
directive: 'EPRT',
|
||||
handler: function (connection, command) {
|
||||
handler: function ({command} = {}) {
|
||||
const [, protocol, ip, port] = _.chain(command).get('arg', '').split('|').value();
|
||||
const family = FAMILY[protocol];
|
||||
if (!family) return connection.reply(504, 'Unknown network protocol');
|
||||
if (!family) return this.reply(504, 'Unknown network protocol');
|
||||
|
||||
connection.connector = new ActiveConnector(connection);
|
||||
return connection.connector.setupConnection(ip, port, family)
|
||||
.then(() => connection.reply(200));
|
||||
this.connector = new ActiveConnector(this);
|
||||
return this.connector.setupConnection(ip, port, family)
|
||||
.then(() => this.reply(200));
|
||||
},
|
||||
syntax: '{{cmd}} |<protocol>|<address>|<port>|',
|
||||
description: 'Specifies an address and port to which the server should connect'
|
||||
|
||||
@@ -2,13 +2,13 @@ const PassiveConnector = require('../../connector/passive');
|
||||
|
||||
module.exports = {
|
||||
directive: 'EPSV',
|
||||
handler: function (connection) {
|
||||
connection.connector = new PassiveConnector(connection);
|
||||
return connection.connector.setupServer()
|
||||
handler: function () {
|
||||
this.connector = new PassiveConnector(this);
|
||||
return this.connector.setupServer()
|
||||
.then(server => {
|
||||
const {port} = server.address();
|
||||
|
||||
return connection.reply(229, `EPSV OK (|||${port}|)`);
|
||||
return this.reply(229, `EPSV OK (|||${port}|)`);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} [<protocol>]',
|
||||
|
||||
@@ -2,7 +2,7 @@ const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
directive: 'FEAT',
|
||||
handler: function (connection) {
|
||||
handler: function () {
|
||||
const registry = require('../registry');
|
||||
const features = Object.keys(registry)
|
||||
.reduce((feats, cmd) => {
|
||||
@@ -16,8 +16,8 @@ module.exports = {
|
||||
raw: true
|
||||
}));
|
||||
return features.length
|
||||
? connection.reply(211, 'Extensions supported', ...features, 'End')
|
||||
: connection.reply(211, 'No features');
|
||||
? this.reply(211, 'Extensions supported', ...features, 'End')
|
||||
: this.reply(211, 'No features');
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'Get the feature list implemented by the server',
|
||||
|
||||
@@ -2,18 +2,18 @@ const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
directive: 'HELP',
|
||||
handler: function (connection, command) {
|
||||
handler: function ({command} = {}) {
|
||||
const registry = require('../registry');
|
||||
const directive = _.upperCase(command.arg);
|
||||
if (directive) {
|
||||
if (!registry.hasOwnProperty(directive)) return connection.reply(502, `Unknown command ${directive}.`);
|
||||
if (!registry.hasOwnProperty(directive)) return this.reply(502, `Unknown command ${directive}.`);
|
||||
|
||||
const {syntax, description} = registry[directive];
|
||||
const reply = _.concat([syntax.replace('{{cmd}}', directive), description]);
|
||||
return connection.reply(214, ...reply);
|
||||
return this.reply(214, ...reply);
|
||||
} else {
|
||||
const supportedCommands = _.chunk(Object.keys(registry), 5).map(chunk => chunk.join('\t'));
|
||||
return connection.reply(211, 'Supported commands:', ...supportedCommands, 'Use "HELP [command]" for syntax help.');
|
||||
return this.reply(211, 'Supported commands:', ...supportedCommands, 'Use "HELP [command]" for syntax help.');
|
||||
}
|
||||
},
|
||||
syntax: '{{cmd}} [<command>]',
|
||||
|
||||
@@ -6,22 +6,22 @@ const getFileStat = require('../../helpers/file-stat');
|
||||
// http://cr.yp.to/ftp/list/eplf.html
|
||||
module.exports = {
|
||||
directive: 'LIST',
|
||||
handler: function (connection, command) {
|
||||
if (!connection.fs) return this.reply(550, 'File system not instantiated');
|
||||
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';
|
||||
|
||||
const path = command.arg || '.';
|
||||
return connection.connector.waitForConnection()
|
||||
.tap(() => connection.commandSocket.pause())
|
||||
.then(() => Promise.resolve(connection.fs.get(path)))
|
||||
.then(stat => stat.isDirectory() ? Promise.resolve(connection.fs.list(path)) : [stat])
|
||||
return this.connector.waitForConnection()
|
||||
.tap(() => this.commandSocket.pause())
|
||||
.then(() => Promise.resolve(this.fs.get(path)))
|
||||
.then(stat => stat.isDirectory() ? Promise.resolve(this.fs.list(path)) : [stat])
|
||||
.then(files => {
|
||||
const getFileMessage = file => {
|
||||
if (simple) return file.name;
|
||||
return getFileStat(file, _.get(connection, 'server.options.file_format', 'ls'));
|
||||
return getFileStat(file, _.get(this, 'server.options.file_format', 'ls'));
|
||||
};
|
||||
|
||||
const fileList = files.map(file => {
|
||||
@@ -29,26 +29,26 @@ module.exports = {
|
||||
return {
|
||||
raw: true,
|
||||
message,
|
||||
socket: connection.connector.socket
|
||||
socket: this.connector.socket
|
||||
};
|
||||
});
|
||||
return connection.reply(150)
|
||||
return this.reply(150)
|
||||
.then(() => {
|
||||
if (fileList.length) return connection.reply({}, ...fileList);
|
||||
if (fileList.length) return this.reply({}, ...fileList);
|
||||
});
|
||||
})
|
||||
.then(() => connection.reply(226))
|
||||
.then(() => this.reply(226))
|
||||
.catch(Promise.TimeoutError, err => {
|
||||
connection.emit('error', err);
|
||||
return connection.reply(425, 'No connection established');
|
||||
log.error(err);
|
||||
return this.reply(425, 'No connection established');
|
||||
})
|
||||
.catch(err => {
|
||||
connection.emit('error', err);
|
||||
return connection.reply(451, err.message || 'No directory');
|
||||
log.error(err);
|
||||
return this.reply(451, err.message || 'No directory');
|
||||
})
|
||||
.finally(() => {
|
||||
connection.connector.end();
|
||||
connection.commandSocket.resume();
|
||||
this.connector.end();
|
||||
this.commandSocket.resume();
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} [<path>]',
|
||||
|
||||
@@ -3,18 +3,18 @@ const moment = require('moment');
|
||||
|
||||
module.exports = {
|
||||
directive: 'MDTM',
|
||||
handler: function (connection, command) {
|
||||
if (!connection.fs) return connection.reply(550, 'File system not instantiated');
|
||||
if (!connection.fs.get) return connection.reply(402, 'Not supported by file system');
|
||||
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');
|
||||
|
||||
return Promise.resolve(connection.fs.get(command.arg))
|
||||
return Promise.resolve(this.fs.get(command.arg))
|
||||
.then(fileStat => {
|
||||
const modificationTime = moment.utc(fileStat.mtime).format('YYYYMMDDHHmmss.SSS');
|
||||
return connection.reply(213, modificationTime);
|
||||
return this.reply(213, modificationTime);
|
||||
})
|
||||
.catch(err => {
|
||||
connection.emit('error', err);
|
||||
return connection.reply(550, err.message);
|
||||
log.error(err);
|
||||
return this.reply(550, err.message);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} <path>',
|
||||
|
||||
@@ -3,18 +3,18 @@ const escapePath = require('../../helpers/escape-path');
|
||||
|
||||
module.exports = {
|
||||
directive: ['MKD', 'XMKD'],
|
||||
handler: function (connection, command) {
|
||||
if (!connection.fs) return connection.reply(550, 'File system not instantiated');
|
||||
if (!connection.fs.mkdir) return connection.reply(402, 'Not supported by file system');
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.mkdir) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return Promise.resolve(connection.fs.mkdir(command.arg))
|
||||
return Promise.resolve(this.fs.mkdir(command.arg))
|
||||
.then(dir => {
|
||||
const path = dir ? `"${escapePath(dir)}"` : undefined;
|
||||
return connection.reply(257, path);
|
||||
return this.reply(257, path);
|
||||
})
|
||||
.catch(err => {
|
||||
connection.emit('error', err);
|
||||
return connection.reply(550, err.message);
|
||||
log.error(err);
|
||||
return this.reply(550, err.message);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} <path>',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
directive: 'MODE',
|
||||
handler: function (connection, command) {
|
||||
return connection.reply(/^S$/i.test(command.arg) ? 200 : 504);
|
||||
handler: function ({command} = {}) {
|
||||
return this.reply(/^S$/i.test(command.arg) ? 200 : 504);
|
||||
},
|
||||
syntax: '{{cmd}} <mode>',
|
||||
description: 'Sets the transfer mode (Stream, Block, or Compressed)',
|
||||
|
||||
@@ -2,8 +2,8 @@ const list = require('./list').handler;
|
||||
|
||||
module.exports = {
|
||||
directive: 'NLST',
|
||||
handler: function () {
|
||||
return list.call(this, ...arguments);
|
||||
handler: function (args) {
|
||||
return list.call(this, args);
|
||||
},
|
||||
syntax: '{{cmd}} [<path>]',
|
||||
description: 'Returns a list of file names in a specified directory'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
directive: 'NOOP',
|
||||
handler: function (connection) {
|
||||
return connection.reply(200);
|
||||
handler: function () {
|
||||
return this.reply(200);
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'No operation',
|
||||
|
||||
@@ -7,14 +7,14 @@ const OPTIONS = {
|
||||
|
||||
module.exports = {
|
||||
directive: 'OPTS',
|
||||
handler: function (connection, command) {
|
||||
if (!_.has(command, 'arg')) return connection.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 connection.reply(500);
|
||||
return OPTIONS[option].call(this, ...args);
|
||||
if (!OPTIONS.hasOwnProperty(option)) return this.reply(500);
|
||||
return OPTIONS[option].call(this, args);
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'Select options for a feature'
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
module.exports = {
|
||||
directive: 'PASS',
|
||||
handler: function (connection, command) {
|
||||
if (!connection.username) return this.reply(503);
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.username) return this.reply(503);
|
||||
if (this.authenticated) return this.reply(202);
|
||||
|
||||
// 332 : require account name (ACCT)
|
||||
|
||||
const password = command.arg;
|
||||
if (!password) return this.reply(501, 'Must provide password');
|
||||
return connection.login(connection.username, password)
|
||||
return this.login(this.username, password)
|
||||
.then(() => {
|
||||
return connection.reply(230);
|
||||
return this.reply(230);
|
||||
})
|
||||
.catch(err => {
|
||||
connection.emit('error', err);
|
||||
return connection.reply(530, err.message || 'Authentication failed');
|
||||
log.error(err);
|
||||
return this.reply(530, err.message || 'Authentication failed');
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} <password>',
|
||||
|
||||
@@ -2,17 +2,17 @@ const PassiveConnector = require('../../connector/passive');
|
||||
|
||||
module.exports = {
|
||||
directive: 'PASV',
|
||||
handler: function (connection) {
|
||||
connection.connector = new PassiveConnector(connection);
|
||||
return connection.connector.setupServer()
|
||||
handler: function () {
|
||||
this.connector = new PassiveConnector(this);
|
||||
return this.connector.setupServer()
|
||||
.then(server => {
|
||||
const address = connection.server.url.hostname;
|
||||
const address = this.server.url.hostname;
|
||||
const {port} = server.address();
|
||||
const host = address.replace(/\./g, ',');
|
||||
const portByte1 = port / 256 | 0;
|
||||
const portByte2 = port % 256;
|
||||
|
||||
return connection.reply(227, `PASV OK (${host},${portByte1},${portByte2})`);
|
||||
return this.reply(227, `PASV OK (${host},${portByte1},${portByte2})`);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
module.exports = {
|
||||
directive: 'PBSZ',
|
||||
handler: function (connection, command) {
|
||||
if (!connection.secure) return connection.reply(202, 'Not suppored');
|
||||
connection.bufferSize = parseInt(command.arg, 10);
|
||||
return connection.reply(200, connection.bufferSize === 0 ? 'OK' : 'Buffer too large: PBSZ=0');
|
||||
handler: function ({command} = {}) {
|
||||
if (!this.secure) return this.reply(202, 'Not suppored');
|
||||
this.bufferSize = parseInt(command.arg, 10);
|
||||
return this.reply(200, this.bufferSize === 0 ? 'OK' : 'Buffer too large: PBSZ=0');
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'Protection Buffer Size',
|
||||
|
||||
@@ -3,18 +3,18 @@ const ActiveConnector = require('../../connector/active');
|
||||
|
||||
module.exports = {
|
||||
directive: 'PORT',
|
||||
handler: function (connection, command) {
|
||||
connection.connector = new ActiveConnector(connection);
|
||||
handler: function ({command} = {}) {
|
||||
this.connector = new ActiveConnector(this);
|
||||
|
||||
const rawConnection = _.get(command, 'arg', '').split(',');
|
||||
if (rawConnection.length !== 6) return connection.reply(425);
|
||||
if (rawConnection.length !== 6) return this.reply(425);
|
||||
|
||||
const ip = rawConnection.slice(0, 4).join('.');
|
||||
const portBytes = rawConnection.slice(4).map(p => parseInt(p));
|
||||
const port = portBytes[0] * 256 + portBytes[1];
|
||||
|
||||
return connection.connector.setupConnection(ip, port)
|
||||
.then(() => connection.reply(200));
|
||||
return this.connector.setupConnection(ip, port)
|
||||
.then(() => this.reply(200));
|
||||
},
|
||||
syntax: '{{cmd}} <x>,<x>,<x>,<x>,<y>,<y>',
|
||||
description: 'Specifies an address and port to which the server should connect'
|
||||
|
||||
@@ -2,16 +2,16 @@ const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
directive: 'PROT',
|
||||
handler: function (connection, command) {
|
||||
if (!connection.secure) return connection.reply(202, 'Not suppored');
|
||||
if (!connection.bufferSize && typeof connection.bufferSize !== 'number') return connection.reply(503);
|
||||
handler: function ({command} = {}) {
|
||||
if (!this.secure) return this.reply(202, 'Not suppored');
|
||||
if (!this.bufferSize && typeof this.bufferSize !== 'number') return this.reply(503);
|
||||
|
||||
switch (_.toUpper(command.arg)) {
|
||||
case 'P': return connection.reply(200, 'OK');
|
||||
case 'P': return this.reply(200, 'OK');
|
||||
case 'C':
|
||||
case 'S':
|
||||
case 'E': return connection.reply(536, 'Not supported');
|
||||
default: return connection.reply(504);
|
||||
case 'E': return this.reply(536, 'Not supported');
|
||||
default: return this.reply(504);
|
||||
}
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
|
||||
@@ -3,18 +3,18 @@ const escapePath = require('../../helpers/escape-path');
|
||||
|
||||
module.exports = {
|
||||
directive: ['PWD', 'XPWD'],
|
||||
handler: function (connection) {
|
||||
if (!connection.fs) return connection.reply(550, 'File system not instantiated');
|
||||
if (!connection.fs.currentDirectory) return connection.reply(402, 'Not supported by file system');
|
||||
handler: function ({log} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.currentDirectory) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return Promise.resolve(connection.fs.currentDirectory())
|
||||
return Promise.resolve(this.fs.currentDirectory())
|
||||
.then(cwd => {
|
||||
const path = cwd ? `"${escapePath(cwd)}"` : undefined;
|
||||
return connection.reply(257, path);
|
||||
return this.reply(257, path);
|
||||
})
|
||||
.catch(err => {
|
||||
connection.emit('error', err);
|
||||
return connection.reply(550, err.message);
|
||||
log.error(err);
|
||||
return this.reply(550, err.message);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
directive: 'QUIT',
|
||||
handler: function (connection) {
|
||||
return connection.close(221, 'Client called QUIT');
|
||||
handler: function () {
|
||||
return this.close(221, 'Client called QUIT');
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'Disconnect',
|
||||
|
||||
@@ -2,14 +2,14 @@ const _ = require('lodash');
|
||||
|
||||
module.exports = {
|
||||
directive: 'REST',
|
||||
handler: function (connection, command) {
|
||||
handler: function ({command} = {}) {
|
||||
const arg = _.get(command, 'arg');
|
||||
const byteCount = parseInt(arg, 10);
|
||||
|
||||
if (isNaN(byteCount) || byteCount < 0) return connection.reply(501, 'Byte count must be 0 or greater');
|
||||
if (isNaN(byteCount) || byteCount < 0) return this.reply(501, 'Byte count must be 0 or greater');
|
||||
|
||||
connection.restByteCount = byteCount;
|
||||
return connection.reply(350, `Restarting next transfer at ${byteCount}`);
|
||||
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'
|
||||
|
||||
@@ -2,54 +2,50 @@ const Promise = require('bluebird');
|
||||
|
||||
module.exports = {
|
||||
directive: 'RETR',
|
||||
handler: function (connection, command) {
|
||||
if (!connection.fs) return connection.reply(550, 'File system not instantiated');
|
||||
if (!connection.fs.read) return connection.reply(402, 'Not supported by file system');
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.read) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
const filePath = command.arg;
|
||||
|
||||
return connection.connector.waitForConnection()
|
||||
.tap(() => connection.commandSocket.pause())
|
||||
.then(() => Promise.resolve(connection.fs.read(filePath, {start: connection.restByteCount})))
|
||||
return this.connector.waitForConnection()
|
||||
.tap(() => this.commandSocket.pause())
|
||||
.then(() => Promise.resolve(this.fs.read(command.arg, {start: this.restByteCount})))
|
||||
.then(stream => {
|
||||
const destroyConnection = (conn, reject) => err => {
|
||||
if (conn) conn.destroy(err);
|
||||
const destroyConnection = (connection, reject) => err => {
|
||||
if (connection) connection.destroy(err);
|
||||
reject(err);
|
||||
};
|
||||
|
||||
const eventsPromise = new Promise((resolve, reject) => {
|
||||
stream.on('data', data => {
|
||||
if (stream) stream.pause();
|
||||
if (connection.connector.socket) {
|
||||
connection.connector.socket.write(data, connection.transferType, () => stream && stream.resume());
|
||||
if (this.connector.socket) {
|
||||
this.connector.socket.write(data, this.transferType, () => stream && stream.resume());
|
||||
}
|
||||
});
|
||||
stream.once('end', () => resolve());
|
||||
stream.once('error', destroyConnection(connection.connector.socket, reject));
|
||||
stream.once('error', destroyConnection(this.connector.socket, reject));
|
||||
|
||||
connection.connector.socket.once('error', destroyConnection(stream, reject));
|
||||
this.connector.socket.once('error', destroyConnection(stream, reject));
|
||||
});
|
||||
|
||||
connection.restByteCount = 0;
|
||||
this.restByteCount = 0;
|
||||
|
||||
return connection.reply(150).then(() => stream.resume() && connection.connector.socket.resume())
|
||||
return this.reply(150).then(() => stream.resume() && this.connector.socket.resume())
|
||||
.then(() => eventsPromise)
|
||||
.tap(() => connection.emit('RETR', null, filePath))
|
||||
.finally(() => stream.destroy && stream.destroy());
|
||||
})
|
||||
.then(() => connection.reply(226))
|
||||
.then(() => this.reply(226))
|
||||
.catch(Promise.TimeoutError, err => {
|
||||
connection.emit('error', err);
|
||||
return connection.reply(425, 'No connection established');
|
||||
log.error(err);
|
||||
return this.reply(425, 'No connection established');
|
||||
})
|
||||
.catch(err => {
|
||||
connection.emit('error', err);
|
||||
connection.emit('RETR', err);
|
||||
return connection.reply(551, err.message);
|
||||
log.error(err);
|
||||
return this.reply(551, err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
connection.connector.end();
|
||||
connection.commandSocket.resume();
|
||||
this.connector.end();
|
||||
this.commandSocket.resume();
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} <path>',
|
||||
|
||||
@@ -2,8 +2,8 @@ const {handler: dele} = require('./dele');
|
||||
|
||||
module.exports = {
|
||||
directive: ['RMD', 'XRMD'],
|
||||
handler: function (...args) {
|
||||
return dele.call(this, ...args);
|
||||
handler: function (args) {
|
||||
return dele.call(this, args);
|
||||
},
|
||||
syntax: '{{cmd}} <path>',
|
||||
description: 'Remove a directory'
|
||||
|
||||
@@ -2,19 +2,19 @@ const Promise = require('bluebird');
|
||||
|
||||
module.exports = {
|
||||
directive: 'RNFR',
|
||||
handler: function (connection, command) {
|
||||
if (!connection.fs) return connection.reply(550, 'File system not instantiated');
|
||||
if (!connection.fs.get) return connection.reply(402, 'Not supported by file system');
|
||||
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');
|
||||
|
||||
const fileName = command.arg;
|
||||
return Promise.resolve(connection.fs.get(fileName))
|
||||
return Promise.resolve(this.fs.get(fileName))
|
||||
.then(() => {
|
||||
connection.renameFrom = fileName;
|
||||
return connection.reply(350);
|
||||
this.renameFrom = fileName;
|
||||
return this.reply(350);
|
||||
})
|
||||
.catch(err => {
|
||||
connection.emit('error', err);
|
||||
return connection.reply(550, err.message);
|
||||
log.error(err);
|
||||
return this.reply(550, err.message);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} <name>',
|
||||
|
||||
@@ -2,25 +2,25 @@ const Promise = require('bluebird');
|
||||
|
||||
module.exports = {
|
||||
directive: 'RNTO',
|
||||
handler: function (connection, command) {
|
||||
if (!connection.renameFrom) return connection.reply(503);
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.renameFrom) return this.reply(503);
|
||||
|
||||
if (!connection.fs) return connection.reply(550, 'File system not instantiated');
|
||||
if (!connection.fs.rename) return connection.reply(402, 'Not supported by file system');
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.rename) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
const from = connection.renameFrom;
|
||||
const from = this.renameFrom;
|
||||
const to = command.arg;
|
||||
|
||||
return Promise.resolve(connection.fs.rename(from, to))
|
||||
return Promise.resolve(this.fs.rename(from, to))
|
||||
.then(() => {
|
||||
return connection.reply(250);
|
||||
return this.reply(250);
|
||||
})
|
||||
.catch(err => {
|
||||
connection.emit('error', err);
|
||||
return connection.reply(550, err.message);
|
||||
log.error(err);
|
||||
return this.reply(550, err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
delete connection.renameFrom;
|
||||
delete this.renameFrom;
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} <name>',
|
||||
|
||||
@@ -8,7 +8,7 @@ module.exports = {
|
||||
handler: function ({log, command} = {}) {
|
||||
const rawSubCommand = _.get(command, 'arg', '');
|
||||
const subCommand = this.commands.parse(rawSubCommand);
|
||||
const subLog = log.scope(subCommand.directive);
|
||||
const subLog = log.child({subverb: subCommand.directive});
|
||||
|
||||
if (!registry.hasOwnProperty(subCommand.directive)) return this.reply(502);
|
||||
|
||||
|
||||
@@ -2,17 +2,17 @@ const Promise = require('bluebird');
|
||||
|
||||
module.exports = {
|
||||
directive: 'SIZE',
|
||||
handler: function (connection, command) {
|
||||
if (!connection.fs) return connection.reply(550, 'File system not instantiated');
|
||||
if (!connection.fs.get) return connection.reply(402, 'Not supported by file system');
|
||||
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');
|
||||
|
||||
return Promise.resolve(connection.fs.get(command.arg))
|
||||
return Promise.resolve(this.fs.get(command.arg))
|
||||
.then(fileStat => {
|
||||
return connection.reply(213, {message: fileStat.size});
|
||||
return this.reply(213, {message: fileStat.size});
|
||||
})
|
||||
.catch(err => {
|
||||
connection.emit('error', err);
|
||||
return connection.reply(550, err.message);
|
||||
log.error(err);
|
||||
return this.reply(550, err.message);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} <path>',
|
||||
|
||||
@@ -4,25 +4,26 @@ const getFileStat = require('../../helpers/file-stat');
|
||||
|
||||
module.exports = {
|
||||
directive: 'STAT',
|
||||
handler: function (connection, command) {
|
||||
handler: function (args = {}) {
|
||||
const {log, command} = args;
|
||||
const path = _.get(command, 'arg');
|
||||
if (path) {
|
||||
if (!connection.fs) return connection.reply(550, 'File system not instantiated');
|
||||
if (!connection.fs.get) return connection.reply(402, 'Not supported by file system');
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.get) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return Promise.resolve(connection.fs.get(path))
|
||||
return Promise.resolve(this.fs.get(path))
|
||||
.then(stat => {
|
||||
if (stat.isDirectory()) {
|
||||
if (!connection.fs.list) return connection.reply(402, 'Not supported by file system');
|
||||
if (!this.fs.list) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
return Promise.resolve(connection.fs.list(path))
|
||||
return Promise.resolve(this.fs.list(path))
|
||||
.then(stats => [213, stats]);
|
||||
}
|
||||
return [212, [stat]];
|
||||
})
|
||||
.then(([code, fileStats]) => {
|
||||
return Promise.map(fileStats, file => {
|
||||
const message = getFileStat(file, _.get(connection, 'server.options.file_format', 'ls'));
|
||||
const message = getFileStat(file, _.get(this, 'server.options.file_format', 'ls'));
|
||||
return {
|
||||
raw: true,
|
||||
message
|
||||
@@ -30,13 +31,13 @@ module.exports = {
|
||||
})
|
||||
.then(messages => [code, messages]);
|
||||
})
|
||||
.then(([code, messages]) => connection.reply(code, 'Status begin', ...messages, 'Status end'))
|
||||
.then(([code, messages]) => this.reply(code, 'Status begin', ...messages, 'Status end'))
|
||||
.catch(err => {
|
||||
connection.emit('error', err);
|
||||
return connection.reply(450, err.message);
|
||||
log.error(err);
|
||||
return this.reply(450, err.message);
|
||||
});
|
||||
} else {
|
||||
return connection.reply(211, 'Status OK');
|
||||
return this.reply(211, 'Status OK');
|
||||
}
|
||||
},
|
||||
syntax: '{{cmd}} [<path>]',
|
||||
|
||||
@@ -2,62 +2,60 @@ const Promise = require('bluebird');
|
||||
|
||||
module.exports = {
|
||||
directive: 'STOR',
|
||||
handler: function (connection, command) {
|
||||
if (!connection.fs) return connection.reply(550, 'File system not instantiated');
|
||||
if (!connection.fs.write) return connection.reply(402, 'Not supported by file system');
|
||||
handler: function ({log, command} = {}) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.write) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
const append = command.directive === 'APPE';
|
||||
const fileName = command.arg;
|
||||
|
||||
return connection.connector.waitForConnection()
|
||||
.tap(() => connection.commandSocket.pause())
|
||||
.then(() => Promise.resolve(connection.fs.write(fileName, {append, start: connection.restByteCount})))
|
||||
return this.connector.waitForConnection()
|
||||
.tap(() => this.commandSocket.pause())
|
||||
.then(() => Promise.resolve(this.fs.write(fileName, {append, start: this.restByteCount})))
|
||||
.then(stream => {
|
||||
const destroyConnection = (conn, reject) => err => {
|
||||
if (conn) conn.destroy(err);
|
||||
const destroyConnection = (connection, reject) => err => {
|
||||
if (connection) connection.destroy(err);
|
||||
reject(err);
|
||||
};
|
||||
|
||||
const streamPromise = new Promise((resolve, reject) => {
|
||||
stream.once('error', destroyConnection(connection.connector.socket, reject));
|
||||
stream.once('error', destroyConnection(this.connector.socket, reject));
|
||||
stream.once('finish', () => resolve());
|
||||
});
|
||||
|
||||
const socketPromise = new Promise((resolve, reject) => {
|
||||
connection.connector.socket.on('data', data => {
|
||||
if (connection.connector.socket) connection.connector.socket.pause();
|
||||
this.connector.socket.on('data', data => {
|
||||
if (this.connector.socket) this.connector.socket.pause();
|
||||
if (stream) {
|
||||
stream.write(data, connection.transferType, () => connection.connector.socket && connection.connector.socket.resume());
|
||||
stream.write(data, this.transferType, () => this.connector.socket && this.connector.socket.resume());
|
||||
}
|
||||
});
|
||||
connection.connector.socket.once('end', () => {
|
||||
this.connector.socket.once('end', () => {
|
||||
if (stream.listenerCount('close')) stream.emit('close');
|
||||
else stream.end();
|
||||
resolve();
|
||||
});
|
||||
connection.connector.socket.once('error', destroyConnection(stream, reject));
|
||||
this.connector.socket.once('error', destroyConnection(stream, reject));
|
||||
});
|
||||
|
||||
connection.restByteCount = 0;
|
||||
this.restByteCount = 0;
|
||||
|
||||
return connection.reply(150).then(() => connection.connector.socket.resume())
|
||||
return this.reply(150).then(() => this.connector.socket.resume())
|
||||
.then(() => Promise.join(streamPromise, socketPromise))
|
||||
.tap(() => connection.emit('STOR', null, fileName))
|
||||
.finally(() => stream.destroy && stream.destroy());
|
||||
})
|
||||
.then(() => connection.reply(226, fileName))
|
||||
.then(() => this.reply(226, fileName))
|
||||
.catch(Promise.TimeoutError, err => {
|
||||
connection.emit('error', err);
|
||||
return connection.reply(425, 'No connection established');
|
||||
log.error(err);
|
||||
return this.reply(425, 'No connection established');
|
||||
})
|
||||
.catch(err => {
|
||||
connection.emit('error', err);
|
||||
connection.emit('STOR', err);
|
||||
return connection.reply(550, err.message);
|
||||
log.error(err);
|
||||
return this.reply(550, err.message);
|
||||
})
|
||||
.finally(() => {
|
||||
connection.connector.end();
|
||||
connection.commandSocket.resume();
|
||||
this.connector.end();
|
||||
this.commandSocket.resume();
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}} <path>',
|
||||
|
||||
@@ -3,19 +3,19 @@ const {handler: stor} = require('./stor');
|
||||
|
||||
module.exports = {
|
||||
directive: 'STOU',
|
||||
handler: function (connection, command, ...args) {
|
||||
handler: function (args) {
|
||||
if (!this.fs) return this.reply(550, 'File system not instantiated');
|
||||
if (!this.fs.get || !this.fs.getUniqueName) return this.reply(402, 'Not supported by file system');
|
||||
|
||||
const fileName = command.arg;
|
||||
const fileName = args.command.arg;
|
||||
return Promise.try(() => {
|
||||
return Promise.resolve(this.fs.get(fileName))
|
||||
.then(() => Promise.resolve(this.fs.getUniqueName()))
|
||||
.catch(() => Promise.resolve(fileName));
|
||||
})
|
||||
.then(name => {
|
||||
command.arg = name;
|
||||
return stor.call(this, connection, command, ...args);
|
||||
args.command.arg = name;
|
||||
return stor.call(this, args);
|
||||
});
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
directive: 'STRU',
|
||||
handler: function (connection, command) {
|
||||
return connection.reply(/^F$/i.test(command.arg) ? 200 : 504);
|
||||
handler: function ({command} = {}) {
|
||||
return this.reply(/^F$/i.test(command.arg) ? 200 : 504);
|
||||
},
|
||||
syntax: '{{cmd}} <structure>',
|
||||
description: 'Set file transfer structure',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
directive: 'SYST',
|
||||
handler: function (connection) {
|
||||
return connection.reply(215);
|
||||
handler: function () {
|
||||
return this.reply(215);
|
||||
},
|
||||
syntax: '{{cmd}}',
|
||||
description: 'Return system type',
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
module.exports = {
|
||||
directive: 'TYPE',
|
||||
handler: function (connection, command) {
|
||||
handler: function ({command} = {}) {
|
||||
if (/^A[0-9]?$/i.test(command.arg)) {
|
||||
connection.transferType = 'ascii';
|
||||
this.transferType = 'ascii';
|
||||
} else if (/^L[0-9]?$/i.test(command.arg) || /^I$/i.test(command.arg)) {
|
||||
connection.transferType = 'binary';
|
||||
this.transferType = 'binary';
|
||||
} else {
|
||||
return connection.reply(501);
|
||||
return this.reply(501);
|
||||
}
|
||||
return connection.reply(200, `Switch to "${connection.transferType}" transfer mode.`);
|
||||
return this.reply(200, `Switch to "${this.transferType}" transfer mode.`);
|
||||
},
|
||||
syntax: '{{cmd}} <mode>',
|
||||
description: 'Set the transfer mode, binary (I) or ascii (A)',
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
module.exports = {
|
||||
directive: 'USER',
|
||||
handler: function (connection, command) {
|
||||
if (connection.username) return connection.reply(530, 'Username already set');
|
||||
if (connection.authenticated) return connection.reply(230);
|
||||
handler: function ({log, command} = {}) {
|
||||
if (this.username) return this.reply(530, 'Username already set');
|
||||
if (this.authenticated) return this.reply(230);
|
||||
|
||||
connection.username = command.arg;
|
||||
if (!connection.username) return connection.reply(501, 'Must provide username');
|
||||
this.username = command.arg;
|
||||
if (!this.username) return this.reply(501, 'Must provide username');
|
||||
|
||||
if (connection.server.options.anonymous === true && connection.username === 'anonymous' ||
|
||||
connection.username === connection.server.options.anonymous) {
|
||||
return connection.login(connection.username, '@anonymous')
|
||||
if (this.server.options.anonymous === true && this.username === 'anonymous' ||
|
||||
this.username === this.server.options.anonymous) {
|
||||
return this.login(this.username, '@anonymous')
|
||||
.then(() => {
|
||||
return connection.reply(230);
|
||||
return this.reply(230);
|
||||
})
|
||||
.catch(err => {
|
||||
connection.emit('error', err);
|
||||
return connection.reply(530, err.message || 'Authentication failed');
|
||||
log.error(err);
|
||||
return this.reply(530, err.message || 'Authentication failed');
|
||||
});
|
||||
}
|
||||
return connection.reply(331);
|
||||
return this.reply(331);
|
||||
},
|
||||
syntax: '{{cmd}} <username>',
|
||||
description: 'Authentication username',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const _ = require('lodash');
|
||||
const uuid = require('uuid');
|
||||
const Promise = require('bluebird');
|
||||
const EventEmitter = require('events');
|
||||
|
||||
const BaseConnector = require('./connector/base');
|
||||
const FileSystem = require('./fs');
|
||||
@@ -9,14 +8,11 @@ const Commands = require('./commands');
|
||||
const errors = require('./errors');
|
||||
const DEFAULT_MESSAGE = require('./messages');
|
||||
|
||||
class FtpConnection extends EventEmitter {
|
||||
constructor(server, socket) {
|
||||
super();
|
||||
class FtpConnection {
|
||||
constructor(server, options) {
|
||||
this.server = server;
|
||||
this.id = uuid.v4();
|
||||
this.commandSocket = socket;
|
||||
this.log = server.log.scope(`client: ${this.ip}`);
|
||||
// this.log = options.log.child({id: this.id, ip: this.ip});
|
||||
this.log = options.log.child({id: this.id, ip: this.ip});
|
||||
this.commands = new Commands(this);
|
||||
this.transferType = 'binary';
|
||||
this.encoding = 'utf8';
|
||||
@@ -26,26 +22,28 @@ class FtpConnection extends EventEmitter {
|
||||
|
||||
this.connector = new BaseConnector(this);
|
||||
|
||||
this.commandSocket = options.socket;
|
||||
this.commandSocket.on('error', err => {
|
||||
this.log.scope('error event').error(err);
|
||||
this.log.error(err, 'Client error');
|
||||
this.server.emit('client-error', {connection: this, context: 'commandSocket', error: err});
|
||||
});
|
||||
this.commandSocket.on('data', this._handleData.bind(this));
|
||||
this.commandSocket.on('timeout', () => {});
|
||||
this.commandSocket.on('close', () => {
|
||||
if (this.connector) this.connector.end();
|
||||
if (this.commandSocket && !this.commandSocket.destroyed) this.commandSocket.destroy();
|
||||
this.removeAllListeners();
|
||||
});
|
||||
}
|
||||
|
||||
_handleData(data) {
|
||||
const messages = _.compact(data.toString(this.encoding).split('\r\n'));
|
||||
this.log.trace(messages);
|
||||
return Promise.mapSeries(messages, message => this.commands.handle(message));
|
||||
}
|
||||
|
||||
get ip() {
|
||||
try {
|
||||
return this.commandSocket ? this.commandSocket.remoteAddress : undefined;
|
||||
return this.dataSocket ? this.dataSocket.remoteAddress : this.commandSocket.remoteAddress;
|
||||
} catch (ex) {
|
||||
return null;
|
||||
}
|
||||
@@ -116,13 +114,12 @@ class FtpConnection extends EventEmitter {
|
||||
};
|
||||
|
||||
const processLetter = letter => {
|
||||
const log = this.log.scope('reply');
|
||||
return new Promise((resolve, reject) => {
|
||||
if (letter.socket && letter.socket.writable) {
|
||||
log.debug(letter.message, {port: letter.socket.address().port});
|
||||
this.log.trace({port: letter.socket.address().port, encoding: letter.encoding, message: letter.message}, 'Reply');
|
||||
letter.socket.write(letter.message + '\r\n', letter.encoding, err => {
|
||||
if (err) {
|
||||
log.error(err);
|
||||
this.log.error(err);
|
||||
return reject(err);
|
||||
}
|
||||
resolve();
|
||||
|
||||
@@ -7,7 +7,6 @@ class Active extends Connector {
|
||||
constructor(connection) {
|
||||
super(connection);
|
||||
this.type = 'active';
|
||||
this.log = connection.log.scope('active');
|
||||
}
|
||||
|
||||
waitForConnection({timeout = 5000, delay = 250} = {}) {
|
||||
@@ -30,16 +29,10 @@ class Active extends Connector {
|
||||
.then(() => {
|
||||
this.dataSocket = new Socket();
|
||||
this.dataSocket.setEncoding(this.connection.transferType);
|
||||
this.dataSocket.on('error', err => this.connection.emit('error', err));
|
||||
this.dataSocket.on('close', () => {
|
||||
this.log.debug('socket closed');
|
||||
this.end();
|
||||
});
|
||||
this.dataSocket.on('error', err => this.server && this.server.emit('client-error', {connection: this.connection, context: 'dataSocket', error: err}));
|
||||
this.dataSocket.connect({host, port, family}, () => {
|
||||
this.dataSocket.pause();
|
||||
|
||||
this.log.debug('connection', {port, remoteAddress: this.dataSocket.remoteAddress});
|
||||
|
||||
if (this.connection.secure) {
|
||||
const secureContext = tls.createSecureContext(this.server._tls);
|
||||
const secureSocket = new tls.TLSSocket(this.dataSocket, {
|
||||
|
||||
@@ -8,7 +8,10 @@ class Connector {
|
||||
this.dataSocket = null;
|
||||
this.dataServer = null;
|
||||
this.type = false;
|
||||
this.log = connection.log.scope('connector');
|
||||
}
|
||||
|
||||
get log() {
|
||||
return this.connection.log;
|
||||
}
|
||||
|
||||
get socket() {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const net = require('net');
|
||||
const tls = require('tls');
|
||||
const ip = require('ip');
|
||||
const Promise = require('bluebird');
|
||||
|
||||
const Connector = require('./base');
|
||||
@@ -11,7 +10,6 @@ class Passive extends Connector {
|
||||
constructor(connection) {
|
||||
super(connection);
|
||||
this.type = 'passive';
|
||||
this.log = connection.log.scope('passive');
|
||||
}
|
||||
|
||||
waitForConnection({timeout = 5000, delay = 250} = {}) {
|
||||
@@ -37,17 +35,17 @@ class Passive extends Connector {
|
||||
.then(() => this.getPort())
|
||||
.then(port => {
|
||||
const connectionHandler = socket => {
|
||||
if (!ip.isEqual(this.connection.commandSocket.remoteAddress, socket.remoteAddress)) {
|
||||
this.log.error('ip address mismatch', {
|
||||
if (this.connection.commandSocket.remoteAddress !== socket.remoteAddress) {
|
||||
this.log.error({
|
||||
pasv_connection: socket.remoteAddress,
|
||||
cmd_connection: this.connection.commandSocket.remoteAddress
|
||||
});
|
||||
}, 'Connecting addresses do not match');
|
||||
|
||||
socket.destroy();
|
||||
return this.connection.reply(550, 'IP address mismatch')
|
||||
return this.connection.reply(550, 'Remote addresses do not match')
|
||||
.finally(() => this.connection.close());
|
||||
}
|
||||
this.log.debug('connection', {port, remoteAddress: socket.remoteAddress});
|
||||
this.log.trace({port, remoteAddress: socket.remoteAddress}, 'Passive connection fulfilled.');
|
||||
|
||||
if (this.connection.secure) {
|
||||
const secureContext = tls.createSecureContext(this.server._tls);
|
||||
@@ -61,9 +59,9 @@ class Passive extends Connector {
|
||||
}
|
||||
this.dataSocket.connected = true;
|
||||
this.dataSocket.setEncoding(this.connection.transferType);
|
||||
this.dataSocket.on('error', err => this.connection.emit('error', err));
|
||||
this.dataSocket.on('error', err => this.server && this.server.emit('client-error', {connection: this.connection, context: 'dataSocket', error: err}));
|
||||
this.dataSocket.on('close', () => {
|
||||
this.log.debug('socket closed');
|
||||
this.log.trace('Passive connection closed');
|
||||
this.end();
|
||||
});
|
||||
};
|
||||
@@ -71,9 +69,9 @@ class Passive extends Connector {
|
||||
this.dataSocket = null;
|
||||
this.dataServer = net.createServer({pauseOnConnect: true}, connectionHandler);
|
||||
this.dataServer.maxConnections = 1;
|
||||
this.dataServer.on('error', err => this.connection.emit('error', err));
|
||||
this.dataServer.on('error', err => this.server && this.server.emit('client-error', {connection: this.connection, context: 'dataServer', error: err}));
|
||||
this.dataServer.on('close', () => {
|
||||
this.log.debug('server closed');
|
||||
this.log.trace('Passive server closed');
|
||||
this.dataServer = null;
|
||||
});
|
||||
|
||||
@@ -81,7 +79,7 @@ class Passive extends Connector {
|
||||
this.dataServer.listen(port, err => {
|
||||
if (err) reject(err);
|
||||
else {
|
||||
this.log.debug('listening', {port});
|
||||
this.log.debug({port}, 'Passive connection listening');
|
||||
resolve(this.dataServer);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -18,8 +18,7 @@ module.exports = function (fileStat, format = 'ls') {
|
||||
function ls(fileStat) {
|
||||
const now = moment.utc();
|
||||
const mtime = moment.utc(new Date(fileStat.mtime));
|
||||
const timeDiff = now.diff(mtime, 'months');
|
||||
const dateFormat = timeDiff < 6 ? 'MMM DD HH:mm' : 'MMM DD YYYY';
|
||||
const dateFormat = now.diff(mtime, 'months') < 6 ? 'MMM DD HH:mm' : 'MMM DD YYYY';
|
||||
|
||||
return [
|
||||
fileStat.mode ? [
|
||||
|
||||
45
src/index.js
45
src/index.js
@@ -1,22 +1,18 @@
|
||||
const _ = require('lodash');
|
||||
const Promise = require('bluebird');
|
||||
const nodeUrl = require('url');
|
||||
const {Signale} = require('signale');
|
||||
const buyan = require('bunyan');
|
||||
const net = require('net');
|
||||
const tls = require('tls');
|
||||
const fs = require('fs');
|
||||
const EventEmitter = require('events');
|
||||
|
||||
const Connection = require('./connection');
|
||||
const resolveHost = require('./helpers/resolve-host');
|
||||
|
||||
class FtpServer extends EventEmitter {
|
||||
class FtpServer {
|
||||
constructor(url, options = {}) {
|
||||
super();
|
||||
this.options = _.merge({
|
||||
log: new Signale({
|
||||
scope: 'ftp-srv'
|
||||
}),
|
||||
log: buyan.createLogger({name: 'ftp-srv'}),
|
||||
anonymous: false,
|
||||
pasv_range: 22,
|
||||
file_format: 'ls',
|
||||
@@ -37,7 +33,7 @@ class FtpServer extends EventEmitter {
|
||||
this.url = nodeUrl.parse(url || 'ftp://127.0.0.1:21');
|
||||
|
||||
const serverConnectionHandler = socket => {
|
||||
let connection = new Connection(this, socket);
|
||||
let connection = new Connection(this, {log: this.log, socket});
|
||||
this.connections[connection.id] = connection;
|
||||
|
||||
socket.on('close', () => this.disconnectClient(connection.id));
|
||||
@@ -50,13 +46,14 @@ class FtpServer extends EventEmitter {
|
||||
const serverOptions = _.assign(this.isTLS ? this._tls : {}, {pauseOnConnect: true});
|
||||
|
||||
this.server = (this.isTLS ? tls : net).createServer(serverOptions, serverConnectionHandler);
|
||||
this.server.on('error', err => this.log.scope('error event').error(err));
|
||||
this.server.on('error', err => this.log.error(err, '[Event] error'));
|
||||
this.on = this.server.on.bind(this.server);
|
||||
this.once = this.server.once.bind(this.server);
|
||||
this.listeners = this.server.listeners.bind(this.server);
|
||||
|
||||
const quit = _.debounce(this.quit.bind(this), 100);
|
||||
|
||||
process.on('SIGTERM', quit);
|
||||
process.on('SIGINT', quit);
|
||||
process.on('SIGQUIT', quit);
|
||||
process.on('SIGTERM', () => this.quit());
|
||||
process.on('SIGINT', () => this.quit());
|
||||
process.on('SIGQUIT', () => this.quit());
|
||||
}
|
||||
|
||||
get isTLS() {
|
||||
@@ -68,9 +65,7 @@ class FtpServer extends EventEmitter {
|
||||
.then(hostname => {
|
||||
this.url.hostname = hostname;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.server.once('error', reject);
|
||||
this.server.listen(this.url.port, this.url.hostname, err => {
|
||||
this.server.removeListener('error', reject);
|
||||
this.server.listen(this.url.port, err => {
|
||||
if (err) return reject(err);
|
||||
this.log.info({
|
||||
protocol: this.url.protocol.replace(/\W/g, ''),
|
||||
@@ -86,10 +81,14 @@ class FtpServer extends EventEmitter {
|
||||
emitPromise(action, ...data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const params = _.concat(data, [resolve, reject]);
|
||||
this.emit.call(this, action, ...params);
|
||||
this.server.emit(action, ...params);
|
||||
});
|
||||
}
|
||||
|
||||
emit(action, ...data) {
|
||||
this.server.emit(action, ...data);
|
||||
}
|
||||
|
||||
setupTLS(_tls) {
|
||||
if (!_tls) return false;
|
||||
return _.assign({}, _tls, {
|
||||
@@ -124,8 +123,7 @@ class FtpServer extends EventEmitter {
|
||||
try {
|
||||
client.close(0);
|
||||
} catch (err) {
|
||||
this.log.error('Error disconnecting client', err);
|
||||
this.log.debug('User ID', {id});
|
||||
this.log.error(err, 'Error closing connection', {id});
|
||||
} finally {
|
||||
resolve('Disconnected');
|
||||
}
|
||||
@@ -138,16 +136,15 @@ class FtpServer extends EventEmitter {
|
||||
}
|
||||
|
||||
close() {
|
||||
this.log.await('Closing server...');
|
||||
this.log.info('Server closing...');
|
||||
this.server.maxConnections = 0;
|
||||
return Promise.map(Object.keys(this.connections), id => Promise.try(this.disconnectClient.bind(this, id)))
|
||||
.then(() => new Promise(resolve => {
|
||||
this.server.close(err => {
|
||||
if (err) this.log.error('Error closing server', err);
|
||||
if (err) this.log.error(err, 'Error closing server');
|
||||
resolve('Closed');
|
||||
});
|
||||
}))
|
||||
.then(() => this.removeAllListeners());
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const {expect} = require('chai');
|
||||
const Promise = require('bluebird');
|
||||
const {Signale} = require('signale');
|
||||
const bunyan = require('bunyan');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const FtpCommands = require('../../src/commands');
|
||||
@@ -10,7 +10,7 @@ describe('FtpCommands', function () {
|
||||
let commands;
|
||||
let mockConnection = {
|
||||
authenticated: false,
|
||||
log: new Signale('commands'),
|
||||
log: bunyan.createLogger({name: 'FtpCommands'}),
|
||||
reply: () => Promise.resolve({}),
|
||||
server: {
|
||||
options: {
|
||||
@@ -92,7 +92,7 @@ describe('FtpCommands', function () {
|
||||
.then(() => {
|
||||
expect(mockConnection.reply.callCount).to.equal(1);
|
||||
expect(mockConnection.reply.args[0][0]).to.equal(502);
|
||||
expect(mockConnection.reply.args[0][1]).to.match(/blacklist/);
|
||||
expect(mockConnection.reply.args[0][1]).to.match(/blacklisted/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -102,7 +102,7 @@ describe('FtpCommands', function () {
|
||||
.then(() => {
|
||||
expect(mockConnection.reply.callCount).to.equal(1);
|
||||
expect(mockConnection.reply.args[0][0]).to.equal(502);
|
||||
expect(mockConnection.reply.args[0][1]).to.match(/whitelist/);
|
||||
expect(mockConnection.reply.args[0][1]).to.match(/whitelisted/);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -10,10 +10,9 @@ describe(CMD, function () {
|
||||
connector: {
|
||||
waitForConnection: () => Promise.resolve(),
|
||||
end: () => Promise.resolve()
|
||||
},
|
||||
emit: () => Promise.resolve()
|
||||
}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -30,7 +29,7 @@ describe(CMD, function () {
|
||||
mockClient.connector.waitForConnection.restore();
|
||||
sandbox.stub(mockClient.connector, 'waitForConnection').rejects();
|
||||
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.connector.waitForConnection.callCount).to.equal(1);
|
||||
expect(mockClient.connector.end.callCount).to.equal(0);
|
||||
@@ -39,7 +38,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('// successful | active connection', () => {
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.connector.waitForConnection.callCount).to.equal(1);
|
||||
expect(mockClient.connector.end.callCount).to.equal(1);
|
||||
|
||||
@@ -8,7 +8,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => Promise.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -20,7 +20,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('// successful', () => {
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(202);
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ describe(CMD, function () {
|
||||
_tls: {}
|
||||
}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -23,7 +23,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('TLS // supported', () => {
|
||||
return cmdFn(mockClient, {arg: 'TLS', directive: CMD})
|
||||
return cmdFn({command: {arg: 'TLS', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(234);
|
||||
expect(mockClient.secure).to.equal(true);
|
||||
@@ -31,14 +31,14 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('SSL // not supported', () => {
|
||||
return cmdFn(mockClient, {arg: 'SSL', directive: CMD})
|
||||
return cmdFn({command: {arg: 'SSL', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(504);
|
||||
});
|
||||
});
|
||||
|
||||
it('bad // bad', () => {
|
||||
return cmdFn(mockClient, {arg: 'bad', directive: CMD})
|
||||
return cmdFn({command: {arg: 'bad', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(504);
|
||||
});
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
const Promise = require('bluebird');
|
||||
const bunyan = require('bunyan');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'CDUP';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
let log = bunyan.createLogger({name: CMD});
|
||||
const mockClient = {
|
||||
reply: () => Promise.resolve(),
|
||||
fs: {
|
||||
chdir: () => Promise.resolve()
|
||||
}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -24,7 +26,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('.. // successful', () => {
|
||||
return cmdFn(mockClient, {directive: CMD})
|
||||
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('..');
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
const bunyan = require('bunyan');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'CWD';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
let log = bunyan.createLogger({name: CMD});
|
||||
const mockClient = {
|
||||
reply: () => {},
|
||||
fs: {chdir: () => {}}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -23,10 +25,10 @@ describe(CMD, function () {
|
||||
describe('// check', function () {
|
||||
it('fails on no fs', () => {
|
||||
const badMockClient = {reply: () => {}};
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
|
||||
return badCmdFn(mockClient)
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
@@ -34,10 +36,10 @@ describe(CMD, function () {
|
||||
|
||||
it('fails on no fs chdir command', () => {
|
||||
const badMockClient = {reply: () => {}, fs: {}};
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
|
||||
return badCmdFn(badMockClient)
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(402);
|
||||
});
|
||||
@@ -45,7 +47,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('test // successful', () => {
|
||||
return cmdFn(mockClient, {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');
|
||||
@@ -56,7 +58,7 @@ describe(CMD, function () {
|
||||
mockClient.fs.chdir.restore();
|
||||
sandbox.stub(mockClient.fs, 'chdir').resolves('/test');
|
||||
|
||||
return cmdFn(mockClient, {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');
|
||||
@@ -67,7 +69,7 @@ describe(CMD, function () {
|
||||
mockClient.fs.chdir.restore();
|
||||
sandbox.stub(mockClient.fs, 'chdir').rejects(new Error('Bad'));
|
||||
|
||||
return cmdFn(mockClient, {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');
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
const bunyan = require('bunyan');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'DELE';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
let log = bunyan.createLogger({name: CMD});
|
||||
const mockClient = {
|
||||
reply: () => {},
|
||||
fs: {delete: () => {}}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -23,10 +25,10 @@ describe(CMD, function () {
|
||||
describe('// check', function () {
|
||||
it('fails on no fs', () => {
|
||||
const badMockClient = {reply: () => {}};
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
|
||||
return badCmdFn(badMockClient)
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
@@ -37,7 +39,7 @@ describe(CMD, function () {
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
|
||||
return badCmdFn(badMockClient)
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(402);
|
||||
});
|
||||
@@ -45,7 +47,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('test // successful', () => {
|
||||
return cmdFn(mockClient, {arg: 'test', directive: CMD})
|
||||
return cmdFn({log, command: {arg: 'test', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(250);
|
||||
expect(mockClient.fs.delete.args[0][0]).to.equal('test');
|
||||
@@ -56,7 +58,7 @@ describe(CMD, function () {
|
||||
mockClient.fs.delete.restore();
|
||||
sandbox.stub(mockClient.fs, 'delete').rejects(new Error('Bad'));
|
||||
|
||||
return cmdFn(mockClient, {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');
|
||||
|
||||
@@ -10,7 +10,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => Promise.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -23,21 +23,21 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('// unsuccessful | no argument', () => {
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(504);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | invalid argument', () => {
|
||||
return cmdFn(mockClient, {arg: 'blah'})
|
||||
return cmdFn({command: {arg: 'blah'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(504);
|
||||
});
|
||||
});
|
||||
|
||||
it('// successful IPv4', () => {
|
||||
return cmdFn(mockClient, {arg: '|1|192.168.0.100|35286|'})
|
||||
return cmdFn({command: {arg: '|1|192.168.0.100|35286|'}})
|
||||
.then(() => {
|
||||
const [ip, port, family] = ActiveConnector.prototype.setupConnection.args[0];
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
@@ -48,7 +48,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('// successful IPv6', () => {
|
||||
return cmdFn(mockClient, {arg: '|2|8536:933f:e7f3:3e91:6dc1:e8c6:8482:7b23|35286|'})
|
||||
return cmdFn({command: {arg: '|2|8536:933f:e7f3:3e91:6dc1:e8c6:8482:7b23|35286|'}})
|
||||
.then(() => {
|
||||
const [ip, port, family] = ActiveConnector.prototype.setupConnection.args[0];
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
|
||||
@@ -10,7 +10,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => Promise.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -25,7 +25,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('// successful IPv4', () => {
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
const [code, message] = mockClient.reply.args[0];
|
||||
expect(code).to.equal(229);
|
||||
|
||||
@@ -8,7 +8,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => Promise.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -20,28 +20,28 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('// successful', () => {
|
||||
return cmdFn(mockClient, {directive: CMD})
|
||||
return cmdFn({command: {directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(211);
|
||||
});
|
||||
});
|
||||
|
||||
it('help // successful', () => {
|
||||
return cmdFn(mockClient, {arg: 'help', directive: CMD})
|
||||
return cmdFn({command: {arg: 'help', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(214);
|
||||
});
|
||||
});
|
||||
|
||||
it('allo // successful', () => {
|
||||
return cmdFn(mockClient, {arg: 'allo', directive: CMD})
|
||||
return cmdFn({command: {arg: 'allo', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(214);
|
||||
});
|
||||
});
|
||||
|
||||
it('bad // unsuccessful', () => {
|
||||
return cmdFn(mockClient, {arg: 'bad', directive: CMD})
|
||||
return cmdFn({command: {arg: 'bad', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(502);
|
||||
});
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
const Promise = require('bluebird');
|
||||
const bunyan = require('bunyan');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'LIST';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
let log = bunyan.createLogger({name: CMD});
|
||||
const mockClient = {
|
||||
reply: () => {},
|
||||
fs: {
|
||||
@@ -20,7 +22,7 @@ describe(CMD, function () {
|
||||
pause: () => {}
|
||||
}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -87,10 +89,10 @@ describe(CMD, function () {
|
||||
describe('// check', function () {
|
||||
it('fails on no fs', () => {
|
||||
const badMockClient = {reply: () => {}};
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
|
||||
return badCmdFn(badMockClient)
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
@@ -98,10 +100,10 @@ describe(CMD, function () {
|
||||
|
||||
it('fails on no fs list command', () => {
|
||||
const badMockClient = {reply: () => {}, fs: {}};
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
|
||||
return badCmdFn(badMockClient)
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(402);
|
||||
});
|
||||
@@ -109,7 +111,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('. // successful', () => {
|
||||
return cmdFn(mockClient, {directive: CMD})
|
||||
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);
|
||||
@@ -141,7 +143,7 @@ describe(CMD, function () {
|
||||
isDirectory: () => false
|
||||
});
|
||||
|
||||
return cmdFn(mockClient, {directive: CMD, arg: 'testfile.txt'})
|
||||
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);
|
||||
@@ -156,7 +158,7 @@ describe(CMD, function () {
|
||||
mockClient.fs.list.restore();
|
||||
sandbox.stub(mockClient.fs, 'list').rejects(new Error());
|
||||
|
||||
return cmdFn(mockClient, {directive: CMD})
|
||||
return cmdFn({log, command: {directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(451);
|
||||
});
|
||||
@@ -165,7 +167,7 @@ describe(CMD, function () {
|
||||
it('. // unsuccessful (timeout)', () => {
|
||||
sandbox.stub(mockClient.connector, 'waitForConnection').returns(Promise.reject(new Promise.TimeoutError()));
|
||||
|
||||
return cmdFn(mockClient, {directive: CMD})
|
||||
return cmdFn({log, command: {directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(425);
|
||||
});
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
const bunyan = require('bunyan');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'MDTM';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
let log = bunyan.createLogger({name: CMD});
|
||||
const mockClient = {
|
||||
reply: () => {},
|
||||
fs: {get: () => {}}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -23,10 +25,10 @@ describe(CMD, function () {
|
||||
describe('// check', function () {
|
||||
it('fails on no fs', () => {
|
||||
const badMockClient = {reply: () => {}};
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
|
||||
return badCmdFn(badMockClient)
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
@@ -34,10 +36,10 @@ describe(CMD, function () {
|
||||
|
||||
it('fails on no fs get command', () => {
|
||||
const badMockClient = {reply: () => {}, fs: {}};
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
|
||||
return badCmdFn(badMockClient)
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(402);
|
||||
});
|
||||
@@ -45,7 +47,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('. // successful', () => {
|
||||
return cmdFn(mockClient, {directive: CMD})
|
||||
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');
|
||||
@@ -56,7 +58,7 @@ describe(CMD, function () {
|
||||
mockClient.fs.get.restore();
|
||||
sandbox.stub(mockClient.fs, 'get').rejects(new Error());
|
||||
|
||||
return cmdFn(mockClient, {directive: CMD})
|
||||
return cmdFn({log, command: {directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
const bunyan = require('bunyan');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'MKD';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
let log = bunyan.createLogger({name: CMD});
|
||||
const mockClient = {
|
||||
reply: () => {},
|
||||
fs: {mkdir: () => {}}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -23,10 +25,10 @@ describe(CMD, function () {
|
||||
describe('// check', function () {
|
||||
it('fails on no fs', () => {
|
||||
const badMockClient = {reply: () => {}};
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
|
||||
return badCmdFn(badMockClient)
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
@@ -34,10 +36,10 @@ describe(CMD, function () {
|
||||
|
||||
it('fails on no fs mkdir command', () => {
|
||||
const badMockClient = {reply: () => {}, fs: {}};
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
|
||||
return badCmdFn(badMockClient)
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(402);
|
||||
});
|
||||
@@ -45,7 +47,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('test // successful', () => {
|
||||
return cmdFn(mockClient, {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');
|
||||
@@ -56,7 +58,7 @@ describe(CMD, function () {
|
||||
mockClient.fs.mkdir.restore();
|
||||
sandbox.stub(mockClient.fs, 'mkdir').resolves('test');
|
||||
|
||||
return cmdFn(mockClient, {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');
|
||||
@@ -67,7 +69,7 @@ describe(CMD, function () {
|
||||
mockClient.fs.mkdir.restore();
|
||||
sandbox.stub(mockClient.fs, 'mkdir').rejects(new Error('Bad'));
|
||||
|
||||
return cmdFn(mockClient, {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');
|
||||
|
||||
@@ -8,7 +8,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => Promise.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -20,14 +20,14 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('S // successful', () => {
|
||||
return cmdFn(mockClient, {arg: 'S'})
|
||||
return cmdFn({command: {arg: 'S'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
});
|
||||
});
|
||||
|
||||
it('Q // unsuccessful', () => {
|
||||
return cmdFn(mockClient, {arg: 'Q'})
|
||||
return cmdFn({command: {arg: 'Q'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(504);
|
||||
});
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
const Promise = require('bluebird');
|
||||
const bunyan = require('bunyan');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'NLST';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
let log = bunyan.createLogger({name: CMD});
|
||||
const mockClient = {
|
||||
reply: () => {},
|
||||
fs: {
|
||||
@@ -20,7 +22,7 @@ describe(CMD, function () {
|
||||
pause: () => {}
|
||||
}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -85,7 +87,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('. // successful', () => {
|
||||
return cmdFn(mockClient, {directive: CMD})
|
||||
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);
|
||||
@@ -117,7 +119,7 @@ describe(CMD, function () {
|
||||
isDirectory: () => false
|
||||
});
|
||||
|
||||
return cmdFn(mockClient, {directive: CMD, arg: 'testfile.txt'})
|
||||
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);
|
||||
|
||||
@@ -8,7 +8,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => Promise.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -20,7 +20,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('// successful', () => {
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => Promise.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -20,28 +20,28 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('// unsuccessful', () => {
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(501);
|
||||
});
|
||||
});
|
||||
|
||||
it('BAD // unsuccessful', () => {
|
||||
return cmdFn(mockClient, {arg: 'BAD', directive: CMD})
|
||||
return cmdFn({command: {arg: 'BAD', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(500);
|
||||
});
|
||||
});
|
||||
|
||||
it('UTF8 BAD // unsuccessful', () => {
|
||||
return cmdFn(mockClient, {arg: 'UTF8 BAD', directive: CMD})
|
||||
return cmdFn({command: {arg: 'UTF8 BAD', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(501);
|
||||
});
|
||||
});
|
||||
|
||||
it('UTF8 OFF // successful', () => {
|
||||
return cmdFn(mockClient, {arg: 'UTF8 OFF', directive: CMD})
|
||||
return cmdFn({command: {arg: 'UTF8 OFF', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.encoding).to.equal('ascii');
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
@@ -49,7 +49,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('UTF8 ON // successful', () => {
|
||||
return cmdFn(mockClient, {arg: 'UTF8 ON', directive: CMD})
|
||||
return cmdFn({command: {arg: 'UTF8 ON', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.encoding).to.equal('utf8');
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
const bunyan = require('bunyan');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'PASS';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
let log = bunyan.createLogger({name: CMD});
|
||||
const mockClient = {
|
||||
reply: () => {},
|
||||
login: () => {},
|
||||
server: {options: {anonymous: false}},
|
||||
username: 'anonymous'
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -23,7 +25,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('pass // successful', () => {
|
||||
return cmdFn(mockClient, {arg: 'pass', directive: CMD})
|
||||
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']);
|
||||
@@ -33,7 +35,7 @@ describe(CMD, function () {
|
||||
it('// successful (already authenticated)', () => {
|
||||
mockClient.server.options.anonymous = true;
|
||||
mockClient.authenticated = true;
|
||||
return cmdFn(mockClient, {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);
|
||||
@@ -46,7 +48,7 @@ describe(CMD, function () {
|
||||
mockClient.login.restore();
|
||||
sandbox.stub(mockClient, 'login').rejects('bad');
|
||||
|
||||
return cmdFn(mockClient, {arg: 'bad', directive: CMD})
|
||||
return cmdFn({log, command: {arg: 'bad', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(530);
|
||||
});
|
||||
@@ -56,7 +58,7 @@ describe(CMD, function () {
|
||||
mockClient.login.restore();
|
||||
sandbox.stub(mockClient, 'login').rejects({});
|
||||
|
||||
return cmdFn(mockClient, {arg: 'bad', directive: CMD})
|
||||
return cmdFn({log, command: {arg: 'bad', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(530);
|
||||
});
|
||||
@@ -64,7 +66,7 @@ describe(CMD, function () {
|
||||
|
||||
it('bad // unsuccessful', () => {
|
||||
delete mockClient.username;
|
||||
return cmdFn(mockClient, {arg: 'bad', directive: CMD})
|
||||
return cmdFn({log, command: {arg: 'bad', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(503);
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ describe(CMD, function () {
|
||||
reply: () => Promise.resolve(),
|
||||
server: {}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -21,7 +21,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('// unsuccessful', () => {
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(202);
|
||||
});
|
||||
@@ -31,7 +31,7 @@ describe(CMD, function () {
|
||||
mockClient.secure = true;
|
||||
mockClient.server._tls = {};
|
||||
|
||||
return cmdFn(mockClient, {arg: '0'})
|
||||
return cmdFn({command: {arg: '0'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
expect(mockClient.bufferSize).to.equal(0);
|
||||
@@ -42,7 +42,7 @@ describe(CMD, function () {
|
||||
mockClient.secure = true;
|
||||
mockClient.server._tls = {};
|
||||
|
||||
return cmdFn(mockClient, {arg: '10'})
|
||||
return cmdFn({command: {arg: '10'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
expect(mockClient.bufferSize).to.equal(10);
|
||||
|
||||
@@ -10,7 +10,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => Promise.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -23,21 +23,21 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('// unsuccessful | no argument', () => {
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(425);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | invalid argument', () => {
|
||||
return cmdFn(mockClient, {arg: '1,2,3,4,5'})
|
||||
return cmdFn({command: {arg: '1,2,3,4,5'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(425);
|
||||
});
|
||||
});
|
||||
|
||||
it('// successful', () => {
|
||||
return cmdFn(mockClient, {arg: '192,168,0,100,137,214'})
|
||||
return cmdFn({command: {arg: '192,168,0,100,137,214'}})
|
||||
.then(() => {
|
||||
const [ip, port] = ActiveConnector.prototype.setupConnection.args[0];
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
|
||||
@@ -9,7 +9,7 @@ describe(CMD, function () {
|
||||
reply: () => Promise.resolve(),
|
||||
server: {}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -21,7 +21,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('// unsuccessful', () => {
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(202);
|
||||
});
|
||||
@@ -31,7 +31,7 @@ describe(CMD, function () {
|
||||
mockClient.server._tls = {};
|
||||
mockClient.secure = true;
|
||||
|
||||
return cmdFn(mockClient, {arg: 'P'})
|
||||
return cmdFn({command: {arg: 'P'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(503);
|
||||
});
|
||||
@@ -41,7 +41,7 @@ describe(CMD, function () {
|
||||
mockClient.bufferSize = 0;
|
||||
mockClient.secure = true;
|
||||
|
||||
return cmdFn(mockClient, {arg: 'p'})
|
||||
return cmdFn({command: {arg: 'p'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
});
|
||||
@@ -49,7 +49,7 @@ describe(CMD, function () {
|
||||
|
||||
it('// unsuccessful - unsupported', () => {
|
||||
mockClient.secure = true;
|
||||
return cmdFn(mockClient, {arg: 'C'})
|
||||
return cmdFn({command: {arg: 'C'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(536);
|
||||
});
|
||||
@@ -57,7 +57,7 @@ describe(CMD, function () {
|
||||
|
||||
it('// unsuccessful - unknown', () => {
|
||||
mockClient.secure = true;
|
||||
return cmdFn(mockClient, {arg: 'QQ'})
|
||||
return cmdFn({command: {arg: 'QQ'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(504);
|
||||
});
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
const bunyan = require('bunyan');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
|
||||
const CMD = 'PWD';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
let log = bunyan.createLogger({name: CMD});
|
||||
const mockClient = {
|
||||
reply: () => {},
|
||||
fs: {currentDirectory: () => {}}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -26,7 +28,7 @@ describe(CMD, function () {
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
|
||||
return badCmdFn(badMockClient)
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
@@ -37,7 +39,7 @@ describe(CMD, function () {
|
||||
const badCmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(badMockClient);
|
||||
sandbox.stub(badMockClient, 'reply').resolves();
|
||||
|
||||
return badCmdFn(badMockClient)
|
||||
return badCmdFn()
|
||||
.then(() => {
|
||||
expect(badMockClient.reply.args[0][0]).to.equal(402);
|
||||
});
|
||||
@@ -45,7 +47,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('// successful', () => {
|
||||
return cmdFn(mockClient, {arg: 'test', directive: CMD})
|
||||
return cmdFn({log, command: {arg: 'test', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(257);
|
||||
});
|
||||
@@ -55,7 +57,7 @@ describe(CMD, function () {
|
||||
mockClient.fs.currentDirectory.restore();
|
||||
sandbox.stub(mockClient.fs, 'currentDirectory').resolves('/test');
|
||||
|
||||
return cmdFn(mockClient, {arg: 'test', directive: CMD})
|
||||
return cmdFn({log, command: {arg: 'test', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(257);
|
||||
});
|
||||
@@ -65,7 +67,7 @@ describe(CMD, function () {
|
||||
mockClient.fs.currentDirectory.restore();
|
||||
sandbox.stub(mockClient.fs, 'currentDirectory').rejects(new Error('Bad'));
|
||||
|
||||
return cmdFn(mockClient, {arg: 'bad', directive: CMD})
|
||||
return cmdFn({log, command: {arg: 'bad', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
close: () => {}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -19,7 +19,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('// successful', () => {
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.close.callCount).to.equal(1);
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => Promise.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -20,28 +20,28 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('// unsuccessful', () => {
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(501);
|
||||
});
|
||||
});
|
||||
|
||||
it('-1 // unsuccessful', () => {
|
||||
return cmdFn(mockClient, {arg: '-1', directive: CMD})
|
||||
return cmdFn({command: {arg: '-1', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(501);
|
||||
});
|
||||
});
|
||||
|
||||
it('bad // unsuccessful', () => {
|
||||
return cmdFn(mockClient, {arg: 'bad', directive: CMD})
|
||||
return cmdFn({command: {arg: 'bad', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(501);
|
||||
});
|
||||
});
|
||||
|
||||
it('1 // successful', () => {
|
||||
return cmdFn(mockClient, {arg: '1', directive: CMD})
|
||||
return cmdFn({command: {arg: '1', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.restByteCount).to.equal(1);
|
||||
expect(mockClient.reply.args[0][0]).to.equal(350);
|
||||
@@ -49,7 +49,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('0 // successful', () => {
|
||||
return cmdFn(mockClient, {arg: '0', directive: CMD})
|
||||
return cmdFn({command: {arg: '0', directive: CMD}})
|
||||
.then(() => {
|
||||
expect(mockClient.restByteCount).to.equal(0);
|
||||
expect(mockClient.reply.args[0][0]).to.equal(350);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
const Promise = require('bluebird');
|
||||
const bunyan = require('bunyan');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
const EventEmitter = require('events');
|
||||
|
||||
const CMD = 'RETR';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
let emitter;
|
||||
let log = bunyan.createLogger({name: CMD});
|
||||
const mockClient = {
|
||||
commandSocket: {
|
||||
pause: () => {},
|
||||
@@ -20,7 +20,7 @@ describe(CMD, function () {
|
||||
end: () => {}
|
||||
}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -29,11 +29,6 @@ describe(CMD, function () {
|
||||
read: () => {}
|
||||
};
|
||||
|
||||
emitter = new EventEmitter();
|
||||
mockClient.emit = emitter.emit.bind(emitter);
|
||||
mockClient.on = emitter.on.bind(emitter);
|
||||
mockClient.once = emitter.once.bind(emitter);
|
||||
|
||||
sandbox.spy(mockClient, 'reply');
|
||||
});
|
||||
afterEach(() => sandbox.restore());
|
||||
@@ -41,7 +36,7 @@ describe(CMD, function () {
|
||||
it('// unsuccessful | no file system', () => {
|
||||
delete mockClient.fs;
|
||||
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
@@ -50,7 +45,7 @@ describe(CMD, function () {
|
||||
it('// unsuccessful | file system does not have functions', () => {
|
||||
mockClient.fs = {};
|
||||
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(402);
|
||||
});
|
||||
@@ -61,8 +56,7 @@ describe(CMD, function () {
|
||||
return Promise.reject(new Promise.TimeoutError());
|
||||
});
|
||||
|
||||
|
||||
return cmdFn(mockClient, {arg: 'test.txt'})
|
||||
return cmdFn({log, command: {arg: 'test.txt'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(425);
|
||||
});
|
||||
@@ -73,25 +67,9 @@ describe(CMD, function () {
|
||||
return Promise.reject(new Error('test'));
|
||||
});
|
||||
|
||||
return cmdFn(mockClient, {arg: 'test.txt'})
|
||||
return cmdFn({log, command: {arg: 'test.txt'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(551);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | emits error event', () => {
|
||||
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
|
||||
return Promise.reject(new Error('test'));
|
||||
});
|
||||
|
||||
let errorEmitted = false;
|
||||
emitter.once('RETR', err => {
|
||||
errorEmitted = !!err;
|
||||
});
|
||||
|
||||
return cmdFn(mockClient, {arg: 'test.txt'})
|
||||
.then(() => {
|
||||
expect(errorEmitted).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,8 +5,9 @@ const sinon = require('sinon');
|
||||
const CMD = 'RNFR';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
const mockLog = {error: () => {}};
|
||||
const mockClient = {reply: () => Promise.resolve()};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -26,7 +27,7 @@ describe(CMD, function () {
|
||||
it('// unsuccessful | no file system', () => {
|
||||
delete mockClient.fs;
|
||||
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
@@ -35,7 +36,7 @@ describe(CMD, function () {
|
||||
it('// unsuccessful | file system does not have functions', () => {
|
||||
mockClient.fs = {};
|
||||
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(402);
|
||||
});
|
||||
@@ -45,14 +46,14 @@ describe(CMD, function () {
|
||||
mockClient.fs.get.restore();
|
||||
sandbox.stub(mockClient.fs, 'get').rejects(new Error('test'));
|
||||
|
||||
return cmdFn(mockClient, {arg: 'test'})
|
||||
return cmdFn({log: mockLog, command: {arg: 'test'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
});
|
||||
|
||||
it('test // successful', () => {
|
||||
return cmdFn(mockClient, {arg: 'test'})
|
||||
return cmdFn({log: mockLog, command: {arg: 'test'}})
|
||||
.then(() => {
|
||||
expect(mockClient.fs.get.args[0][0]).to.equal('test');
|
||||
expect(mockClient.reply.args[0][0]).to.equal(350);
|
||||
|
||||
@@ -5,8 +5,9 @@ const sinon = require('sinon');
|
||||
const CMD = 'RNTO';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
const mockLog = {error: () => {}};
|
||||
const mockClient = {reply: () => Promise.resolve()};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -27,7 +28,7 @@ describe(CMD, function () {
|
||||
it('// unsuccessful | no renameFrom set', () => {
|
||||
delete mockClient.renameFrom;
|
||||
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(503);
|
||||
});
|
||||
@@ -36,7 +37,7 @@ describe(CMD, function () {
|
||||
it('// unsuccessful | no file system', () => {
|
||||
delete mockClient.fs;
|
||||
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
@@ -45,7 +46,7 @@ describe(CMD, function () {
|
||||
it('// unsuccessful | file system does not have functions', () => {
|
||||
mockClient.fs = {};
|
||||
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(402);
|
||||
});
|
||||
@@ -55,14 +56,14 @@ describe(CMD, function () {
|
||||
mockClient.fs.rename.restore();
|
||||
sandbox.stub(mockClient.fs, 'rename').rejects(new Error('test'));
|
||||
|
||||
return cmdFn(mockClient, {arg: 'new'})
|
||||
return cmdFn({log: mockLog, command: {arg: 'new'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
});
|
||||
|
||||
it('new // successful', () => {
|
||||
return cmdFn(mockClient, {arg: 'new'})
|
||||
return cmdFn({command: {arg: 'new'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(250);
|
||||
expect(mockClient.fs.rename.args[0]).to.eql(['test', 'new']);
|
||||
|
||||
@@ -5,8 +5,9 @@ const sinon = require('sinon');
|
||||
const CMD = 'CHMOD';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
const mockLog = {error: () => {}};
|
||||
const mockClient = {reply: () => Promise.resolve()};
|
||||
const cmdFn = require(`../../../../src/commands/registration/site/${CMD.toLowerCase()}`);
|
||||
const cmdFn = require(`../../../../src/commands/registration/site/${CMD.toLowerCase()}`).bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -25,7 +26,7 @@ describe(CMD, function () {
|
||||
it('// unsuccessful | no file system', done => {
|
||||
delete mockClient.fs;
|
||||
|
||||
cmdFn(mockClient)
|
||||
cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
done();
|
||||
@@ -36,7 +37,7 @@ describe(CMD, function () {
|
||||
it('// unsuccessful | file system does not have functions', done => {
|
||||
mockClient.fs = {};
|
||||
|
||||
cmdFn(mockClient)
|
||||
cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(402);
|
||||
done();
|
||||
@@ -48,7 +49,7 @@ describe(CMD, function () {
|
||||
mockClient.fs.chmod.restore();
|
||||
sandbox.stub(mockClient.fs, 'chmod').rejects(new Error('test'));
|
||||
|
||||
cmdFn(mockClient, {arg: '777 test'})
|
||||
cmdFn({log: mockLog, command: {arg: '777 test'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(500);
|
||||
done();
|
||||
@@ -57,7 +58,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('777 test // successful', done => {
|
||||
cmdFn(mockClient, {arg: '777 test'})
|
||||
cmdFn({log: mockLog, command: {arg: '777 test'}})
|
||||
.then(() => {
|
||||
expect(mockClient.fs.chmod.args[0]).to.eql(['test', 511]);
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const Promise = require('bluebird');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
const bunyan = require('bunyan');
|
||||
|
||||
const siteRegistry = require('../../../../src/commands/registration/site/registry');
|
||||
const FtpCommands = require('../../../../src/commands');
|
||||
@@ -8,11 +9,12 @@ const FtpCommands = require('../../../../src/commands');
|
||||
const CMD = 'SITE';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
const log = bunyan.createLogger({name: 'site-test'});
|
||||
const mockClient = {
|
||||
reply: () => Promise.resolve(),
|
||||
commands: new FtpCommands()
|
||||
};
|
||||
const cmdFn = require(`../../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -24,14 +26,14 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('// unsuccessful', () => {
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn({log})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(502);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful', () => {
|
||||
return cmdFn(mockClient, {arg: 'BAD'})
|
||||
return cmdFn({log, command: {arg: 'BAD'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(502);
|
||||
});
|
||||
@@ -40,7 +42,7 @@ describe(CMD, function () {
|
||||
it('// successful', () => {
|
||||
sandbox.stub(siteRegistry.CHMOD, 'handler').resolves();
|
||||
|
||||
return cmdFn(mockClient, {arg: 'CHMOD test'})
|
||||
return cmdFn({log, command: {arg: 'CHMOD test'}})
|
||||
.then(() => {
|
||||
const {command} = siteRegistry.CHMOD.handler.args[0][0];
|
||||
expect(command.directive).to.equal('CHMOD');
|
||||
|
||||
@@ -5,8 +5,9 @@ const sinon = require('sinon');
|
||||
const CMD = 'SIZE';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
const mockLog = {error: () => {}};
|
||||
const mockClient = {reply: () => Promise.resolve()};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -24,7 +25,7 @@ describe(CMD, function () {
|
||||
it('// unsuccessful | no file system', () => {
|
||||
delete mockClient.fs;
|
||||
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
@@ -33,7 +34,7 @@ describe(CMD, function () {
|
||||
it('// unsuccessful | file system does not have functions', () => {
|
||||
mockClient.fs = {};
|
||||
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(402);
|
||||
});
|
||||
@@ -42,14 +43,14 @@ describe(CMD, function () {
|
||||
it('// unsuccessful | file get fails', () => {
|
||||
sandbox.stub(mockClient.fs, 'get').rejects(new Error('test'));
|
||||
|
||||
return cmdFn(mockClient, {arg: 'test'})
|
||||
return cmdFn({log: mockLog, command: {arg: 'test'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
});
|
||||
|
||||
it('// successful', () => {
|
||||
return cmdFn(mockClient, {arg: 'test'})
|
||||
return cmdFn({command: {arg: 'test'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(213);
|
||||
});
|
||||
|
||||
@@ -5,8 +5,9 @@ const sinon = require('sinon');
|
||||
const CMD = 'STAT';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
const mockLog = {error: () => {}};
|
||||
const mockClient = {reply: () => Promise.resolve()};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -23,7 +24,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('// successful', () => {
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(211);
|
||||
});
|
||||
@@ -32,7 +33,7 @@ describe(CMD, function () {
|
||||
it('// unsuccessful | no file system', () => {
|
||||
delete mockClient.fs;
|
||||
|
||||
return cmdFn(mockClient, {arg: 'test'})
|
||||
return cmdFn({command: {arg: 'test'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
@@ -41,7 +42,7 @@ describe(CMD, function () {
|
||||
it('// unsuccessful | file system does not have functions', () => {
|
||||
mockClient.fs = {};
|
||||
|
||||
return cmdFn(mockClient, {arg: 'test'})
|
||||
return cmdFn({command: {arg: 'test'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(402);
|
||||
});
|
||||
@@ -50,7 +51,7 @@ describe(CMD, function () {
|
||||
it('// unsuccessful | file get fails', () => {
|
||||
sandbox.stub(mockClient.fs, 'get').rejects(new Error('test'));
|
||||
|
||||
return cmdFn(mockClient, {arg: 'test'})
|
||||
return cmdFn({log: mockLog, command: {arg: 'test'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(450);
|
||||
});
|
||||
@@ -76,7 +77,7 @@ describe(CMD, function () {
|
||||
isDirectory: () => false
|
||||
});
|
||||
|
||||
return cmdFn(mockClient, {arg: 'test'})
|
||||
return cmdFn({command: {arg: 'test'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(212);
|
||||
});
|
||||
@@ -121,7 +122,7 @@ describe(CMD, function () {
|
||||
isDirectory: () => true
|
||||
});
|
||||
|
||||
return cmdFn(mockClient, {arg: 'test'})
|
||||
return cmdFn({command: {arg: 'test'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(213);
|
||||
});
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
const Promise = require('bluebird');
|
||||
const bunyan = require('bunyan');
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
const EventEmitter = require('events');
|
||||
|
||||
const CMD = 'STOR';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
let emitter;
|
||||
let log = bunyan.createLogger({name: CMD});
|
||||
const mockClient = {
|
||||
commandSocket: {
|
||||
pause: () => {},
|
||||
@@ -20,7 +20,7 @@ describe(CMD, function () {
|
||||
end: () => {}
|
||||
}
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -29,11 +29,6 @@ describe(CMD, function () {
|
||||
write: () => {}
|
||||
};
|
||||
|
||||
emitter = new EventEmitter();
|
||||
mockClient.emit = emitter.emit.bind(emitter);
|
||||
mockClient.on = emitter.on.bind(emitter);
|
||||
mockClient.once = emitter.once.bind(emitter);
|
||||
|
||||
sandbox.spy(mockClient, 'reply');
|
||||
});
|
||||
afterEach(() => sandbox.restore());
|
||||
@@ -41,7 +36,7 @@ describe(CMD, function () {
|
||||
it('// unsuccessful | no file system', () => {
|
||||
delete mockClient.fs;
|
||||
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
@@ -50,7 +45,7 @@ describe(CMD, function () {
|
||||
it('// unsuccessful | file system does not have functions', () => {
|
||||
mockClient.fs = {};
|
||||
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(402);
|
||||
});
|
||||
@@ -61,7 +56,7 @@ describe(CMD, function () {
|
||||
return Promise.reject(new Promise.TimeoutError());
|
||||
});
|
||||
|
||||
return cmdFn(mockClient, {arg: 'test.txt'})
|
||||
return cmdFn({log, command: {arg: 'test.txt'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(425);
|
||||
});
|
||||
@@ -72,25 +67,9 @@ describe(CMD, function () {
|
||||
return Promise.reject(new Error('test'));
|
||||
});
|
||||
|
||||
return cmdFn(mockClient, {arg: 'test.txt'})
|
||||
return cmdFn({log, command: {arg: 'test.txt'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful | emits error event', () => {
|
||||
sandbox.stub(mockClient.connector, 'waitForConnection').callsFake(function () {
|
||||
return Promise.reject(new Error('test'));
|
||||
});
|
||||
|
||||
let errorEmitted = false;
|
||||
emitter.once('STOR', err => {
|
||||
errorEmitted = !!err;
|
||||
});
|
||||
|
||||
return cmdFn(mockClient, {arg: 'test.txt'})
|
||||
.then(() => {
|
||||
expect(errorEmitted).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => Promise.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -33,7 +33,7 @@ describe(CMD, function () {
|
||||
it('// unsuccessful | no file system', () => {
|
||||
delete mockClient.fs;
|
||||
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(550);
|
||||
});
|
||||
@@ -42,7 +42,7 @@ describe(CMD, function () {
|
||||
it('// unsuccessful | file system does not have functions', () => {
|
||||
mockClient.fs = {};
|
||||
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(402);
|
||||
});
|
||||
@@ -52,7 +52,7 @@ describe(CMD, function () {
|
||||
mockClient.fs.get.restore();
|
||||
sandbox.stub(mockClient.fs, 'get').rejects({});
|
||||
|
||||
return cmdFn(mockClient, {arg: 'good'})
|
||||
return cmdFn({command: {arg: 'good'}})
|
||||
.then(() => {
|
||||
const call = stor.handler.call.args[0][1];
|
||||
expect(call).to.have.property('command');
|
||||
@@ -63,7 +63,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('// successful | generates unique name', () => {
|
||||
return cmdFn(mockClient, {arg: 'bad'})
|
||||
return cmdFn({command: {arg: 'bad'}})
|
||||
.then(() => {
|
||||
const call = stor.handler.call.args[0][1];
|
||||
expect(call).to.have.property('command');
|
||||
|
||||
@@ -8,7 +8,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => Promise.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -20,14 +20,14 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('// successful', () => {
|
||||
return cmdFn(mockClient, {arg: 'F'})
|
||||
return cmdFn({command: {arg: 'F'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
});
|
||||
});
|
||||
|
||||
it('// unsuccessful', () => {
|
||||
return cmdFn(mockClient, {arg: 'X'})
|
||||
return cmdFn({command: {arg: 'X'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(504);
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => Promise.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -20,7 +20,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('// successful', () => {
|
||||
return cmdFn(mockClient)
|
||||
return cmdFn()
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(215);
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ describe(CMD, function () {
|
||||
const mockClient = {
|
||||
reply: () => Promise.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handle;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -21,7 +21,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('A // successful', () => {
|
||||
return cmdFn(mockClient, {arg: 'A'})
|
||||
return cmdFn({command: {arg: 'A'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(200);
|
||||
expect(mockClient.transferType).to.equal('ascii');
|
||||
|
||||
@@ -5,12 +5,15 @@ const sinon = require('sinon');
|
||||
const CMD = 'USER';
|
||||
describe(CMD, function () {
|
||||
let sandbox;
|
||||
const mockLog = {
|
||||
error: () => {}
|
||||
};
|
||||
const mockClient = {
|
||||
reply: () => Promise.resolve(),
|
||||
server: {options: {}},
|
||||
login: () => Promise.resolve()
|
||||
};
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler;
|
||||
const cmdFn = require(`../../../src/commands/registration/${CMD.toLowerCase()}`).handler.bind(mockClient);
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create();
|
||||
@@ -26,7 +29,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('test // successful | prompt for password', () => {
|
||||
return cmdFn(mockClient, {arg: 'test'})
|
||||
return cmdFn({command: {arg: 'test'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(331);
|
||||
});
|
||||
@@ -35,7 +38,7 @@ describe(CMD, function () {
|
||||
it('test // successful | anonymous login', () => {
|
||||
mockClient.server.options = {anonymous: true};
|
||||
|
||||
return cmdFn(mockClient, {arg: 'anonymous'})
|
||||
return cmdFn({command: {arg: 'anonymous'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(230);
|
||||
expect(mockClient.login.callCount).to.equal(1);
|
||||
@@ -43,7 +46,7 @@ describe(CMD, function () {
|
||||
});
|
||||
|
||||
it('test // unsuccessful | no username provided', () => {
|
||||
return cmdFn(mockClient, { })
|
||||
return cmdFn({command: { }})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(501);
|
||||
expect(mockClient.login.callCount).to.equal(0);
|
||||
@@ -53,7 +56,7 @@ describe(CMD, function () {
|
||||
it('test // unsuccessful | already set username', () => {
|
||||
mockClient.username = 'test';
|
||||
|
||||
return cmdFn(mockClient, {arg: 'test'})
|
||||
return cmdFn({command: {arg: 'test'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(530);
|
||||
expect(mockClient.login.callCount).to.equal(0);
|
||||
@@ -63,7 +66,7 @@ describe(CMD, function () {
|
||||
it('test // successful | regular login if anonymous is true', () => {
|
||||
mockClient.server.options = {anonymous: true};
|
||||
|
||||
return cmdFn(mockClient, {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);
|
||||
@@ -73,7 +76,7 @@ describe(CMD, function () {
|
||||
it('test // successful | anonymous login with set username', () => {
|
||||
mockClient.server.options = {anonymous: 'sillyrabbit'};
|
||||
|
||||
return cmdFn(mockClient, {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);
|
||||
@@ -85,7 +88,7 @@ describe(CMD, function () {
|
||||
mockClient.login.restore();
|
||||
sandbox.stub(mockClient, 'login').rejects(new Error('test'));
|
||||
|
||||
return cmdFn(mockClient, {arg: 'anonymous'})
|
||||
return cmdFn({log: mockLog, command: {arg: 'anonymous'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(530);
|
||||
expect(mockClient.login.callCount).to.equal(1);
|
||||
@@ -95,7 +98,7 @@ describe(CMD, function () {
|
||||
it('test // successful | does not login if already authenticated', () => {
|
||||
mockClient.authenticated = true;
|
||||
|
||||
return cmdFn(mockClient, {arg: 'sillyrabbit'})
|
||||
return cmdFn({log: mockLog, command: {arg: 'sillyrabbit'}})
|
||||
.then(() => {
|
||||
expect(mockClient.reply.args[0][0]).to.equal(230);
|
||||
expect(mockClient.login.callCount).to.equal(0);
|
||||
|
||||
@@ -4,6 +4,7 @@ const sinon = require('sinon');
|
||||
|
||||
const Promise = require('bluebird');
|
||||
const net = require('net');
|
||||
const bunyan = require('bunyan');
|
||||
|
||||
const PassiveConnector = require('../../src/connector/passive');
|
||||
|
||||
@@ -13,6 +14,7 @@ describe('Connector - Passive //', function () {
|
||||
reply: () => Promise.resolve({}),
|
||||
close: () => Promise.resolve({}),
|
||||
encoding: 'utf8',
|
||||
log: bunyan.createLogger({name: 'passive-test'}),
|
||||
commandSocket: {},
|
||||
server: {options: {}}
|
||||
};
|
||||
@@ -62,7 +64,7 @@ describe('Connector - Passive //', function () {
|
||||
return passive.setupServer()
|
||||
.then(shouldNotResolve)
|
||||
.catch(err => {
|
||||
expect(err).to.be.instanceOf(RangeError);
|
||||
expect(err.name).to.equal('RangeError');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,20 +1,9 @@
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
const moment = require('moment');
|
||||
|
||||
const fileStat = require('../../src/helpers/file-stat');
|
||||
const errors = require('../../src/errors');
|
||||
|
||||
describe('helpers // file-stat', function () {
|
||||
let sandbox;
|
||||
|
||||
before(function () {
|
||||
sandbox = sinon.sandbox.create();
|
||||
});
|
||||
afterEach(function () {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
const STAT = {
|
||||
name: 'test1',
|
||||
dev: 2114,
|
||||
@@ -55,11 +44,6 @@ describe('helpers // file-stat', function () {
|
||||
|
||||
describe('format - ls //', function () {
|
||||
it('formats correctly', () => {
|
||||
const momentStub = sandbox.stub(moment, 'utc').callThrough();
|
||||
momentStub.onFirstCall().callsFake(function () {
|
||||
return moment.utc(new Date('Sept 10 2016'));
|
||||
});
|
||||
|
||||
const format = fileStat(STAT, 'ls');
|
||||
expect(format).to.equal('-rwxrwxrwx 1 85 100 527 Oct 10 23:24 test1');
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint no-unused-expressions: 0 */
|
||||
const {expect} = require('chai');
|
||||
const sinon = require('sinon');
|
||||
const bunyan = require('bunyan');
|
||||
const Promise = require('bluebird');
|
||||
const _ = require('lodash');
|
||||
const fs = require('fs');
|
||||
@@ -14,6 +15,7 @@ describe('Integration', function () {
|
||||
|
||||
let client;
|
||||
let sandbox;
|
||||
let log = bunyan.createLogger({name: 'test-runner'});
|
||||
let server;
|
||||
|
||||
let connection;
|
||||
@@ -36,6 +38,7 @@ describe('Integration', function () {
|
||||
|
||||
function startServer(url, options = {}) {
|
||||
server = new FtpServer(url, _.assign({
|
||||
log,
|
||||
pasv_range: 8881,
|
||||
greeting: ['hello', 'world'],
|
||||
anonymous: true
|
||||
@@ -186,11 +189,6 @@ describe('Integration', function () {
|
||||
it('STOR tést.txt', done => {
|
||||
const buffer = Buffer.from('test text file');
|
||||
const fsPath = `${clientDirectory}/${name}/tést.txt`;
|
||||
|
||||
connection.once('STOR', err => {
|
||||
expect(err).to.not.exist;
|
||||
});
|
||||
|
||||
client.put(buffer, 'tést.txt', err => {
|
||||
expect(err).to.not.exist;
|
||||
setTimeout(() => {
|
||||
@@ -221,10 +219,6 @@ describe('Integration', function () {
|
||||
});
|
||||
|
||||
it('RETR tést.txt', done => {
|
||||
connection.once('RETR', err => {
|
||||
expect(err).to.not.exist;
|
||||
});
|
||||
|
||||
client.get('tést.txt', (err, stream) => {
|
||||
expect(err).to.not.exist;
|
||||
let text = '';
|
||||
|
||||
5
test/mochabunyan.opts
Normal file
5
test/mochabunyan.opts
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"mute":false,
|
||||
"level":"debug",
|
||||
"reporter":"spec"
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
require('dotenv').load();
|
||||
const bunyan = require('bunyan');
|
||||
|
||||
const FtpServer = require('../src');
|
||||
|
||||
const log = bunyan.createLogger({name: 'test'});
|
||||
log.level('trace');
|
||||
const server = new FtpServer('ftp://127.0.0.1:8880', {
|
||||
log,
|
||||
pasv_range: 8881,
|
||||
greeting: ['Welcome', 'to', 'the', 'jungle!'],
|
||||
tls: {
|
||||
|
||||
Reference in New Issue
Block a user