https://github.com/kamicane/simple-ftpd
a simple ftp server
https://github.com/kamicane/simple-ftpd
ftp-server
Last synced: 5 months ago
JSON representation
a simple ftp server
- Host: GitHub
- URL: https://github.com/kamicane/simple-ftpd
- Owner: kamicane
- Created: 2017-01-29T23:59:27.000Z (over 9 years ago)
- Default Branch: master
- Last Pushed: 2018-07-21T10:15:37.000Z (almost 8 years ago)
- Last Synced: 2025-09-28T22:36:35.378Z (9 months ago)
- Topics: ftp-server
- Language: JavaScript
- Size: 20.5 KB
- Stars: 34
- Watchers: 6
- Forks: 10
- Open Issues: 7
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# simple-ftpd
`simple-ftpd` is a event based ftp-server for node.js.
## Features
- Supports multiple modern ftp clients such as FileZilla, WinSCP, Transmission.
- A real File System is not required.
- For now it only supports basic authentication.
- It supports passive mode only.
- It only supports streams for writing and reading files.
- It currently only supports a subset of FTP commands, but it should be enough for any modern FTP client.
- It is intended to be used mainly to create local network ftp servers.
Used [pyftpdlib](https://github.com/giampaolo/pyftpdlib) extensively as a reference for the FTP protocol specification, as reading RFC documents is super boring :=)
## Install
```sh
yarn add simple-ftpd
# or
npm install simple-ftpd --save
```
```js
const ftpd = require('simple-ftpd')
```
## API
Syntax: `ftpd(opts, sessionCallback)` or `ftpd(host:port, sessionCallback)`
Should you need more control, the `ftpd` function will return a [`net.Server`](https://nodejs.org/api/net.html#net_class_net_server) instance.
## Example
Simple, fs-based ftp server implementation example:
```js
'use strict'
const ftpd = require('simple-ftpd')
ftpd({ host: '127.0.0.1', port: 1337, root: '/public/files' }, (session) => {
session.on('pass', (username, password, cb) => {
if (username === 'superadmin' && password === '53cr3t') {
session.readOnly = false
session.root = '/private/secret/files'
cb(null, 'Welcome admin')
} else {
cb(null, 'Welcome guest')
}
})
session.on('stat', fs.stat)
// AKA
// session.on('stat', (pathName, cb) => {
// fs.stat(pathName, cb)
// })
session.on('readdir', fs.readdir)
// AKA
// session.on('readdir', (pathName, cb) => {
// fs.readdir(pathName, cb)
// })
session.on('read', (pathName, offset, cb) => {
cb(null, fs.createReadStream(pathName, { start: offset }))
})
session.on('write', (pathName, offset, cb) => {
cb(null, fs.createWriteStream(pathName, { start: offset }))
})
// I'd do some checking if I were you, but hey.
session.on('mkdir', fs.mkdir)
session.on('unlink', fs.unlink)
session.on('rename', fs.rename)
session.on('remove', require('rimraf'))
})
```
## Options
| option | | description |
| --- | --- | --- |
| `host` | required | Must be a valid ipv4 address (for now). Defaults to `127.0.0.1` |
| `port` | required | Server port. Defaults to `1337`. |
| `readOnly` | optional | Disables client write requests. Defaults to `true`. Can be overridden on a connection-basis by setting the readOnly property on the session object before logging in. |
| `root` | optional | The path passed to events will always be joined to `root`. Can be overridden on a connection-basis by setting the root property on the session object before logging in. The ftp client will not see `root`, and will think he's at `/`. This is simply a convenience to avoid manually joining the path you get in every event. |
| `maxConnections` | optional | Maximum number of server connections. Defaults to 10. |
Passive connections will be initialized on an unused port assigned by the os.
Instead of options, you can pass `${host}:${port}` (`"192.168.1.10:1337"`).
## Events
Every event gets some arguments and a node-style callback you must call with an Error object (if any) and results.
If you do not listen to a specific event, then that feature will become unavailable to the client.
For example, if you do not listen to `write` events, the client will get a 502 error when trying to write files.
### `pass [username, password, cb]`
The client wants to login with `username` and `password`.
Will consider the user logged in if no error is passed to `cb()`
User names are always accepted, because (from pyftpdlib):
> In order to prevent a
> malicious client from determining valid usernames on a server,
> it is suggested by RFC-2577 that a server always return 331 to
> the USER command and then reject the combination of username
> and password for an invalid username when PASS is provided later.
### `read [pathName, offset, cb]`:
The client wants to read the file at `pathName`, starting at `offset`.
Requires a readable stream to be passed as `cb(null, readStream)`.
### `write [pathName, offset, cb]`:
The client wants to write a file at `pathName`, starting at `offset`.
Requires a writable stream to be passed as `cb(null, writeStream)`.
### `stat [pathName, cb]`:
the client wants the stats to a file or directory at `pathName`.
Requires a `fs.stat`-like object to be passed as `cb(null, stat)`.
Properties are:
```js
{
// fs.stat mode
mode: 16822,
// size in bytes
size: 12345,
// lastModifiedTime as Date or number (or whatever gets parsed by moment)
mtime: Date.now(),
// optional, will use it for display if available, otherwise "owner"
uname: 'kamicane',
// optional, will use it for display if available, otherwise "group"
gname: 'admins',
}
```
### `readdir [pathName, cb]`:
The client wants a list of files in the directory `pathName`. requires an array of file names, relative to pathName, to be passed as `cb(null, list)`.
### `mkdir [pathName, cb]`:
The client wants to create a directory at `pathName`.
Will consider the directory written if no error is passed to `cb()`
### `unlink [pathName, cb]`:
The client wants to delete the file at `pathName`.
Will consider the file deleted if no error is passed to `cb()`
### `remove [pathName, cb]`:
The client wants to remove a directory, and all of its contents, at `pathName`.
Will consider the directory and its contents removed if no error is passed to `cb()`
### `rename [fromName, toName, cb]`:
The client wants to rename a file or directory, from `fromName` to `toName`.
Will consider the file renamed if no error is passed to `cb()`
## CLI
When installed globally, ftpd will provide a small cli, which will quickly create a fs-based ftp server.
You can use it like this:
```sh
ftp-server /public/files --host 192.168.0.1 --port 1234 --max-connections 10
```
* `--host` will default to `127.0.0.1`
* `--port` will default to `1337`
* `--max-connections` will default to `10`
* `--read-only` will default to `true`
* Unless specified, the ftp root will default to `process.cwd()`
The cli will accept any login, so be careful when setting `--read-only` to `false`.
## License
MIT